Bilibili-Evolved/registry/lib/components/video/subtitle/subtitle-converter.ts
2021-10-26 13:19:37 +08:00

103 lines
3.2 KiB
TypeScript

export const SubtitleLocation = {
TopLeft: 7,
TopCenter: 8,
TopRight: 9,
BottomLeft: 1,
BottomCenter: 2,
BottomRight: 3,
}
const getLocationName = (location: number) => Object.entries(SubtitleLocation)
.filter(([, value]) => value === location)
.map(([name]) => name)
.shift()
export enum SubtitleSize {
VerySmall = 1,
Small,
Medium,
Large,
VeryLarge,
}
export interface SubtitleConverterConfig {
title: string
width: number
height: number
color: string
size: number
opacity: number
location: number
boxPadding: number
boxMargin: number
}
export interface SubtitleItem {
from: number
to: number
location: number
content: string
}
export class SubtitleConverter {
static defaultConfig: SubtitleConverterConfig
config: SubtitleConverterConfig
constructor(config: {
title: string
width: number
height: number
color?: string
size?: number
opacity?: number
location?: number
boxPadding?: number
boxMargin?: number
}) {
this.config = Object.assign(SubtitleConverter.defaultConfig, config)
}
private async getAssMeta() {
const { convertHexColorForStyle } = await import('@/components/video/ass-utils')
const color = convertHexColorForStyle(this.config.color) // 前景色貌似不受字幕设置里的透明度控制
const background = convertHexColorForStyle('#000000', this.config.opacity)
const fontStyles = []
const fontSize = ((10 * (this.config.size - 3) + 48) * this.config.height) / 720
console.log(fontSize)
for (const [name, value] of Object.entries(SubtitleLocation)) {
fontStyles.push(`Style: ${name},微软雅黑,${fontSize},${color},${color},${background},${background},0,0,0,0,100,100,0,0,3,${this.config.boxPadding},0,${value},${this.config.boxMargin},${this.config.boxMargin},${this.config.boxMargin},0`)
}
return `
[Script Info]
; Script generated by Bilibili Evolved Danmaku Converter
; https://github.com/the1812/Bilibili-Evolved/
Title: ${this.config.title}
ScriptType: v4.00+
PlayResX: ${this.config.width}
PlayResY: ${this.config.height}
Timer: 10.0000
WrapStyle: 0
ScaledBorderAndShadow: no
[V4+ Styles]
Format: Name, Fontname, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
${fontStyles.join('\n')}
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text`.trim()
}
async convertToAss(subtitles: SubtitleItem[]) {
const { convertTimeByEndTime, normalizeContent } = await import('@/components/video/ass-utils')
const meta = await this.getAssMeta()
return `${meta}\n${subtitles.map(s => {
const [startTime, endTime] = convertTimeByEndTime(s.from, s.to)
const dialogue = `Dialogue: 0,${startTime},${endTime},${getLocationName(this.config.location)},,0,0,0,,${normalizeContent(s.content)}`
return dialogue
}).join('\n')}`
}
}
SubtitleConverter.defaultConfig = {
title: '',
color: '#ffffff',
width: 1920,
height: 1080,
size: SubtitleSize.Medium,
opacity: 0.5,
location: SubtitleLocation.BottomCenter,
boxPadding: 1,
boxMargin: 32,
}