karaoke-gen 0.50.0__py3-none-any.whl → 0.55.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.

Files changed (26) hide show
  1. karaoke_gen/__init__.py +7 -0
  2. {karaoke_prep → karaoke_gen}/audio_processor.py +8 -3
  3. {karaoke_prep → karaoke_gen}/karaoke_finalise/karaoke_finalise.py +8 -1
  4. karaoke_prep/karaoke_prep.py → karaoke_gen/karaoke_gen.py +12 -6
  5. {karaoke_prep → karaoke_gen}/lyrics_processor.py +34 -10
  6. {karaoke_prep → karaoke_gen}/utils/bulk_cli.py +2 -2
  7. {karaoke_prep → karaoke_gen}/utils/gen_cli.py +32 -94
  8. {karaoke_prep → karaoke_gen}/video_generator.py +5 -5
  9. {karaoke_gen-0.50.0.dist-info → karaoke_gen-0.55.0.dist-info}/METADATA +37 -12
  10. karaoke_gen-0.55.0.dist-info/RECORD +23 -0
  11. karaoke_gen-0.55.0.dist-info/entry_points.txt +4 -0
  12. karaoke_gen-0.50.0.dist-info/RECORD +0 -23
  13. karaoke_gen-0.50.0.dist-info/entry_points.txt +0 -4
  14. karaoke_prep/__init__.py +0 -1
  15. {karaoke_prep → karaoke_gen}/config.py +0 -0
  16. {karaoke_prep → karaoke_gen}/file_handler.py +0 -0
  17. {karaoke_prep → karaoke_gen}/karaoke_finalise/__init__.py +0 -0
  18. {karaoke_prep → karaoke_gen}/metadata.py +0 -0
  19. {karaoke_prep → karaoke_gen}/resources/AvenirNext-Bold.ttf +0 -0
  20. {karaoke_prep → karaoke_gen}/resources/Montserrat-Bold.ttf +0 -0
  21. {karaoke_prep → karaoke_gen}/resources/Oswald-Bold.ttf +0 -0
  22. {karaoke_prep → karaoke_gen}/resources/Oswald-SemiBold.ttf +0 -0
  23. {karaoke_prep → karaoke_gen}/resources/Zurich_Cn_BT_Bold.ttf +0 -0
  24. {karaoke_prep → karaoke_gen}/utils/__init__.py +0 -0
  25. {karaoke_gen-0.50.0.dist-info → karaoke_gen-0.55.0.dist-info}/LICENSE +0 -0
  26. {karaoke_gen-0.50.0.dist-info → karaoke_gen-0.55.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,7 @@
1
+ import warnings
2
+
3
+ # Suppress specific SyntaxWarnings from third-party packages
4
+ warnings.filterwarnings("ignore", category=SyntaxWarning, module="pydub.*")
5
+ warnings.filterwarnings("ignore", category=SyntaxWarning, module="syrics.*")
6
+
7
+ from .karaoke_gen import KaraokePrep
@@ -133,8 +133,13 @@ class AudioProcessor:
133
133
  runtime_mins = runtime.total_seconds() / 60
134
134
 
135
135
  # Get process command line
136
- proc = psutil.Process(pid)
137
- cmd = " ".join(proc.cmdline())
136
+ try:
137
+ proc = psutil.Process(pid)
138
+ cmdline_args = proc.cmdline()
139
+ # Handle potential bytes in cmdline args (cross-platform compatibility)
140
+ cmd = " ".join(arg.decode('utf-8', errors='replace') if isinstance(arg, bytes) else arg for arg in cmdline_args)
141
+ except (psutil.AccessDenied, psutil.NoSuchProcess):
142
+ cmd = "<command unavailable>"
138
143
 
139
144
  self.logger.info(
140
145
  f"Waiting for other audio separation process to complete before starting separation for {artist_title}...\n"
@@ -182,7 +187,7 @@ class AudioProcessor:
182
187
  stems_dir = self._create_stems_directory(track_output_dir)
183
188
  result = {"clean_instrumental": {}, "other_stems": {}, "backing_vocals": {}, "combined_instrumentals": {}}
184
189
 
185
- if os.environ.get("KARAOKE_PREP_SKIP_AUDIO_SEPARATION"):
190
+ if os.environ.get("KARAOKE_GEN_SKIP_AUDIO_SEPARATION"):
186
191
  return result
187
192
 
188
193
  result["clean_instrumental"] = self._separate_clean_instrumental(
@@ -924,7 +924,14 @@ class KaraokeFinalise:
924
924
  else:
925
925
  shutil.copy2(output_files["final_karaoke_lossy_mp4"], dest_mp4_file) # Changed to use lossy MP4
926
926
  shutil.copy2(output_files["final_karaoke_lossy_720p_mp4"], dest_720p_mp4_file)
927
- shutil.copy2(output_files["final_karaoke_cdg_zip"], dest_zip_file)
927
+
928
+ # Only copy CDG ZIP if CDG creation is enabled
929
+ if self.enable_cdg and "final_karaoke_cdg_zip" in output_files:
930
+ shutil.copy2(output_files["final_karaoke_cdg_zip"], dest_zip_file)
931
+ self.logger.info(f"Copied CDG ZIP file to public share directory")
932
+ else:
933
+ self.logger.info(f"CDG creation disabled, skipping CDG ZIP copy")
934
+
928
935
  self.logger.info(f"Copied final files to public share directory")
929
936
 
930
937
  def sync_public_share_dir_to_rclone_destination(self):
@@ -382,14 +382,20 @@ class KaraokePrep:
382
382
  # Run transcription in a separate thread
383
383
  transcription_future = asyncio.create_task(
384
384
  asyncio.to_thread(
385
- # Delegate to LyricsProcessor
386
- self.lyrics_processor.transcribe_lyrics, processed_track["input_audio_wav"], lyrics_artist, lyrics_title, track_output_dir
385
+ # Delegate to LyricsProcessor - pass original artist/title for filenames, lyrics_artist/lyrics_title for processing
386
+ self.lyrics_processor.transcribe_lyrics,
387
+ processed_track["input_audio_wav"],
388
+ self.artist, # Original artist for filename generation
389
+ self.title, # Original title for filename generation
390
+ track_output_dir,
391
+ lyrics_artist, # Lyrics artist for processing
392
+ lyrics_title # Lyrics title for processing
387
393
  )
388
394
  )
389
395
  self.logger.info(f"Transcription future created, type: {type(transcription_future)}")
390
396
 
391
- # Default to a placeholder future if separation won't run
392
- separation_future = asyncio.sleep(0)
397
+ # Default to a placeholder task if separation won't run
398
+ separation_future = asyncio.create_task(asyncio.sleep(0))
393
399
 
394
400
  # Only create real separation future if not skipping AND no existing instrumental provided
395
401
  if not self.skip_separation and not self.existing_instrumental:
@@ -501,7 +507,7 @@ class KaraokePrep:
501
507
  processed_track["title_video"] = os.path.join(track_output_dir, f"{artist_title} (Title).mov")
502
508
 
503
509
  # Use FileHandler._file_exists
504
- if not self.file_handler._file_exists(processed_track["title_video"]) and not os.environ.get("KARAOKE_PREP_SKIP_TITLE_END_SCREENS"):
510
+ if not self.file_handler._file_exists(processed_track["title_video"]) and not os.environ.get("KARAOKE_GEN_SKIP_TITLE_END_SCREENS"):
505
511
  self.logger.info(f"Creating title video...")
506
512
  # Delegate to VideoGenerator
507
513
  self.video_generator.create_title_video(
@@ -520,7 +526,7 @@ class KaraokePrep:
520
526
  processed_track["end_video"] = os.path.join(track_output_dir, f"{artist_title} (End).mov")
521
527
 
522
528
  # Use FileHandler._file_exists
523
- if not self.file_handler._file_exists(processed_track["end_video"]) and not os.environ.get("KARAOKE_PREP_SKIP_TITLE_END_SCREENS"):
529
+ if not self.file_handler._file_exists(processed_track["end_video"]) and not os.environ.get("KARAOKE_GEN_SKIP_TITLE_END_SCREENS"):
524
530
  self.logger.info(f"Creating end screen video...")
525
531
  # Delegate to VideoGenerator
526
532
  self.video_generator.create_end_video(
@@ -102,14 +102,33 @@ class LyricsProcessor:
102
102
 
103
103
  return processed_lines
104
104
 
105
- def transcribe_lyrics(self, input_audio_wav, artist, title, track_output_dir):
105
+ def transcribe_lyrics(self, input_audio_wav, artist, title, track_output_dir, lyrics_artist=None, lyrics_title=None):
106
+ """
107
+ Transcribe lyrics for a track.
108
+
109
+ Args:
110
+ input_audio_wav: Path to the audio file
111
+ artist: Original artist name (used for filename generation)
112
+ title: Original title (used for filename generation)
113
+ track_output_dir: Output directory path
114
+ lyrics_artist: Artist name for lyrics processing (defaults to artist if None)
115
+ lyrics_title: Title for lyrics processing (defaults to title if None)
116
+ """
117
+ # Use original artist/title for filename generation
118
+ filename_artist = artist
119
+ filename_title = title
120
+
121
+ # Use lyrics_artist/lyrics_title for actual lyrics processing, fall back to originals if not provided
122
+ processing_artist = lyrics_artist or artist
123
+ processing_title = lyrics_title or title
124
+
106
125
  self.logger.info(
107
- f"Transcribing lyrics for track {artist} - {title} from audio file: {input_audio_wav} with output directory: {track_output_dir}"
126
+ f"Transcribing lyrics for track {processing_artist} - {processing_title} from audio file: {input_audio_wav} with output directory: {track_output_dir}"
108
127
  )
109
128
 
110
- # Check for existing files first using sanitized names
111
- sanitized_artist = sanitize_filename(artist)
112
- sanitized_title = sanitize_filename(title)
129
+ # Check for existing files first using sanitized names from ORIGINAL artist/title for consistency
130
+ sanitized_artist = sanitize_filename(filename_artist)
131
+ sanitized_title = sanitize_filename(filename_title)
113
132
  parent_video_path = os.path.join(track_output_dir, f"{sanitized_artist} - {sanitized_title} (With Vocals).mkv")
114
133
  parent_lrc_path = os.path.join(track_output_dir, f"{sanitized_artist} - {sanitized_title} (Karaoke).lrc")
115
134
 
@@ -137,10 +156,15 @@ class LyricsProcessor:
137
156
  "ass_filepath": parent_video_path,
138
157
  }
139
158
 
140
- # Create lyrics subdirectory for new transcription
159
+ # Create lyrics directory if it doesn't exist
141
160
  os.makedirs(lyrics_dir, exist_ok=True)
142
161
  self.logger.info(f"Created lyrics directory: {lyrics_dir}")
143
162
 
163
+ # Set render_video to False if explicitly disabled
164
+ render_video = self.render_video
165
+ if not render_video:
166
+ self.logger.info("Video rendering disabled, skipping video output")
167
+
144
168
  # Load environment variables
145
169
  load_dotenv()
146
170
  env_config = {
@@ -165,7 +189,7 @@ class LyricsProcessor:
165
189
  output_config = OutputConfig(
166
190
  output_styles_json=self.style_params_json,
167
191
  output_dir=lyrics_dir,
168
- render_video=self.render_video,
192
+ render_video=render_video,
169
193
  fetch_lyrics=True,
170
194
  run_transcription=not self.skip_transcription,
171
195
  run_correction=True,
@@ -180,11 +204,11 @@ class LyricsProcessor:
180
204
  # Add this log entry to debug the OutputConfig
181
205
  self.logger.info(f"Instantiating LyricsTranscriber with OutputConfig: {output_config}")
182
206
 
183
- # Initialize transcriber with new config objects
207
+ # Initialize transcriber with new config objects - use PROCESSING artist/title for lyrics work
184
208
  transcriber = LyricsTranscriber(
185
209
  audio_filepath=input_audio_wav,
186
- artist=artist,
187
- title=title,
210
+ artist=processing_artist, # Use lyrics_artist for processing
211
+ title=processing_title, # Use lyrics_title for processing
188
212
  transcriber_config=transcriber_config,
189
213
  lyrics_config=lyrics_config,
190
214
  output_config=output_config,
@@ -7,8 +7,8 @@ import csv
7
7
  import asyncio
8
8
  import json
9
9
  import sys
10
- from karaoke_prep import KaraokePrep
11
- from karaoke_prep.karaoke_finalise import KaraokeFinalise
10
+ from karaoke_gen import KaraokePrep
11
+ from karaoke_gen.karaoke_finalise import KaraokeFinalise
12
12
 
13
13
  # Global logger
14
14
  logger = logging.getLogger(__name__)
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env python
2
- print("DEBUG: gen_cli.py starting imports...")
3
2
  import argparse
4
3
  import logging
5
4
  from importlib import metadata
@@ -10,10 +9,8 @@ import json
10
9
  import asyncio
11
10
  import time
12
11
  import pyperclip
13
- from karaoke_prep import KaraokePrep
14
- from karaoke_prep.karaoke_finalise import KaraokeFinalise
15
-
16
- print("DEBUG: gen_cli.py imports complete.")
12
+ from karaoke_gen import KaraokePrep
13
+ from karaoke_gen.karaoke_finalise import KaraokeFinalise
17
14
 
18
15
 
19
16
  def is_url(string):
@@ -27,15 +24,12 @@ def is_file(string):
27
24
 
28
25
 
29
26
  async def async_main():
30
- print("DEBUG: async_main() started.")
31
27
  logger = logging.getLogger(__name__)
32
28
  log_handler = logging.StreamHandler()
33
29
  log_formatter = logging.Formatter(fmt="%(asctime)s.%(msecs)03d - %(levelname)s - %(module)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
34
30
  log_handler.setFormatter(log_formatter)
35
31
  logger.addHandler(log_handler)
36
32
 
37
- print("DEBUG: async_main() logger configured.")
38
-
39
33
  parser = argparse.ArgumentParser(
40
34
  description="Generate karaoke videos with synchronized lyrics. Handles the entire process from downloading audio and lyrics to creating the final video.",
41
35
  formatter_class=lambda prog: argparse.RawTextHelpFormatter(prog, max_help_position=54),
@@ -53,7 +47,6 @@ async def async_main():
53
47
  package_version = metadata.version("karaoke-gen")
54
48
  except metadata.PackageNotFoundError:
55
49
  package_version = "unknown"
56
- print("DEBUG: Could not find version for karaoke-gen")
57
50
 
58
51
  parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {package_version}")
59
52
 
@@ -290,8 +283,6 @@ async def async_main():
290
283
 
291
284
  args = parser.parse_args()
292
285
 
293
- print("DEBUG: async_main() args parsed.")
294
-
295
286
  # Handle test email template case first
296
287
  if args.test_email_template:
297
288
  log_level = getattr(logging, args.log_level.upper())
@@ -305,8 +296,6 @@ async def async_main():
305
296
  kfinalise.test_email_template()
306
297
  return
307
298
 
308
- print("DEBUG: async_main() continuing after test_email_template check.")
309
-
310
299
  # Handle edit-lyrics mode
311
300
  if args.edit_lyrics:
312
301
  log_level = getattr(logging, args.log_level.upper())
@@ -484,8 +473,6 @@ async def async_main():
484
473
  raise e
485
474
 
486
475
  return
487
-
488
- print("DEBUG: async_main() continuing after edit_lyrics check.")
489
476
 
490
477
  # Handle finalise-only mode
491
478
  if args.finalise_only:
@@ -512,7 +499,11 @@ async def async_main():
512
499
  logger.error(f"Invalid JSON in CDG styles configuration file: {e}")
513
500
  sys.exit(1)
514
501
  return # Explicit return for testing
515
-
502
+ except KeyError:
503
+ logger.error(f"'cdg' key not found in style parameters file: {args.style_params_json}")
504
+ sys.exit(1)
505
+ return # Explicit return for testing
506
+
516
507
  kfinalise = KaraokeFinalise(
517
508
  log_formatter=log_formatter,
518
509
  log_level=log_level,
@@ -530,13 +521,16 @@ async def async_main():
530
521
  discord_webhook_url=args.discord_webhook_url,
531
522
  email_template_file=args.email_template_file,
532
523
  cdg_styles=cdg_styles,
533
- keep_brand_code=args.keep_brand_code,
524
+ keep_brand_code=getattr(args, 'keep_brand_code', False),
534
525
  non_interactive=args.yes,
535
526
  )
536
-
527
+
537
528
  try:
538
529
  track = kfinalise.process()
539
- logger.info(f"Karaoke finalisation processing complete! Output files:")
530
+ logger.info(f"Successfully completed finalisation for: {track['artist']} - {track['title']}")
531
+
532
+ # Display summary of outputs
533
+ logger.info(f"Karaoke finalisation complete! Output files:")
540
534
  logger.info(f"")
541
535
  logger.info(f"Track: {track['artist']} - {track['title']}")
542
536
  logger.info(f"")
@@ -564,7 +558,7 @@ async def async_main():
564
558
  logger.info(f"")
565
559
  logger.info(f"Organization:")
566
560
  logger.info(f" Brand Code: {track['brand_code']}")
567
- logger.info(f" New Directory: {track['new_brand_code_dir_path']}")
561
+ logger.info(f" Directory: {track['new_brand_code_dir_path']}")
568
562
 
569
563
  if track["youtube_url"] or track["brand_code_dir_sharing_link"]:
570
564
  logger.info(f"")
@@ -592,8 +586,6 @@ async def async_main():
592
586
 
593
587
  return
594
588
 
595
- print("DEBUG: async_main() parsed positional args.")
596
-
597
589
  # For prep or full workflow, parse input arguments
598
590
  input_media, artist, title, filename_pattern = None, None, None, None
599
591
 
@@ -643,23 +635,18 @@ async def async_main():
643
635
  log_level = getattr(logging, args.log_level.upper())
644
636
  logger.setLevel(log_level)
645
637
 
646
- print("DEBUG: async_main() log level set.")
647
-
648
638
  # Set up environment variables for lyrics-only mode
649
639
  if args.lyrics_only:
650
640
  args.skip_separation = True
651
- os.environ["KARAOKE_PREP_SKIP_AUDIO_SEPARATION"] = "1"
652
- os.environ["KARAOKE_PREP_SKIP_TITLE_END_SCREENS"] = "1"
641
+ os.environ["KARAOKE_GEN_SKIP_AUDIO_SEPARATION"] = "1"
642
+ os.environ["KARAOKE_GEN_SKIP_TITLE_END_SCREENS"] = "1"
653
643
  logger.info("Lyrics-only mode enabled: skipping audio separation and title/end screen generation")
654
644
 
655
- print("DEBUG: async_main() instantiating KaraokePrep...")
656
-
657
645
  # Step 1: Run KaraokePrep
658
- logger.info(f"KaraokePrep beginning with input_media: {input_media} artist: {artist} and title: {title}")
659
646
  kprep_coroutine = KaraokePrep(
647
+ input_media=input_media,
660
648
  artist=artist,
661
649
  title=title,
662
- input_media=input_media,
663
650
  filename_pattern=filename_pattern,
664
651
  dry_run=args.dry_run,
665
652
  log_formatter=log_formatter,
@@ -688,64 +675,16 @@ async def async_main():
688
675
  # No await needed for constructor
689
676
  kprep = kprep_coroutine
690
677
 
691
- print("DEBUG: async_main() KaraokePrep instantiated.")
692
-
693
- print(f"DEBUG: kprep type: {type(kprep)}")
694
- print(f"DEBUG: kprep.process type: {type(kprep.process)}")
695
- process_coroutine = kprep.process()
696
- print(f"DEBUG: process_coroutine type: {type(process_coroutine)}")
697
- tracks = await process_coroutine
698
-
699
- print("DEBUG: async_main() kprep.process() finished.")
678
+ # Create final tracks data structure
679
+ tracks = await kprep.process()
700
680
 
701
- # If prep-only mode, display detailed output and exit
681
+ # If prep-only mode, we're done
702
682
  if args.prep_only:
703
- logger.info(f"Karaoke Prep complete! Output files:")
704
-
705
- for track in tracks:
706
- logger.info(f"")
707
- logger.info(f"Track: {track['artist']} - {track['title']}")
708
- logger.info(f" Input Media: {track['input_media']}")
709
- logger.info(f" Input WAV Audio: {track['input_audio_wav']}")
710
- logger.info(f" Input Still Image: {track['input_still_image']}")
711
- logger.info(f" Lyrics: {track['lyrics']}")
712
- logger.info(f" Processed Lyrics: {track['processed_lyrics']}")
713
-
714
- logger.info(f" Separated Audio:")
715
-
716
- # Clean Instrumental
717
- logger.info(f" Clean Instrumental Model:")
718
- for stem_type, file_path in track["separated_audio"]["clean_instrumental"].items():
719
- logger.info(f" {stem_type.capitalize()}: {file_path}")
720
-
721
- # Other Stems
722
- logger.info(f" Other Stems Models:")
723
- for model, stems in track["separated_audio"]["other_stems"].items():
724
- logger.info(f" Model: {model}")
725
- for stem_type, file_path in stems.items():
726
- logger.info(f" {stem_type.capitalize()}: {file_path}")
727
-
728
- # Backing Vocals
729
- logger.info(f" Backing Vocals Models:")
730
- for model, stems in track["separated_audio"]["backing_vocals"].items():
731
- logger.info(f" Model: {model}")
732
- for stem_type, file_path in stems.items():
733
- logger.info(f" {stem_type.capitalize()}: {file_path}")
734
-
735
- # Combined Instrumentals
736
- logger.info(f" Combined Instrumentals:")
737
- for model, file_path in track["separated_audio"]["combined_instrumentals"].items():
738
- logger.info(f" Model: {model}")
739
- logger.info(f" Combined Instrumental: {file_path}")
740
-
741
- logger.info("Preparation phase complete. Exiting due to --prep-only flag.")
683
+ logger.info("Prep-only mode: skipping finalisation phase")
742
684
  return
743
685
 
744
- print("DEBUG: async_main() continuing after prep_only check.")
745
-
746
686
  # Step 2: For each track, run KaraokeFinalise
747
687
  for track in tracks:
748
- print(f"DEBUG: async_main() starting finalise loop for track: {track.get('track_output_dir')}")
749
688
  logger.info(f"Starting finalisation phase for {track['artist']} - {track['title']}...")
750
689
 
751
690
  # Use the track directory that was actually created by KaraokePrep
@@ -776,8 +715,11 @@ async def async_main():
776
715
  logger.error(f"Invalid JSON in CDG styles configuration file: {e}")
777
716
  sys.exit(1)
778
717
  return # Explicit return for testing
718
+ except KeyError:
719
+ logger.error(f"'cdg' key not found in style parameters file: {args.style_params_json}")
720
+ sys.exit(1)
721
+ return # Explicit return for testing
779
722
 
780
- # Initialize KaraokeFinalise
781
723
  kfinalise = KaraokeFinalise(
782
724
  log_formatter=log_formatter,
783
725
  log_level=log_level,
@@ -795,16 +737,16 @@ async def async_main():
795
737
  discord_webhook_url=args.discord_webhook_url,
796
738
  email_template_file=args.email_template_file,
797
739
  cdg_styles=cdg_styles,
798
- keep_brand_code=args.keep_brand_code,
740
+ keep_brand_code=getattr(args, 'keep_brand_code', False),
799
741
  non_interactive=args.yes,
800
742
  )
801
743
 
802
744
  try:
803
745
  final_track = kfinalise.process()
804
- logger.info(f"Successfully completed processing for: {track['artist']} - {track['title']}")
746
+ logger.info(f"Successfully completed processing: {final_track['artist']} - {final_track['title']}")
805
747
 
806
748
  # Display summary of outputs
807
- logger.info(f"Karaoke processing complete! Output files:")
749
+ logger.info(f"Karaoke generation complete! Output files:")
808
750
  logger.info(f"")
809
751
  logger.info(f"Track: {final_track['artist']} - {final_track['title']}")
810
752
  logger.info(f"")
@@ -832,7 +774,7 @@ async def async_main():
832
774
  logger.info(f"")
833
775
  logger.info(f"Organization:")
834
776
  logger.info(f" Brand Code: {final_track['brand_code']}")
835
- logger.info(f" New Directory: {final_track['new_brand_code_dir_path']}")
777
+ logger.info(f" Directory: {final_track['new_brand_code_dir_path']}")
836
778
 
837
779
  if final_track["youtube_url"] or final_track["brand_code_dir_sharing_link"]:
838
780
  logger.info(f"")
@@ -854,20 +796,16 @@ async def async_main():
854
796
  logger.info(f" (YouTube URL copied to clipboard)")
855
797
  except Exception as e:
856
798
  logger.warning(f" Failed to copy YouTube URL to clipboard: {str(e)}")
857
-
858
799
  except Exception as e:
859
- logger.error(f"Error during finalisation: {str(e)}")
800
+ logger.error(f"An error occurred during finalisation, see stack trace below: {str(e)}")
860
801
  raise e
861
-
862
- print("DEBUG: async_main() finished.")
802
+
803
+ return
863
804
 
864
805
 
865
806
  def main():
866
- print("DEBUG: main() started.")
867
807
  asyncio.run(async_main())
868
- print("DEBUG: main() finished.")
869
808
 
870
809
 
871
810
  if __name__ == "__main__":
872
- print("DEBUG: __main__ block executing.")
873
811
  main()
@@ -71,7 +71,7 @@ class VideoGenerator:
71
71
  else:
72
72
  # Try to load from package resources
73
73
  try:
74
- with pkg_resources.path("karaoke_prep.resources", format["font"]) as font_path:
74
+ with pkg_resources.path("karaoke_gen.resources", format["font"]) as font_path:
75
75
  font_path = str(font_path)
76
76
  except Exception as e:
77
77
  self.logger.warning(f"Could not load font from resources: {e}, falling back to default font")
@@ -96,7 +96,7 @@ class VideoGenerator:
96
96
 
97
97
  def calculate_text_size_to_fit(self, draw, text, font_path, region):
98
98
  font_size = 500 # Start with a large font size
99
- font = ImageFont.truetype(font_path, size=font_size) if os.path.exists(font_path) else ImageFont.load_default()
99
+ font = ImageFont.truetype(font_path, size=font_size) if font_path and os.path.exists(font_path) else ImageFont.load_default()
100
100
 
101
101
  def get_text_size(text, font):
102
102
  bbox = draw.textbbox((0, 0), text, font=font)
@@ -117,7 +117,7 @@ class VideoGenerator:
117
117
 
118
118
  # Reset font size for two-line layout
119
119
  font_size = 500
120
- font = ImageFont.truetype(font_path, size=font_size) if os.path.exists(font_path) else ImageFont.load_default()
120
+ font = ImageFont.truetype(font_path, size=font_size) if font_path and os.path.exists(font_path) else ImageFont.load_default()
121
121
 
122
122
  while True:
123
123
  text_width1, text_height1 = get_text_size(line1, font)
@@ -134,9 +134,9 @@ class VideoGenerator:
134
134
  font_size -= 10
135
135
  if font_size <= 0:
136
136
  raise ValueError("Cannot fit text within the defined region.")
137
- font = ImageFont.truetype(font_path, size=font_size) if os.path.exists(font_path) else ImageFont.load_default()
137
+ font = ImageFont.truetype(font_path, size=font_size) if font_path and os.path.exists(font_path) else ImageFont.load_default()
138
138
 
139
- font = ImageFont.truetype(font_path, size=font_size) if os.path.exists(font_path) else ImageFont.load_default()
139
+ font = ImageFont.truetype(font_path, size=font_size) if font_path and os.path.exists(font_path) else ImageFont.load_default()
140
140
  text_width, text_height = get_text_size(text, font)
141
141
 
142
142
  return font, text
@@ -1,19 +1,20 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: karaoke-gen
3
- Version: 0.50.0
3
+ Version: 0.55.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
  Author: Andrew Beveridge
7
7
  Author-email: andrew@beveridge.uk
8
- Requires-Python: >=3.10,<3.13
8
+ Requires-Python: >=3.10,<3.14
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
14
15
  Requires-Dist: argparse (>=1.4.0)
15
16
  Requires-Dist: attrs (>=24.2.0)
16
- Requires-Dist: audio-separator[cpu] (>=0.21.0)
17
+ Requires-Dist: audio-separator[cpu] (>=0.34.0)
17
18
  Requires-Dist: beautifulsoup4 (>=4)
18
19
  Requires-Dist: cattrs (>=24.1.2)
19
20
  Requires-Dist: fetch-lyrics-from-genius (>=0.1)
@@ -24,9 +25,9 @@ Requires-Dist: google-auth-httplib2
24
25
  Requires-Dist: google-auth-oauthlib
25
26
  Requires-Dist: kbputils (>=0.0.16,<0.0.17)
26
27
  Requires-Dist: lyrics-converter (>=0.2.1)
27
- Requires-Dist: lyrics-transcriber (>=0.34)
28
+ Requires-Dist: lyrics-transcriber (>=0.54)
28
29
  Requires-Dist: lyricsgenius (>=3)
29
- Requires-Dist: numpy (>=1,<2)
30
+ Requires-Dist: numpy (>=2)
30
31
  Requires-Dist: pillow (>=10.1)
31
32
  Requires-Dist: psutil (>=7.0.0,<8.0.0)
32
33
  Requires-Dist: pyinstaller (>=6.3)
@@ -35,20 +36,25 @@ Requires-Dist: pytest-asyncio (>=0.23.5,<0.24.0)
35
36
  Requires-Dist: requests (>=2)
36
37
  Requires-Dist: thefuzz (>=0.22)
37
38
  Requires-Dist: toml (>=0.10)
38
- Requires-Dist: torch (<2.5)
39
+ Requires-Dist: torch (>=2.7)
39
40
  Requires-Dist: yt-dlp
40
- Project-URL: Documentation, https://github.com/karaokenerds/karaoke-gen/blob/main/README.md
41
- Project-URL: Homepage, https://github.com/karaokenerds/karaoke-gen
42
- Project-URL: Repository, https://github.com/karaokenerds/karaoke-gen
41
+ Project-URL: Documentation, https://github.com/nomadkaraoke/karaoke-gen/blob/main/README.md
42
+ Project-URL: Homepage, https://github.com/nomadkaraoke/karaoke-gen
43
+ Project-URL: Repository, https://github.com/nomadkaraoke/karaoke-gen
43
44
  Description-Content-Type: text/markdown
44
45
 
45
- # Karaoke Gen
46
+ # Karaoke Generator 🎶 🎥 🚀
46
47
 
47
- Generate karaoke videos with synchronized lyrics. Handles the entire process from downloading audio and lyrics to creating the final video with title screens.
48
+ ![PyPI - Version](https://img.shields.io/pypi/v/karaoke-gen)
49
+ ![Python Version](https://img.shields.io/badge/python-3.10+-blue)
50
+ ![Tests](https://github.com/nomadkaraoke/karaoke-gen/workflows/Test%20and%20Publish/badge.svg)
51
+ ![Test Coverage](https://codecov.io/gh/nomadkaraoke/karaoke-gen/branch/main/graph/badge.svg)
52
+
53
+ Generate karaoke videos with instrumental audio and synchronized lyrics. Handles the entire process from downloading audio and lyrics to creating the final video with title screens, uploading the resulting video to YouTube.
48
54
 
49
55
  ## Overview
50
56
 
51
- Karaoke Gen is a comprehensive tool for creating high-quality karaoke videos. It automates the entire workflow:
57
+ Karaoke Generator is a comprehensive tool for creating high-quality karaoke videos. It automates the entire workflow:
52
58
 
53
59
  1. **Download** audio and lyrics for a specified song
54
60
  2. **Separate** audio stems (vocals, instrumental)
@@ -134,6 +140,25 @@ For a complete list of options:
134
140
  karaoke-gen --help
135
141
  ```
136
142
 
143
+ ## Development
144
+
145
+ ### Running Tests
146
+
147
+ The project uses pytest for testing with unit and integration tests:
148
+
149
+ ```bash
150
+ # Run all tests (unit tests first, then integration tests)
151
+ pytest
152
+
153
+ # Run only unit tests (fast feedback during development)
154
+ pytest -m "not integration"
155
+
156
+ # Run only integration tests (comprehensive end-to-end testing)
157
+ pytest -m integration
158
+ ```
159
+
160
+ Unit tests run quickly and provide fast feedback, while integration tests are slower but test the full workflow end-to-end.
161
+
137
162
  ## License
138
163
 
139
164
  MIT
@@ -0,0 +1,23 @@
1
+ karaoke_gen/__init__.py,sha256=ViryQjs8ALc8A7mqJGHu028zajF5-Za_etFagXlo6kk,269
2
+ karaoke_gen/audio_processor.py,sha256=VnFRO1yhljHxAUnHs8Ir6fobnvIE7isF8-qdymFw2bQ,19581
3
+ karaoke_gen/config.py,sha256=I3h-940ZXvbrCNq_xcWHPMIB76cl-VNQYcK7-qgB-YI,6833
4
+ karaoke_gen/file_handler.py,sha256=sQNfEpqIir4Jd-V0VXUGZlsItt7pxZ8n_muzc1ONYck,8479
5
+ karaoke_gen/karaoke_finalise/__init__.py,sha256=HqZ7TIhgt_tYZ-nb_NNCaejWAcF_aK-7wJY5TaW_keM,46
6
+ karaoke_gen/karaoke_finalise/karaoke_finalise.py,sha256=k7rC0O2sHcx2vNXbJrYfN0yRoKVJ8uohz9biAO3Eqgk,56269
7
+ karaoke_gen/karaoke_gen.py,sha256=a-75k5tVGrfwplWiBsEyJG5nyCMSVy86IUdWIdFsYbw,37628
8
+ karaoke_gen/lyrics_processor.py,sha256=OBLJEm9HnavQpJBpx-ML8BjxHG8N47lAMd3Uw5K7Nr0,11308
9
+ karaoke_gen/metadata.py,sha256=PkwTnxX7fwbRmo_8ysC2zAMYSdZTtJtXWQypgNzssz8,4729
10
+ karaoke_gen/resources/AvenirNext-Bold.ttf,sha256=YxgKz2OP46lwLPCpIZhVa8COi_9KRDSXw4n8dIHHQSs,327048
11
+ karaoke_gen/resources/Montserrat-Bold.ttf,sha256=mLFIaBDC7M-qF9RhCoPBJ5TAeY716etBrqA4eUKSoYc,198120
12
+ karaoke_gen/resources/Oswald-Bold.ttf,sha256=S_2mLpNkBsDTe8FQRzrj1Qr-wloGETMJgoAcSKdi1lw,87604
13
+ karaoke_gen/resources/Oswald-SemiBold.ttf,sha256=G-vSJeeyEVft7D4s7FZQtGfXAViWPjzGCImV2a4u9d8,87608
14
+ karaoke_gen/resources/Zurich_Cn_BT_Bold.ttf,sha256=WNG5LOQ-uGUF_WWT5aQHzVbyWvQqGO5sZ4E-nRmvPuI,37780
15
+ karaoke_gen/utils/__init__.py,sha256=FpOHyeBRB06f3zMoLBUJHTDZACrabg-DoyBTxNKYyNY,722
16
+ karaoke_gen/utils/bulk_cli.py,sha256=uqAHnlidY-f_RhsQIHqZDnrznWRKhqpEDX2uiR1CUQs,18841
17
+ karaoke_gen/utils/gen_cli.py,sha256=sAZ-sau_3dI2hNBOZfiZqJjRf_cJFtuvZLy1V6URcxM,35688
18
+ karaoke_gen/video_generator.py,sha256=B7BQBrjkyvk3L3sctnPXnvr1rzkw0NYx5UCAl0ZiVx0,18464
19
+ karaoke_gen-0.55.0.dist-info/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
20
+ karaoke_gen-0.55.0.dist-info/METADATA,sha256=rqYrGi6oMV3I3IpzPJc9IaeECCANH8GzxbtSc3QkPuo,5502
21
+ karaoke_gen-0.55.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
22
+ karaoke_gen-0.55.0.dist-info/entry_points.txt,sha256=IZY3O8i7m-qkmPuqgpAcxiS2fotNc6hC-CDWvNmoUEY,107
23
+ karaoke_gen-0.55.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ karaoke-bulk=karaoke_gen.utils.bulk_cli:main
3
+ karaoke-gen=karaoke_gen.utils.gen_cli:main
4
+
@@ -1,23 +0,0 @@
1
- karaoke_prep/__init__.py,sha256=gwWt2-Z35n3jLfLG0phvz9IVZeM7rH5f3mflqUV79X4,38
2
- karaoke_prep/audio_processor.py,sha256=Po2x3x5JyRe5yGUhJlzM5_mw9E7GijUbv-_hc_kAXqs,19131
3
- karaoke_prep/config.py,sha256=I3h-940ZXvbrCNq_xcWHPMIB76cl-VNQYcK7-qgB-YI,6833
4
- karaoke_prep/file_handler.py,sha256=sQNfEpqIir4Jd-V0VXUGZlsItt7pxZ8n_muzc1ONYck,8479
5
- karaoke_prep/karaoke_finalise/__init__.py,sha256=HqZ7TIhgt_tYZ-nb_NNCaejWAcF_aK-7wJY5TaW_keM,46
6
- karaoke_prep/karaoke_finalise/karaoke_finalise.py,sha256=sDW50vz4UMVEcrqB7WXx2bNT9Hvza-EOx8KqGHvGNYA,55917
7
- karaoke_prep/karaoke_prep.py,sha256=nu5Ayg3UjlYiCICRr3hGQUNYRkFv6W3QvcCJEnQtFgM,37177
8
- karaoke_prep/lyrics_processor.py,sha256=Yuax-FlcL_aLcLIPPAKyIjjwrNlnvAWGJOFlLbAUXEE,9994
9
- karaoke_prep/metadata.py,sha256=PkwTnxX7fwbRmo_8ysC2zAMYSdZTtJtXWQypgNzssz8,4729
10
- karaoke_prep/resources/AvenirNext-Bold.ttf,sha256=YxgKz2OP46lwLPCpIZhVa8COi_9KRDSXw4n8dIHHQSs,327048
11
- karaoke_prep/resources/Montserrat-Bold.ttf,sha256=mLFIaBDC7M-qF9RhCoPBJ5TAeY716etBrqA4eUKSoYc,198120
12
- karaoke_prep/resources/Oswald-Bold.ttf,sha256=S_2mLpNkBsDTe8FQRzrj1Qr-wloGETMJgoAcSKdi1lw,87604
13
- karaoke_prep/resources/Oswald-SemiBold.ttf,sha256=G-vSJeeyEVft7D4s7FZQtGfXAViWPjzGCImV2a4u9d8,87608
14
- karaoke_prep/resources/Zurich_Cn_BT_Bold.ttf,sha256=WNG5LOQ-uGUF_WWT5aQHzVbyWvQqGO5sZ4E-nRmvPuI,37780
15
- karaoke_prep/utils/__init__.py,sha256=FpOHyeBRB06f3zMoLBUJHTDZACrabg-DoyBTxNKYyNY,722
16
- karaoke_prep/utils/bulk_cli.py,sha256=Lezs8XNLk2Op0b6wmmupZGU3owgdMoLN4PFVRQQbRxM,18843
17
- karaoke_prep/utils/gen_cli.py,sha256=vNhs0Oyi3sIkzNLvVXQCCsCF5Ku5bt75xdu4IsfAX3A,38326
18
- karaoke_prep/video_generator.py,sha256=agtE7zDfY-4COjb7yT8aSPxvNGpOORV_lRtKczGbokM,18409
19
- karaoke_gen-0.50.0.dist-info/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
20
- karaoke_gen-0.50.0.dist-info/METADATA,sha256=WWboqA8QSLhczte82rbRqlvJxDG5SMeSND0lmT4dUso,4563
21
- karaoke_gen-0.50.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
22
- karaoke_gen-0.50.0.dist-info/entry_points.txt,sha256=8OkNvWtcr6Zceif8PbpYr6MBa2cFd_B1vJXP6Xcdyks,109
23
- karaoke_gen-0.50.0.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- [console_scripts]
2
- karaoke-bulk=karaoke_prep.utils.bulk_cli:main
3
- karaoke-gen=karaoke_prep.utils.gen_cli:main
4
-
karaoke_prep/__init__.py DELETED
@@ -1 +0,0 @@
1
- from .karaoke_prep import KaraokePrep
File without changes
File without changes
File without changes
File without changes
File without changes