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.
@@ -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.debug("loading config settings")
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
- text_image = text_image.crop(text_image.getbbox())
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
- # Draw text onto image
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
- (CDG_SCREEN_WIDTH - text_image.width) // 2,
1611
- (CDG_SCREEN_HEIGHT - text_image.height) // 2,
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.1
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=-3Dqv3L-JFsXWQkUENnkpnEbSVRMhPmhEj6a7yl5TRY,24896
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=gWziaLnCo8iV7Igp7yFn5oFUVctRDtkLlcfx6O-f98Y,90780
109
- lyrics_transcriber/output/cdgmaker/config.py,sha256=dOsOaPg9XawR3sWdTBoQiYn7urQWafV2KzedhI6BHYU,4043
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=K0BGr4I4BKAMn2tkX3QBtA_CWi7XUi1exP4EwBKxjZs,11028
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.1.dist-info/LICENSE,sha256=BiPihPDxhxIPEx6yAxVfAljD5Bhm_XG2teCbPEj_m0Y,1069
152
- lyrics_transcriber-0.49.1.dist-info/METADATA,sha256=_-bnym3Ipygm-UjVEzPf7KOJnRvIJ5r3qits-FWMtjs,6017
153
- lyrics_transcriber-0.49.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
154
- lyrics_transcriber-0.49.1.dist-info/entry_points.txt,sha256=kcp-bSFkCACAEA0t166Kek0HpaJUXRo5SlF5tVrqNBU,216
155
- lyrics_transcriber-0.49.1.dist-info/RECORD,,
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,,