UNPKG

ultrastar2ass

Version:

Convert Ultrastar karaoke files to ASS files

107 lines (100 loc) 3.54 kB
#!/usr/bin/env node import { asyncExists, asyncReadFile, clone, msToAss } from './utils'; import UltrastarParser from './ultrastar'; import stringify from 'ass-stringify'; import { SyllabesConfig } from './types'; import ass = require('./assTemplate'); function generateASSLine(line: any, styles: any, duet: boolean) { const ASSLine = []; let startMs = line.start; const stopMs = line.end + 100; line.syllables.forEach((syl: any) => ASSLine.push( (syl.text.startsWith(' ') && ASSLine.length > 0 ? ' ' : '') + '{\\k' + Math.floor(syl.duration / 10) + '}' + syl.text.trim() + (syl.text.endsWith(' ') ? ' ' : '') )); const dialogue = clone(ass.dialogue); const comment = clone(ass.dialogue); dialogue.value.Start = msToAss(startMs - 900 < 0 ? 0 : startMs - 900); comment.value.Start = msToAss(startMs); dialogue.value.End = msToAss(stopMs); comment.value.End = msToAss(stopMs); dialogue.value.Text = '{\\k' + (startMs - 900 < 0 ? (900 - startMs) / 10 : 100) + ass.dialogueScript + ASSLine.join(''); dialogue.value.Effect = 'fx'; dialogue.value.Style = duet ? styles.body[2].value.Name : styles.body[1].value.Name; comment.value.Text = ASSLine.join(''); comment.value.Effect = 'karaoke'; comment.key = 'Comment'; comment.value.Style = duet ? styles.body[2].value.Name : styles.body[1].value.Name; return { dialogue, comment }; } function sortStartTime(a: any, b: any) { if (a.value.Start < b.value.Start) return -1; if (a.value.Start > b.value.Start) return 1; return 0; } /** Convert Ultrastar data (txt) to ASS */ export function convertToASS(time: string, options: SyllabesConfig): string { const ultrastar = new UltrastarParser(options); const kara = ultrastar.parse(time); const dialogues = []; const comments = []; const styles = clone(ass.styles); if (kara.meta.duet) { styles.body[1].value.Name = kara.meta.duetsingerp1 || 'Default'; styles.body.push(clone(styles.body[1])); styles.body[2].value.Name = kara.meta.duetsingerp2 || 'Default2'; styles.body[2].value.SecondaryColour = '&H000F4BE1'; } const script = clone(ass.dialogue); script.value.Effect = ass.scriptFX; script.value.Text = ass.script; script.key = 'Comment'; comments.push(script); for (const line of kara.track) { const ASSLines = generateASSLine(line, styles, false); comments.push(clone(ASSLines.comment)); dialogues.push(clone(ASSLines.dialogue)); } if (kara.meta.duet) { for (const line of kara.track_duet) { const ASSLines = generateASSLine(line, styles, true); comments.push(clone(ASSLines.comment)); dialogues.push(clone(ASSLines.dialogue)); } } comments.sort(sortStartTime); dialogues.sort(sortStartTime); const events = clone(ass.events); events.body = events.body.concat(comments, dialogues); return stringify([ass.scriptInfo, styles, events]); } async function mainCLI() { if (!process.argv[2]) { throw `Ultrastar2ass - Convert Ultrastar karaoke to ASS file Usage: ultrastar2ass myfile.txt Options: --encoding=input-file-encoding (default: utf8) Output goes to stdout `; } const txtFile = process.argv[2]; let encoding = 'utf8' if (process.argv[3] && process.argv[3].startsWith('--encoding=')) { encoding = process.argv[3].replace('--encoding=', '') } if (!await asyncExists(txtFile)) throw `File ${txtFile} does not exist`; const txt = await asyncReadFile(txtFile, encoding); return convertToASS(txt, { syllable_precision: true }); } if (require.main === module) mainCLI() .then(data => console.log(data)) .catch(err => console.log(err));