karaoke-gen 0.50.0__tar.gz → 0.55.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.50.0 → karaoke_gen-0.55.0}/PKG-INFO +37 -12
- {karaoke_gen-0.50.0 → karaoke_gen-0.55.0}/README.md +27 -3
- karaoke_gen-0.55.0/karaoke_gen/__init__.py +7 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/audio_processor.py +8 -3
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/karaoke_finalise/karaoke_finalise.py +8 -1
- karaoke_gen-0.50.0/karaoke_prep/karaoke_prep.py → karaoke_gen-0.55.0/karaoke_gen/karaoke_gen.py +12 -6
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/lyrics_processor.py +34 -10
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/utils/bulk_cli.py +2 -2
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/utils/gen_cli.py +32 -94
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/video_generator.py +5 -5
- karaoke_gen-0.55.0/pyproject.toml +81 -0
- karaoke_gen-0.50.0/karaoke_prep/__init__.py +0 -1
- karaoke_gen-0.50.0/pyproject.toml +0 -56
- {karaoke_gen-0.50.0 → karaoke_gen-0.55.0}/LICENSE +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/config.py +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/file_handler.py +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/karaoke_finalise/__init__.py +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/metadata.py +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/AvenirNext-Bold.ttf +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/Montserrat-Bold.ttf +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/Oswald-Bold.ttf +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/Oswald-SemiBold.ttf +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/Zurich_Cn_BT_Bold.ttf +0 -0
- {karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/utils/__init__.py +0 -0
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: karaoke-gen
|
|
3
|
-
Version: 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.
|
|
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.
|
|
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.
|
|
28
|
+
Requires-Dist: lyrics-transcriber (>=0.54)
|
|
28
29
|
Requires-Dist: lyricsgenius (>=3)
|
|
29
|
-
Requires-Dist: numpy (>=
|
|
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 (
|
|
39
|
+
Requires-Dist: torch (>=2.7)
|
|
39
40
|
Requires-Dist: yt-dlp
|
|
40
|
-
Project-URL: Documentation, https://github.com/
|
|
41
|
-
Project-URL: Homepage, https://github.com/
|
|
42
|
-
Project-URL: Repository, https://github.com/
|
|
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
|
|
46
|
+
# Karaoke Generator 🎶 🎥 🚀
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+

|
|
49
|
+

|
|
50
|
+

|
|
51
|
+

|
|
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
|
|
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
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
# Karaoke
|
|
1
|
+
# Karaoke Generator 🎶 🎥 🚀
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
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.
|
|
4
9
|
|
|
5
10
|
## Overview
|
|
6
11
|
|
|
7
|
-
Karaoke
|
|
12
|
+
Karaoke Generator is a comprehensive tool for creating high-quality karaoke videos. It automates the entire workflow:
|
|
8
13
|
|
|
9
14
|
1. **Download** audio and lyrics for a specified song
|
|
10
15
|
2. **Separate** audio stems (vocals, instrumental)
|
|
@@ -90,6 +95,25 @@ For a complete list of options:
|
|
|
90
95
|
karaoke-gen --help
|
|
91
96
|
```
|
|
92
97
|
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
### Running Tests
|
|
101
|
+
|
|
102
|
+
The project uses pytest for testing with unit and integration tests:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Run all tests (unit tests first, then integration tests)
|
|
106
|
+
pytest
|
|
107
|
+
|
|
108
|
+
# Run only unit tests (fast feedback during development)
|
|
109
|
+
pytest -m "not integration"
|
|
110
|
+
|
|
111
|
+
# Run only integration tests (comprehensive end-to-end testing)
|
|
112
|
+
pytest -m integration
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Unit tests run quickly and provide fast feedback, while integration tests are slower but test the full workflow end-to-end.
|
|
116
|
+
|
|
93
117
|
## License
|
|
94
118
|
|
|
95
119
|
MIT
|
|
@@ -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
|
-
|
|
137
|
-
|
|
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("
|
|
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
|
-
|
|
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):
|
karaoke_gen-0.50.0/karaoke_prep/karaoke_prep.py → karaoke_gen-0.55.0/karaoke_gen/karaoke_gen.py
RENAMED
|
@@ -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,
|
|
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
|
|
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("
|
|
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("
|
|
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 {
|
|
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(
|
|
112
|
-
sanitized_title = sanitize_filename(
|
|
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
|
|
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=
|
|
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=
|
|
187
|
-
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
|
|
11
|
-
from
|
|
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
|
|
14
|
-
from
|
|
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
|
|
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"
|
|
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"
|
|
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["
|
|
652
|
-
os.environ["
|
|
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
|
-
|
|
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,
|
|
681
|
+
# If prep-only mode, we're done
|
|
702
682
|
if args.prep_only:
|
|
703
|
-
logger.info(
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
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"
|
|
800
|
+
logger.error(f"An error occurred during finalisation, see stack trace below: {str(e)}")
|
|
860
801
|
raise e
|
|
861
|
-
|
|
862
|
-
|
|
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("
|
|
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
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "karaoke-gen"
|
|
3
|
+
version = "0.55.0"
|
|
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
|
+
authors = ["Andrew Beveridge <andrew@beveridge.uk>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{ include = "karaoke_gen" }]
|
|
9
|
+
homepage = "https://github.com/nomadkaraoke/karaoke-gen"
|
|
10
|
+
repository = "https://github.com/nomadkaraoke/karaoke-gen"
|
|
11
|
+
documentation = "https://github.com/nomadkaraoke/karaoke-gen/blob/main/README.md"
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dependencies]
|
|
14
|
+
python = ">=3.10,<3.14"
|
|
15
|
+
torch = ">=2.7"
|
|
16
|
+
requests = ">=2"
|
|
17
|
+
beautifulsoup4 = ">=4"
|
|
18
|
+
yt-dlp = "*"
|
|
19
|
+
lyricsgenius = ">=3"
|
|
20
|
+
fetch-lyrics-from-genius = ">=0.1"
|
|
21
|
+
pillow = ">=10.1"
|
|
22
|
+
pyinstaller = ">=6.3"
|
|
23
|
+
google-api-python-client = "*"
|
|
24
|
+
google-auth = "*"
|
|
25
|
+
google-auth-oauthlib = "*"
|
|
26
|
+
google-auth-httplib2 = "*"
|
|
27
|
+
thefuzz = ">=0.22"
|
|
28
|
+
numpy = ">=2"
|
|
29
|
+
audio-separator = { version = ">=0.34.0", extras = ["cpu"] }
|
|
30
|
+
lyrics-converter = ">=0.2.1"
|
|
31
|
+
lyrics-transcriber = ">=0.54"
|
|
32
|
+
kbputils = "^0.0.16"
|
|
33
|
+
attrs = ">=24.2.0"
|
|
34
|
+
cattrs = ">=24.1.2"
|
|
35
|
+
toml = ">=0.10"
|
|
36
|
+
argparse = ">=1.4.0"
|
|
37
|
+
psutil = "^7.0.0"
|
|
38
|
+
pyperclip = "*"
|
|
39
|
+
pytest-asyncio = "^0.23.5"
|
|
40
|
+
ffmpeg-python = "^0.2.0"
|
|
41
|
+
|
|
42
|
+
[tool.poetry.group.dev.dependencies]
|
|
43
|
+
black = ">=23"
|
|
44
|
+
poetry = "*"
|
|
45
|
+
pytest = ">=7.0"
|
|
46
|
+
pytest-cov = ">=4.0"
|
|
47
|
+
pytest-mock = ">=3.10"
|
|
48
|
+
pytest-asyncio = ">=0.21.0"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
[tool.poetry.scripts]
|
|
52
|
+
karaoke-gen = 'karaoke_gen.utils.gen_cli:main'
|
|
53
|
+
karaoke-bulk = 'karaoke_gen.utils.bulk_cli:main'
|
|
54
|
+
|
|
55
|
+
[tool.black]
|
|
56
|
+
line-length = 140
|
|
57
|
+
|
|
58
|
+
[build-system]
|
|
59
|
+
requires = ["poetry-core"]
|
|
60
|
+
build-backend = "poetry.core.masonry.api"
|
|
61
|
+
|
|
62
|
+
[tool.pytest.ini_options]
|
|
63
|
+
testpaths = ["tests/unit", "tests/integration"]
|
|
64
|
+
python_files = ["test_*.py"]
|
|
65
|
+
addopts = [
|
|
66
|
+
"--cov=karaoke_gen",
|
|
67
|
+
"--cov-report=term-missing",
|
|
68
|
+
"--cov-report=html:htmlcov",
|
|
69
|
+
"--asyncio-mode=auto",
|
|
70
|
+
]
|
|
71
|
+
markers = [
|
|
72
|
+
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
73
|
+
"integration: marks tests as integration tests",
|
|
74
|
+
"asyncio: mark test as async",
|
|
75
|
+
]
|
|
76
|
+
filterwarnings = [
|
|
77
|
+
"ignore::DeprecationWarning",
|
|
78
|
+
"ignore:'audioop' is deprecated:DeprecationWarning",
|
|
79
|
+
"ignore::RuntimeWarning",
|
|
80
|
+
"ignore::UserWarning",
|
|
81
|
+
]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .karaoke_prep import KaraokePrep
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "karaoke-gen"
|
|
3
|
-
version = "0.50.0"
|
|
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
|
-
authors = ["Andrew Beveridge <andrew@beveridge.uk>"]
|
|
6
|
-
license = "MIT"
|
|
7
|
-
readme = "README.md"
|
|
8
|
-
packages = [{ include = "karaoke_prep" }]
|
|
9
|
-
homepage = "https://github.com/karaokenerds/karaoke-gen"
|
|
10
|
-
repository = "https://github.com/karaokenerds/karaoke-gen"
|
|
11
|
-
documentation = "https://github.com/karaokenerds/karaoke-gen/blob/main/README.md"
|
|
12
|
-
|
|
13
|
-
[tool.poetry.dependencies]
|
|
14
|
-
python = ">=3.10,<3.13"
|
|
15
|
-
torch = "<2.5"
|
|
16
|
-
requests = ">=2"
|
|
17
|
-
beautifulsoup4 = ">=4"
|
|
18
|
-
yt-dlp = "*"
|
|
19
|
-
lyricsgenius = ">=3"
|
|
20
|
-
fetch-lyrics-from-genius = ">=0.1"
|
|
21
|
-
pillow = ">=10.1"
|
|
22
|
-
pyinstaller = ">=6.3"
|
|
23
|
-
google-api-python-client = "*"
|
|
24
|
-
google-auth = "*"
|
|
25
|
-
google-auth-oauthlib = "*"
|
|
26
|
-
google-auth-httplib2 = "*"
|
|
27
|
-
thefuzz = ">=0.22"
|
|
28
|
-
numpy = "^1"
|
|
29
|
-
audio-separator = { version = ">=0.21.0", extras = ["cpu"] }
|
|
30
|
-
lyrics-converter = ">=0.2.1"
|
|
31
|
-
lyrics-transcriber = ">=0.34"
|
|
32
|
-
kbputils = "^0.0.16"
|
|
33
|
-
attrs = ">=24.2.0"
|
|
34
|
-
cattrs = ">=24.1.2"
|
|
35
|
-
toml = ">=0.10"
|
|
36
|
-
argparse = ">=1.4.0"
|
|
37
|
-
psutil = "^7.0.0"
|
|
38
|
-
pyperclip = "*"
|
|
39
|
-
pytest-asyncio = "^0.23.5"
|
|
40
|
-
ffmpeg-python = "^0.2.0"
|
|
41
|
-
|
|
42
|
-
[tool.poetry.group.dev.dependencies]
|
|
43
|
-
black = ">=23"
|
|
44
|
-
poetry = "*"
|
|
45
|
-
pytest-mock = "*"
|
|
46
|
-
|
|
47
|
-
[tool.poetry.scripts]
|
|
48
|
-
karaoke-gen = 'karaoke_prep.utils.gen_cli:main'
|
|
49
|
-
karaoke-bulk = 'karaoke_prep.utils.bulk_cli:main'
|
|
50
|
-
|
|
51
|
-
[tool.black]
|
|
52
|
-
line-length = 140
|
|
53
|
-
|
|
54
|
-
[build-system]
|
|
55
|
-
requires = ["poetry-core"]
|
|
56
|
-
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/karaoke_finalise/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/AvenirNext-Bold.ttf
RENAMED
|
File without changes
|
{karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/Montserrat-Bold.ttf
RENAMED
|
File without changes
|
{karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/Oswald-Bold.ttf
RENAMED
|
File without changes
|
{karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/Oswald-SemiBold.ttf
RENAMED
|
File without changes
|
{karaoke_gen-0.50.0/karaoke_prep → karaoke_gen-0.55.0/karaoke_gen}/resources/Zurich_Cn_BT_Bold.ttf
RENAMED
|
File without changes
|
|
File without changes
|