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.

@@ -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"Renaming Vocals file {file} to {vocals_path}")
76
- os.rename(file, vocals_path)
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"Renaming Instrumental file {file} to {instrumental_path}")
79
- os.rename(file, instrumental_path)
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"Renaming other stem file {file} to {other_stem_path}")
90
- os.rename(file, other_stem_path)
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"Renaming other stem file {file} to {other_stem_path}")
102
- os.rename(file, other_stem_path)
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
- os.rename(file, vocals_path)
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
- os.rename(file, instrumental_path)
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
- os.rename(file, other_stem_path)
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
- os.rename(file, lead_vocals_path)
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
- os.rename(file, backing_vocals_path)
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
@@ -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
- "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36",
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
- with ydl(ydl_opts) as ydl_instance:
53
- ydl_instance.download([url])
54
-
55
- # Search for the file with any extension
56
- downloaded_files = glob.glob(f"{output_filename_no_extension}.*")
57
- if downloaded_files:
58
- downloaded_file_name = downloaded_files[0] # Assume the first match is the correct one
59
- self.logger.info(f"Download finished, returning downloaded filename: {downloaded_file_name}")
60
- return downloaded_file_name
61
- else:
62
- self.logger.error("No files found matching the download pattern.")
63
- return None
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"""
@@ -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()]
@@ -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=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=True,
221
+ generate_cdg=False, # Also defer CDG generation to Phase 2
199
222
  video_resolution="4k",
200
- enable_review=not self.skip_transcription_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
- extracted_info = None
8
- if input_url is not None:
9
- # If a URL is provided, use it to extract the metadata
10
- with ydl({"quiet": True}) as ydl_instance:
11
- extracted_info = ydl_instance.extract_info(input_url, download=False)
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
- # If no URL is provided, use the query to search for the top result
14
- ydl_opts = {"quiet": "True", "format": "bestaudio", "noplaylist": "True", "extract_flat": True}
15
- with ydl(ydl_opts) as ydl_instance:
16
- query = f"{input_artist} {input_title}"
17
- search_results = ydl_instance.extract_info(f"ytsearch1:{query}", download=False)
18
- if search_results and "entries" in search_results and search_results["entries"]:
19
- extracted_info = search_results["entries"][0]
20
- else:
21
- # Raise IndexError to match the expected exception in tests
22
- raise IndexError(f"No search results found on YouTube for query: {input_artist} {input_title}")
23
-
24
- if not extracted_info:
25
- raise Exception(f"Failed to extract info for query: {input_artist} {input_title} or URL: {input_url}")
26
-
27
- return extracted_info
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.55.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.54)
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=VnFRO1yhljHxAUnHs8Ir6fobnvIE7isF8-qdymFw2bQ,19581
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=sQNfEpqIir4Jd-V0VXUGZlsItt7pxZ8n_muzc1ONYck,8479
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=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
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.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,,
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,,