karaoke-gen 0.55.0__py3-none-any.whl → 0.56.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.
- karaoke_gen/audio_processor.py +13 -13
- karaoke_gen/file_handler.py +50 -14
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +7 -1
- karaoke_gen/karaoke_gen.py +11 -4
- karaoke_gen/lyrics_processor.py +39 -3
- karaoke_gen/metadata.py +71 -21
- {karaoke_gen-0.55.0.dist-info → karaoke_gen-0.56.0.dist-info}/METADATA +4 -2
- {karaoke_gen-0.55.0.dist-info → karaoke_gen-0.56.0.dist-info}/RECORD +11 -11
- {karaoke_gen-0.55.0.dist-info → karaoke_gen-0.56.0.dist-info}/LICENSE +0 -0
- {karaoke_gen-0.55.0.dist-info → karaoke_gen-0.56.0.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.55.0.dist-info → karaoke_gen-0.56.0.dist-info}/entry_points.txt +0 -0
karaoke_gen/audio_processor.py
CHANGED
|
@@ -72,11 +72,11 @@ class AudioProcessor:
|
|
|
72
72
|
|
|
73
73
|
for file in output_files:
|
|
74
74
|
if "(Vocals)" in file:
|
|
75
|
-
self.logger.info(f"
|
|
76
|
-
|
|
75
|
+
self.logger.info(f"Moving Vocals file {file} to {vocals_path}")
|
|
76
|
+
shutil.move(file, vocals_path)
|
|
77
77
|
elif "(Instrumental)" in file:
|
|
78
|
-
self.logger.info(f"
|
|
79
|
-
|
|
78
|
+
self.logger.info(f"Moving Instrumental file {file} to {instrumental_path}")
|
|
79
|
+
shutil.move(file, instrumental_path)
|
|
80
80
|
elif model_name in file:
|
|
81
81
|
# Example filename 1: "Freddie Jackson - All I'll Ever Ask (feat. Najee) (Local)_(Piano)_htdemucs_6s.flac"
|
|
82
82
|
# Example filename 2: "Freddie Jackson - All I'll Ever Ask (feat. Najee) (Local)_(Guitar)_htdemucs_6s.flac"
|
|
@@ -86,8 +86,8 @@ class AudioProcessor:
|
|
|
86
86
|
stem_name = stem_name.strip("()") # Remove parentheses if present
|
|
87
87
|
|
|
88
88
|
other_stem_path = os.path.join(track_output_dir, f"{artist_title} ({stem_name} {model_name}).{self.lossless_output_format}")
|
|
89
|
-
self.logger.info(f"
|
|
90
|
-
|
|
89
|
+
self.logger.info(f"Moving other stem file {file} to {other_stem_path}")
|
|
90
|
+
shutil.move(file, other_stem_path)
|
|
91
91
|
|
|
92
92
|
elif model_name_no_extension in file:
|
|
93
93
|
# Example filename 1: "Freddie Jackson - All I'll Ever Ask (feat. Najee) (Local)_(Piano)_htdemucs_6s.flac"
|
|
@@ -98,8 +98,8 @@ class AudioProcessor:
|
|
|
98
98
|
stem_name = stem_name.strip("()") # Remove parentheses if present
|
|
99
99
|
|
|
100
100
|
other_stem_path = os.path.join(track_output_dir, f"{artist_title} ({stem_name} {model_name}).{self.lossless_output_format}")
|
|
101
|
-
self.logger.info(f"
|
|
102
|
-
|
|
101
|
+
self.logger.info(f"Moving other stem file {file} to {other_stem_path}")
|
|
102
|
+
shutil.move(file, other_stem_path)
|
|
103
103
|
|
|
104
104
|
self.logger.info(f"Separation complete! Output file(s): {vocals_path} {instrumental_path}")
|
|
105
105
|
|
|
@@ -262,10 +262,10 @@ class AudioProcessor:
|
|
|
262
262
|
|
|
263
263
|
for file in clean_output_files:
|
|
264
264
|
if "(Vocals)" in file and not self._file_exists(vocals_path):
|
|
265
|
-
|
|
265
|
+
shutil.move(file, vocals_path)
|
|
266
266
|
result["vocals"] = vocals_path
|
|
267
267
|
elif "(Instrumental)" in file and not self._file_exists(instrumental_path):
|
|
268
|
-
|
|
268
|
+
shutil.move(file, instrumental_path)
|
|
269
269
|
result["instrumental"] = instrumental_path
|
|
270
270
|
else:
|
|
271
271
|
result["vocals"] = vocals_path
|
|
@@ -298,7 +298,7 @@ class AudioProcessor:
|
|
|
298
298
|
new_filename = f"{artist_title} ({stem_name} {model}).{self.lossless_output_format}"
|
|
299
299
|
other_stem_path = os.path.join(stems_dir, new_filename)
|
|
300
300
|
if not self._file_exists(other_stem_path):
|
|
301
|
-
|
|
301
|
+
shutil.move(file, other_stem_path)
|
|
302
302
|
result[model][stem_name] = other_stem_path
|
|
303
303
|
|
|
304
304
|
return result
|
|
@@ -318,10 +318,10 @@ class AudioProcessor:
|
|
|
318
318
|
|
|
319
319
|
for file in backing_vocals_output:
|
|
320
320
|
if "(Vocals)" in file and not self._file_exists(lead_vocals_path):
|
|
321
|
-
|
|
321
|
+
shutil.move(file, lead_vocals_path)
|
|
322
322
|
result[model]["lead_vocals"] = lead_vocals_path
|
|
323
323
|
elif "(Instrumental)" in file and not self._file_exists(backing_vocals_path):
|
|
324
|
-
|
|
324
|
+
shutil.move(file, backing_vocals_path)
|
|
325
325
|
result[model]["backing_vocals"] = backing_vocals_path
|
|
326
326
|
else:
|
|
327
327
|
result[model]["lead_vocals"] = lead_vocals_path
|
karaoke_gen/file_handler.py
CHANGED
|
@@ -39,28 +39,64 @@ class FileHandler:
|
|
|
39
39
|
|
|
40
40
|
return copied_file_name
|
|
41
41
|
|
|
42
|
-
def download_video(self, url, output_filename_no_extension):
|
|
42
|
+
def download_video(self, url, output_filename_no_extension, cookies_str=None):
|
|
43
43
|
self.logger.debug(f"Downloading media from URL {url} to filename {output_filename_no_extension} + (as yet) unknown extension")
|
|
44
44
|
|
|
45
45
|
ydl_opts = {
|
|
46
46
|
"quiet": True,
|
|
47
47
|
"format": "bv*+ba/b", # if a combined video + audio format is better than the best video-only format use the combined format
|
|
48
48
|
"outtmpl": f"{output_filename_no_extension}.%(ext)s",
|
|
49
|
-
|
|
49
|
+
# Enhanced anti-detection options
|
|
50
|
+
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
51
|
+
"referer": "https://www.youtube.com/",
|
|
52
|
+
"sleep_interval": 1,
|
|
53
|
+
"max_sleep_interval": 3,
|
|
54
|
+
"fragment_retries": 3,
|
|
55
|
+
"extractor_retries": 3,
|
|
56
|
+
"retries": 3,
|
|
57
|
+
# Headers to appear more human
|
|
58
|
+
"http_headers": {
|
|
59
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
60
|
+
"Accept-Language": "en-us,en;q=0.5",
|
|
61
|
+
"Accept-Encoding": "gzip, deflate",
|
|
62
|
+
"DNT": "1",
|
|
63
|
+
"Connection": "keep-alive",
|
|
64
|
+
"Upgrade-Insecure-Requests": "1",
|
|
65
|
+
},
|
|
50
66
|
}
|
|
51
67
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
# Add cookies if provided
|
|
69
|
+
if cookies_str:
|
|
70
|
+
self.logger.info("Using provided cookies for enhanced YouTube download access")
|
|
71
|
+
# Save cookies to a temporary file
|
|
72
|
+
import tempfile
|
|
73
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
74
|
+
f.write(cookies_str)
|
|
75
|
+
ydl_opts['cookiefile'] = f.name
|
|
76
|
+
else:
|
|
77
|
+
self.logger.info("No cookies provided for download - attempting standard download")
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
with ydl(ydl_opts) as ydl_instance:
|
|
81
|
+
ydl_instance.download([url])
|
|
82
|
+
|
|
83
|
+
# Search for the file with any extension
|
|
84
|
+
downloaded_files = glob.glob(f"{output_filename_no_extension}.*")
|
|
85
|
+
if downloaded_files:
|
|
86
|
+
downloaded_file_name = downloaded_files[0] # Assume the first match is the correct one
|
|
87
|
+
self.logger.info(f"Download finished, returning downloaded filename: {downloaded_file_name}")
|
|
88
|
+
return downloaded_file_name
|
|
89
|
+
else:
|
|
90
|
+
self.logger.error("No files found matching the download pattern.")
|
|
91
|
+
return None
|
|
92
|
+
finally:
|
|
93
|
+
# Clean up temporary cookie file if it was created
|
|
94
|
+
if cookies_str and 'cookiefile' in ydl_opts:
|
|
95
|
+
try:
|
|
96
|
+
import os
|
|
97
|
+
os.unlink(ydl_opts['cookiefile'])
|
|
98
|
+
except:
|
|
99
|
+
pass
|
|
64
100
|
|
|
65
101
|
def extract_still_image_from_video(self, input_filename, output_filename_no_extension):
|
|
66
102
|
output_filename = output_filename_no_extension + ".png"
|
|
@@ -636,7 +636,13 @@ class KaraokeFinalise:
|
|
|
636
636
|
self.logger.info(f"DRY RUN: Would run command: {command}")
|
|
637
637
|
else:
|
|
638
638
|
self.logger.info(f"Running command: {command}")
|
|
639
|
-
os.system(command)
|
|
639
|
+
exit_code = os.system(command)
|
|
640
|
+
|
|
641
|
+
# Check if command failed (non-zero exit code)
|
|
642
|
+
if exit_code != 0:
|
|
643
|
+
error_msg = f"Command failed with exit code {exit_code}: {command}"
|
|
644
|
+
self.logger.error(error_msg)
|
|
645
|
+
raise Exception(error_msg)
|
|
640
646
|
|
|
641
647
|
def remux_with_instrumental(self, with_vocals_file, instrumental_audio, output_file):
|
|
642
648
|
"""Remux the video with instrumental audio to create karaoke version"""
|
karaoke_gen/karaoke_gen.py
CHANGED
|
@@ -69,6 +69,8 @@ class KaraokePrep:
|
|
|
69
69
|
style_params_json=None,
|
|
70
70
|
# Add the new parameter
|
|
71
71
|
skip_separation=False,
|
|
72
|
+
# YouTube/Online Configuration
|
|
73
|
+
cookies_str=None,
|
|
72
74
|
):
|
|
73
75
|
self.log_level = log_level
|
|
74
76
|
self.log_formatter = log_formatter
|
|
@@ -124,6 +126,9 @@ class KaraokePrep:
|
|
|
124
126
|
self.render_bounding_boxes = render_bounding_boxes # Passed to VideoGenerator
|
|
125
127
|
self.style_params_json = style_params_json # Passed to LyricsProcessor
|
|
126
128
|
|
|
129
|
+
# YouTube/Online Config
|
|
130
|
+
self.cookies_str = cookies_str # Passed to metadata extraction and file download
|
|
131
|
+
|
|
127
132
|
# Load style parameters using the config module
|
|
128
133
|
self.style_params = load_style_params(self.style_params_json, self.logger)
|
|
129
134
|
|
|
@@ -197,7 +202,7 @@ class KaraokePrep:
|
|
|
197
202
|
# Compatibility methods for tests - these call the new functions in metadata.py
|
|
198
203
|
def extract_info_for_online_media(self, input_url=None, input_artist=None, input_title=None):
|
|
199
204
|
"""Compatibility method that calls the function in metadata.py"""
|
|
200
|
-
self.extracted_info = extract_info_for_online_media(input_url, input_artist, input_title, self.logger)
|
|
205
|
+
self.extracted_info = extract_info_for_online_media(input_url, input_artist, input_title, self.logger, self.cookies_str)
|
|
201
206
|
return self.extracted_info
|
|
202
207
|
|
|
203
208
|
def parse_single_track_metadata(self, input_artist, input_title):
|
|
@@ -242,7 +247,7 @@ class KaraokePrep:
|
|
|
242
247
|
self.logger.warning(f"Input media '{self.input_media}' is not a file and self.url was not set. Attempting to treat as URL.")
|
|
243
248
|
# This path requires calling extract/parse again, less efficient
|
|
244
249
|
try:
|
|
245
|
-
extracted = extract_info_for_online_media(self.input_media, self.artist, self.title, self.logger)
|
|
250
|
+
extracted = extract_info_for_online_media(self.input_media, self.artist, self.title, self.logger, self.cookies_str)
|
|
246
251
|
if extracted:
|
|
247
252
|
metadata_result = parse_track_metadata(
|
|
248
253
|
extracted, self.artist, self.title, self.persistent_artist, self.logger
|
|
@@ -345,7 +350,7 @@ class KaraokePrep:
|
|
|
345
350
|
|
|
346
351
|
self.logger.info(f"Downloading input media from {self.url}...")
|
|
347
352
|
# Delegate to FileHandler
|
|
348
|
-
processed_track["input_media"] = self.file_handler.download_video(self.url, output_filename_no_extension)
|
|
353
|
+
processed_track["input_media"] = self.file_handler.download_video(self.url, output_filename_no_extension, self.cookies_str)
|
|
349
354
|
|
|
350
355
|
self.logger.info("Extracting still image from downloaded media (if input is video)...")
|
|
351
356
|
# Delegate to FileHandler
|
|
@@ -681,7 +686,7 @@ class KaraokePrep:
|
|
|
681
686
|
self.url = self.input_media
|
|
682
687
|
# Use the imported extract_info_for_online_media function
|
|
683
688
|
self.extracted_info = extract_info_for_online_media(
|
|
684
|
-
input_url=self.url, input_artist=self.artist, input_title=self.title, logger=self.logger
|
|
689
|
+
input_url=self.url, input_artist=self.artist, input_title=self.title, logger=self.logger, cookies_str=self.cookies_str
|
|
685
690
|
)
|
|
686
691
|
|
|
687
692
|
if self.extracted_info and "playlist_count" in self.extracted_info:
|
|
@@ -690,4 +695,6 @@ class KaraokePrep:
|
|
|
690
695
|
return await self.process_playlist()
|
|
691
696
|
else:
|
|
692
697
|
self.logger.info(f"Input URL is not a playlist, processing single track")
|
|
698
|
+
# Parse metadata to extract artist and title before processing
|
|
699
|
+
self.parse_single_track_metadata(self.artist, self.title)
|
|
693
700
|
return [await self.prep_single_track()]
|
karaoke_gen/lyrics_processor.py
CHANGED
|
@@ -2,6 +2,7 @@ import os
|
|
|
2
2
|
import re
|
|
3
3
|
import logging
|
|
4
4
|
import shutil
|
|
5
|
+
import json
|
|
5
6
|
from lyrics_transcriber import LyricsTranscriber, OutputConfig, TranscriberConfig, LyricsConfig
|
|
6
7
|
from lyrics_transcriber.core.controller import LyricsControllerResult
|
|
7
8
|
from dotenv import load_dotenv
|
|
@@ -186,18 +187,40 @@ class LyricsProcessor:
|
|
|
186
187
|
lyrics_file=self.lyrics_file,
|
|
187
188
|
)
|
|
188
189
|
|
|
190
|
+
# Detect if we're running in a serverless environment (Modal)
|
|
191
|
+
# Modal sets specific environment variables we can check for
|
|
192
|
+
is_serverless = (
|
|
193
|
+
os.getenv("MODAL_TASK_ID") is not None or
|
|
194
|
+
os.getenv("MODAL_FUNCTION_NAME") is not None or
|
|
195
|
+
os.path.exists("/.modal") # Modal creates this directory in containers
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# In serverless environment, disable interactive review even if skip_transcription_review=False
|
|
199
|
+
# This preserves CLI behavior while fixing serverless hanging
|
|
200
|
+
enable_review_setting = not self.skip_transcription_review and not is_serverless
|
|
201
|
+
|
|
202
|
+
if is_serverless and not self.skip_transcription_review:
|
|
203
|
+
self.logger.info("Detected serverless environment - disabling interactive review to prevent hanging")
|
|
204
|
+
|
|
205
|
+
# In serverless environment, disable video generation during Phase 1 to save compute
|
|
206
|
+
# Video will be generated in Phase 2 after human review
|
|
207
|
+
serverless_render_video = render_video and not is_serverless
|
|
208
|
+
|
|
209
|
+
if is_serverless and render_video:
|
|
210
|
+
self.logger.info("Detected serverless environment - deferring video generation until after review")
|
|
211
|
+
|
|
189
212
|
output_config = OutputConfig(
|
|
190
213
|
output_styles_json=self.style_params_json,
|
|
191
214
|
output_dir=lyrics_dir,
|
|
192
|
-
render_video=
|
|
215
|
+
render_video=serverless_render_video, # Disable video in serverless Phase 1
|
|
193
216
|
fetch_lyrics=True,
|
|
194
217
|
run_transcription=not self.skip_transcription,
|
|
195
218
|
run_correction=True,
|
|
196
219
|
generate_plain_text=True,
|
|
197
220
|
generate_lrc=True,
|
|
198
|
-
generate_cdg=
|
|
221
|
+
generate_cdg=False, # Also defer CDG generation to Phase 2
|
|
199
222
|
video_resolution="4k",
|
|
200
|
-
enable_review=
|
|
223
|
+
enable_review=enable_review_setting,
|
|
201
224
|
subtitle_offset_ms=self.subtitle_offset_ms,
|
|
202
225
|
)
|
|
203
226
|
|
|
@@ -240,6 +263,19 @@ class LyricsProcessor:
|
|
|
240
263
|
)
|
|
241
264
|
transcriber_outputs["corrected_lyrics_text_filepath"] = results.corrected_txt
|
|
242
265
|
|
|
266
|
+
# Save correction data to JSON file for review interface
|
|
267
|
+
# Use the expected filename format: "{artist} - {title} (Lyrics Corrections).json"
|
|
268
|
+
corrections_filename = f"{filename_artist} - {filename_title} (Lyrics Corrections).json"
|
|
269
|
+
corrections_filepath = os.path.join(lyrics_dir, corrections_filename)
|
|
270
|
+
|
|
271
|
+
# Use the CorrectionResult's to_dict() method to serialize
|
|
272
|
+
correction_data = results.transcription_corrected.to_dict()
|
|
273
|
+
|
|
274
|
+
with open(corrections_filepath, 'w') as f:
|
|
275
|
+
json.dump(correction_data, f, indent=2)
|
|
276
|
+
|
|
277
|
+
self.logger.info(f"Saved correction data to {corrections_filepath}")
|
|
278
|
+
|
|
243
279
|
if transcriber_outputs:
|
|
244
280
|
self.logger.info(f"*** Transcriber Filepath Outputs: ***")
|
|
245
281
|
for key, value in transcriber_outputs.items():
|
karaoke_gen/metadata.py
CHANGED
|
@@ -1,30 +1,80 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import yt_dlp.YoutubeDL as ydl
|
|
3
3
|
|
|
4
|
-
def extract_info_for_online_media(input_url, input_artist, input_title, logger):
|
|
4
|
+
def extract_info_for_online_media(input_url, input_artist, input_title, logger, cookies_str=None):
|
|
5
5
|
"""Extracts metadata using yt-dlp, either from a URL or via search."""
|
|
6
6
|
logger.info(f"Extracting info for input_url: {input_url} input_artist: {input_artist} input_title: {input_title}")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
|
|
8
|
+
# Set up yt-dlp options with enhanced anti-detection
|
|
9
|
+
base_opts = {
|
|
10
|
+
"quiet": True,
|
|
11
|
+
# Anti-detection options
|
|
12
|
+
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
13
|
+
"referer": "https://www.youtube.com/",
|
|
14
|
+
"sleep_interval": 1,
|
|
15
|
+
"max_sleep_interval": 3,
|
|
16
|
+
"fragment_retries": 3,
|
|
17
|
+
"extractor_retries": 3,
|
|
18
|
+
"retries": 3,
|
|
19
|
+
# Headers to appear more human
|
|
20
|
+
"http_headers": {
|
|
21
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
22
|
+
"Accept-Language": "en-us,en;q=0.5",
|
|
23
|
+
"Accept-Encoding": "gzip, deflate",
|
|
24
|
+
"DNT": "1",
|
|
25
|
+
"Connection": "keep-alive",
|
|
26
|
+
"Upgrade-Insecure-Requests": "1",
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Add cookies if provided
|
|
31
|
+
if cookies_str:
|
|
32
|
+
logger.info("Using provided cookies for enhanced YouTube access")
|
|
33
|
+
# Save cookies to a temporary file
|
|
34
|
+
import tempfile
|
|
35
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
36
|
+
f.write(cookies_str)
|
|
37
|
+
base_opts['cookiefile'] = f.name
|
|
12
38
|
else:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
39
|
+
logger.info("No cookies provided - attempting standard extraction")
|
|
40
|
+
|
|
41
|
+
extracted_info = None
|
|
42
|
+
try:
|
|
43
|
+
if input_url is not None:
|
|
44
|
+
# If a URL is provided, use it to extract the metadata
|
|
45
|
+
with ydl(base_opts) as ydl_instance:
|
|
46
|
+
extracted_info = ydl_instance.extract_info(input_url, download=False)
|
|
47
|
+
else:
|
|
48
|
+
# If no URL is provided, use the query to search for the top result
|
|
49
|
+
search_opts = base_opts.copy()
|
|
50
|
+
search_opts.update({
|
|
51
|
+
"format": "bestaudio",
|
|
52
|
+
"noplaylist": "True",
|
|
53
|
+
"extract_flat": True
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
with ydl(search_opts) as ydl_instance:
|
|
57
|
+
query = f"{input_artist} {input_title}"
|
|
58
|
+
search_results = ydl_instance.extract_info(f"ytsearch1:{query}", download=False)
|
|
59
|
+
if search_results and "entries" in search_results and search_results["entries"]:
|
|
60
|
+
extracted_info = search_results["entries"][0]
|
|
61
|
+
else:
|
|
62
|
+
# Raise IndexError to match the expected exception in tests
|
|
63
|
+
raise IndexError(f"No search results found on YouTube for query: {input_artist} {input_title}")
|
|
64
|
+
|
|
65
|
+
if not extracted_info:
|
|
66
|
+
raise Exception(f"Failed to extract info for query: {input_artist} {input_title} or URL: {input_url}")
|
|
67
|
+
|
|
68
|
+
return extracted_info
|
|
69
|
+
|
|
70
|
+
finally:
|
|
71
|
+
# Clean up temporary cookie file if it was created
|
|
72
|
+
if cookies_str and 'cookiefile' in base_opts:
|
|
73
|
+
try:
|
|
74
|
+
import os
|
|
75
|
+
os.unlink(base_opts['cookiefile'])
|
|
76
|
+
except:
|
|
77
|
+
pass
|
|
28
78
|
|
|
29
79
|
|
|
30
80
|
def parse_track_metadata(extracted_info, current_artist, current_title, persistent_artist, logger):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: karaoke-gen
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.56.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
|
|
@@ -25,14 +25,16 @@ Requires-Dist: google-auth-httplib2
|
|
|
25
25
|
Requires-Dist: google-auth-oauthlib
|
|
26
26
|
Requires-Dist: kbputils (>=0.0.16,<0.0.17)
|
|
27
27
|
Requires-Dist: lyrics-converter (>=0.2.1)
|
|
28
|
-
Requires-Dist: lyrics-transcriber (>=0.
|
|
28
|
+
Requires-Dist: lyrics-transcriber (>=0.58)
|
|
29
29
|
Requires-Dist: lyricsgenius (>=3)
|
|
30
|
+
Requires-Dist: modal (>=1.0.5,<2.0.0)
|
|
30
31
|
Requires-Dist: numpy (>=2)
|
|
31
32
|
Requires-Dist: pillow (>=10.1)
|
|
32
33
|
Requires-Dist: psutil (>=7.0.0,<8.0.0)
|
|
33
34
|
Requires-Dist: pyinstaller (>=6.3)
|
|
34
35
|
Requires-Dist: pyperclip
|
|
35
36
|
Requires-Dist: pytest-asyncio (>=0.23.5,<0.24.0)
|
|
37
|
+
Requires-Dist: python-multipart (>=0.0.20,<0.0.21)
|
|
36
38
|
Requires-Dist: requests (>=2)
|
|
37
39
|
Requires-Dist: thefuzz (>=0.22)
|
|
38
40
|
Requires-Dist: toml (>=0.10)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
karaoke_gen/__init__.py,sha256=ViryQjs8ALc8A7mqJGHu028zajF5-Za_etFagXlo6kk,269
|
|
2
|
-
karaoke_gen/audio_processor.py,sha256=
|
|
2
|
+
karaoke_gen/audio_processor.py,sha256=gqQo8dsG_4SEO5kwyT76DiU4jCNyiGpi6TT1R3imdGY,19591
|
|
3
3
|
karaoke_gen/config.py,sha256=I3h-940ZXvbrCNq_xcWHPMIB76cl-VNQYcK7-qgB-YI,6833
|
|
4
|
-
karaoke_gen/file_handler.py,sha256=
|
|
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
|
-
karaoke_gen/karaoke_finalise/karaoke_finalise.py,sha256=
|
|
7
|
-
karaoke_gen/karaoke_gen.py,sha256=
|
|
8
|
-
karaoke_gen/lyrics_processor.py,sha256=
|
|
9
|
-
karaoke_gen/metadata.py,sha256=
|
|
6
|
+
karaoke_gen/karaoke_finalise/karaoke_finalise.py,sha256=C2o9iRg5qgWc8qxNVzy8puA8W2ZtKJq28dnxXxS1RMs,56556
|
|
7
|
+
karaoke_gen/karaoke_gen.py,sha256=NEyb-AWLdqJL4nXg21o_YbutZVVbKt08TTPAYgSBDao,38052
|
|
8
|
+
karaoke_gen/lyrics_processor.py,sha256=054zBeYaCFti6lHRfYyHYdioM9YhBrpI52kHsSCh_KI,13363
|
|
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
|
|
12
12
|
karaoke_gen/resources/Oswald-Bold.ttf,sha256=S_2mLpNkBsDTe8FQRzrj1Qr-wloGETMJgoAcSKdi1lw,87604
|
|
@@ -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=sAZ-sau_3dI2hNBOZfiZqJjRf_cJFtuvZLy1V6URcxM,35688
|
|
18
18
|
karaoke_gen/video_generator.py,sha256=B7BQBrjkyvk3L3sctnPXnvr1rzkw0NYx5UCAl0ZiVx0,18464
|
|
19
|
-
karaoke_gen-0.
|
|
20
|
-
karaoke_gen-0.
|
|
21
|
-
karaoke_gen-0.
|
|
22
|
-
karaoke_gen-0.
|
|
23
|
-
karaoke_gen-0.
|
|
19
|
+
karaoke_gen-0.56.0.dist-info/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
|
|
20
|
+
karaoke_gen-0.56.0.dist-info/METADATA,sha256=4zBpc8AJtrh7d6vuYczv5vdii4IdW7XjROzysGZji4E,5591
|
|
21
|
+
karaoke_gen-0.56.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
22
|
+
karaoke_gen-0.56.0.dist-info/entry_points.txt,sha256=IZY3O8i7m-qkmPuqgpAcxiS2fotNc6hC-CDWvNmoUEY,107
|
|
23
|
+
karaoke_gen-0.56.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|