From 5987ff95c637bf73dafd8fdf0879062122c749f9 Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Thu, 19 Dec 2024 01:30:46 +1030 Subject: [PATCH] Fix some regressions and add CSP headers to suppress some noise --- Pipfile | 1 + ktsite/settings.py | 9 +++ lyrics/static/lyrics/input.js | 37 +++++++-- lyrics/static/lyrics/metronome.js | 24 ++++++ lyrics/static/lyrics/style.css | 19 ++++- lyrics/static/lyrics/subtitle_generator.js | 38 ++++++++++ lyrics/static/lyrics/video.js | 7 +- lyrics/static/test.ass | 88 ++++++++++++++++++++++ lyrics/templates/lyrics/index.html | 31 +++++++- 9 files changed, 237 insertions(+), 17 deletions(-) create mode 100644 lyrics/static/lyrics/metronome.js create mode 100644 lyrics/static/lyrics/subtitle_generator.js create mode 100644 lyrics/static/test.ass diff --git a/Pipfile b/Pipfile index b7a81df..35fa85e 100644 --- a/Pipfile +++ b/Pipfile @@ -8,6 +8,7 @@ fugashi = "*" unidic = "*" pykakasi = "*" django = "*" +django-csp = "*" [dev-packages] diff --git a/ktsite/settings.py b/ktsite/settings.py index 2e0c3cf..00b2bef 100644 --- a/ktsite/settings.py +++ b/ktsite/settings.py @@ -32,6 +32,7 @@ ALLOWED_HOSTS = ['topcez-lhw', '127.0.0.1'] INSTALLED_APPS = [ 'lyrics', + 'csp', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -41,6 +42,7 @@ INSTALLED_APPS = [ ] MIDDLEWARE = [ + 'csp.middleware.CSPMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -122,3 +124,10 @@ STATIC_URL = 'static/' # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# CSP +# CSP_REPORT_ONLY = True +CSP_DEFAULT_SRC = ["'self'", '*', "'unsafe-inline'", 'blob:'] +CSP_STYLE_SRC = ["'self'", "'unsafe-inline'"] +CSP_REPORT_URI = ["'http://127.0.0.1'"] +CSP_WORKER_SRC = ["'self'", '*', "'unsafe-inline'"] diff --git a/lyrics/static/lyrics/input.js b/lyrics/static/lyrics/input.js index 17ccbcc..ea1dc8a 100644 --- a/lyrics/static/lyrics/input.js +++ b/lyrics/static/lyrics/input.js @@ -3,9 +3,11 @@ const css_font_sizes = ['0%', 'xx-small', 'x-small', 'small', 'medium', 'large', const string_replacements_element = document.querySelector('#word_replacements_pre'); const word_overrides_element = document.querySelector('#word_replacements_post'); const lyrics_input_element = document.querySelector('#lyrics_input_textarea'); +const lyrics_tl_input_element = document.querySelector('#lyrics_tl_input_textarea'); const lyrics_output_element = document.querySelector('#lyrics_output'); const arrangement_input_element = document.querySelector('#arrangement_input_textarea'); const arrangement_output_element = document.querySelector('#arrangement_output'); +const lyrics_output_show_tl_element = document.querySelector('#lyrics_output_show_tl'); const lyrics_output_show_romaji_element = document.querySelector('#lyrics_output_show_romaji'); const lyrics_output_show_kanji_element = document.querySelector('#lyrics_output_show_kanji'); const req_tokenization_url = './tokenize'; @@ -38,11 +40,11 @@ function format_parsed_line(line) { return html; } -function update_lyrics_output(data) { - tokenized_lyric_lines = data.parsed_lines; +function update_lyrics_output() { var html = ''; lyric_section_ranges = {}; + const tl_lines = lyrics_tl_input_element.value.split('\n'); var current_lyric_section var line_counter = 0 tokenized_lyric_lines.forEach(line => { @@ -57,6 +59,8 @@ function update_lyrics_output(data) { current_lyric_section = section_match[1]; lyric_section_ranges[current_lyric_section] = [line_counter]; } + } else { + if (line_counter < tl_lines.length) html += `${tl_lines[line_counter]}
`; } html += format_parsed_line(line); line_counter++; @@ -70,10 +74,14 @@ function update_lyrics_output(data) { update_arrangement_output(); } +function on_receive_tokenized_lyrics(data) { + tokenized_lyric_lines = data.parsed_lines; + update_lyrics_output(); +} function update_arrangement_output() { console.log('Updating arrangement output'); - + const tl_lines = lyrics_tl_input_element.value.split('\n'); const arrangement_str = arrangement_input_element.value; var html = ''; arrangement_str.split(',').forEach(section => { @@ -84,6 +92,9 @@ function update_arrangement_output() { // html += `[${s}]
`; // Section name is already in the line range :) const [i0, i1] = lyric_section_ranges[s]; for (let i = i0; i < i1; i++) { + if ((typeof tokenized_lyric_lines[i]) != "string") { + if (i < tl_lines.length) html += `${tl_lines[i]}
`; + } html += format_parsed_line(tokenized_lyric_lines[i]); } } else { @@ -121,29 +132,41 @@ function lyrics_input_updated() { }) .then((rsp) => { console.debug(rsp); - update_lyrics_output(rsp); + on_receive_tokenized_lyrics(rsp); }) .catch((error) => { console.error(error); }) } +function update_outputs() { + update_lyrics_output(); + update_arrangement_output(); +} // lyrics_input_element.addEventListener('keyup', (event) => {if (update_lyrics_keys.has(event.key)) lyrics_input_updated();}); lyrics_input_element.addEventListener('change', lyrics_input_updated); +lyrics_tl_input_element.addEventListener('change', update_outputs); arrangement_input_element.addEventListener('change', update_arrangement_output); function update_output_styles() { style_lyrics_kanji.style.display = lyrics_output_show_kanji_element.value > 0 ? "inline" : "none"; style_lyrics_romaji.style.display = lyrics_output_show_romaji_element.value > 0 ? "inline" : "none"; + style_lyrics_tl.style.display = lyrics_output_show_tl_element.value > 0 ? "inline" : "none"; style_lyrics_kanji.style.fontSize = css_font_sizes[lyrics_output_show_kanji_element.value]; style_lyrics_romaji.style.fontSize = css_font_sizes[lyrics_output_show_romaji_element.value]; + style_lyrics_tl.style.fontSize = css_font_sizes[lyrics_output_show_tl_element.value]; } const stylesheet = document.styleSheets[0]; -console.log(stylesheet.cssRules) +// console.log(stylesheet.cssRules) const style_lyrics_kanji = stylesheet.cssRules[stylesheet.insertRule('.lyrics-kanji {display: inline}')]; const style_lyrics_romaji = stylesheet.cssRules[stylesheet.insertRule('.lyrics-romaji {display: inline}')]; -console.log(style_lyrics_kanji) -console.log(style_lyrics_romaji) +const style_lyrics_tl = stylesheet.cssRules[stylesheet.insertRule('.lyrics-tl {display: inline}')]; +// console.log(style_lyrics_kanji) +// console.log(style_lyrics_romaji) lyrics_output_show_kanji_element.addEventListener('change', update_output_styles); lyrics_output_show_romaji_element.addEventListener('change', update_output_styles); +lyrics_output_show_tl_element.addEventListener('change', update_output_styles); update_output_styles(); + +document.getElementById('btn_syllable').addEventListener('click', () => {console.log(`Syllable button clicked! ${performance.now()}ms`)}); +document.getElementById('btn_metronome').addEventListener('click', () => {console.log(`Metronome button clicked! ${performance.now()}ms`)}); diff --git a/lyrics/static/lyrics/metronome.js b/lyrics/static/lyrics/metronome.js new file mode 100644 index 0000000..1dabef6 --- /dev/null +++ b/lyrics/static/lyrics/metronome.js @@ -0,0 +1,24 @@ +function noteDurationToMs (bpm, dur, type) { + return 60000 * 4 * dur * type / bpm +} + +function scheduleNote(ac, time, dur) { + var osc = ac.createOscillator(); + osc.connect( ac.destination ); + osc.start(time); + osc.stop(time + dur); +} + +const ac = new AudioContext(); +let lastNote= ac.currentTime; +const step = noteDurationToMs(120, 1 / 4, 1) / 1000; +const lookAhead = step / 2; + +setInterval(()=> { + const diff = ac.currentTime - lastNote; + if (diff >= lookAhead) { + const nextNote = lastNote + step; + scheduleNote(ac, nextNote, 0.025) + lastNote = nextNote; + } +},15) diff --git a/lyrics/static/lyrics/style.css b/lyrics/static/lyrics/style.css index ba42e38..718267f 100644 --- a/lyrics/static/lyrics/style.css +++ b/lyrics/static/lyrics/style.css @@ -1,10 +1,11 @@ -video, input { +video { display: block; } -input { +/* input { + display: block; width: 100%; -} +} */ .info { background-color: aqua; @@ -41,6 +42,15 @@ input { #arrangement_input { max-width: 260px; } +#line_timer { + max-width: 200px; +} +#syllable_timer { + max-width: 200px; +} +#syllable_timer input { + max-width: 48px; +} .flex-container { display: flex; @@ -70,3 +80,6 @@ input { .lyrics-kanji { font-family: Droid Sans Japanese; } +.lyrics-tl { + font-style: italic; +} diff --git a/lyrics/static/lyrics/subtitle_generator.js b/lyrics/static/lyrics/subtitle_generator.js new file mode 100644 index 0000000..33b54a3 --- /dev/null +++ b/lyrics/static/lyrics/subtitle_generator.js @@ -0,0 +1,38 @@ +// This is mostly a port of the python code. It might desync from that over time. +function generate_subtitles() { + const format_defaults = { + 'PlayResX': 1280, + 'PlayResY': 720, + 'LatinFont': 'Droid Sans', + 'JapaneseFont': 'Droid Sans Japanese', + 'TranslationSize': 36, + 'RomajiSize': 48, + 'KanjiSize': 72, + 'KanjiVMargin': 20, + 'FuriSize': 36, + 'FuriVMargin': 85, + 'KaraokeColourFuture': '000019FF', + 'KaraokeColourPast': 'E02A0A00', + } + const f = format_defaults; + s = `[Script Info] +ScriptType: v4.00+ +WrapStyle: 0 +ScaledBorderAndShadow: yes +YCbCr Matrix: TV.709 +PlayResX: ${f.PlayResX} +PlayResY: ${f.PlayResY} + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,${f.LatinFont},72,&H002A0A00,&H000019FF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,8,30,30,25,1 +Style: Kanji,${f.JapaneseFont},${f.KanjiSize},&H${f.KaraokeColourPast},&H${f.KaraokeColourFuture},&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,4.0,0,2,30,30,${f.KanjiVMargin},1 +Style: Furigana,${f.JapaneseFont},${f.FuriSize},&H${f.KaraokeColourPast},&H${f.KaraokeColourFuture},&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,2,0,0,${f.FuriVMargin},1 +Style: Romaji,${f.LatinFont},${f.RomajiSize},&H${f.KaraokeColourPast},&H${f.KaraokeColourFuture},&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,8,30,30,20,1 +Style: Translation,${f.LatinFont},${f.TranslationSize},&H00FFFFFF,&H000019FF,&H00000000,&H00000000,0,1,0,0,100,100,0,0,1,1.0,3,8,30,30,20,1 + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text` + // TODO: add the lines + return s; +} \ No newline at end of file diff --git a/lyrics/static/lyrics/video.js b/lyrics/static/lyrics/video.js index ae6c13d..6311cf0 100644 --- a/lyrics/static/lyrics/video.js +++ b/lyrics/static/lyrics/video.js @@ -4,11 +4,12 @@ import JASSUB from '../jass/jassub.es.js' 'use strict' var URL = window.URL || window.webkitURL var displayMessage = function (message, isError) { - var element = document.querySelector('#message') + var element = document.querySelector('#video_message') element.innerHTML = message element.className = isError ? 'error' : 'info' } var playSelectedFile = function (event) { + console.log('Attempting to play video file'); var file = this.files[0] var type = file.type var videoNode = document.querySelector('video') @@ -25,13 +26,13 @@ import JASSUB from '../jass/jassub.es.js' var fileURL = URL.createObjectURL(file) videoNode.src = fileURL } - var inputNode = document.querySelector('input') + var inputNode = document.querySelector('#video_selector_input') inputNode.addEventListener('change', playSelectedFile, false) })() // import font from '../jass/DroidSansJapanese.ttf' const renderer = new JASSUB({ - video: document.querySelector('video'), + video: document.querySelector('#local_video'), subUrl: '../test.ass', fonts: [], // fallbackFont: 'liberation sans', diff --git a/lyrics/static/test.ass b/lyrics/static/test.ass new file mode 100644 index 0000000..1b6237c --- /dev/null +++ b/lyrics/static/test.ass @@ -0,0 +1,88 @@ +[Script Info] +ScriptType: v4.00+ +WrapStyle: 0 +ScaledBorderAndShadow: yes +YCbCr Matrix: TV.709 +PlayResX: 1280 +PlayResY: 720 + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,Droid Sans,72,&H002A0A00,&H000019FF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,8,30,30,25,1 +Style: Kanji,Droid Sans Japanese,72,&HE02A0A00,&H000019FF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,4.0,0,2,30,30,20,1 +Style: Furigana,Droid Sans Japanese,36,&HE02A0A00,&H000019FF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,2,0,0,85,1 +Style: Romaji,Droid Sans,48,&HE02A0A00,&H000019FF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,2.5,0,8,30,30,20,1 +Style: Translation,Droid Sans,36,&H00FFFFFF,&H000019FF,&H00000000,&H00000000,0,1,0,0,100,100,0,0,1,1.0,3,8,30,30,20,1 + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,00:01:08.00,00:01:20.50,Translation,,,,,,Rain and snow fall from the heavens. Moisten the earth and make it sprout +Dialogue: 0,00:01:08.00,00:01:20.50,Romaji,,,,,,{\k0}{\K50}a{\K50}me{\k0} {\K50}ya{\k0} {\K50}yu{\K50}ki{\k0} {\K50}ga{\k0} {\K50}ten{\k0} {\K50}ka{\K50}ra{\k0} {\K50}fu{\K50}tte{\k0} {\K50}chi{\k0} {\K50}wo{\k0} {\K50}u{\K50}ru{\K50}o{\K50}shi{\k0} {\K50}me{\k0} {\K50}wo{\k0} {\K50}da{\K50}sa{\k0} {\K50}se{\K50}ru +Dialogue: 0,00:01:08.00,00:01:20.50,Kanji,,,,,,{\k0}{\K100}雨{\K50}や{\K100}雪{\K50}が{\K50}天{\K50}か{\K50}ら{\K50}降{\K50}って{\K50}地{\K50}を{\K150}潤{\K50}し{\k0} {\K50}芽{\K50}を{\K50}出{\K50}さ{\K50}せ{\K50}る +Dialogue: 1,00:01:08.00,00:01:20.50,Furigana,,90,1190,,,{\k0}{\K50}あ{\K50}め +Dialogue: 1,00:01:08.00,00:01:20.50,Furigana,,200,1080,,,{\k150}{\K50}ゆ{\K50}き +Dialogue: 1,00:01:08.00,00:01:20.50,Furigana,,310,970,,,{\k300}{\K50}てん +Dialogue: 1,00:01:08.00,00:01:20.50,Furigana,,475,805,,,{\k450}{\K50}ふ +Dialogue: 1,00:01:08.00,00:01:20.50,Furigana,,640,640,,,{\k550}{\K50}ち +Dialogue: 1,00:01:08.00,00:01:20.50,Furigana,,750,530,,,{\k650}{\K50}う{\K50}る{\K50}お +Dialogue: 1,00:01:08.00,00:01:20.50,Furigana,,915,365,,,{\k850}{\K50}め +Dialogue: 1,00:01:08.00,00:01:20.50,Furigana,,1025,255,,,{\k950}{\K50}だ +Dialogue: 0,00:01:20.50,00:01:33.50,Translation,,,,,,Seed to those who sow. And bread to those who eat. +Dialogue: 0,00:01:20.50,00:01:33.50,Romaji,,,,,,{\k0}{\K50}ta{\K50}ne{\K50}ma{\K50}ki{\k0} {\K50}ku{\k0} {\K50}nin{\k0} {\K50}ni{\k0} {\K50}shu{\k0} {\K50}wo{\k0} {\K50}a{\K50}ta{\K50}e{\k0} {\K50}ta{\K50}be{\K50}ru{\k0} {\K50}hi{\K50}to{\k0} {\K50}ni{\k0} {\K50}pan{\k0} {\K50}wo{\k0} {\K50}a{\K50}ta{\K50}e{\K50}ru +Dialogue: 0,00:01:20.50,00:01:33.50,Kanji,,,,,,{\k0}{\K200}種蒔{\K50}く{\K50}人{\K50}に{\k0} {\K50}種{\K50}を{\K100}与{\K50}え{\k0} {\K50}食{\K50}べ{\K50}る{\K100}人{\K50}に{\k0} {\K50}パン{\K50}を{\K100}与{\K50}え{\K50}る +Dialogue: 1,00:01:20.50,00:01:33.50,Furigana,,62,1217,,,{\k0}{\K50}た{\K50}ね{\K50}ま{\K50}き +Dialogue: 1,00:01:20.50,00:01:33.50,Furigana,,200,1080,,,{\k250}{\K50}にん +Dialogue: 1,00:01:20.50,00:01:33.50,Furigana,,365,915,,,{\k350}{\K50}しゅ +Dialogue: 1,00:01:20.50,00:01:33.50,Furigana,,475,805,,,{\k450}{\K50}あ{\K50}た +Dialogue: 1,00:01:20.50,00:01:33.50,Furigana,,640,640,,,{\k600}{\K50}た +Dialogue: 1,00:01:20.50,00:01:33.50,Furigana,,805,475,,,{\k750}{\K50}ひ{\K50}と +Dialogue: 1,00:01:20.50,00:01:33.50,Furigana,,1135,145,,,{\k1000}{\K50}あ{\K50}た +Dialogue: 0,00:01:33.50,00:01:46.00,Translation,,,,,,The Lord's thoughts always go beyond my thoughts +Dialogue: 0,00:01:33.50,00:01:46.00,Romaji,,,,,,{\k0}{\K50}shu{\k0} {\K50}no{\k0} {\K50}o{\K50}mo{\K50}i{\k0} {\K50}ha{\k0} {\K50}i{\K50}tsu{\k0} {\K50}de{\k0} {\K50}mo{\k0} {\K50}wa{\K50}ta{\K50}shi{\k0} {\K50}no{\k0} {\K50}o{\K50}mo{\K50}i{\k0} {\K50}wo{\k0} {\K50}ko{\K50}e{\k0} {\K50}te{\k0} {\K50}i{\K50}ku +Dialogue: 0,00:01:33.50,00:01:46.00,Kanji,,,,,,{\k0}{\K50}主{\K50}の{\K100}思{\K50}い{\K50}は{\K50}い{\K50}つ{\K50}で{\K50}も{\k0} {\K150}私{\K50}の{\K100}思{\K50}い{\K50}を{\K50}超{\K50}え{\K50}て{\K50}い{\K50}く +Dialogue: 1,00:01:33.50,00:01:46.00,Furigana,,117,1162,,,{\k0}{\K50}しゅ +Dialogue: 1,00:01:33.50,00:01:46.00,Furigana,,227,1052,,,{\k100}{\K50}お{\K50}も +Dialogue: 1,00:01:33.50,00:01:46.00,Furigana,,667,612,,,{\k500}{\K50}わ{\K50}た{\K50}し +Dialogue: 1,00:01:33.50,00:01:46.00,Furigana,,777,502,,,{\k700}{\K50}お{\K50}も +Dialogue: 1,00:01:33.50,00:01:46.00,Furigana,,942,337,,,{\k900}{\K50}こ +Dialogue: 0,00:01:46.00,00:01:58.50,Translation,,,,,,The words you have spoken Will not return in vain. And I will see it done. +Dialogue: 0,00:01:46.00,00:01:58.50,Romaji,,,,,,{\k0}{\K50}a{\K50}na{\K50}ta{\k0} {\K50}ga{\k0} {\K50}tsu{\K50}ge{\k0} {\K50}ta{\k0} {\K50}ko{\K50}to{\K50}ba{\k0} {\K50}ha{\k0} {\K50}mu{\K50}na{\K50}shi{\K50}ku{\k0} {\K50}ka{\K50}e{\K50}ru{\k0} {\K50}ko{\K50}to{\k0} {\K50}ha{\k0} {\K50}na{\K50}i +Dialogue: 0,00:01:46.00,00:01:58.50,Kanji,,,,,,{\k0}{\K50}あ{\K50}な{\K50}た{\K50}が{\K50}告{\K50}げ{\K50}た{\K150}言葉{\K50}は{\k0} {\K100}空{\K50}し{\K50}く{\K100}帰{\K50}る{\K50}こ{\K50}と{\K50}は{\K50}な{\K50}い +Dialogue: 1,00:01:46.00,00:01:58.50,Furigana,,310,970,,,{\k200}{\K50}つ +Dialogue: 1,00:01:46.00,00:01:58.50,Furigana,,502,777,,,{\k350}{\K50}こ{\K50}と{\K50}ば +Dialogue: 1,00:01:46.00,00:01:58.50,Furigana,,695,585,,,{\k550}{\K50}む{\K50}な +Dialogue: 1,00:01:46.00,00:01:58.50,Furigana,,860,420,,,{\k750}{\K50}か{\K50}え +Dialogue: 0,00:01:58.50,00:02:12.00,Translation,,,,,,You will succeed in the mission you have given. +Dialogue: 0,00:01:58.50,00:02:12.00,Romaji,,,,,,{\k0}{\K50}ka{\K50}na{\K50}ra{\K50}zu{\k0} {\K50}so{\K50}re{\k0} {\K50}wo{\k0} {\K50}na{\K50}shi{\K50}to{\K50}ge{\k0} {\K50}a{\K50}ta{\K50}e{\k0} {\K50}ta{\k0} {\K50}shi{\K50}me{\K50}i{\k0} {\K50}se{\K50}i{\K50}ko{\K50}u{\k0} {\K50}sa{\k0} {\K50}se{\K50}ru +Dialogue: 0,00:01:58.50,00:02:12.00,Kanji,,,,,,{\k0}{\K150}必{\K50}ず{\K50}そ{\K50}れ{\K50}を{\k0} {\K50}成{\K50}し{\K50}遂{\K50}げ{\k0} {\K100}与{\K50}え{\K50}た{\K150}使命{\k0} {\K200}成功{\K50}さ{\K50}せ{\K50}る +Dialogue: 1,00:01:58.50,00:02:12.00,Furigana,,62,1217,,,{\k0}{\K50}か{\K50}な{\K50}ら +Dialogue: 1,00:01:58.50,00:02:12.00,Furigana,,392,887,,,{\k350}{\K50}な +Dialogue: 1,00:01:58.50,00:02:12.00,Furigana,,502,777,,,{\k450}{\K50}と +Dialogue: 1,00:01:58.50,00:02:12.00,Furigana,,667,612,,,{\k550}{\K50}あ{\K50}た +Dialogue: 1,00:01:58.50,00:02:12.00,Furigana,,860,420,,,{\k750}{\K50}し{\K50}め{\K50}い +Dialogue: 1,00:01:58.50,00:02:12.00,Furigana,,1025,255,,,{\k900}{\K50}せ{\K50}い{\K50}こ{\K50}う +Dialogue: 0,00:02:12.00,00:02:26.50,Translation,,,,,,Praise the Lord, my soul. Rejoice and be glad, and see what the Lord has done. +Dialogue: 0,00:02:12.00,00:02:26.50,Romaji,,,,,,{\k0}{\K50}ho{\K50}me{\K50}ta{\K50}ta{\K50}e{\K50}yo{\k0} {\K50}wa{\K50}ga{\k0} {\K50}ta{\K50}ma{\K50}shi{\K50}i{\k0} {\K50}yo{\k0} {\K50}yo{\K50}ro{\K50}ko{\K50}bi{\k0} {\K50}mo{\K50}te{\k0} {\K50}shu{\k0} {\K50}no{\k0} {\K50}mi{\k0} {\K50}wa{\K50}sa{\k0} {\K50}wo{\k0} {\K50}mi{\K50}yo +Dialogue: 0,00:02:12.00,00:02:26.50,Kanji,,,,,,{\k0}{\K50}褒{\K50}め{\K50}た{\K50}た{\K50}え{\K50}よ{\k0} {\K50}我{\K50}が{\K200}魂{\K50}よ{\k0} {\K150}喜{\K50}び{\K50}持{\K50}て{\k0} {\K50}主{\K50}の{\K50}み{\K50}わ{\K50}さ{\K50}を{\K50}見{\K50}よ +Dialogue: 1,00:02:12.00,00:02:26.50,Furigana,,-20,1300,,,{\k0}{\K50}ほ +Dialogue: 1,00:02:12.00,00:02:26.50,Furigana,,365,915,,,{\k300}{\K50}わ +Dialogue: 1,00:02:12.00,00:02:26.50,Furigana,,475,805,,,{\k400}{\K50}た{\K50}ま{\K50}し{\K50}い +Dialogue: 1,00:02:12.00,00:02:26.50,Furigana,,640,640,,,{\k650}{\K50}よ{\K50}ろ{\K50}こ +Dialogue: 1,00:02:12.00,00:02:26.50,Furigana,,750,530,,,{\k850}{\K50}も +Dialogue: 1,00:02:12.00,00:02:26.50,Furigana,,915,365,,,{\k950}{\K50}しゅ +Dialogue: 1,00:02:12.00,00:02:26.50,Furigana,,1245,35,,,{\k1250}{\K50}み +Dialogue: 0,00:02:26.50,00:02:40.00,Translation,,,,,,Hallelujah. Glory to the Lord. +Dialogue: 0,00:02:26.50,00:02:40.00,Romaji,,,,,,{\k0}{\K50}ha{\K50}re{\K50}ru{\K50}ya{\k0} {\K50}shu{\k0} {\K50}ni{\k0} {\K50}e{\K50}i{\K50}ko{\K50}u{\k0} {\K50}a{\K50}re{\k0} {\K50}i{\K50}ke{\K50}ru{\k0} {\K50}ka{\K50}mi{\k0} {\K50}ni{\k0} {\K50}yu{\K50}da{\K50}ne{\k0} {\K50}te{\k0} {\K50}i{\K50}ki{\K50}ru +Dialogue: 0,00:02:26.50,00:02:40.00,Kanji,,,,,,{\k0}{\K50}ハ{\K50}レ{\K50}ル{\K50}ヤ{\k0} {\K50}主{\K50}に{\K200}栄光{\K50}あ{\K50}れ{\k0} {\K50}生{\K50}け{\K50}る{\K100}神{\K50}に{\k0} {\K100}委{\K50}ね{\K50}て{\K50}生{\K50}き{\K50}る +Dialogue: 1,00:02:26.50,00:02:40.00,Furigana,,282,997,,,{\k200}{\K50}しゅ +Dialogue: 1,00:02:26.50,00:02:40.00,Furigana,,420,860,,,{\k300}{\K50}え{\K50}い{\K50}こ{\K50}う +Dialogue: 1,00:02:26.50,00:02:40.00,Furigana,,667,612,,,{\k600}{\K50}い +Dialogue: 1,00:02:26.50,00:02:40.00,Furigana,,832,447,,,{\k750}{\K50}か{\K50}み +Dialogue: 1,00:02:26.50,00:02:40.00,Furigana,,997,282,,,{\k900}{\K50}ゆ{\K50}だ +Dialogue: 1,00:02:26.50,00:02:40.00,Furigana,,1162,117,,,{\k1100}{\K50}い +Dialogue: 0,00:02:40.00,00:02:47.50,Translation,,,,,,I entrust my life to the living God. The living God is with us. +Dialogue: 0,00:02:40.00,00:02:47.50,Romaji,,,,,,{\k0}{\K50}i{\K50}ke{\K50}ru{\k0} {\K50}ka{\K50}mi{\k0} {\K50}ga{\k0} {\K50}to{\K50}mo{\k0} {\K50}ni{\k0} {\K50}o{\K50}sa{\k0} {\K50}re{\K50}ru +Dialogue: 0,00:02:40.00,00:02:47.50,Kanji,,,,,,{\k0}{\K50}生{\K50}け{\K50}る{\K100}神{\K50}が{\k0} {\K100}共{\K50}に{\K50}お{\K50}さ{\K50}れ{\K50}る +Dialogue: 1,00:02:40.00,00:02:47.50,Furigana,,337,942,,,{\k0}{\K50}い +Dialogue: 1,00:02:40.00,00:02:47.50,Furigana,,502,777,,,{\k150}{\K50}か{\K50}み +Dialogue: 1,00:02:40.00,00:02:47.50,Furigana,,667,612,,,{\k300}{\K50}と{\K50}も diff --git a/lyrics/templates/lyrics/index.html b/lyrics/templates/lyrics/index.html index c114c3d..6ea3f2d 100644 --- a/lyrics/templates/lyrics/index.html +++ b/lyrics/templates/lyrics/index.html @@ -64,12 +64,17 @@

Lyrics Input

- +

Japanese

+ +

Translation

+
+ + @@ -84,13 +89,31 @@
+ +
+

Syllable Timer

+ This will have a metronome and a button for tapping out the rhythm of a given line.
+ + + + + + +
+ +
+

Line Timer

+ Tap the start of each line along with the video to record timing information.
+ + +

Video

You may select a local video file for subtitle timing and preview. It will not be uploaded anywhere. -
- - +
+ +

Lyrics Output