karaoke-gen 0.64.0__py3-none-any.whl → 0.65.0__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.

Potentially problematic release.


This version of karaoke-gen might be problematic. Click here for more details.

@@ -52,6 +52,60 @@ class AudioProcessor:
52
52
  self.logger.info(f"File already exists, skipping creation: {file_path}")
53
53
  return exists
54
54
 
55
+ def pad_audio_file(self, input_audio, output_audio, padding_seconds):
56
+ """
57
+ Add silence to the start of an audio file using ffmpeg.
58
+
59
+ This ensures the instrumental tracks are synchronized with vocals when
60
+ countdown padding has been applied by the LyricsTranscriber.
61
+
62
+ Args:
63
+ input_audio: Path to input audio file
64
+ output_audio: Path for output padded audio file
65
+ padding_seconds: Amount of silence to add in seconds (e.g., 3.0)
66
+
67
+ Raises:
68
+ Exception: If ffmpeg command fails
69
+ """
70
+ self.logger.info(f"Padding audio file with {padding_seconds}s of silence: {input_audio}")
71
+
72
+ # Use ffmpeg to prepend silence to the audio file
73
+ # This matches the approach used in LyricsTranscriber for vocal padding
74
+ cmd = [
75
+ "ffmpeg",
76
+ "-y", # Overwrite output file
77
+ "-hide_banner",
78
+ "-loglevel", "error",
79
+ "-f", "lavfi",
80
+ "-t", str(padding_seconds),
81
+ "-i", f"anullsrc=channel_layout=stereo:sample_rate=44100",
82
+ "-i", input_audio,
83
+ "-filter_complex", "[0:a][1:a]concat=n=2:v=0:a=1[out]",
84
+ "-map", "[out]",
85
+ "-c:a", self.lossless_output_format.lower(),
86
+ output_audio,
87
+ ]
88
+
89
+ try:
90
+ import subprocess
91
+ result = subprocess.run(
92
+ cmd,
93
+ capture_output=True,
94
+ text=True,
95
+ timeout=300, # 5 minute timeout
96
+ check=True
97
+ )
98
+ self.logger.info(f"Successfully padded audio file: {output_audio}")
99
+
100
+ except subprocess.CalledProcessError as e:
101
+ error_msg = f"Failed to pad audio file {input_audio}: {e.stderr}"
102
+ self.logger.error(error_msg)
103
+ raise Exception(error_msg)
104
+ except subprocess.TimeoutExpired:
105
+ error_msg = f"Timeout while padding audio file {input_audio}"
106
+ self.logger.error(error_msg)
107
+ raise Exception(error_msg)
108
+
55
109
  def separate_audio(self, audio_file, model_name, artist_title, track_output_dir, instrumental_path, vocals_path):
56
110
  if audio_file is None or not os.path.isfile(audio_file):
57
111
  raise Exception("Error: Invalid audio source provided.")
@@ -717,3 +771,68 @@ class AudioProcessor:
717
771
 
718
772
  self.logger.info(f"Normalized audio saved, replacing: {output_path}")
719
773
  self.logger.debug(f"Original peak: {peak_amplitude} dB, Applied gain: {gain_db} dB")
