From 4b1af657f7acb5860b4d980c903a32ba60441e10 Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Fri, 20 Dec 2024 03:36:31 +1030 Subject: [PATCH] Subtitle pre-empting and crossfades. I'm happy with this output for now. --- lyrics/static/lyrics/input.js | 44 +++++++++++++++++----- lyrics/static/lyrics/subtitle_generator.js | 6 ++- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/lyrics/static/lyrics/input.js b/lyrics/static/lyrics/input.js index 97c1160..0d0e955 100644 --- a/lyrics/static/lyrics/input.js +++ b/lyrics/static/lyrics/input.js @@ -248,6 +248,7 @@ function load_song(data) { // lyrics_input_updated() lyrics_input_element.dispatchEvent(new Event('change', {})); + // generate_subtitles(); } document.getElementById('load_song').addEventListener('change', event => { 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' function 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 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 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 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)); let lines = []; // 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'); let out_idx = 0; - arrangement_line_numbers.forEach(src_line_idx => { - const t0 = manual_line_timecodes?.[out_idx] ?? placeholder_line_timecodes[out_idx]; - const t1 = manual_line_timecodes?.[out_idx+1] ?? placeholder_line_timecodes[out_idx+1]; + const N = arr_line_numbers.length; + for (let i = 0; i < N; i++) { + 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 total_beats = (src_line_idx<0) ? (-src_line_idx) : (line_syllable_total_beats?.[src_line_idx] ?? 1); 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); 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 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 acc = 0; + let acc = early_fade_in_b; line_syllable_timings?.[src_line_idx]?.forEach(b => { acc += b; arr_syllable_cs.push(Math.floor(100*acc*spb)); @@ -310,7 +335,7 @@ function generate_subtitles() { translated_line: tl_lines?.[src_line_idx] ?? '', }, tokenized_line)); out_idx++; - }); + } const subtitles = generate_subtitles_from_data({ song_title: document.getElementById('song_title').value, @@ -319,7 +344,8 @@ function generate_subtitles() { song_lyricist_en: document.getElementById('song_lyricist_en').value, song_composer: document.getElementById('song_composer').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, }); subtitle_editor_textarea.value = subtitles; diff --git a/lyrics/static/lyrics/subtitle_generator.js b/lyrics/static/lyrics/subtitle_generator.js index 8054197..e941e82 100644 --- a/lyrics/static/lyrics/subtitle_generator.js +++ b/lyrics/static/lyrics/subtitle_generator.js @@ -87,7 +87,9 @@ Dialogue: 0,${t0s},${t1s},Title,,,,,,${title_fade}${data.song_title_en}\\NMusic: const res_xh = res_x/2; // Add the lines + let layer = 0; // Flip this every line so we can crossfade them instead of push them away data.lines?.forEach(line => { + layer = 1-layer; const t0 = line.t0; const t1 = line.t1; 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 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 furi_preamble = `Dialogue: 1,${t0s},${t1s}`; // Different layer to avoid kanji collision detection + const sub_preamble = `Dialogue: ${layer},${t0s},${t1s}`; + const furi_preamble = `Dialogue: ${layer+2},${t0s},${t1s}`; // Different layer to avoid kanji collision detection // Translation line is easy and static s += `${sub_preamble},Translation,,,,,,${lyric_fade}${line.translated_line}\n`;