karaoke-gen 0.63.1__tar.gz → 0.65.0__tar.gz
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.
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/PKG-INFO +2 -2
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/audio_processor.py +119 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/karaoke_gen.py +60 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/lyrics_processor.py +11 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/pyproject.toml +2 -2
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/LICENSE +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/README.md +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/__init__.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/config.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/file_handler.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/karaoke_finalise/__init__.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/karaoke_finalise/karaoke_finalise.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/metadata.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/resources/AvenirNext-Bold.ttf +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/resources/Montserrat-Bold.ttf +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/resources/Oswald-Bold.ttf +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/resources/Oswald-SemiBold.ttf +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/resources/Zurich_Cn_BT_Bold.ttf +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/utils/__init__.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/utils/bulk_cli.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/utils/gen_cli.py +0 -0
- {karaoke_gen-0.63.1 → karaoke_gen-0.65.0}/karaoke_gen/video_generator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: karaoke-gen
|
|
3
|
-
Version: 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.
|
|
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)
|
|
@@ -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
|
[tool.poetry]
|
|
2
2
|
name = "karaoke-gen"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.65.0"
|
|
4
4
|
description = "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
|
authors = ["Andrew Beveridge <andrew@beveridge.uk>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -28,7 +28,7 @@ thefuzz = ">=0.22"
|
|
|
28
28
|
numpy = ">=2"
|
|
29
29
|
audio-separator = { version = ">=0.34.0", extras = ["cpu"] }
|
|
30
30
|
lyrics-converter = ">=0.2.1"
|
|
31
|
-
lyrics-transcriber = ">=0.
|
|
31
|
+
lyrics-transcriber = ">=0.72"
|
|
32
32
|
kbputils = "^0.0.16"
|
|
33
33
|
attrs = ">=24.2.0"
|
|
34
34
|
cattrs = ">=24.1.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|