774
+
775
+ def apply_countdown_padding_to_instrumentals(self, separation_result, padding_seconds, artist_title, track_output_dir):
776
+ """
777
+ Apply countdown padding to all instrumental audio files.
778
+
779
+ When LyricsTranscriber adds countdown padding to vocals, this method ensures
780
+ all instrumental tracks are padded by the same amount to maintain synchronization.
781
+
782
+ Args:
783
+ separation_result: Dictionary containing paths to separated audio files
784
+ padding_seconds: Amount of padding to apply (e.g., 3.0)
785
+ artist_title: Artist and title string for naming padded files
786
+ track_output_dir: Output directory for padded files
787
+
788
+ Returns:
789
+ Dictionary with updated paths to padded instrumental files
790
+ """
791
+ self.logger.info(
792
+ f"Applying {padding_seconds}s countdown padding to all instrumental files to match vocal padding"
793
+ )
794
+
795
+ padded_result = {
796
+ "clean_instrumental": {},
797
+ "other_stems": {},
798
+ "backing_vocals": {},
799
+ "combined_instrumentals": {},
800
+ }
801
+
802
+ # Pad clean instrumental
803
+ if "clean_instrumental" in separation_result and separation_result["clean_instrumental"].get("instrumental"):
804
+ original_instrumental = separation_result["clean_instrumental"]["instrumental"]
805
+
806
+ # Insert "Padded" before the file extension
807
+ base, ext = os.path.splitext(original_instrumental)
808
+ padded_instrumental = f"{base} (Padded){ext}"
809
+
810
+ if not self._file_exists(padded_instrumental):
811
+ self.logger.info(f"Padding clean instrumental: {original_instrumental}")
812
+ self.pad_audio_file(original_instrumental, padded_instrumental, padding_seconds)
813
+
814
+ padded_result["clean_instrumental"]["instrumental"] = padded_instrumental
815
+ padded_result["clean_instrumental"]["vocals"] = separation_result["clean_instrumental"].get("vocals")
816
+
817
+ # Pad combined instrumentals (instrumental + backing vocals)
818
+ if "combined_instrumentals" in separation_result:
819
+ for model, combined_path in separation_result["combined_instrumentals"].items():
820
+ base, ext = os.path.splitext(combined_path)
821
+ padded_combined = f"{base} (Padded){ext}"
822
+
823
+ if not self._file_exists(padded_combined):
824
+ self.logger.info(f"Padding combined instrumental ({model}): {combined_path}")
825
+ self.pad_audio_file(combined_path, padded_combined, padding_seconds)
826
+
827
+ padded_result["combined_instrumentals"][model] = padded_combined
828
+
829
+ # Copy over other stems and backing vocals without padding
830
+ # (these are typically not used in final output, but preserve the structure)
831
+ padded_result["other_stems"] = separation_result.get("other_stems", {})
832
+ padded_result["backing_vocals"] = separation_result.get("backing_vocals", {})
833
+
834
+ self.logger.info(
835
+ f"✓ Countdown padding applied to {len(padded_result['combined_instrumentals']) + 1} instrumental file(s)"
836
+ )
837
+
838
+ return padded_result
@@ -392,6 +392,10 @@ class KaraokePrep:
392
392
  self.logger.info("Skipping lyrics fetch as requested.")
393
393
  processed_track["lyrics"] = None
394
394
  processed_track["processed_lyrics"] = None
395
+ # No countdown padding when lyrics are skipped
396
+ processed_track["countdown_padding_added"] = False
397
+ processed_track["countdown_padding_seconds"] = 0.0
398
+ processed_track["padded_vocals_audio"] = None
395
399
  else:
396
400
  lyrics_artist = self.lyrics_artist or self.artist
397
401
  lyrics_title = self.lyrics_title or self.title
@@ -483,6 +487,20 @@ class KaraokePrep:
483
487
  if isinstance(transcriber_outputs, dict):
484
488
  self.lyrics = transcriber_outputs.get("corrected_lyrics_text")
485
489
  processed_track["lyrics"] = transcriber_outputs.get("corrected_lyrics_text_filepath")
490
+
491
+ # Capture countdown padding information
492
+ processed_track["countdown_padding_added"] = transcriber_outputs.get("countdown_padding_added", False)
493
+ processed_track["countdown_padding_seconds"] = transcriber_outputs.get("countdown_padding_seconds", 0.0)
494
+ processed_track["padded_vocals_audio"] = transcriber_outputs.get("padded_audio_filepath")
495
+
496
+ if processed_track["countdown_padding_added"]:
497
+ self.logger.info(
498
+ f"=== COUNTDOWN PADDING DETECTED ==="
499
+ )
500
+ self.logger.info(
501
+ f"Vocals have been padded with {processed_track['countdown_padding_seconds']}s of silence. "
502
+ f"Instrumental tracks will be padded after separation to maintain synchronization."
503
+ )
486
504
  else:
487
505
  self.logger.warning(f"Unexpected type for transcriber_outputs: {type(transcriber_outputs)}, value: {transcriber_outputs}")
488
506
  else:
@@ -586,6 +604,23 @@ class KaraokePrep:
586
604
  "instrumental": instrumental_path,
587
605
  "vocals": None,
588
606
  }
