Subtitle pre-empting and crossfades. I'm happy with this output for now.
This commit is contained in:
parent
ece3fab18e
commit
4b1af657f7
|
@ -248,6 +248,7 @@ function load_song(data) {
|
||||||
|
|
||||||
// lyrics_input_updated()
|
// lyrics_input_updated()
|
||||||
lyrics_input_element.dispatchEvent(new Event('change', {}));
|
lyrics_input_element.dispatchEvent(new Event('change', {}));
|
||||||
|
// generate_subtitles();
|
||||||
}
|
}
|
||||||
document.getElementById('load_song').addEventListener('change', event => {
|
document.getElementById('load_song').addEventListener('change', event => {
|
||||||
console.log('Attempting to load json file');
|
console.log('Attempting to load json file');
|
||||||
|
@ -265,24 +266,34 @@ btn_download_subtitles.addEventListener('click', () => {
|
||||||
import generate_subtitles_from_data from './subtitle_generator.js'
|
import generate_subtitles_from_data from './subtitle_generator.js'
|
||||||
function generate_subtitles() {
|
function generate_subtitles() {
|
||||||
console.log('Attempting to generate subtitles');
|
console.log('Attempting to generate subtitles');
|
||||||
|
// Strip out empties/labels from line numbers, only looking at lines and rests
|
||||||
|
let arr_line_numbers = arrangement_line_numbers.filter(l => {
|
||||||
|
if (l < 0) return true; // Rest
|
||||||
|
if ((typeof tokenized_lyric_lines?.[l] ?? '') == "string") return false; // Label line
|
||||||
|
return true
|
||||||
|
});
|
||||||
|
|
||||||
// Grab manual line timestamps
|
// Grab manual line timestamps
|
||||||
const manual_line_timecodes = line_timer_element.value.split('\n').filter(s=>s.trim()).map(s=>parseFloat(s.split('\t')[0]));
|
const manual_line_timecodes = line_timer_element.value.split('\n').filter(s=>s.trim()).map(s=>parseFloat(s.split('\t')[0]));
|
||||||
const placeholder_t0 = 72.0;
|
const placeholder_t0 = 72.0;
|
||||||
const seconds_per_line = 10.0;
|
const seconds_per_line = 10.0;
|
||||||
const placeholder_line_timecodes = Array(arrangement_line_numbers.length+1).fill().map((_,i) => Math.floor(i*seconds_per_line + placeholder_t0));
|
const placeholder_line_timecodes = Array(arr_line_numbers.length+1).fill().map((_,i) => Math.floor(i*seconds_per_line + placeholder_t0));
|
||||||
// Grab syllable beat counts
|
// Grab syllable beat counts
|
||||||
const line_syllable_timings = lyrics_timing_input_element.value.split('\n').map(l => l.split(' ').filter(c=>c).map(c=>parseInt(c)));
|
const line_syllable_timings = lyrics_timing_input_element.value.split('\n').map(l => l.split(' ').filter(c=>c).map(c=>parseInt(c)));
|
||||||
const line_syllable_total_beats = line_syllable_timings.map(l=>l.reduce((acc,x)=>acc+x));
|
const line_syllable_total_beats = line_syllable_timings.map(l=>l.reduce((acc,x)=>acc+x));
|
||||||
|
|
||||||
let lines = [];
|
let lines = [];
|
||||||
// let t0 = manual_line_timecodes?.[0] ?? placeholder_line_timecodes[0];
|
// let t0 = manual_line_timecodes?.[0] ?? placeholder_line_timecodes[0];
|
||||||
|
const early_fade_beats = 2.4;
|
||||||
|
const early_fadein_beats = 3.0;
|
||||||
|
|
||||||
const tl_lines = lyrics_tl_input_element.value.split('\n');
|
const tl_lines = lyrics_tl_input_element.value.split('\n');
|
||||||
let out_idx = 0;
|
let out_idx = 0;
|
||||||
arrangement_line_numbers.forEach(src_line_idx => {
|
const N = arr_line_numbers.length;
|
||||||
const t0 = manual_line_timecodes?.[out_idx] ?? placeholder_line_timecodes[out_idx];
|
for (let i = 0; i < N; i++) {
|
||||||
const t1 = manual_line_timecodes?.[out_idx+1] ?? placeholder_line_timecodes[out_idx+1];
|
const src_line_idx = arr_line_numbers[i];
|
||||||
|
let t0 = manual_line_timecodes?.[out_idx] ?? placeholder_line_timecodes[out_idx];
|
||||||
|
let t1 = manual_line_timecodes?.[out_idx+1] ?? placeholder_line_timecodes[out_idx+1];
|
||||||
const duration = t1-t0;
|
const duration = t1-t0;
|
||||||
const total_beats = (src_line_idx<0) ? (-src_line_idx) : (line_syllable_total_beats?.[src_line_idx] ?? 1);
|
const total_beats = (src_line_idx<0) ? (-src_line_idx) : (line_syllable_total_beats?.[src_line_idx] ?? 1);
|
||||||
const bpm = 60.0*total_beats/duration;
|
const bpm = 60.0*total_beats/duration;
|
||||||
|
@ -290,14 +301,28 @@ function generate_subtitles() {
|
||||||
console.log(src_line_idx, out_idx, t0, t1, total_beats, bpm, spb);
|
console.log(src_line_idx, out_idx, t0, t1, total_beats, bpm, spb);
|
||||||
|
|
||||||
const tokenized_line = tokenized_lyric_lines[src_line_idx];
|
const tokenized_line = tokenized_lyric_lines[src_line_idx];
|
||||||
if ((typeof tokenized_line) == "string") return; // Label line
|
if ((typeof tokenized_line) == "string") continue; // Label line
|
||||||
if (src_line_idx < 0) { // Rest beats - still increment the output line
|
if (src_line_idx < 0) { // Rest beats - still increment the output line
|
||||||
out_idx++;
|
out_idx++;
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Peek next line to see if we should cut out early or extend into a rest/instrumental
|
||||||
|
const next_line_num = arr_line_numbers?.[i+1] ?? -16; // Default to nice long rest at end of song
|
||||||
|
if (next_line_num < 0) {
|
||||||
|
t1 += (-next_line_num)*spb/4; // Extend 1/4 into the rest
|
||||||
|
} else {
|
||||||
|
t1 -= spb*early_fade_beats; // Back off early to make room
|
||||||
|
}
|
||||||
|
// Peek previous line to see how early we can fade in
|
||||||
|
const prev_line_num = arr_line_numbers?.[i-1] ?? -16; // Default to nice long rest at start of song
|
||||||
|
const early_fade_in_b = (prev_line_num < 0)
|
||||||
|
? ((-prev_line_num)/8) // Extend 1/8 into the rest
|
||||||
|
: (early_fadein_beats) // Jump in early
|
||||||
|
t0 -= early_fade_in_b * spb;
|
||||||
|
|
||||||
let arr_syllable_cs = [];
|
let arr_syllable_cs = [];
|
||||||
let acc = 0;
|
let acc = early_fade_in_b;
|
||||||
line_syllable_timings?.[src_line_idx]?.forEach(b => {
|
line_syllable_timings?.[src_line_idx]?.forEach(b => {
|
||||||
acc += b;
|
acc += b;
|
||||||
arr_syllable_cs.push(Math.floor(100*acc*spb));
|
arr_syllable_cs.push(Math.floor(100*acc*spb));
|
||||||
|
@ -310,7 +335,7 @@ function generate_subtitles() {
|
||||||
translated_line: tl_lines?.[src_line_idx] ?? '',
|
translated_line: tl_lines?.[src_line_idx] ?? '',
|
||||||
}, tokenized_line));
|
}, tokenized_line));
|
||||||
out_idx++;
|
out_idx++;
|
||||||
});
|
}
|
||||||
|
|
||||||
const subtitles = generate_subtitles_from_data({
|
const subtitles = generate_subtitles_from_data({
|
||||||
song_title: document.getElementById('song_title').value,
|
song_title: document.getElementById('song_title').value,
|
||||||
|
@ -319,7 +344,8 @@ function generate_subtitles() {
|
||||||
song_lyricist_en: document.getElementById('song_lyricist_en').value,
|
song_lyricist_en: document.getElementById('song_lyricist_en').value,
|
||||||
song_composer: document.getElementById('song_composer').value,
|
song_composer: document.getElementById('song_composer').value,
|
||||||
song_composer_en: document.getElementById('song_composer_en').value,
|
song_composer_en: document.getElementById('song_composer_en').value,
|
||||||
title_fade_duration_ms: 2500,
|
title_fade_duration_ms: 4000,
|
||||||
|
lyric_fade_duration_ms: 275,
|
||||||
lines: lines,
|
lines: lines,
|
||||||
});
|
});
|
||||||
subtitle_editor_textarea.value = subtitles;
|
subtitle_editor_textarea.value = subtitles;
|
||||||
|
|
|
@ -87,7 +87,9 @@ Dialogue: 0,${t0s},${t1s},Title,,,,,,${title_fade}${data.song_title_en}\\NMusic:
|
||||||
const res_xh = res_x/2;
|
const res_xh = res_x/2;
|
||||||
|
|
||||||
// Add the lines
|
// Add the lines
|
||||||
|
let layer = 0; // Flip this every line so we can crossfade them instead of push them away
|
||||||
data.lines?.forEach(line => {
|
data.lines?.forEach(line => {
|
||||||
|
layer = 1-layer;
|
||||||
const t0 = line.t0;
|
const t0 = line.t0;
|
||||||
const t1 = line.t1;
|
const t1 = line.t1;
|
||||||
const t0s = timecode(t0);
|
const t0s = timecode(t0);
|
||||||
|
@ -98,8 +100,8 @@ Dialogue: 0,${t0s},${t1s},Title,,,,,,${title_fade}${data.song_title_en}\\NMusic:
|
||||||
const fallback_syl_duration_cs = duration_cs/num_syllables;
|
const fallback_syl_duration_cs = duration_cs/num_syllables;
|
||||||
const centiseconds = line.arr_syllables_cs ?? Array(num_syllables+1).fill().map((_,i) => Math.floor(i*fallback_syl_duration_cs));
|
const centiseconds = line.arr_syllables_cs ?? Array(num_syllables+1).fill().map((_,i) => Math.floor(i*fallback_syl_duration_cs));
|
||||||
|
|
||||||
const sub_preamble = `Dialogue: 0,${t0s},${t1s}`;
|
const sub_preamble = `Dialogue: ${layer},${t0s},${t1s}`;
|
||||||
const furi_preamble = `Dialogue: 1,${t0s},${t1s}`; // Different layer to avoid kanji collision detection
|
const furi_preamble = `Dialogue: ${layer+2},${t0s},${t1s}`; // Different layer to avoid kanji collision detection
|
||||||
|
|
||||||
// Translation line is easy and static
|
// Translation line is easy and static
|
||||||
s += `${sub_preamble},Translation,,,,,,${lyric_fade}${line.translated_line}\n`;
|
s += `${sub_preamble},Translation,,,,,,${lyric_fade}${line.translated_line}\n`;
|
||||||
|
|
Loading…
Reference in New Issue