mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
155 lines
4.8 KiB
TypeScript
155 lines
4.8 KiB
TypeScript
import { XmlDanmaku, XmlDanmakuDocument } from './xml-danmaku'
|
||
import { Duration, BlockTypes, Resolution, AssDanmaku, AssDanmakuDocument } from './ass-danmaku'
|
||
import { DanmakuStack } from './danmaku-stack'
|
||
import { Danmaku, DanmakuType } from './danmaku'
|
||
|
||
export interface DanmakuConverterConfig {
|
||
title: string
|
||
font: string
|
||
alpha: number
|
||
bold: boolean
|
||
duration: Duration
|
||
blockTypes: BlockTypes
|
||
resolution: Resolution
|
||
bottomMarginPercent: number
|
||
}
|
||
export const getDefaultDanmakuConverterConfig = (title: string): DanmakuConverterConfig => {
|
||
return {
|
||
title,
|
||
font: "微软雅黑",
|
||
alpha: 0.6,
|
||
duration: (danmaku: Danmaku) => {
|
||
switch (danmaku.type) {
|
||
case DanmakuType.Bottom:
|
||
case DanmakuType.Top:
|
||
return 4;
|
||
default:
|
||
return 6;
|
||
}
|
||
},
|
||
blockTypes: [DanmakuType.Special, DanmakuType.Special2],
|
||
resolution: {
|
||
x: 1920,
|
||
y: 1080,
|
||
},
|
||
bottomMarginPercent: 0.15,
|
||
bold: false,
|
||
}
|
||
}
|
||
export class DanmakuConverter {
|
||
static white = 16777215 // Dec color of white danmaku
|
||
title: string
|
||
font: string
|
||
alpha: string
|
||
duration: Duration
|
||
blockTypes: BlockTypes
|
||
resolution: Resolution
|
||
bold: boolean
|
||
danmakuStack: DanmakuStack
|
||
constructor({ title, font, alpha, duration, blockTypes, resolution, bottomMarginPercent, bold }: DanmakuConverterConfig) {
|
||
this.title = title
|
||
this.font = font
|
||
this.alpha = Math.round(alpha * 100).toString(16).toUpperCase()
|
||
this.duration = duration
|
||
this.blockTypes = blockTypes
|
||
this.resolution = resolution
|
||
this.bold = bold
|
||
this.danmakuStack = new DanmakuStack(font, resolution, duration, bottomMarginPercent)
|
||
}
|
||
get fontStyles() {
|
||
return {
|
||
30: `Style: Large,${this.font},64,&H${this.alpha}FFFFFF,&H${this.alpha}FFFFFF,&H${this.alpha}000000,&H${this.alpha}000000,${this.bold ? '1' : '0'},0,0,0,100,100,0,0,1,1.2,0,5,0,0,0,0`,
|
||
25: `Style: Medium,${this.font},52,&H${this.alpha}FFFFFF,&H${this.alpha}FFFFFF,&H${this.alpha}000000,&H${this.alpha}000000,${this.bold ? '1' : '0'},0,0,0,100,100,0,0,1,1.2,0,5,0,0,0,0`,
|
||
18: `Style: Small,${this.font},36,&H${this.alpha}FFFFFF,&H${this.alpha}FFFFFF,&H${this.alpha}000000,&H${this.alpha}000000,${this.bold ? '1' : '0'},0,0,0,100,100,0,0,1,1.2,0,5,0,0,0,0`
|
||
}
|
||
}
|
||
convertToAssDocument(xml: string) {
|
||
const xmlDanmakuDocument = new XmlDanmakuDocument(xml)
|
||
const assDanmakus = []
|
||
for (const xmlDanmaku of xmlDanmakuDocument.danmakus.sort((a, b) => a.startTime - b.startTime)) {
|
||
// 跳过设置为屏蔽的弹幕类型
|
||
if (this.blockTypes.indexOf(xmlDanmaku.type) !== -1 ||
|
||
this.blockTypes.indexOf('color') !== -1 && xmlDanmaku.color !== DanmakuConverter.white) {
|
||
continue
|
||
}
|
||
const [startTime, endTime] = this.convertTime(xmlDanmaku.startTime, this.duration(xmlDanmaku))
|
||
assDanmakus.push(new AssDanmaku({
|
||
content: this.convertText(xmlDanmaku.content),
|
||
time: startTime,
|
||
endTime: endTime,
|
||
type: xmlDanmaku.type.valueOf().toString(),
|
||
fontSize: xmlDanmaku.fontSize.toString(),
|
||
color: xmlDanmaku.color.toString(),
|
||
typeTag: this.convertType(xmlDanmaku),
|
||
colorTag: this.convertColor(xmlDanmaku.color)
|
||
}))
|
||
}
|
||
return new AssDanmakuDocument(
|
||
assDanmakus,
|
||
this.title,
|
||
this.fontStyles,
|
||
this.blockTypes,
|
||
this.resolution
|
||
)
|
||
}
|
||
convertText(text: string) {
|
||
const map = {
|
||
'{': '{',
|
||
'}': '}',
|
||
'&': '&',
|
||
'<': '<',
|
||
'>': '>',
|
||
'"': '"',
|
||
''': "'"
|
||
}
|
||
for (const [key, value] of Object.entries(map)) {
|
||
text = text.replace(new RegExp(key, 'g'), value)
|
||
}
|
||
return text
|
||
}
|
||
convertType(danmaku: Danmaku) {
|
||
return this.danmakuStack.push(danmaku).tags
|
||
}
|
||
convertColor(decColor: number) {
|
||
if (decColor === DanmakuConverter.white) {
|
||
return ''
|
||
}
|
||
const hex = decColor.toString(16)
|
||
const red = hex.substring(0, 2)
|
||
const green = hex.substring(2, 4)
|
||
const blue = hex.substring(4, 6)
|
||
return `\\c&H${blue}${green}${red}&`
|
||
}
|
||
convertTime(startTime: number, duration: number) {
|
||
function round(number: number) {
|
||
const [integer, decimal = '00'] = String(number).split('.')
|
||
return `${integer.padStart(2, '0')}.${decimal.substr(0, 2).padEnd(2, '0')}`
|
||
}
|
||
function secondsToTime(seconds: number) {
|
||
let hours = 0
|
||
let minutes = 0
|
||
while (seconds >= 60) {
|
||
seconds -= 60
|
||
minutes++
|
||
}
|
||
while (minutes >= 60) {
|
||
minutes -= 60
|
||
hours++
|
||
}
|
||
return `${hours}:${String(minutes).padStart(2, '0')}:${round(seconds)}`
|
||
}
|
||
return [secondsToTime(startTime), secondsToTime(startTime + duration)]
|
||
}
|
||
}
|
||
export default {
|
||
export: {
|
||
AssDanmaku,
|
||
AssDanmakuDocument,
|
||
Danmaku,
|
||
DanmakuConverter,
|
||
DanmakuStack,
|
||
XmlDanmaku,
|
||
XmlDanmakuDocument
|
||
}
|
||
}
|