607
+
608
+ # If countdown padding was added to vocals, pad the custom instrumental too
609
+ if processed_track.get("countdown_padding_added", False):
610
+ padding_seconds = processed_track["countdown_padding_seconds"]
611
+ self.logger.info(
612
+ f"Countdown padding detected - applying {padding_seconds}s padding to custom instrumental"
613
+ )
614
+
615
+ base, ext = os.path.splitext(instrumental_path)
616
+ padded_instrumental_path = f"{base} (Padded){ext}"
617
+
618
+ if not self.file_handler._file_exists(padded_instrumental_path):
619
+ self.audio_processor.pad_audio_file(instrumental_path, padded_instrumental_path, padding_seconds)
620
+
621
+ # Update the path to use the padded version
622
+ processed_track["separated_audio"]["Custom"]["instrumental"] = padded_instrumental_path
623
+ self.logger.info(f"✓ Custom instrumental has been padded and synchronized with vocals")
589
624
  elif "separated_audio" not in processed_track or not processed_track["separated_audio"]:
590
625
  # Only run separation if it wasn't already done in parallel processing
591
626
  self.logger.info(f"Separation was not completed in parallel processing, running separation for track: {self.title} by {self.artist}")
@@ -597,6 +632,31 @@ class KaraokePrep:
597
632
  else:
598
633
  self.logger.info("Audio separation was already completed in parallel processing, skipping duplicate separation.")
599
634
 
635
+ # Apply countdown padding to instrumental files if needed
636
+ if processed_track.get("countdown_padding_added", False):
637
+ padding_seconds = processed_track["countdown_padding_seconds"]
638
+ self.logger.info(
639
+ f"=== APPLYING COUNTDOWN PADDING TO INSTRUMENTALS ==="
640
+ )
641
+ self.logger.info(
642
+ f"Applying {padding_seconds}s padding to all instrumental files to sync with vocal countdown"
643
+ )
644
+
645
+ # Apply padding using AudioProcessor
646
+ padded_separation_result = self.audio_processor.apply_countdown_padding_to_instrumentals(
647
+ separation_result=processed_track["separated_audio"],
648
+ padding_seconds=padding_seconds,
649
+ artist_title=artist_title,
650
+ track_output_dir=track_output_dir,
651
+ )
652
+
653
+ # Update processed_track with padded file paths
654
+ processed_track["separated_audio"] = padded_separation_result
655
+
656
+ self.logger.info(
657
+ f"✓ All instrumental files have been padded and are now synchronized with vocals"
658
+ )
659
+
600
660
  self.logger.info("Script finished, audio downloaded, lyrics fetched and audio separated!")
601
661
 
602
662
  return processed_track
@@ -286,6 +286,17 @@ class LyricsProcessor:
286
286
 
287
287
  self.logger.info(f"Saved correction data to {corrections_filepath}")
288
288
 
289
+ # Capture countdown padding information for syncing with instrumental audio
290
+ transcriber_outputs["countdown_padding_added"] = getattr(results, "countdown_padding_added", False)
291
+ transcriber_outputs["countdown_padding_seconds"] = getattr(results, "countdown_padding_seconds", 0.0)
292
+ transcriber_outputs["padded_audio_filepath"] = getattr(results, "padded_audio_filepath", None)
293
+
294
+ if transcriber_outputs["countdown_padding_added"]:
295
+ self.logger.info(
296
+ f"Countdown padding detected: {transcriber_outputs['countdown_padding_seconds']}s added to vocals. "
297
+ f"Instrumental audio will need to be padded accordingly."
298
+ )
299
+
289
300
  if transcriber_outputs:
290
301
  self.logger.info(f"*** Transcriber Filepath Outputs: ***")
291
302
  for key, value in transcriber_outputs.items():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karaoke-gen
3
- Version: 0.64.0
3
+ Version: 0.65.0
4
4
  Summary: Generate karaoke videos with synchronized lyrics. Handles the entire process from downloading audio and lyrics to creating the final video with title screens.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -26,7 +26,7 @@ Requires-Dist: google-auth-httplib2
26
26
  Requires-Dist: google-auth-oauthlib
27
27
  Requires-Dist: kbputils (>=0.0.16,<0.0.17)
28
28
  Requires-Dist: lyrics-converter (>=0.2.1)
29
- Requires-Dist: lyrics-transcriber (>=0.69)
29
+ Requires-Dist: lyrics-transcriber (>=0.72)
30
30
  Requires-Dist: lyricsgenius (>=3)
31
31
  Requires-Dist: matplotlib (>=3)
32
32
  Requires-Dist: modal (>=1.0.5,<2.0.0)
