lyrics-transcriber 0.49.1__py3-none-any.whl → 0.49.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lyrics_transcriber/output/cdg.py +4 -1
- lyrics_transcriber/output/cdgmaker/composer.py +165 -118
- lyrics_transcriber/output/cdgmaker/config.py +1 -1
- lyrics_transcriber/output/cdgmaker/render.py +1 -1
- {lyrics_transcriber-0.49.1.dist-info → lyrics_transcriber-0.49.2.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.49.1.dist-info → lyrics_transcriber-0.49.2.dist-info}/RECORD +9 -9
- {lyrics_transcriber-0.49.1.dist-info → lyrics_transcriber-0.49.2.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.49.1.dist-info → lyrics_transcriber-0.49.2.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.49.1.dist-info → lyrics_transcriber-0.49.2.dist-info}/entry_points.txt +0 -0
lyrics_transcriber/output/cdg.py
CHANGED
@@ -188,7 +188,7 @@ class CDGGenerator:
|
|
188
188
|
|
189
189
|
def _compose_cdg(self, toml_file: str) -> None:
|
190
190
|
"""Compose CDG using KaraokeComposer."""
|
191
|
-
kc = KaraokeComposer.from_file(toml_file)
|
191
|
+
kc = KaraokeComposer.from_file(toml_file, logger=self.logger)
|
192
192
|
kc.compose()
|
193
193
|
# kc.create_mp4(height=1080, fps=30)
|
194
194
|
|
@@ -302,6 +302,7 @@ class CDGGenerator:
|
|
302
302
|
"lead_in_duration",
|
303
303
|
"lead_in_total",
|
304
304
|
"title_artist_gap",
|
305
|
+
"title_top_padding",
|
305
306
|
"intro_duration_seconds",
|
306
307
|
"first_syllable_buffer_seconds",
|
307
308
|
"outro_background",
|
@@ -414,6 +415,8 @@ class CDGGenerator:
|
|
414
415
|
"title_screen_transition": cdg_styles["title_screen_transition"],
|
415
416
|
"instrumentals": instrumentals,
|
416
417
|
"intro_duration_seconds": cdg_styles["intro_duration_seconds"],
|
418
|
+
"title_top_padding": cdg_styles["title_top_padding"],
|
419
|
+
"title_artist_gap": cdg_styles["title_artist_gap"],
|
417
420
|
"first_syllable_buffer_seconds": cdg_styles["first_syllable_buffer_seconds"],
|
418
421
|
"outro_background": cdg_styles["outro_background"],
|
419
422
|
"outro_transition": cdg_styles["outro_transition"],
|
@@ -27,8 +27,6 @@ from .utils import *
|
|
27
27
|
|
28
28
|
import logging
|
29
29
|
|
30
|
-
logger = logging.getLogger(__name__)
|
31
|
-
|
32
30
|
ASS_REQUIREMENTS = True
|
33
31
|
try:
|
34
32
|
import ass
|
@@ -204,13 +202,16 @@ class KaraokeComposer:
|
|
204
202
|
self,
|
205
203
|
config: Settings,
|
206
204
|
relative_dir: "StrOrBytesPath | Path" = "",
|
205
|
+
logger=None,
|
207
206
|
):
|
208
207
|
self.config = config
|
209
208
|
self.relative_dir = Path(relative_dir)
|
210
|
-
logger.
|
209
|
+
self.logger = logger or logging.getLogger(__name__)
|
210
|
+
|
211
|
+
self.logger.debug("loading config settings")
|
211
212
|
|
212
213
|
font_path = self.config.font
|
213
|
-
logger.debug(f"font_path: {font_path}")
|
214
|
+
self.logger.debug(f"font_path: {font_path}")
|
214
215
|
try:
|
215
216
|
# First, use the font path directly from the config
|
216
217
|
if not Path(font_path).is_file():
|
@@ -223,7 +224,7 @@ class KaraokeComposer:
|
|
223
224
|
raise FileNotFoundError(f"Font file not found: {self.config.font}")
|
224
225
|
self.font = ImageFont.truetype(str(font_path), self.config.font_size)
|
225
226
|
except Exception as e:
|
226
|
-
logger.error(f"Error loading font: {e}")
|
227
|
+
self.logger.error(f"Error loading font: {e}")
|
227
228
|
raise
|
228
229
|
|
229
230
|
# Set color table for lyrics sections
|
@@ -254,13 +255,13 @@ class KaraokeComposer:
|
|
254
255
|
padvalue=self.UNUSED_COLOR,
|
255
256
|
)
|
256
257
|
)
|
257
|
-
logger.debug(f"Color table: {self.color_table}")
|
258
|
+
self.logger.debug(f"Color table: {self.color_table}")
|
258
259
|
|
259
260
|
self.max_tile_height = 0
|
260
261
|
self.lyrics: list[LyricInfo] = []
|
261
262
|
# Process lyric sets
|
262
263
|
for ci, lyric in enumerate(self.config.lyrics):
|
263
|
-
logger.debug(f"processing config lyric {ci}")
|
264
|
+
self.logger.debug(f"processing config lyric {ci}")
|
264
265
|
lines: list[list[str]] = []
|
265
266
|
line_singers: list[int] = []
|
266
267
|
for textline in re.split(r"\n+", lyric.text):
|
@@ -290,21 +291,22 @@ class KaraokeComposer:
|
|
290
291
|
)
|
291
292
|
]
|
292
293
|
|
293
|
-
logger.debug(f"singer {singer}: {syllables}")
|
294
|
+
self.logger.debug(f"singer {singer}: {syllables}")
|
294
295
|
lines.append(syllables)
|
295
296
|
line_singers.append(singer)
|
296
297
|
|
297
|
-
logger.debug(f"rendering line images and masks for lyric {ci}")
|
298
|
+
self.logger.debug(f"rendering line images and masks for lyric {ci}")
|
298
299
|
line_images, line_masks = render_lines_and_masks(
|
299
300
|
lines,
|
300
301
|
font=self.font,
|
301
302
|
stroke_width=self.config.stroke_width,
|
302
303
|
stroke_type=self.config.stroke_type,
|
304
|
+
logger=self.logger,
|
303
305
|
)
|
304
306
|
max_height = 0
|
305
307
|
for li, image in enumerate(line_images):
|
306
308
|
if image.width > CDG_VISIBLE_WIDTH:
|
307
|
-
logger.warning(
|
309
|
+
self.logger.warning(
|
308
310
|
f"line {li} too wide\n"
|
309
311
|
f"max width is {CDG_VISIBLE_WIDTH} pixel(s); "
|
310
312
|
f"actual width is {image.width} pixel(s)\n"
|
@@ -317,7 +319,7 @@ class KaraokeComposer:
|
|
317
319
|
|
318
320
|
lyric_lines: list[LineInfo] = []
|
319
321
|
sync_i = 0
|
320
|
-
logger.debug(f"setting sync points for lyric {ci}")
|
322
|
+
self.logger.debug(f"setting sync points for lyric {ci}")
|
321
323
|
for li, (line, singer, line_image, line_mask) in enumerate(
|
322
324
|
zip(
|
323
325
|
lines,
|
@@ -402,7 +404,7 @@ class KaraokeComposer:
|
|
402
404
|
# Add vertical offset to lines to vertically center them
|
403
405
|
max_height = max(line.image.height for lyric in self.lyrics for line in lyric.lines)
|
404
406
|
line_offset = (self.max_tile_height * CDG_TILE_HEIGHT - max_height) // 2
|
405
|
-
logger.debug(f"lines will be vertically offset by {line_offset} pixel(s)")
|
407
|
+
self.logger.debug(f"lines will be vertically offset by {line_offset} pixel(s)")
|
406
408
|
if line_offset:
|
407
409
|
for lyric in self.lyrics:
|
408
410
|
for line in lyric.lines:
|
@@ -411,7 +413,7 @@ class KaraokeComposer:
|
|
411
413
|
self.sync_offset = sync_to_cdg(self.config.sync_offset)
|
412
414
|
|
413
415
|
self.writer = CDGWriter()
|
414
|
-
logger.info("config settings loaded")
|
416
|
+
self.logger.info("config settings loaded")
|
415
417
|
|
416
418
|
self._set_draw_times()
|
417
419
|
|
@@ -419,6 +421,7 @@ class KaraokeComposer:
|
|
419
421
|
def from_file(
|
420
422
|
cls,
|
421
423
|
file: "FileDescriptorOrPath",
|
424
|
+
logger=None,
|
422
425
|
) -> Self:
|
423
426
|
converter = Converter(prefer_attrib_converters=True)
|
424
427
|
relative_dir = Path(file).parent
|
@@ -426,6 +429,7 @@ class KaraokeComposer:
|
|
426
429
|
return cls(
|
427
430
|
converter.structure(tomllib.load(stream), Settings),
|
428
431
|
relative_dir=relative_dir,
|
432
|
+
logger=logger,
|
429
433
|
)
|
430
434
|
|
431
435
|
@classmethod
|
@@ -454,7 +458,7 @@ class KaraokeComposer:
|
|
454
458
|
def _set_draw_times(self):
|
455
459
|
self.lyric_times: list[LyricTimes] = []
|
456
460
|
for lyric in self.lyrics:
|
457
|
-
logger.debug(f"setting draw times for lyric {lyric.lyric_index}")
|
461
|
+
self.logger.debug(f"setting draw times for lyric {lyric.lyric_index}")
|
458
462
|
line_count = len(lyric.lines)
|
459
463
|
line_draw: list[int] = [0] * line_count
|
460
464
|
line_erase: list[int] = [0] * line_count
|
@@ -513,15 +517,15 @@ class KaraokeComposer:
|
|
513
517
|
line_erase[end_line] = erase_time
|
514
518
|
erase_time += self.LINE_DRAW_ERASE_GAP
|
515
519
|
|
516
|
-
logger.debug(f"lyric {lyric.lyric_index} draw times: {line_draw!r}")
|
517
|
-
logger.debug(f"lyric {lyric.lyric_index} erase times: {line_erase!r}")
|
520
|
+
self.logger.debug(f"lyric {lyric.lyric_index} draw times: {line_draw!r}")
|
521
|
+
self.logger.debug(f"lyric {lyric.lyric_index} erase times: {line_erase!r}")
|
518
522
|
self.lyric_times.append(
|
519
523
|
LyricTimes(
|
520
524
|
line_draw=line_draw,
|
521
525
|
line_erase=line_erase,
|
522
526
|
)
|
523
527
|
)
|
524
|
-
logger.info("draw times set")
|
528
|
+
self.logger.info("draw times set")
|
525
529
|
|
526
530
|
def _set_draw_times_page(
|
527
531
|
self,
|
@@ -557,7 +561,7 @@ class KaraokeComposer:
|
|
557
561
|
|
558
562
|
# Warn the user if there's not likely to be enough time
|
559
563
|
if minimum_time < 32:
|
560
|
-
logger.warning("not enough bandwidth to clear screen on lyric " f"{wipe.lyric_index} line {wipe.line_index}")
|
564
|
+
self.logger.warning("not enough bandwidth to clear screen on lyric " f"{wipe.lyric_index} line {wipe.line_index}")
|
561
565
|
|
562
566
|
# If there's not enough time between the end of the last line
|
563
567
|
# and the start of this line, but there is enough time between
|
@@ -771,9 +775,9 @@ class KaraokeComposer:
|
|
771
775
|
if self.config.clear_mode == LyricClearMode.PAGE and len(self.lyrics) > 1:
|
772
776
|
raise RuntimeError("page mode doesn't support more than one lyric set")
|
773
777
|
|
774
|
-
logger.debug("loading song file")
|
778
|
+
self.logger.debug("loading song file")
|
775
779
|
song: AudioSegment = AudioSegment.from_file(file_relative_to(self.config.file, self.relative_dir))
|
776
|
-
logger.info("song file loaded")
|
780
|
+
self.logger.info("song file loaded")
|
777
781
|
|
778
782
|
self.lyric_packet_indices: set[int] = set()
|
779
783
|
self.instrumental_times: list[int] = []
|
@@ -819,7 +823,7 @@ class KaraokeComposer:
|
|
819
823
|
should_instrumental = current_time >= instrumental_time
|
820
824
|
# If there should not be an instrumental section now
|
821
825
|
if not should_instrumental:
|
822
|
-
logger.debug("instrumental intro is not present; clearing")
|
826
|
+
self.logger.debug("instrumental intro is not present; clearing")
|
823
827
|
# Clear the screen
|
824
828
|
self.writer.queue_packets(
|
825
829
|
[
|
@@ -830,7 +834,7 @@ class KaraokeComposer:
|
|
830
834
|
if self.config.border is not None:
|
831
835
|
self.writer.queue_packet(border_preset(self.BORDER))
|
832
836
|
else:
|
833
|
-
logger.debug("instrumental intro is present; not clearing")
|
837
|
+
self.logger.debug("instrumental intro is present; not clearing")
|
834
838
|
|
835
839
|
# While there are lines to draw/erase, or syllables to
|
836
840
|
# highlight, or events in the highlight/draw queues, or
|
@@ -861,7 +865,7 @@ class KaraokeComposer:
|
|
861
865
|
)
|
862
866
|
|
863
867
|
# Add audio padding to intro
|
864
|
-
logger.debug("padding intro of audio file")
|
868
|
+
self.logger.debug("padding intro of audio file")
|
865
869
|
intro_silence: AudioSegment = AudioSegment.silent(
|
866
870
|
self.intro_delay * 1000 // CDG_FPS,
|
867
871
|
frame_rate=song.frame_rate,
|
@@ -872,7 +876,7 @@ class KaraokeComposer:
|
|
872
876
|
# outro (or next instrumental section) begins immediately after
|
873
877
|
# the end of the last syllable, which would be abrupt.
|
874
878
|
if self.config.clear_mode == LyricClearMode.PAGE:
|
875
|
-
logger.debug("clear mode is page; adding padding before outro")
|
879
|
+
self.logger.debug("clear mode is page; adding padding before outro")
|
876
880
|
self.writer.queue_packets([no_instruction()] * 3 * CDG_FPS)
|
877
881
|
|
878
882
|
# Calculate video padding before outro
|
@@ -884,17 +888,17 @@ class KaraokeComposer:
|
|
884
888
|
int(self.audio.duration_seconds * CDG_FPS),
|
885
889
|
self.writer.packets_queued + OUTRO_DURATION,
|
886
890
|
)
|
887
|
-
logger.debug(f"song should be {end} frame(s) long")
|
891
|
+
self.logger.debug(f"song should be {end} frame(s) long")
|
888
892
|
padding_before_outro = (end - OUTRO_DURATION) - self.writer.packets_queued
|
889
|
-
logger.debug(f"queueing {padding_before_outro} packets before outro")
|
893
|
+
self.logger.debug(f"queueing {padding_before_outro} packets before outro")
|
890
894
|
self.writer.queue_packets([no_instruction()] * padding_before_outro)
|
891
895
|
|
892
896
|
# Compose the outro (and thus, finish the video)
|
893
897
|
self._compose_outro(end)
|
894
|
-
logger.info("karaoke file composed")
|
898
|
+
self.logger.info("karaoke file composed")
|
895
899
|
|
896
900
|
# Add audio padding to outro (and thus, finish the audio)
|
897
|
-
logger.debug("padding outro of audio file")
|
901
|
+
self.logger.debug("padding outro of audio file")
|
898
902
|
outro_silence: AudioSegment = AudioSegment.silent(
|
899
903
|
((self.writer.packets_queued * 1000 // CDG_FPS) - int(self.audio.duration_seconds * 1000)),
|
900
904
|
frame_rate=song.frame_rate,
|
@@ -904,24 +908,24 @@ class KaraokeComposer:
|
|
904
908
|
# Write CDG and MP3 data to ZIP file
|
905
909
|
outname = self.config.outname
|
906
910
|
zipfile_name = self.relative_dir / Path(f"{outname}.zip")
|
907
|
-
logger.debug(f"creating {zipfile_name}")
|
911
|
+
self.logger.debug(f"creating {zipfile_name}")
|
908
912
|
with ZipFile(zipfile_name, "w") as zipfile:
|
909
913
|
cdg_bytes = BytesIO()
|
910
|
-
logger.debug("writing cdg packets to stream")
|
914
|
+
self.logger.debug("writing cdg packets to stream")
|
911
915
|
self.writer.write_packets(cdg_bytes)
|
912
|
-
logger.debug(f"writing stream to zipfile as {outname}.cdg")
|
916
|
+
self.logger.debug(f"writing stream to zipfile as {outname}.cdg")
|
913
917
|
cdg_bytes.seek(0)
|
914
918
|
zipfile.writestr(f"{outname}.cdg", cdg_bytes.read())
|
915
919
|
|
916
920
|
mp3_bytes = BytesIO()
|
917
|
-
logger.debug("writing mp3 data to stream")
|
921
|
+
self.logger.debug("writing mp3 data to stream")
|
918
922
|
self.audio.export(mp3_bytes, format="mp3")
|
919
|
-
logger.debug(f"writing stream to zipfile as {outname}.mp3")
|
923
|
+
self.logger.debug(f"writing stream to zipfile as {outname}.mp3")
|
920
924
|
mp3_bytes.seek(0)
|
921
925
|
zipfile.writestr(f"{outname}.mp3", mp3_bytes.read())
|
922
|
-
logger.info(f"karaoke files written to {zipfile_name}")
|
926
|
+
self.logger.info(f"karaoke files written to {zipfile_name}")
|
923
927
|
except Exception as e:
|
924
|
-
logger.error(f"Error in compose: {str(e)}", exc_info=True)
|
928
|
+
self.logger.error(f"Error in compose: {str(e)}", exc_info=True)
|
925
929
|
raise
|
926
930
|
|
927
931
|
def _compose_lyric(
|
@@ -954,10 +958,10 @@ class KaraokeComposer:
|
|
954
958
|
composer_state.this_page = line_draw_info.line_index // lyric.lines_per_page
|
955
959
|
# If this line is the start of a new page
|
956
960
|
if composer_state.this_page > composer_state.last_page:
|
957
|
-
logger.debug(f"going from page {composer_state.last_page} to " f"page {composer_state.this_page} in page mode")
|
961
|
+
self.logger.debug(f"going from page {composer_state.last_page} to " f"page {composer_state.this_page} in page mode")
|
958
962
|
# If we have not just cleared the screen
|
959
963
|
if not composer_state.just_cleared:
|
960
|
-
logger.debug("clearing screen on page transition")
|
964
|
+
self.logger.debug("clearing screen on page transition")
|
961
965
|
# Clear the last page
|
962
966
|
page_clear_packets = [
|
963
967
|
*memory_preset_repeat(self.BACKGROUND),
|
@@ -975,12 +979,12 @@ class KaraokeComposer:
|
|
975
979
|
# Update the current frame time
|
976
980
|
current_time += len(page_clear_packets)
|
977
981
|
else:
|
978
|
-
logger.debug("not clearing screen on page transition")
|
982
|
+
self.logger.debug("not clearing screen on page transition")
|
979
983
|
|
980
984
|
# Queue the erasing of this line if necessary
|
981
985
|
if should_erase_this_line:
|
982
986
|
assert line_erase_info is not None
|
983
|
-
logger.debug(
|
987
|
+
self.logger.debug(
|
984
988
|
f"t={self.writer.packets_queued}: erasing lyric " f"{line_erase_info.lyric_index} line " f"{line_erase_info.line_index}"
|
985
989
|
)
|
986
990
|
if line_erase_info.text.strip():
|
@@ -993,12 +997,12 @@ class KaraokeComposer:
|
|
993
997
|
)
|
994
998
|
)
|
995
999
|
else:
|
996
|
-
logger.debug("line is blank; not erased")
|
1000
|
+
self.logger.debug("line is blank; not erased")
|
997
1001
|
state.line_erase += 1
|
998
1002
|
# Queue the drawing of this line if necessary
|
999
1003
|
if should_draw_this_line:
|
1000
1004
|
assert line_draw_info is not None
|
1001
|
-
logger.debug(
|
1005
|
+
self.logger.debug(
|
1002
1006
|
f"t={self.writer.packets_queued}: drawing lyric " f"{line_draw_info.lyric_index} line " f"{line_draw_info.line_index}"
|
1003
1007
|
)
|
1004
1008
|
if line_draw_info.text.strip():
|
@@ -1012,7 +1016,7 @@ class KaraokeComposer:
|
|
1012
1016
|
)
|
1013
1017
|
)
|
1014
1018
|
else:
|
1015
|
-
logger.debug("line is blank; not drawn")
|
1019
|
+
self.logger.debug("line is blank; not drawn")
|
1016
1020
|
state.line_draw += 1
|
1017
1021
|
|
1018
1022
|
# NOTE If this line has no syllables, we must advance the
|
@@ -1085,28 +1089,28 @@ class KaraokeComposer:
|
|
1085
1089
|
# XXX This is hardcoded.
|
1086
1090
|
instrumental_time = last_syllable.end_offset + 450
|
1087
1091
|
else:
|
1088
|
-
logger.debug("forcing next instrumental not to " "wait; it does not occur at or before " "the end of this line")
|
1092
|
+
self.logger.debug("forcing next instrumental not to " "wait; it does not occur at or before " "the end of this line")
|
1089
1093
|
instrumental.wait = False
|
1090
1094
|
should_instrumental = current_time >= instrumental_time
|
1091
1095
|
# If there should be an instrumental section now
|
1092
1096
|
if should_instrumental:
|
1093
1097
|
assert instrumental is not None
|
1094
|
-
logger.debug("time for an instrumental section")
|
1098
|
+
self.logger.debug("time for an instrumental section")
|
1095
1099
|
if instrumental.wait:
|
1096
|
-
logger.debug("this instrumental section waited for the previous " "line to finish")
|
1100
|
+
self.logger.debug("this instrumental section waited for the previous " "line to finish")
|
1097
1101
|
else:
|
1098
|
-
logger.debug("this instrumental did not wait for the previous " "line to finish")
|
1102
|
+
self.logger.debug("this instrumental did not wait for the previous " "line to finish")
|
1099
1103
|
|
1100
|
-
logger.debug("_compose_lyric: Purging all highlight/draw queues")
|
1104
|
+
self.logger.debug("_compose_lyric: Purging all highlight/draw queues")
|
1101
1105
|
for st in lyric_states:
|
1102
1106
|
if instrumental.wait:
|
1103
1107
|
if st.highlight_queue:
|
1104
|
-
logger.warning("_compose_lyric: Unexpected items in highlight queue when instrumental waited")
|
1108
|
+
self.logger.warning("_compose_lyric: Unexpected items in highlight queue when instrumental waited")
|
1105
1109
|
if st.draw_queue:
|
1106
1110
|
if st == state:
|
1107
|
-
logger.debug("_compose_lyric: Queueing remaining draw packets for current state")
|
1111
|
+
self.logger.debug("_compose_lyric: Queueing remaining draw packets for current state")
|
1108
1112
|
else:
|
1109
|
-
logger.warning("_compose_lyric: Unexpected items in draw queue for non-current state")
|
1113
|
+
self.logger.warning("_compose_lyric: Unexpected items in draw queue for non-current state")
|
1110
1114
|
self.writer.queue_packets(st.draw_queue)
|
1111
1115
|
|
1112
1116
|
# Purge highlight/draw queues
|
@@ -1142,27 +1146,27 @@ class KaraokeComposer:
|
|
1142
1146
|
if line_draw_time is None:
|
1143
1147
|
should_clear = False
|
1144
1148
|
|
1145
|
-
logger.info(f"_compose_lyric: Composing instrumental. End time: {instrumental_end}, Should clear: {should_clear}")
|
1149
|
+
self.logger.info(f"_compose_lyric: Composing instrumental. End time: {instrumental_end}, Should clear: {should_clear}")
|
1146
1150
|
try:
|
1147
1151
|
self._compose_instrumental(instrumental, instrumental_end)
|
1148
1152
|
except Exception as e:
|
1149
|
-
logger.error(f"Error in _compose_instrumental: {str(e)}", exc_info=True)
|
1153
|
+
self.logger.error(f"Error in _compose_instrumental: {str(e)}", exc_info=True)
|
1150
1154
|
raise
|
1151
1155
|
|
1152
1156
|
if should_clear:
|
1153
|
-
logger.debug("_compose_lyric: Clearing screen after instrumental")
|
1157
|
+
self.logger.debug("_compose_lyric: Clearing screen after instrumental")
|
1154
1158
|
self.writer.queue_packets(
|
1155
1159
|
[
|
1156
1160
|
*memory_preset_repeat(self.BACKGROUND),
|
1157
1161
|
*load_color_table(self.color_table),
|
1158
1162
|
]
|
1159
1163
|
)
|
1160
|
-
logger.debug(f"_compose_lyric: Loaded color table: {self.color_table}")
|
1164
|
+
self.logger.debug(f"_compose_lyric: Loaded color table: {self.color_table}")
|
1161
1165
|
if self.config.border is not None:
|
1162
1166
|
self.writer.queue_packet(border_preset(self.BORDER))
|
1163
1167
|
composer_state.just_cleared = True
|
1164
1168
|
else:
|
1165
|
-
logger.debug("not clearing screen after instrumental")
|
1169
|
+
self.logger.debug("not clearing screen after instrumental")
|
1166
1170
|
# Advance to the next instrumental section
|
1167
1171
|
instrumental = next_instrumental
|
1168
1172
|
return
|
@@ -1266,7 +1270,7 @@ class KaraokeComposer:
|
|
1266
1270
|
)
|
1267
1271
|
|
1268
1272
|
# Warn the user
|
1269
|
-
logger.warning(
|
1273
|
+
self.logger.warning(
|
1270
1274
|
"Not enough time to highlight lyric %d line %d syllable %d. "
|
1271
1275
|
"Ideal duration is %d column(s); actual duration is %d column(s). "
|
1272
1276
|
"Syllable text: %s",
|
@@ -1293,9 +1297,9 @@ class KaraokeComposer:
|
|
1293
1297
|
instrumental: SettingsInstrumental,
|
1294
1298
|
end: int | None,
|
1295
1299
|
):
|
1296
|
-
logger.info(f"Composing instrumental section. End time: {end}")
|
1300
|
+
self.logger.info(f"Composing instrumental section. End time: {end}")
|
1297
1301
|
try:
|
1298
|
-
logger.info("composing instrumental section")
|
1302
|
+
self.logger.info("composing instrumental section")
|
1299
1303
|
self.instrumental_times.append(self.writer.packets_queued)
|
1300
1304
|
self.writer.queue_packets(
|
1301
1305
|
[
|
@@ -1305,7 +1309,7 @@ class KaraokeComposer:
|
|
1305
1309
|
]
|
1306
1310
|
)
|
1307
1311
|
|
1308
|
-
logger.debug("rendering instrumental text")
|
1312
|
+
self.logger.debug("rendering instrumental text")
|
1309
1313
|
text = instrumental.text.split("\n")
|
1310
1314
|
instrumental_font = ImageFont.truetype(self.config.font, 20)
|
1311
1315
|
text_images = render_lines(
|
@@ -1377,7 +1381,7 @@ class KaraokeComposer:
|
|
1377
1381
|
y += instrumental.line_tile_height * CDG_TILE_HEIGHT
|
1378
1382
|
|
1379
1383
|
if instrumental.image is not None:
|
1380
|
-
logger.debug("creating instrumental background image")
|
1384
|
+
self.logger.debug("creating instrumental background image")
|
1381
1385
|
try:
|
1382
1386
|
# Load background image
|
1383
1387
|
background_image = self._load_image(
|
@@ -1390,13 +1394,13 @@ class KaraokeComposer:
|
|
1390
1394
|
],
|
1391
1395
|
)
|
1392
1396
|
except FileNotFoundError as e:
|
1393
|
-
logger.error(f"Failed to load instrumental image: {e}")
|
1397
|
+
self.logger.error(f"Failed to load instrumental image: {e}")
|
1394
1398
|
# Fallback to simple screen if image can't be loaded
|
1395
1399
|
instrumental.image = None
|
1396
|
-
logger.warning("Falling back to simple screen for instrumental")
|
1400
|
+
self.logger.warning("Falling back to simple screen for instrumental")
|
1397
1401
|
|
1398
1402
|
if instrumental.image is None:
|
1399
|
-
logger.debug("no instrumental image; drawing simple screen")
|
1403
|
+
self.logger.debug("no instrumental image; drawing simple screen")
|
1400
1404
|
color_table = list(
|
1401
1405
|
pad(
|
1402
1406
|
[
|
@@ -1416,13 +1420,13 @@ class KaraokeComposer:
|
|
1416
1420
|
*text_image_packets,
|
1417
1421
|
]
|
1418
1422
|
)
|
1419
|
-
logger.debug(f"loaded color table in compose_instrumental: {color_table}")
|
1423
|
+
self.logger.debug(f"loaded color table in compose_instrumental: {color_table}")
|
1420
1424
|
else:
|
1421
1425
|
# Queue palette packets
|
1422
1426
|
palette = list(batched(background_image.getpalette(), 3))
|
1423
1427
|
if len(palette) < 8:
|
1424
1428
|
color_table = list(pad(palette, 8, padvalue=self.UNUSED_COLOR))
|
1425
|
-
logger.debug(f"loaded color table in compose_instrumental: {color_table}")
|
1429
|
+
self.logger.debug(f"loaded color table in compose_instrumental: {color_table}")
|
1426
1430
|
self.writer.queue_packet(
|
1427
1431
|
load_color_table_lo(
|
1428
1432
|
color_table,
|
@@ -1430,18 +1434,18 @@ class KaraokeComposer:
|
|
1430
1434
|
)
|
1431
1435
|
else:
|
1432
1436
|
color_table = list(pad(palette, 16, padvalue=self.UNUSED_COLOR))
|
1433
|
-
logger.debug(f"loaded color table in compose_instrumental: {color_table}")
|
1437
|
+
self.logger.debug(f"loaded color table in compose_instrumental: {color_table}")
|
1434
1438
|
self.writer.queue_packets(
|
1435
1439
|
load_color_table(
|
1436
1440
|
color_table,
|
1437
1441
|
)
|
1438
1442
|
)
|
1439
1443
|
|
1440
|
-
logger.debug("drawing instrumental text")
|
1444
|
+
self.logger.debug("drawing instrumental text")
|
1441
1445
|
# Queue text packets
|
1442
1446
|
self.writer.queue_packets(text_image_packets)
|
1443
1447
|
|
1444
|
-
logger.debug("rendering instrumental text over background image")
|
1448
|
+
self.logger.debug("rendering instrumental text over background image")
|
1445
1449
|
# HACK To properly draw and layer everything, I need to
|
1446
1450
|
# create a version of the background image that has the text
|
1447
1451
|
# overlaid onto it, and is tile-aligned. This requires some
|
@@ -1450,7 +1454,7 @@ class KaraokeComposer:
|
|
1450
1454
|
padright = -(instrumental.x + background_image.width) % CDG_TILE_WIDTH
|
1451
1455
|
padtop = instrumental.y % CDG_TILE_HEIGHT
|
1452
1456
|
padbottom = -(instrumental.y + background_image.height) % CDG_TILE_HEIGHT
|
1453
|
-
logger.debug(f"padding L={padleft} R={padright} T={padtop} B={padbottom}")
|
1457
|
+
self.logger.debug(f"padding L={padleft} R={padright} T={padtop} B={padbottom}")
|
1454
1458
|
# Create axis-aligned background image with proper size and
|
1455
1459
|
# palette
|
1456
1460
|
aligned_background_image = Image.new(
|
@@ -1485,9 +1489,9 @@ class KaraokeComposer:
|
|
1485
1489
|
)
|
1486
1490
|
),
|
1487
1491
|
)
|
1488
|
-
logger.debug("instrumental background image packed in " f"{len(list(it.chain(*packets.values())))} packet(s)")
|
1492
|
+
self.logger.debug("instrumental background image packed in " f"{len(list(it.chain(*packets.values())))} packet(s)")
|
1489
1493
|
|
1490
|
-
logger.debug("applying instrumental transition")
|
1494
|
+
self.logger.debug("applying instrumental transition")
|
1491
1495
|
# Queue background image packets (and apply transition)
|
1492
1496
|
if instrumental.transition is None:
|
1493
1497
|
for coord_packets in packets.values():
|
@@ -1498,7 +1502,7 @@ class KaraokeComposer:
|
|
1498
1502
|
self.writer.queue_packets(packets.get(coord, []))
|
1499
1503
|
|
1500
1504
|
if end is None:
|
1501
|
-
logger.debug('this instrumental will last "forever"')
|
1505
|
+
self.logger.debug('this instrumental will last "forever"')
|
1502
1506
|
return
|
1503
1507
|
|
1504
1508
|
# Wait until 3 seconds before the next line should be drawn
|
@@ -1507,7 +1511,7 @@ class KaraokeComposer:
|
|
1507
1511
|
end_time = max(current_time, end - preparation_time)
|
1508
1512
|
wait_time = end_time - current_time
|
1509
1513
|
|
1510
|
-
logger.debug(f"waiting for {wait_time} frame(s) before showing next lyrics")
|
1514
|
+
self.logger.debug(f"waiting for {wait_time} frame(s) before showing next lyrics")
|
1511
1515
|
self.writer.queue_packets([no_instruction()] * wait_time)
|
1512
1516
|
|
1513
1517
|
# Clear the screen for the next lyrics
|
@@ -1517,25 +1521,25 @@ class KaraokeComposer:
|
|
1517
1521
|
*load_color_table(self.color_table),
|
1518
1522
|
]
|
1519
1523
|
)
|
1520
|
-
logger.debug(f"loaded color table in compose_instrumental: {self.color_table}")
|
1524
|
+
self.logger.debug(f"loaded color table in compose_instrumental: {self.color_table}")
|
1521
1525
|
if self.config.border is not None:
|
1522
1526
|
self.writer.queue_packet(border_preset(self.BORDER))
|
1523
1527
|
|
1524
|
-
logger.debug("instrumental section ended")
|
1528
|
+
self.logger.debug("instrumental section ended")
|
1525
1529
|
except Exception as e:
|
1526
|
-
logger.error(f"Error in _compose_instrumental: {str(e)}", exc_info=True)
|
1530
|
+
self.logger.error(f"Error in _compose_instrumental: {str(e)}", exc_info=True)
|
1527
1531
|
raise
|
1528
1532
|
|
1529
1533
|
def _compose_intro(self):
|
1530
1534
|
# TODO Make it so the intro screen is not hardcoded
|
1531
|
-
logger.debug("composing intro")
|
1535
|
+
self.logger.debug("composing intro")
|
1532
1536
|
self.writer.queue_packets(
|
1533
1537
|
[
|
1534
1538
|
*memory_preset_repeat(0),
|
1535
1539
|
]
|
1536
1540
|
)
|
1537
1541
|
|
1538
|
-
logger.debug("loading intro background image")
|
1542
|
+
self.logger.debug("loading intro background image")
|
1539
1543
|
# Load background image
|
1540
1544
|
background_image = self._load_image(
|
1541
1545
|
self.config.title_screen_background,
|
@@ -1552,12 +1556,23 @@ class KaraokeComposer:
|
|
1552
1556
|
MAX_HEIGHT = 200
|
1553
1557
|
# Try rendering the title and artist to an image
|
1554
1558
|
while True:
|
1555
|
-
logger.debug(f"trying song title at size {bigfont_size}")
|
1559
|
+
self.logger.debug(f"trying song title at size {bigfont_size}")
|
1556
1560
|
text_image = Image.new("P", (CDG_VISIBLE_WIDTH, MAX_HEIGHT * 2), 0)
|
1557
1561
|
y = 0
|
1562
|
+
|
1563
|
+
if self.config.title_top_padding:
|
1564
|
+
self.logger.info(f"title top padding set to {self.config.title_top_padding} in config, setting as initial y position")
|
1565
|
+
y = self.config.title_top_padding
|
1566
|
+
self.logger.info(f"Initial y position with padding: {y}")
|
1567
|
+
else:
|
1568
|
+
self.logger.info("no title top padding configured; starting with y = 0")
|
1569
|
+
self.logger.info(f"Initial y position without padding: {y}")
|
1570
|
+
|
1558
1571
|
bigfont = ImageFont.truetype(self.config.font, bigfont_size)
|
1559
1572
|
|
1560
1573
|
# Draw song title
|
1574
|
+
title_start_y = y
|
1575
|
+
self.logger.info(f"Starting to draw title at y={y}")
|
1561
1576
|
for image in render_lines(
|
1562
1577
|
get_wrapped_text(
|
1563
1578
|
self.config.title,
|
@@ -1573,11 +1588,16 @@ class KaraokeComposer:
|
|
1573
1588
|
mask=image.point(lambda v: v and 255, "1"),
|
1574
1589
|
)
|
1575
1590
|
y += int(bigfont.size)
|
1591
|
+
title_end_y = y
|
1592
|
+
self.logger.info(f"Finished drawing title at y={y}, title height={title_end_y - title_start_y}")
|
1576
1593
|
|
1577
1594
|
# Add vertical gap between title and artist using configured value
|
1578
1595
|
y += self.config.title_artist_gap
|
1596
|
+
self.logger.info(f"After adding title_artist_gap of {self.config.title_artist_gap}, y is now {y}")
|
1579
1597
|
|
1580
1598
|
# Draw song artist
|
1599
|
+
artist_start_y = y
|
1600
|
+
self.logger.info(f"Starting to draw artist at y={y}")
|
1581
1601
|
for image in render_lines(
|
1582
1602
|
get_wrapped_text(
|
1583
1603
|
self.config.artist,
|
@@ -1593,22 +1613,49 @@ class KaraokeComposer:
|
|
1593
1613
|
mask=image.point(lambda v: v and 255, "1"),
|
1594
1614
|
)
|
1595
1615
|
y += int(smallfont.size)
|
1616
|
+
artist_end_y = y
|
1617
|
+
self.logger.info(f"Finished drawing artist at y={y}, artist height={artist_end_y - artist_start_y}")
|
1618
|
+
self.logger.info(f"Total content height before cropping: {artist_end_y - title_start_y}")
|
1596
1619
|
|
1597
1620
|
# Break out of loop only if text box ends up small enough
|
1598
|
-
|
1621
|
+
bbox = text_image.getbbox()
|
1622
|
+
self.logger.info(f"Original bounding box from getbbox(): {bbox}")
|
1623
|
+
if bbox is None:
|
1624
|
+
# If there's no content, still create a minimal bbox
|
1625
|
+
bbox = (0, 0, text_image.width, 1)
|
1626
|
+
self.logger.info("No content found, created minimal bbox")
|
1627
|
+
|
1628
|
+
# We'll crop to just the content area, without padding
|
1629
|
+
original_height = text_image.height
|
1630
|
+
text_image = text_image.crop(bbox)
|
1631
|
+
self.logger.info(f"After cropping: text_image dimensions={text_image.width}x{text_image.height}, height difference={original_height - text_image.height}")
|
1632
|
+
|
1599
1633
|
if text_image.height <= MAX_HEIGHT:
|
1600
|
-
logger.debug("height just right")
|
1634
|
+
self.logger.debug("height just right")
|
1601
1635
|
break
|
1602
1636
|
# If text box is not small enough, reduce font size of title
|
1603
|
-
logger.debug("height too big; reducing font size")
|
1637
|
+
self.logger.debug("height too big; reducing font size")
|
1604
1638
|
bigfont_size -= 2
|
1605
1639
|
|
1606
|
-
#
|
1640
|
+
# Calculate position - center horizontally, but add padding to vertical position
|
1641
|
+
center_x = (CDG_SCREEN_WIDTH - text_image.width) // 2
|
1642
|
+
|
1643
|
+
# Standard centered position
|
1644
|
+
standard_center_y = (CDG_SCREEN_HEIGHT - text_image.height) // 2
|
1645
|
+
|
1646
|
+
# Add the title_top_padding to shift the entire content downward
|
1647
|
+
padding_offset = self.config.title_top_padding if self.config.title_top_padding else 0
|
1648
|
+
final_y = standard_center_y + padding_offset
|
1649
|
+
|
1650
|
+
self.logger.info(f"Pasting text image ({text_image.width}x{text_image.height}) onto background")
|
1651
|
+
self.logger.info(f"Standard centered position would be y={standard_center_y}")
|
1652
|
+
self.logger.info(f"With padding offset of {padding_offset}, final position is y={final_y}")
|
1653
|
+
|
1607
1654
|
background_image.paste(
|
1608
1655
|
text_image,
|
1609
1656
|
(
|
1610
|
-
|
1611
|
-
|
1657
|
+
center_x,
|
1658
|
+
final_y,
|
1612
1659
|
),
|
1613
1660
|
mask=text_image.point(lambda v: v and 255, "1"),
|
1614
1661
|
)
|
@@ -1617,7 +1664,7 @@ class KaraokeComposer:
|
|
1617
1664
|
palette = list(batched(background_image.getpalette(), 3))
|
1618
1665
|
if len(palette) < 8:
|
1619
1666
|
color_table = list(pad(palette, 8, padvalue=self.UNUSED_COLOR))
|
1620
|
-
logger.debug(f"loaded color table in compose_intro: {color_table}")
|
1667
|
+
self.logger.debug(f"loaded color table in compose_intro: {color_table}")
|
1621
1668
|
self.writer.queue_packet(
|
1622
1669
|
load_color_table_lo(
|
1623
1670
|
color_table,
|
@@ -1625,7 +1672,7 @@ class KaraokeComposer:
|
|
1625
1672
|
)
|
1626
1673
|
else:
|
1627
1674
|
color_table = list(pad(palette, 16, padvalue=self.UNUSED_COLOR))
|
1628
|
-
logger.debug(f"loaded color table in compose_intro: {color_table}")
|
1675
|
+
self.logger.debug(f"loaded color table in compose_intro: {color_table}")
|
1629
1676
|
self.writer.queue_packets(
|
1630
1677
|
load_color_table(
|
1631
1678
|
color_table,
|
@@ -1634,7 +1681,7 @@ class KaraokeComposer:
|
|
1634
1681
|
|
1635
1682
|
# Render background image to packets
|
1636
1683
|
packets = image_to_packets(background_image, (0, 0))
|
1637
|
-
logger.debug("intro background image packed in " f"{len(list(it.chain(*packets.values())))} packet(s)")
|
1684
|
+
self.logger.debug("intro background image packed in " f"{len(list(it.chain(*packets.values())))} packet(s)")
|
1638
1685
|
|
1639
1686
|
# Queue background image packets (and apply transition)
|
1640
1687
|
transition = Image.open(package_dir / "transitions" / f"{self.config.title_screen_transition}.png")
|
@@ -1652,30 +1699,30 @@ class KaraokeComposer:
|
|
1652
1699
|
first_syllable_start_offset = min(
|
1653
1700
|
syllable.start_offset for lyric in self.lyrics for line in lyric.lines for syllable in line.syllables
|
1654
1701
|
)
|
1655
|
-
logger.debug(f"first syllable starts at {first_syllable_start_offset}")
|
1702
|
+
self.logger.debug(f"first syllable starts at {first_syllable_start_offset}")
|
1656
1703
|
|
1657
1704
|
MINIMUM_FIRST_SYLLABLE_TIME_FOR_NO_SILENCE = INTRO_DURATION + FIRST_SYLLABLE_BUFFER
|
1658
1705
|
# If the first syllable is within buffer+intro time, add silence
|
1659
1706
|
# Otherwise, don't add any silence
|
1660
1707
|
if first_syllable_start_offset < MINIMUM_FIRST_SYLLABLE_TIME_FOR_NO_SILENCE:
|
1661
1708
|
self.intro_delay = MINIMUM_FIRST_SYLLABLE_TIME_FOR_NO_SILENCE
|
1662
|
-
logger.info(
|
1709
|
+
self.logger.info(
|
1663
1710
|
f"First syllable within {self.config.intro_duration_seconds + self.config.first_syllable_buffer_seconds} seconds. Adding {self.intro_delay} frames of silence."
|
1664
1711
|
)
|
1665
1712
|
else:
|
1666
1713
|
self.intro_delay = 0
|
1667
|
-
logger.info("First syllable after buffer period. No additional silence needed.")
|
1714
|
+
self.logger.info("First syllable after buffer period. No additional silence needed.")
|
1668
1715
|
|
1669
1716
|
def _compose_outro(self, end: int):
|
1670
1717
|
# TODO Make it so the outro screen is not hardcoded
|
1671
|
-
logger.debug("composing outro")
|
1718
|
+
self.logger.debug("composing outro")
|
1672
1719
|
self.writer.queue_packets(
|
1673
1720
|
[
|
1674
1721
|
*memory_preset_repeat(0),
|
1675
1722
|
]
|
1676
1723
|
)
|
1677
1724
|
|
1678
|
-
logger.debug("loading outro background image")
|
1725
|
+
self.logger.debug("loading outro background image")
|
1679
1726
|
# Load background image
|
1680
1727
|
background_image = self._load_image(
|
1681
1728
|
self.config.outro_background,
|
@@ -1691,7 +1738,7 @@ class KaraokeComposer:
|
|
1691
1738
|
MAX_HEIGHT = 200
|
1692
1739
|
|
1693
1740
|
# Render text to an image
|
1694
|
-
logger.debug(f"rendering outro text")
|
1741
|
+
self.logger.debug(f"rendering outro text")
|
1695
1742
|
text_image = Image.new("P", (CDG_VISIBLE_WIDTH, MAX_HEIGHT * 2), 0)
|
1696
1743
|
y = 0
|
1697
1744
|
|
@@ -1759,7 +1806,7 @@ class KaraokeComposer:
|
|
1759
1806
|
|
1760
1807
|
# Render background image to packets
|
1761
1808
|
packets = image_to_packets(background_image, (0, 0))
|
1762
|
-
logger.debug("intro background image packed in " f"{len(list(it.chain(*packets.values())))} packet(s)")
|
1809
|
+
self.logger.debug("intro background image packed in " f"{len(list(it.chain(*packets.values())))} packet(s)")
|
1763
1810
|
|
1764
1811
|
# Queue background image packets (and apply transition)
|
1765
1812
|
transition = Image.open(package_dir / "transitions" / f"{self.config.outro_transition}.png")
|
@@ -1776,14 +1823,14 @@ class KaraokeComposer:
|
|
1776
1823
|
if partial_palette is None:
|
1777
1824
|
partial_palette = []
|
1778
1825
|
|
1779
|
-
logger.debug("loading image")
|
1826
|
+
self.logger.debug("loading image")
|
1780
1827
|
image_rgba = Image.open(file_relative_to(image_path, self.relative_dir)).convert("RGBA")
|
1781
1828
|
image = image_rgba.convert("RGB")
|
1782
1829
|
|
1783
1830
|
# REVIEW How many colors should I allow? Should I make this
|
1784
1831
|
# configurable?
|
1785
1832
|
COLORS = 16 - len(partial_palette)
|
1786
|
-
logger.debug(f"quantizing to {COLORS} color(s)")
|
1833
|
+
self.logger.debug(f"quantizing to {COLORS} color(s)")
|
1787
1834
|
# Reduce colors with quantization and dithering
|
1788
1835
|
image = image.quantize(
|
1789
1836
|
colors=COLORS,
|
@@ -1805,18 +1852,18 @@ class KaraokeComposer:
|
|
1805
1852
|
]
|
1806
1853
|
)
|
1807
1854
|
image = image.quantize()
|
1808
|
-
logger.debug(f"image uses {max(image.getdata()) + 1} color(s)")
|
1855
|
+
self.logger.debug(f"image uses {max(image.getdata()) + 1} color(s)")
|
1809
1856
|
|
1810
1857
|
if partial_palette:
|
1811
|
-
logger.debug(f"prepending {len(partial_palette)} color(s) to palette")
|
1858
|
+
self.logger.debug(f"prepending {len(partial_palette)} color(s) to palette")
|
1812
1859
|
# Add offset to color indices
|
1813
1860
|
image.putdata(image.getdata(), offset=len(partial_palette))
|
1814
1861
|
# Place other colors in palette
|
1815
1862
|
image.putpalette(list(it.chain(*partial_palette)) + image.getpalette())
|
1816
1863
|
|
1817
|
-
logger.debug(f"palette: {list(batched(image.getpalette(), 3))!r}")
|
1864
|
+
self.logger.debug(f"palette: {list(batched(image.getpalette(), 3))!r}")
|
1818
1865
|
|
1819
|
-
logger.debug("masking out non-transparent parts of image")
|
1866
|
+
self.logger.debug("masking out non-transparent parts of image")
|
1820
1867
|
# Create mask for non-transparent parts of image
|
1821
1868
|
# NOTE We allow alpha values from 128 to 255 (half-transparent
|
1822
1869
|
# to opaque).
|
@@ -1882,7 +1929,7 @@ class KaraokeComposer:
|
|
1882
1929
|
|
1883
1930
|
# Create ASS subtitle object
|
1884
1931
|
# (ASS = Advanced Sub Station. Get your mind out of the gutter.)
|
1885
|
-
logger.debug("creating ASS subtitle object")
|
1932
|
+
self.logger.debug("creating ASS subtitle object")
|
1886
1933
|
assdoc = ass.Document()
|
1887
1934
|
assdoc.fields.update(
|
1888
1935
|
Title="",
|
@@ -1895,7 +1942,7 @@ class KaraokeComposer:
|
|
1895
1942
|
|
1896
1943
|
# Load lyric font using fontTools
|
1897
1944
|
# NOTE We do this because we need some of the font's metadata.
|
1898
|
-
logger.debug("loading metadata from font")
|
1945
|
+
self.logger.debug("loading metadata from font")
|
1899
1946
|
font = ttLib.TTFont(self.font.path)
|
1900
1947
|
|
1901
1948
|
# NOTE The ASS Style lines need the "fontname as used by
|
@@ -1922,7 +1969,7 @@ class KaraokeComposer:
|
|
1922
1969
|
|
1923
1970
|
# Create a style for each singer
|
1924
1971
|
for i, singer in enumerate(self.config.singers, 1):
|
1925
|
-
logger.debug(f"creating ASS style for singer {i}")
|
1972
|
+
self.logger.debug(f"creating ASS style for singer {i}")
|
1926
1973
|
assdoc.styles.append(
|
1927
1974
|
ass.Style(
|
1928
1975
|
name=f"Singer{i}",
|
@@ -1955,7 +2002,7 @@ class KaraokeComposer:
|
|
1955
2002
|
# Skip line if it has no syllables
|
1956
2003
|
if not line.syllables:
|
1957
2004
|
continue
|
1958
|
-
logger.debug(f"creating event for lyric {ci} line {li}")
|
2005
|
+
self.logger.debug(f"creating event for lyric {ci} line {li}")
|
1959
2006
|
|
1960
2007
|
# Get intended draw time of line
|
1961
2008
|
line_draw_time = cdg_to_sync(times.line_draw[li]) + offset
|
@@ -2040,13 +2087,13 @@ class KaraokeComposer:
|
|
2040
2087
|
|
2041
2088
|
outname = self.config.outname
|
2042
2089
|
assfile_name = self.relative_dir / Path(f"{outname}.ass")
|
2043
|
-
logger.debug(f"dumping ASS object to {assfile_name}")
|
2090
|
+
self.logger.debug(f"dumping ASS object to {assfile_name}")
|
2044
2091
|
# HACK If I don't specify "utf-8-sig" as the encoding, the
|
2045
2092
|
# python-ass module gives me a warning telling me to. This adds
|
2046
2093
|
# a "byte order mark" to the ASS file (seemingly unnecessarily).
|
2047
2094
|
with open(assfile_name, "w", encoding="utf-8-sig") as assfile:
|
2048
2095
|
assdoc.dump_file(assfile)
|
2049
|
-
logger.info(f"ASS object dumped to {assfile_name}")
|
2096
|
+
self.logger.info(f"ASS object dumped to {assfile_name}")
|
2050
2097
|
|
2051
2098
|
def create_mp4(self, height: int = 720, fps: int = 30):
|
2052
2099
|
if not MP4_REQUIREMENTS:
|
@@ -2059,27 +2106,27 @@ class KaraokeComposer:
|
|
2059
2106
|
# composed, but without the lyrics. We create this by replacing
|
2060
2107
|
# all lyric-drawing packets with no-instruction packets.
|
2061
2108
|
platecdg_name = self.relative_dir / Path(f"{outname}.plate.cdg")
|
2062
|
-
logger.debug(f"writing plate CDG to {platecdg_name}")
|
2109
|
+
self.logger.debug(f"writing plate CDG to {platecdg_name}")
|
2063
2110
|
with open(platecdg_name, "wb") as platecdg:
|
2064
|
-
logger.debug("writing plate")
|
2111
|
+
self.logger.debug("writing plate")
|
2065
2112
|
for i, packet in enumerate(self.writer.packets):
|
2066
2113
|
packet_to_write = packet
|
2067
2114
|
if i in self.lyric_packet_indices:
|
2068
2115
|
packet_to_write = no_instruction()
|
2069
2116
|
self.writer.write_packet(platecdg, packet_to_write)
|
2070
|
-
logger.info(f"plate CDG written to {platecdg_name}")
|
2117
|
+
self.logger.info(f"plate CDG written to {platecdg_name}")
|
2071
2118
|
|
2072
2119
|
# Create an MP3 file for the audio
|
2073
2120
|
platemp3_name = self.relative_dir / Path(f"{outname}.plate.mp3")
|
2074
|
-
logger.debug(f"writing plate MP3 to {platemp3_name}")
|
2121
|
+
self.logger.debug(f"writing plate MP3 to {platemp3_name}")
|
2075
2122
|
self.audio.export(platemp3_name, format="mp3")
|
2076
|
-
logger.info(f"plate MP3 written to {platemp3_name}")
|
2123
|
+
self.logger.info(f"plate MP3 written to {platemp3_name}")
|
2077
2124
|
|
2078
2125
|
# Create a subtitle file for the HQ lyrics
|
2079
2126
|
self.create_ass()
|
2080
2127
|
assfile_name = self.relative_dir / Path(f"{outname}.ass")
|
2081
2128
|
|
2082
|
-
logger.debug("building ffmpeg command for encoding MP4")
|
2129
|
+
self.logger.debug("building ffmpeg command for encoding MP4")
|
2083
2130
|
video = (
|
2084
2131
|
ffmpeg.input(platecdg_name).video
|
2085
2132
|
# Pad the end of the video by a few seconds
|
@@ -2124,16 +2171,16 @@ class KaraokeComposer:
|
|
2124
2171
|
# padded video.
|
2125
2172
|
shortest=None,
|
2126
2173
|
).overwrite_output()
|
2127
|
-
logger.debug(f"ffmpeg command: {mp4.compile()}")
|
2174
|
+
self.logger.debug(f"ffmpeg command: {mp4.compile()}")
|
2128
2175
|
mp4.run()
|
2129
2176
|
|
2130
|
-
logger.debug("deleting plate CDG")
|
2177
|
+
self.logger.debug("deleting plate CDG")
|
2131
2178
|
platecdg_name.unlink()
|
2132
|
-
logger.info("plate CDG deleted")
|
2179
|
+
self.logger.info("plate CDG deleted")
|
2133
2180
|
|
2134
|
-
logger.debug("deleting plate MP3")
|
2181
|
+
self.logger.debug("deleting plate MP3")
|
2135
2182
|
platemp3_name.unlink()
|
2136
|
-
logger.info("plate MP3 deleted")
|
2183
|
+
self.logger.info("plate MP3 deleted")
|
2137
2184
|
|
2138
2185
|
# !SECTION
|
2139
2186
|
# endregion
|
@@ -126,7 +126,7 @@ class Settings:
|
|
126
126
|
artist_color: RGBColor = field(converter=to_rgbcolor, default="#ffffff")
|
127
127
|
title_screen_transition: str = "centertexttoplogobottomtext"
|
128
128
|
title_artist_gap: int = 30
|
129
|
-
|
129
|
+
title_top_padding: int = 0
|
130
130
|
intro_duration_seconds: float = 5.0
|
131
131
|
first_syllable_buffer_seconds: float = 3.0
|
132
132
|
|
@@ -7,7 +7,6 @@ from .config import *
|
|
7
7
|
|
8
8
|
|
9
9
|
import logging
|
10
|
-
logger = logging.getLogger(__name__)
|
11
10
|
|
12
11
|
|
13
12
|
RENDERED_BLANK = 0
|
@@ -169,6 +168,7 @@ def render_lines_and_masks(
|
|
169
168
|
stroke_width: int = 0,
|
170
169
|
stroke_type: StrokeType = StrokeType.OCTAGON,
|
171
170
|
render_masks: bool = True,
|
171
|
+
logger: logging.Logger = logging.getLogger(__name__),
|
172
172
|
) -> tuple[list[Image.Image], list[list[Image.Image]]]:
|
173
173
|
"""
|
174
174
|
Render set of karaoke lines as `PIL.Image.Image`s, and masks for
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: lyrics-transcriber
|
3
|
-
Version: 0.49.
|
3
|
+
Version: 0.49.2
|
4
4
|
Summary: Automatically create synchronised lyrics files in ASS and MidiCo LRC formats with word-level timestamps, using Whisper and lyrics from Genius and Spotify
|
5
5
|
License: MIT
|
6
6
|
Author: Andrew Beveridge
|
@@ -102,15 +102,15 @@ lyrics_transcriber/output/ass/lyrics_screen.py,sha256=gRzUsDMLEtZZPuv77xk7M0FzCp
|
|
102
102
|
lyrics_transcriber/output/ass/section_detector.py,sha256=TsSf4E0fleC-Tzd5KK6q4m-wjGiu6TvGDtHdR6sUqvc,3922
|
103
103
|
lyrics_transcriber/output/ass/section_screen.py,sha256=QeUaIeDXs_Es33W5aqyVSaZzMwUx-b60vbAww3aQfls,4185
|
104
104
|
lyrics_transcriber/output/ass/style.py,sha256=ty3IGorlOZ_Q-TxeA02hNb5Pb0mA755dOb8bqKr1k7U,6880
|
105
|
-
lyrics_transcriber/output/cdg.py,sha256
|
105
|
+
lyrics_transcriber/output/cdg.py,sha256=ZDH0bxCyMl0YEKL_Y9ec33u_Hk92E9C5yQu98UxNHv0,25079
|
106
106
|
lyrics_transcriber/output/cdgmaker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
107
107
|
lyrics_transcriber/output/cdgmaker/cdg.py,sha256=nBqkw0JOois-NI27CkwHblLuBaoL-sHyJb2SntX7m8s,6733
|
108
|
-
lyrics_transcriber/output/cdgmaker/composer.py,sha256=
|
109
|
-
lyrics_transcriber/output/cdgmaker/config.py,sha256=
|
108
|
+
lyrics_transcriber/output/cdgmaker/composer.py,sha256=_67PBhg8-EvrRUATCRzEGhzpntKRbwBP7uwf04_W_MA,93997
|
109
|
+
lyrics_transcriber/output/cdgmaker/config.py,sha256=1mOXwLfbuTavoyV-3ykWK9e8YZN3JHh0I3b3buh1EfA,4073
|
110
110
|
lyrics_transcriber/output/cdgmaker/images/instrumental.png,sha256=EKUcJJGj95ceNqw7M-O9ltX4HZIaCaSKjJucKVDTSb8,14834
|
111
111
|
lyrics_transcriber/output/cdgmaker/images/intro.png,sha256=XeN6i8aKaQObfRwcgHT8ajQAJIDVjXEd7C5tu7DtriU,17603
|
112
112
|
lyrics_transcriber/output/cdgmaker/pack.py,sha256=tuD7Udt3fpWiEVRnzO3VarULwmQ21QYE_MYc6XSpudg,15870
|
113
|
-
lyrics_transcriber/output/cdgmaker/render.py,sha256=
|
113
|
+
lyrics_transcriber/output/cdgmaker/render.py,sha256=If4XE5x4N3YvOiyyOtlwki6CF1W0irnlN-WHgxnzkCo,11053
|
114
114
|
lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png,sha256=GK8AqHHL3q6QGSvhCc51CzeQ-TvQGjYAfjxAgcGzZVw,37174
|
115
115
|
lyrics_transcriber/output/cdgmaker/transitions/circlein.png,sha256=jeRXV1Sf9HrN-H710_Xj6nsDk0QS8RGefdaxdD2l4FI,25030
|
116
116
|
lyrics_transcriber/output/cdgmaker/transitions/circleout.png,sha256=sQ4YSMiLy78hIuWUgvoWeuvj_OKmLUxD2XLgx5-_DqI,44026
|
@@ -148,8 +148,8 @@ lyrics_transcriber/transcribers/base_transcriber.py,sha256=T3m4ZCwZ9Bpv6Jvb2hNcn
|
|
148
148
|
lyrics_transcriber/transcribers/whisper.py,sha256=YcCB1ic9H6zL1GS0jD0emu8-qlcH0QVEjjjYB4aLlIQ,13260
|
149
149
|
lyrics_transcriber/types.py,sha256=d73cDstrEI_tVgngDYYYFwjZNs6OVBuAB_QDkga7dWA,19841
|
150
150
|
lyrics_transcriber/utils/word_utils.py,sha256=-cMGpj9UV4F6IsoDKAV2i1aiqSO8eI91HMAm_igtVMk,958
|
151
|
-
lyrics_transcriber-0.49.
|
152
|
-
lyrics_transcriber-0.49.
|
153
|
-
lyrics_transcriber-0.49.
|
154
|
-
lyrics_transcriber-0.49.
|
155
|
-
lyrics_transcriber-0.49.
|
151
|
+
lyrics_transcriber-0.49.2.dist-info/LICENSE,sha256=BiPihPDxhxIPEx6yAxVfAljD5Bhm_XG2teCbPEj_m0Y,1069
|
152
|
+
lyrics_transcriber-0.49.2.dist-info/METADATA,sha256=KZ9OpfbPe-bKTBkcOwifWIkcxWBthYnMT_I3KEHSNSA,6017
|
153
|
+
lyrics_transcriber-0.49.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
154
|
+
lyrics_transcriber-0.49.2.dist-info/entry_points.txt,sha256=kcp-bSFkCACAEA0t166Kek0HpaJUXRo5SlF5tVrqNBU,216
|
155
|
+
lyrics_transcriber-0.49.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{lyrics_transcriber-0.49.1.dist-info → lyrics_transcriber-0.49.2.dist-info}/entry_points.txt
RENAMED
File without changes
|