@@ -1,11 +1,11 @@
1
1
  karaoke_gen/__init__.py,sha256=ViryQjs8ALc8A7mqJGHu028zajF5-Za_etFagXlo6kk,269
2
- karaoke_gen/audio_processor.py,sha256=XMQcEUgLCcMbh4R3onZF4nPezWRFx4JYRk-k3RTUNPk,36901
2
+ karaoke_gen/audio_processor.py,sha256=uU7uqms2hQn_xWGEZ105tnUMwHuLTDSGKwzGRGUv1I4,42313
3
3
  karaoke_gen/config.py,sha256=YY0QOlGcS342iF12be48WJ9mF_SdXW6TzFG8LXLT_J4,8354
4
4
  karaoke_gen/file_handler.py,sha256=c86-rGF7Fusl0uEIZFnreT7PJfK7lmUaEgauU8BBzjY,10024
5
5
  karaoke_gen/karaoke_finalise/__init__.py,sha256=HqZ7TIhgt_tYZ-nb_NNCaejWAcF_aK-7wJY5TaW_keM,46
6
6
  karaoke_gen/karaoke_finalise/karaoke_finalise.py,sha256=WaC-zRIsfi5IeecJVNitMn4eDZ3Kzbfd7rGf2wULZgA,85344
7
- karaoke_gen/karaoke_gen.py,sha256=FDZovDEKPhvv4pQhGU06zvncK5k6dP9MW4Gj4nti3a4,39243
8
- karaoke_gen/lyrics_processor.py,sha256=eUyu0d1OZyWwmpyNCBTKrV1grNzbZ91pFIXnz7le04k,14203
7
+ karaoke_gen/karaoke_gen.py,sha256=dgLqjzHFbtVNZK-jK-bSx5lgeGTUF-OQrD1raD9MlmM,43032
8
+ karaoke_gen/lyrics_processor.py,sha256=R8vO0tF7-k5PVDiXrUMGd-4Fqa4M3QNInLy9Y3XhsgA,14912
9
9
  karaoke_gen/metadata.py,sha256=TprFzWj-iJ7ghrXlHFMPzzqzuHzWeNvs3zGaND-z9Ds,6503
10
10
  karaoke_gen/resources/AvenirNext-Bold.ttf,sha256=YxgKz2OP46lwLPCpIZhVa8COi_9KRDSXw4n8dIHHQSs,327048
11
11
  karaoke_gen/resources/Montserrat-Bold.ttf,sha256=mLFIaBDC7M-qF9RhCoPBJ5TAeY716etBrqA4eUKSoYc,198120
@@ -16,8 +16,8 @@ karaoke_gen/utils/__init__.py,sha256=FpOHyeBRB06f3zMoLBUJHTDZACrabg-DoyBTxNKYyNY
16
16
  karaoke_gen/utils/bulk_cli.py,sha256=uqAHnlidY-f_RhsQIHqZDnrznWRKhqpEDX2uiR1CUQs,18841
17
17
  karaoke_gen/utils/gen_cli.py,sha256=gZwICT3OyhUa3Y-R0YiwCGIdjNKqg06lKQQnzA7TA34,36428
18
18
  karaoke_gen/video_generator.py,sha256=B7BQBrjkyvk3L3sctnPXnvr1rzkw0NYx5UCAl0ZiVx0,18464
19
- karaoke_gen-0.64.0.dist-info/METADATA,sha256=oB81lzsNdxs2Iwmh-n96USDyqlELlMs1nJpOZHvwWkk,7367
20
- karaoke_gen-0.64.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
21
- karaoke_gen-0.64.0.dist-info/entry_points.txt,sha256=IZY3O8i7m-qkmPuqgpAcxiS2fotNc6hC-CDWvNmoUEY,107
22
- karaoke_gen-0.64.0.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
23
- karaoke_gen-0.64.0.dist-info/RECORD,,
19
+ karaoke_gen-0.65.0.dist-info/METADATA,sha256=Q-IlsjbwBWy6CQb8ssYqDH9di_Tycuo_IAZ9WpocHSc,7367
20
+ karaoke_gen-0.65.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
21
+ karaoke_gen-0.65.0.dist-info/entry_points.txt,sha256=IZY3O8i7m-qkmPuqgpAcxiS2fotNc6hC-CDWvNmoUEY,107
22
+ karaoke_gen-0.65.0.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
23
+ karaoke_gen-0.65.0.dist-info/RECORD,,