karaoke-gen 0.57.0__py3-none-any.whl → 0.71.23__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.
- karaoke_gen/audio_fetcher.py +461 -0
- karaoke_gen/audio_processor.py +407 -30
- karaoke_gen/config.py +62 -113
- karaoke_gen/file_handler.py +32 -59
- karaoke_gen/karaoke_finalise/karaoke_finalise.py +148 -67
- karaoke_gen/karaoke_gen.py +270 -61
- karaoke_gen/lyrics_processor.py +13 -1
- karaoke_gen/metadata.py +78 -73
- karaoke_gen/pipeline/__init__.py +87 -0
- karaoke_gen/pipeline/base.py +215 -0
- karaoke_gen/pipeline/context.py +230 -0
- karaoke_gen/pipeline/executors/__init__.py +21 -0
- karaoke_gen/pipeline/executors/local.py +159 -0
- karaoke_gen/pipeline/executors/remote.py +257 -0
- karaoke_gen/pipeline/stages/__init__.py +27 -0
- karaoke_gen/pipeline/stages/finalize.py +202 -0
- karaoke_gen/pipeline/stages/render.py +165 -0
- karaoke_gen/pipeline/stages/screens.py +139 -0
- karaoke_gen/pipeline/stages/separation.py +191 -0
- karaoke_gen/pipeline/stages/transcription.py +191 -0
- karaoke_gen/style_loader.py +531 -0
- karaoke_gen/utils/bulk_cli.py +6 -0
- karaoke_gen/utils/cli_args.py +424 -0
- karaoke_gen/utils/gen_cli.py +26 -261
- karaoke_gen/utils/remote_cli.py +1815 -0
- karaoke_gen/video_background_processor.py +351 -0
- karaoke_gen-0.71.23.dist-info/METADATA +610 -0
- karaoke_gen-0.71.23.dist-info/RECORD +275 -0
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/WHEEL +1 -1
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info}/entry_points.txt +1 -0
- lyrics_transcriber/__init__.py +10 -0
- lyrics_transcriber/cli/__init__.py +0 -0
- lyrics_transcriber/cli/cli_main.py +285 -0
- lyrics_transcriber/core/__init__.py +0 -0
- lyrics_transcriber/core/config.py +50 -0
- lyrics_transcriber/core/controller.py +520 -0
- lyrics_transcriber/correction/__init__.py +0 -0
- lyrics_transcriber/correction/agentic/__init__.py +9 -0
- lyrics_transcriber/correction/agentic/adapter.py +71 -0
- lyrics_transcriber/correction/agentic/agent.py +313 -0
- lyrics_transcriber/correction/agentic/feedback/aggregator.py +12 -0
- lyrics_transcriber/correction/agentic/feedback/collector.py +17 -0
- lyrics_transcriber/correction/agentic/feedback/retention.py +24 -0
- lyrics_transcriber/correction/agentic/feedback/store.py +76 -0
- lyrics_transcriber/correction/agentic/handlers/__init__.py +24 -0
- lyrics_transcriber/correction/agentic/handlers/ambiguous.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/background_vocals.py +68 -0
- lyrics_transcriber/correction/agentic/handlers/base.py +51 -0
- lyrics_transcriber/correction/agentic/handlers/complex_multi_error.py +46 -0
- lyrics_transcriber/correction/agentic/handlers/extra_words.py +74 -0
- lyrics_transcriber/correction/agentic/handlers/no_error.py +42 -0
- lyrics_transcriber/correction/agentic/handlers/punctuation.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/registry.py +60 -0
- lyrics_transcriber/correction/agentic/handlers/repeated_section.py +44 -0
- lyrics_transcriber/correction/agentic/handlers/sound_alike.py +126 -0
- lyrics_transcriber/correction/agentic/models/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/models/ai_correction.py +31 -0
- lyrics_transcriber/correction/agentic/models/correction_session.py +30 -0
- lyrics_transcriber/correction/agentic/models/enums.py +38 -0
- lyrics_transcriber/correction/agentic/models/human_feedback.py +30 -0
- lyrics_transcriber/correction/agentic/models/learning_data.py +26 -0
- lyrics_transcriber/correction/agentic/models/observability_metrics.py +28 -0
- lyrics_transcriber/correction/agentic/models/schemas.py +46 -0
- lyrics_transcriber/correction/agentic/models/utils.py +19 -0
- lyrics_transcriber/correction/agentic/observability/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/observability/langfuse_integration.py +35 -0
- lyrics_transcriber/correction/agentic/observability/metrics.py +46 -0
- lyrics_transcriber/correction/agentic/observability/performance.py +19 -0
- lyrics_transcriber/correction/agentic/prompts/__init__.py +2 -0
- lyrics_transcriber/correction/agentic/prompts/classifier.py +227 -0
- lyrics_transcriber/correction/agentic/providers/__init__.py +6 -0
- lyrics_transcriber/correction/agentic/providers/base.py +36 -0
- lyrics_transcriber/correction/agentic/providers/circuit_breaker.py +145 -0
- lyrics_transcriber/correction/agentic/providers/config.py +73 -0
- lyrics_transcriber/correction/agentic/providers/constants.py +24 -0
- lyrics_transcriber/correction/agentic/providers/health.py +28 -0
- lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +212 -0
- lyrics_transcriber/correction/agentic/providers/model_factory.py +209 -0
- lyrics_transcriber/correction/agentic/providers/response_cache.py +218 -0
- lyrics_transcriber/correction/agentic/providers/response_parser.py +111 -0
- lyrics_transcriber/correction/agentic/providers/retry_executor.py +127 -0
- lyrics_transcriber/correction/agentic/router.py +35 -0
- lyrics_transcriber/correction/agentic/workflows/__init__.py +5 -0
- lyrics_transcriber/correction/agentic/workflows/consensus_workflow.py +24 -0
- lyrics_transcriber/correction/agentic/workflows/correction_graph.py +59 -0
- lyrics_transcriber/correction/agentic/workflows/feedback_workflow.py +24 -0
- lyrics_transcriber/correction/anchor_sequence.py +1043 -0
- lyrics_transcriber/correction/corrector.py +760 -0
- lyrics_transcriber/correction/feedback/__init__.py +2 -0
- lyrics_transcriber/correction/feedback/schemas.py +107 -0
- lyrics_transcriber/correction/feedback/store.py +236 -0
- lyrics_transcriber/correction/handlers/__init__.py +0 -0
- lyrics_transcriber/correction/handlers/base.py +52 -0
- lyrics_transcriber/correction/handlers/extend_anchor.py +149 -0
- lyrics_transcriber/correction/handlers/levenshtein.py +189 -0
- lyrics_transcriber/correction/handlers/llm.py +293 -0
- lyrics_transcriber/correction/handlers/llm_providers.py +60 -0
- lyrics_transcriber/correction/handlers/no_space_punct_match.py +154 -0
- lyrics_transcriber/correction/handlers/relaxed_word_count_match.py +85 -0
- lyrics_transcriber/correction/handlers/repeat.py +88 -0
- lyrics_transcriber/correction/handlers/sound_alike.py +259 -0
- lyrics_transcriber/correction/handlers/syllables_match.py +252 -0
- lyrics_transcriber/correction/handlers/word_count_match.py +80 -0
- lyrics_transcriber/correction/handlers/word_operations.py +187 -0
- lyrics_transcriber/correction/operations.py +352 -0
- lyrics_transcriber/correction/phrase_analyzer.py +435 -0
- lyrics_transcriber/correction/text_utils.py +30 -0
- lyrics_transcriber/frontend/.gitignore +23 -0
- lyrics_transcriber/frontend/.yarn/releases/yarn-4.7.0.cjs +935 -0
- lyrics_transcriber/frontend/.yarnrc.yml +3 -0
- lyrics_transcriber/frontend/README.md +50 -0
- lyrics_transcriber/frontend/REPLACE_ALL_FUNCTIONALITY.md +210 -0
- lyrics_transcriber/frontend/__init__.py +25 -0
- lyrics_transcriber/frontend/eslint.config.js +28 -0
- lyrics_transcriber/frontend/index.html +18 -0
- lyrics_transcriber/frontend/package.json +42 -0
- lyrics_transcriber/frontend/public/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/public/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/public/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/public/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/public/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/public/favicon.ico +0 -0
- lyrics_transcriber/frontend/public/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/src/App.tsx +212 -0
- lyrics_transcriber/frontend/src/api.ts +239 -0
- lyrics_transcriber/frontend/src/components/AIFeedbackModal.tsx +77 -0
- lyrics_transcriber/frontend/src/components/AddLyricsModal.tsx +114 -0
- lyrics_transcriber/frontend/src/components/AgenticCorrectionMetrics.tsx +204 -0
- lyrics_transcriber/frontend/src/components/AudioPlayer.tsx +180 -0
- lyrics_transcriber/frontend/src/components/CorrectedWordWithActions.tsx +167 -0
- lyrics_transcriber/frontend/src/components/CorrectionAnnotationModal.tsx +359 -0
- lyrics_transcriber/frontend/src/components/CorrectionDetailCard.tsx +281 -0
- lyrics_transcriber/frontend/src/components/CorrectionMetrics.tsx +162 -0
- lyrics_transcriber/frontend/src/components/DurationTimelineView.tsx +257 -0
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
- lyrics_transcriber/frontend/src/components/EditModal.tsx +702 -0
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +496 -0
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +379 -0
- lyrics_transcriber/frontend/src/components/FileUpload.tsx +77 -0
- lyrics_transcriber/frontend/src/components/FindReplaceModal.tsx +467 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +387 -0
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +1373 -0
- lyrics_transcriber/frontend/src/components/MetricsDashboard.tsx +51 -0
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +67 -0
- lyrics_transcriber/frontend/src/components/ModelSelector.tsx +23 -0
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +144 -0
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +268 -0
- lyrics_transcriber/frontend/src/components/ReplaceAllLyricsModal.tsx +688 -0
- lyrics_transcriber/frontend/src/components/ReviewChangesModal.tsx +354 -0
- lyrics_transcriber/frontend/src/components/SegmentDetailsModal.tsx +64 -0
- lyrics_transcriber/frontend/src/components/TimelineEditor.tsx +376 -0
- lyrics_transcriber/frontend/src/components/TimingOffsetModal.tsx +131 -0
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +256 -0
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +187 -0
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +379 -0
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +56 -0
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +87 -0
- lyrics_transcriber/frontend/src/components/shared/constants.ts +20 -0
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +180 -0
- lyrics_transcriber/frontend/src/components/shared/styles.ts +13 -0
- lyrics_transcriber/frontend/src/components/shared/types.js +2 -0
- lyrics_transcriber/frontend/src/components/shared/types.ts +129 -0
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +177 -0
- lyrics_transcriber/frontend/src/components/shared/utils/localStorage.ts +78 -0
- lyrics_transcriber/frontend/src/components/shared/utils/referenceLineCalculator.ts +75 -0
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +360 -0
- lyrics_transcriber/frontend/src/components/shared/utils/timingUtils.ts +110 -0
- lyrics_transcriber/frontend/src/components/shared/utils/wordUtils.ts +22 -0
- lyrics_transcriber/frontend/src/hooks/useManualSync.ts +435 -0
- lyrics_transcriber/frontend/src/main.tsx +17 -0
- lyrics_transcriber/frontend/src/theme.ts +177 -0
- lyrics_transcriber/frontend/src/types/global.d.ts +9 -0
- lyrics_transcriber/frontend/src/types.js +2 -0
- lyrics_transcriber/frontend/src/types.ts +199 -0
- lyrics_transcriber/frontend/src/validation.ts +132 -0
- lyrics_transcriber/frontend/src/vite-env.d.ts +1 -0
- lyrics_transcriber/frontend/tsconfig.app.json +26 -0
- lyrics_transcriber/frontend/tsconfig.json +25 -0
- lyrics_transcriber/frontend/tsconfig.node.json +23 -0
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -0
- lyrics_transcriber/frontend/update_version.js +11 -0
- lyrics_transcriber/frontend/vite.config.d.ts +2 -0
- lyrics_transcriber/frontend/vite.config.js +10 -0
- lyrics_transcriber/frontend/vite.config.ts +11 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-192x192.png +0 -0
- lyrics_transcriber/frontend/web_assets/android-chrome-512x512.png +0 -0
- lyrics_transcriber/frontend/web_assets/apple-touch-icon.png +0 -0
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js +42039 -0
- lyrics_transcriber/frontend/web_assets/assets/index-DdJTDWH3.js.map +1 -0
- lyrics_transcriber/frontend/web_assets/favicon-16x16.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon-32x32.png +0 -0
- lyrics_transcriber/frontend/web_assets/favicon.ico +0 -0
- lyrics_transcriber/frontend/web_assets/index.html +18 -0
- lyrics_transcriber/frontend/web_assets/nomad-karaoke-logo.png +0 -0
- lyrics_transcriber/frontend/yarn.lock +3752 -0
- lyrics_transcriber/lyrics/__init__.py +0 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +211 -0
- lyrics_transcriber/lyrics/file_provider.py +95 -0
- lyrics_transcriber/lyrics/genius.py +384 -0
- lyrics_transcriber/lyrics/lrclib.py +231 -0
- lyrics_transcriber/lyrics/musixmatch.py +156 -0
- lyrics_transcriber/lyrics/spotify.py +290 -0
- lyrics_transcriber/lyrics/user_input_provider.py +44 -0
- lyrics_transcriber/output/__init__.py +0 -0
- lyrics_transcriber/output/ass/__init__.py +21 -0
- lyrics_transcriber/output/ass/ass.py +2088 -0
- lyrics_transcriber/output/ass/ass_specs.txt +732 -0
- lyrics_transcriber/output/ass/config.py +180 -0
- lyrics_transcriber/output/ass/constants.py +23 -0
- lyrics_transcriber/output/ass/event.py +94 -0
- lyrics_transcriber/output/ass/formatters.py +132 -0
- lyrics_transcriber/output/ass/lyrics_line.py +265 -0
- lyrics_transcriber/output/ass/lyrics_screen.py +252 -0
- lyrics_transcriber/output/ass/section_detector.py +89 -0
- lyrics_transcriber/output/ass/section_screen.py +106 -0
- lyrics_transcriber/output/ass/style.py +187 -0
- lyrics_transcriber/output/cdg.py +619 -0
- lyrics_transcriber/output/cdgmaker/__init__.py +0 -0
- lyrics_transcriber/output/cdgmaker/cdg.py +262 -0
- lyrics_transcriber/output/cdgmaker/composer.py +2260 -0
- lyrics_transcriber/output/cdgmaker/config.py +151 -0
- lyrics_transcriber/output/cdgmaker/images/instrumental.png +0 -0
- lyrics_transcriber/output/cdgmaker/images/intro.png +0 -0
- lyrics_transcriber/output/cdgmaker/pack.py +507 -0
- lyrics_transcriber/output/cdgmaker/render.py +346 -0
- lyrics_transcriber/output/cdgmaker/transitions/centertexttoplogobottomtext.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circlein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/circleout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/fizzle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/largecentertexttoplogo.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/rectangle.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/spiral.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/topleftmusicalnotes.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipein.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeleft.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wipeout.png +0 -0
- lyrics_transcriber/output/cdgmaker/transitions/wiperight.png +0 -0
- lyrics_transcriber/output/cdgmaker/utils.py +132 -0
- lyrics_transcriber/output/countdown_processor.py +267 -0
- lyrics_transcriber/output/fonts/AvenirNext-Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSans-VariableFont_opsz,wght.ttf +0 -0
- lyrics_transcriber/output/fonts/DMSerifDisplay-Regular.ttf +0 -0
- lyrics_transcriber/output/fonts/Oswald-SemiBold.ttf +0 -0
- lyrics_transcriber/output/fonts/Zurich_Cn_BT_Bold.ttf +0 -0
- lyrics_transcriber/output/fonts/arial.ttf +0 -0
- lyrics_transcriber/output/fonts/georgia.ttf +0 -0
- lyrics_transcriber/output/fonts/verdana.ttf +0 -0
- lyrics_transcriber/output/generator.py +257 -0
- lyrics_transcriber/output/lrc_to_cdg.py +61 -0
- lyrics_transcriber/output/lyrics_file.py +102 -0
- lyrics_transcriber/output/plain_text.py +96 -0
- lyrics_transcriber/output/segment_resizer.py +431 -0
- lyrics_transcriber/output/subtitles.py +397 -0
- lyrics_transcriber/output/video.py +544 -0
- lyrics_transcriber/review/__init__.py +0 -0
- lyrics_transcriber/review/server.py +676 -0
- lyrics_transcriber/storage/__init__.py +0 -0
- lyrics_transcriber/storage/dropbox.py +225 -0
- lyrics_transcriber/transcribers/__init__.py +0 -0
- lyrics_transcriber/transcribers/audioshake.py +290 -0
- lyrics_transcriber/transcribers/base_transcriber.py +157 -0
- lyrics_transcriber/transcribers/whisper.py +330 -0
- lyrics_transcriber/types.py +648 -0
- lyrics_transcriber/utils/__init__.py +0 -0
- lyrics_transcriber/utils/word_utils.py +27 -0
- karaoke_gen-0.57.0.dist-info/METADATA +0 -167
- karaoke_gen-0.57.0.dist-info/RECORD +0 -23
- {karaoke_gen-0.57.0.dist-info → karaoke_gen-0.71.23.dist-info/licenses}/LICENSE +0 -0
|
@@ -46,6 +46,7 @@ class KaraokeFinalise:
|
|
|
46
46
|
non_interactive=False,
|
|
47
47
|
user_youtube_credentials=None, # Add support for pre-stored credentials
|
|
48
48
|
server_side_mode=False, # New parameter for server-side deployment
|
|
49
|
+
selected_instrumental_file=None, # Add support for pre-selected instrumental file
|
|
49
50
|
):
|
|
50
51
|
self.log_level = log_level
|
|
51
52
|
self.log_formatter = log_formatter
|
|
@@ -103,6 +104,7 @@ class KaraokeFinalise:
|
|
|
103
104
|
self.non_interactive = non_interactive
|
|
104
105
|
self.user_youtube_credentials = user_youtube_credentials
|
|
105
106
|
self.server_side_mode = server_side_mode
|
|
107
|
+
self.selected_instrumental_file = selected_instrumental_file
|
|
106
108
|
|
|
107
109
|
self.suffixes = {
|
|
108
110
|
"title_mov": " (Title).mov",
|
|
@@ -151,7 +153,8 @@ class KaraokeFinalise:
|
|
|
151
153
|
self.ffmpeg_base_command += " -y"
|
|
152
154
|
|
|
153
155
|
# Detect and configure hardware acceleration
|
|
154
|
-
|
|
156
|
+
# TODO: Re-enable this once we figure out why the resulting MP4s are 10x larger than when encoded with x264...
|
|
157
|
+
self.nvenc_available = False # self.detect_nvenc_support()
|
|
155
158
|
self.configure_hardware_acceleration()
|
|
156
159
|
|
|
157
160
|
def check_input_files_exist(self, base_name, with_vocals_file, instrumental_audio_file):
|
|
@@ -254,9 +257,19 @@ class KaraokeFinalise:
|
|
|
254
257
|
|
|
255
258
|
self.logger.debug(f"YouTube upload checks passed, enabling YouTube upload")
|
|
256
259
|
self.youtube_upload_enabled = True
|
|
260
|
+
|
|
261
|
+
# Also enable YouTube upload if pre-stored credentials are provided (server-side mode)
|
|
262
|
+
elif self.user_youtube_credentials is not None and self.youtube_description_file is not None:
|
|
263
|
+
if not os.path.isfile(self.youtube_description_file):
|
|
264
|
+
raise Exception(f"YouTube description file does not exist: {self.youtube_description_file}")
|
|
265
|
+
|
|
266
|
+
self.logger.debug(f"Pre-stored YouTube credentials provided, enabling YouTube upload")
|
|
267
|
+
self.youtube_upload_enabled = True
|
|
257
268
|
|
|
258
269
|
# Enable discord notifications if webhook URL is provided and is valid URL
|
|
259
270
|
if self.discord_webhook_url is not None:
|
|
271
|
+
# Strip whitespace/newlines that may have been introduced from environment variables or secrets
|
|
272
|
+
self.discord_webhook_url = self.discord_webhook_url.strip()
|
|
260
273
|
if not self.discord_webhook_url.startswith("https://discord.com/api/webhooks/"):
|
|
261
274
|
raise Exception(f"Discord webhook URL is not valid: {self.discord_webhook_url}")
|
|
262
275
|
|
|
@@ -409,26 +422,33 @@ class KaraokeFinalise:
|
|
|
409
422
|
if "items" in response and len(response["items"]) > 0:
|
|
410
423
|
for item in response["items"]:
|
|
411
424
|
found_title = item["snippet"]["title"]
|
|
412
|
-
|
|
413
|
-
|
|
425
|
+
|
|
426
|
+
# In server-side mode, require an exact match to avoid false positives.
|
|
427
|
+
# Otherwise, use fuzzy matching for interactive CLI usage.
|
|
428
|
+
if self.server_side_mode:
|
|
429
|
+
is_match = youtube_title.lower() == found_title.lower()
|
|
430
|
+
similarity_score = 100 if is_match else 0
|
|
431
|
+
else:
|
|
432
|
+
similarity_score = fuzz.ratio(youtube_title.lower(), found_title.lower())
|
|
433
|
+
is_match = similarity_score >= 70
|
|
434
|
+
|
|
435
|
+
if is_match:
|
|
414
436
|
found_id = item["id"]["videoId"]
|
|
415
437
|
self.logger.info(
|
|
416
438
|
f"Potential match found on YouTube channel with ID: {found_id} and title: {found_title} (similarity: {similarity_score}%)"
|
|
417
439
|
)
|
|
418
|
-
|
|
419
|
-
# In non-interactive mode,
|
|
440
|
+
|
|
441
|
+
# In non-interactive mode (server mode), we don't prompt. Just record the match and return.
|
|
420
442
|
if self.non_interactive:
|
|
421
|
-
self.logger.info(f"Non-interactive mode,
|
|
443
|
+
self.logger.info(f"Non-interactive mode, found a match.")
|
|
422
444
|
self.youtube_video_id = found_id
|
|
423
445
|
self.youtube_url = f"{self.youtube_url_prefix}{self.youtube_video_id}"
|
|
424
|
-
self.skip_notifications = True
|
|
425
446
|
return True
|
|
426
|
-
|
|
447
|
+
|
|
427
448
|
confirmation = input(f"Is '{found_title}' the video you are finalising? (y/n): ").strip().lower()
|
|
428
449
|
if confirmation == "y":
|
|
429
450
|
self.youtube_video_id = found_id
|
|
430
451
|
self.youtube_url = f"{self.youtube_url_prefix}{self.youtube_video_id}"
|
|
431
|
-
self.skip_notifications = True
|
|
432
452
|
return True
|
|
433
453
|
|
|
434
454
|
self.logger.info(f"No matching video found with title: {youtube_title}")
|
|
@@ -480,8 +500,12 @@ class KaraokeFinalise:
|
|
|
480
500
|
max_length = 95
|
|
481
501
|
youtube_title = self.truncate_to_nearest_word(youtube_title, max_length)
|
|
482
502
|
|
|
503
|
+
# In server-side mode, we should always replace videos if an exact match is found.
|
|
504
|
+
# Otherwise, respect the replace_existing flag from CLI.
|
|
505
|
+
should_replace = True if self.server_side_mode else replace_existing
|
|
506
|
+
|
|
483
507
|
if self.check_if_video_title_exists_on_youtube_channel(youtube_title):
|
|
484
|
-
if
|
|
508
|
+
if should_replace:
|
|
485
509
|
self.logger.info(f"Video already exists on YouTube, deleting before re-upload: {self.youtube_url}")
|
|
486
510
|
if self.delete_youtube_video(self.youtube_video_id):
|
|
487
511
|
self.logger.info(f"Successfully deleted existing video, proceeding with upload")
|
|
@@ -530,10 +554,17 @@ class KaraokeFinalise:
|
|
|
530
554
|
self.logger.info(f"Uploaded video to YouTube: {self.youtube_url}")
|
|
531
555
|
|
|
532
556
|
# Uploading the thumbnail
|
|
533
|
-
if input_files["title_jpg"]:
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
557
|
+
if input_files.get("title_jpg") and os.path.isfile(input_files["title_jpg"]):
|
|
558
|
+
try:
|
|
559
|
+
self.logger.info(f"Uploading thumbnail from: {input_files['title_jpg']}")
|
|
560
|
+
media_thumbnail = MediaFileUpload(input_files["title_jpg"], mimetype="image/jpeg")
|
|
561
|
+
youtube.thumbnails().set(videoId=self.youtube_video_id, media_body=media_thumbnail).execute()
|
|
562
|
+
self.logger.info(f"Uploaded thumbnail for video ID {self.youtube_video_id}")
|
|
563
|
+
except Exception as e:
|
|
564
|
+
self.logger.error(f"Failed to upload thumbnail: {e}")
|
|
565
|
+
self.logger.warning("Video uploaded but thumbnail not set. You may need to set it manually on YouTube.")
|
|
566
|
+
else:
|
|
567
|
+
self.logger.warning(f"Thumbnail file not found, skipping thumbnail upload: {input_files.get('title_jpg')}")
|
|
537
568
|
|
|
538
569
|
def get_next_brand_code(self):
|
|
539
570
|
"""
|
|
@@ -746,13 +777,13 @@ class KaraokeFinalise:
|
|
|
746
777
|
# Hardware-accelerated version
|
|
747
778
|
gpu_command = (
|
|
748
779
|
f'{self.ffmpeg_base_command} {self.hwaccel_decode_flags} -i "{input_file}" '
|
|
749
|
-
f'-c:v {self.video_encoder} {self.get_nvenc_quality_settings("high")} -c:a {self.aac_codec} {self.mp4_flags} "{output_file}"'
|
|
780
|
+
f'-c:v {self.video_encoder} {self.get_nvenc_quality_settings("high")} -c:a {self.aac_codec} -ar 48000 {self.mp4_flags} "{output_file}"'
|
|
750
781
|
)
|
|
751
782
|
|
|
752
783
|
# Software fallback version
|
|
753
784
|
cpu_command = (
|
|
754
785
|
f'{self.ffmpeg_base_command} -i "{input_file}" '
|
|
755
|
-
f'-c:v libx264 -c:a {self.aac_codec} {self.mp4_flags} "{output_file}"'
|
|
786
|
+
f'-c:v libx264 -c:a {self.aac_codec} -ar 48000 {self.mp4_flags} "{output_file}"'
|
|
756
787
|
)
|
|
757
788
|
|
|
758
789
|
self.execute_command_with_fallback(gpu_command, cpu_command, "Converting MOV video to MP4")
|
|
@@ -782,7 +813,7 @@ class KaraokeFinalise:
|
|
|
782
813
|
# Hardware acceleration doesn't provide significant benefit for copy operations
|
|
783
814
|
ffmpeg_command = (
|
|
784
815
|
f'{self.ffmpeg_base_command} -i "{input_file}" '
|
|
785
|
-
f'-c:v copy -c:a {self.aac_codec} -b:a 320k {self.mp4_flags} "{output_file}"'
|
|
816
|
+
f'-c:v copy -c:a {self.aac_codec} -ar 48000 -b:a 320k {self.mp4_flags} "{output_file}"'
|
|
786
817
|
)
|
|
787
818
|
self.execute_command(ffmpeg_command, "Creating MP4 version with AAC audio")
|
|
788
819
|
|
|
@@ -803,14 +834,14 @@ class KaraokeFinalise:
|
|
|
803
834
|
f'{self.ffmpeg_base_command} {self.hwaccel_decode_flags} -i "{input_file}" '
|
|
804
835
|
f'-c:v {self.video_encoder} -vf "{self.scale_filter}=1280:720" '
|
|
805
836
|
f'{self.get_nvenc_quality_settings("medium")} -b:v 2000k '
|
|
806
|
-
f'-c:a {self.aac_codec} -b:a 128k {self.mp4_flags} "{output_file}"'
|
|
837
|
+
f'-c:a {self.aac_codec} -ar 48000 -b:a 128k {self.mp4_flags} "{output_file}"'
|
|
807
838
|
)
|
|
808
839
|
|
|
809
840
|
# Software fallback version
|
|
810
841
|
cpu_command = (
|
|
811
842
|
f'{self.ffmpeg_base_command} -i "{input_file}" '
|
|
812
843
|
f'-c:v libx264 -vf "scale=1280:720" -b:v 2000k -preset medium -tune animation '
|
|
813
|
-
f'-c:a {self.aac_codec} -b:a 128k {self.mp4_flags} "{output_file}"'
|
|
844
|
+
f'-c:a {self.aac_codec} -ar 48000 -b:a 128k {self.mp4_flags} "{output_file}"'
|
|
814
845
|
)
|
|
815
846
|
|
|
816
847
|
self.execute_command_with_fallback(gpu_command, cpu_command, "Encoding 720p version of the final video")
|
|
@@ -829,7 +860,7 @@ class KaraokeFinalise:
|
|
|
829
860
|
return env_mov_input, ffmpeg_filter
|
|
830
861
|
|
|
831
862
|
def remux_and_encode_output_video_files(self, with_vocals_file, input_files, output_files):
|
|
832
|
-
self.logger.info(f"Remuxing and encoding output video files...")
|
|
863
|
+
self.logger.info(f"Remuxing and encoding output video files (4 formats, ~15-20 minutes total)...")
|
|
833
864
|
|
|
834
865
|
# Check if output files already exist
|
|
835
866
|
if os.path.isfile(output_files["final_karaoke_lossless_mp4"]) and os.path.isfile(output_files["final_karaoke_lossless_mkv"]):
|
|
@@ -840,16 +871,20 @@ class KaraokeFinalise:
|
|
|
840
871
|
return
|
|
841
872
|
|
|
842
873
|
# Create karaoke version with instrumental audio
|
|
874
|
+
self.logger.info(f"[Step 1/6] Remuxing video with instrumental audio...")
|
|
843
875
|
self.remux_with_instrumental(with_vocals_file, input_files["instrumental_audio"], output_files["karaoke_mp4"])
|
|
844
876
|
|
|
845
877
|
# Convert the with vocals video to MP4 if needed
|
|
846
878
|
if not with_vocals_file.endswith(".mp4"):
|
|
879
|
+
self.logger.info(f"[Step 2/6] Converting karaoke video to MP4...")
|
|
847
880
|
self.convert_mov_to_mp4(with_vocals_file, output_files["with_vocals_mp4"])
|
|
848
881
|
|
|
849
882
|
# Delete the with vocals mov after successfully converting it to mp4
|
|
850
883
|
if not self.dry_run and os.path.isfile(with_vocals_file):
|
|
851
884
|
self.logger.info(f"Deleting with vocals MOV file: {with_vocals_file}")
|
|
852
885
|
os.remove(with_vocals_file)
|
|
886
|
+
else:
|
|
887
|
+
self.logger.info(f"[Step 2/6] Skipped - video already in MP4 format")
|
|
853
888
|
|
|
854
889
|
# Quote file paths to handle special characters
|
|
855
890
|
title_mov_file = shlex.quote(os.path.abspath(input_files["title_mov"]))
|
|
@@ -858,10 +893,17 @@ class KaraokeFinalise:
|
|
|
858
893
|
# Prepare concat filter for combining videos
|
|
859
894
|
env_mov_input, ffmpeg_filter = self.prepare_concat_filter(input_files)
|
|
860
895
|
|
|
861
|
-
# Create all output versions
|
|
896
|
+
# Create all output versions with progress logging
|
|
897
|
+
self.logger.info(f"[Step 3/6] Encoding lossless 4K MP4 (title + karaoke + end, ~5 minutes)...")
|
|
862
898
|
self.encode_lossless_mp4(title_mov_file, karaoke_mp4_file, env_mov_input, ffmpeg_filter, output_files["final_karaoke_lossless_mp4"])
|
|
899
|
+
|
|
900
|
+
self.logger.info(f"[Step 4/6] Encoding lossy 4K MP4 with AAC audio (~1 minute)...")
|
|
863
901
|
self.encode_lossy_mp4(output_files["final_karaoke_lossless_mp4"], output_files["final_karaoke_lossy_mp4"])
|
|
902
|
+
|
|
903
|
+
self.logger.info(f"[Step 5/6] Creating MKV with FLAC audio for YouTube (~1 minute)...")
|
|
864
904
|
self.encode_lossless_mkv(output_files["final_karaoke_lossless_mp4"], output_files["final_karaoke_lossless_mkv"])
|
|
905
|
+
|
|
906
|
+
self.logger.info(f"[Step 6/6] Encoding 720p version (~3 minutes)...")
|
|
865
907
|
self.encode_720p_version(output_files["final_karaoke_lossless_mp4"], output_files["final_karaoke_lossy_720p_mp4"])
|
|
866
908
|
|
|
867
909
|
# Skip user confirmation in non-interactive mode for Modal deployment
|
|
@@ -1064,7 +1106,7 @@ class KaraokeFinalise:
|
|
|
1064
1106
|
os.remove(file_path)
|
|
1065
1107
|
self.logger.info(f"Deleted .DS_Store file: {file_path}")
|
|
1066
1108
|
|
|
1067
|
-
rclone_cmd = f"rclone copy -v {shlex.quote(self.public_share_dir)} {shlex.quote(self.rclone_destination)}"
|
|
1109
|
+
rclone_cmd = f"rclone copy -v --ignore-existing {shlex.quote(self.public_share_dir)} {shlex.quote(self.rclone_destination)}"
|
|
1068
1110
|
self.execute_command(rclone_cmd, "Copying to cloud destination")
|
|
1069
1111
|
|
|
1070
1112
|
def post_discord_notification(self):
|
|
@@ -1073,6 +1115,11 @@ class KaraokeFinalise:
|
|
|
1073
1115
|
if self.skip_notifications:
|
|
1074
1116
|
self.logger.info(f"Skipping Discord notification as video was previously uploaded to YouTube")
|
|
1075
1117
|
return
|
|
1118
|
+
|
|
1119
|
+
# Only post if we have a YouTube URL
|
|
1120
|
+
if not self.youtube_url:
|
|
1121
|
+
self.logger.info(f"Skipping Discord notification - no YouTube URL available")
|
|
1122
|
+
return
|
|
1076
1123
|
|
|
1077
1124
|
if self.dry_run:
|
|
1078
1125
|
self.logger.info(
|
|
@@ -1189,7 +1236,7 @@ class KaraokeFinalise:
|
|
|
1189
1236
|
current_dir = os.getcwd()
|
|
1190
1237
|
|
|
1191
1238
|
# Use rclone copy to upload the entire current directory to the remote destination
|
|
1192
|
-
rclone_upload_cmd = f"rclone copy -v {shlex.quote(current_dir)} {shlex.quote(remote_dest)}"
|
|
1239
|
+
rclone_upload_cmd = f"rclone copy -v --ignore-existing {shlex.quote(current_dir)} {shlex.quote(remote_dest)}"
|
|
1193
1240
|
|
|
1194
1241
|
if self.dry_run:
|
|
1195
1242
|
self.logger.info(f"DRY RUN: Would upload current directory to: {remote_dest}")
|
|
@@ -1254,14 +1301,23 @@ class KaraokeFinalise:
|
|
|
1254
1301
|
self.upload_final_mp4_to_youtube_with_title_thumbnail(artist, title, input_files, output_files, replace_existing)
|
|
1255
1302
|
except Exception as e:
|
|
1256
1303
|
self.logger.error(f"Failed to upload video to YouTube: {e}")
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1304
|
+
if not self.non_interactive:
|
|
1305
|
+
print("Please manually upload the video to YouTube.")
|
|
1306
|
+
print()
|
|
1307
|
+
self.youtube_video_id = input("Enter the manually uploaded YouTube video ID: ").strip()
|
|
1308
|
+
self.youtube_url = f"{self.youtube_url_prefix}{self.youtube_video_id}"
|
|
1309
|
+
self.logger.info(f"Using manually provided YouTube video ID: {self.youtube_video_id}")
|
|
1310
|
+
else:
|
|
1311
|
+
self.logger.error("YouTube upload failed in non-interactive mode, skipping")
|
|
1262
1312
|
|
|
1263
|
-
|
|
1313
|
+
# Discord notification - runs independently of YouTube upload
|
|
1314
|
+
# Wrapped in try/except so failures don't crash the entire job
|
|
1315
|
+
if self.discord_notication_enabled:
|
|
1316
|
+
try:
|
|
1264
1317
|
self.post_discord_notification()
|
|
1318
|
+
except Exception as e:
|
|
1319
|
+
self.logger.error(f"Failed to send Discord notification: {e}")
|
|
1320
|
+
self.logger.warning("Continuing without Discord notification - this is non-fatal")
|
|
1265
1321
|
|
|
1266
1322
|
# Handle folder organization - different logic for server-side vs local mode
|
|
1267
1323
|
if self.server_side_mode and self.brand_prefix and self.organised_dir_rclone_root:
|
|
@@ -1362,6 +1418,12 @@ class KaraokeFinalise:
|
|
|
1362
1418
|
self.logger.info("Email template file not provided, skipping email draft creation.")
|
|
1363
1419
|
return
|
|
1364
1420
|
|
|
1421
|
+
if not self.youtube_client_secrets_file:
|
|
1422
|
+
self.logger.error("Email template file was provided, but youtube_client_secrets_file is required for Gmail authentication.")
|
|
1423
|
+
self.logger.error("Please provide --youtube_client_secrets_file parameter to enable email draft creation.")
|
|
1424
|
+
self.logger.info("Skipping email draft creation.")
|
|
1425
|
+
return
|
|
1426
|
+
|
|
1365
1427
|
with open(self.email_template_file, "r") as f:
|
|
1366
1428
|
template = f.read()
|
|
1367
1429
|
|
|
@@ -1416,89 +1478,87 @@ class KaraokeFinalise:
|
|
|
1416
1478
|
return "aac"
|
|
1417
1479
|
|
|
1418
1480
|
def detect_nvenc_support(self):
|
|
1419
|
-
"""Detect if NVENC hardware encoding is available
|
|
1481
|
+
"""Detect if NVENC hardware encoding is available."""
|
|
1420
1482
|
try:
|
|
1421
|
-
self.logger.info("🔍 Detecting NVENC hardware acceleration
|
|
1483
|
+
self.logger.info("🔍 Detecting NVENC hardware acceleration...")
|
|
1422
1484
|
|
|
1423
1485
|
if self.dry_run:
|
|
1424
|
-
self.logger.info("DRY RUN: Assuming NVENC is available")
|
|
1486
|
+
self.logger.info(" DRY RUN: Assuming NVENC is available")
|
|
1425
1487
|
return True
|
|
1426
1488
|
|
|
1427
1489
|
import subprocess
|
|
1428
1490
|
import os
|
|
1429
1491
|
import shutil
|
|
1430
1492
|
|
|
1431
|
-
#
|
|
1493
|
+
# Check for nvidia-smi (indicates NVIDIA driver presence)
|
|
1432
1494
|
try:
|
|
1433
1495
|
nvidia_smi_result = subprocess.run(["nvidia-smi", "--query-gpu=name,driver_version", "--format=csv,noheader"],
|
|
1434
1496
|
capture_output=True, text=True, timeout=10)
|
|
1435
1497
|
if nvidia_smi_result.returncode == 0:
|
|
1436
1498
|
gpu_info = nvidia_smi_result.stdout.strip()
|
|
1437
|
-
self.logger.info(f"✓ NVIDIA GPU detected: {gpu_info}")
|
|
1499
|
+
self.logger.info(f" ✓ NVIDIA GPU detected: {gpu_info}")
|
|
1438
1500
|
else:
|
|
1439
|
-
self.logger.
|
|
1501
|
+
self.logger.debug(f"nvidia-smi failed: {nvidia_smi_result.stderr}")
|
|
1502
|
+
self.logger.info(" ✗ NVENC not available (no NVIDIA GPU)")
|
|
1440
1503
|
return False
|
|
1441
|
-
except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.CalledProcessError):
|
|
1442
|
-
self.logger.
|
|
1504
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.CalledProcessError) as e:
|
|
1505
|
+
self.logger.debug(f"nvidia-smi not available: {e}")
|
|
1506
|
+
self.logger.info(" ✗ NVENC not available (no NVIDIA GPU)")
|
|
1443
1507
|
return False
|
|
1444
1508
|
|
|
1445
|
-
#
|
|
1509
|
+
# Check for NVENC encoders in FFmpeg
|
|
1446
1510
|
try:
|
|
1447
1511
|
encoders_cmd = f"{self.ffmpeg_base_command} -hide_banner -encoders 2>/dev/null | grep nvenc"
|
|
1448
1512
|
encoders_result = subprocess.run(encoders_cmd, shell=True, capture_output=True, text=True, timeout=10)
|
|
1449
1513
|
if encoders_result.returncode == 0 and "nvenc" in encoders_result.stdout:
|
|
1450
1514
|
nvenc_encoders = [line.strip() for line in encoders_result.stdout.split('\n') if 'nvenc' in line]
|
|
1451
|
-
self.logger.
|
|
1452
|
-
for encoder in nvenc_encoders:
|
|
1453
|
-
if encoder:
|
|
1454
|
-
self.logger.info(f" {encoder}")
|
|
1515
|
+
self.logger.debug(f"Found NVENC encoders: {nvenc_encoders}")
|
|
1455
1516
|
else:
|
|
1456
|
-
self.logger.
|
|
1517
|
+
self.logger.debug("No NVENC encoders found in FFmpeg")
|
|
1518
|
+
self.logger.info(" ✗ NVENC not available (no FFmpeg support)")
|
|
1457
1519
|
return False
|
|
1458
1520
|
except Exception as e:
|
|
1459
|
-
self.logger.
|
|
1521
|
+
self.logger.debug(f"Failed to check FFmpeg NVENC encoders: {e}")
|
|
1522
|
+
self.logger.info(" ✗ NVENC not available")
|
|
1460
1523
|
return False
|
|
1461
1524
|
|
|
1462
|
-
#
|
|
1525
|
+
# Check for libcuda.so.1 (critical for NVENC)
|
|
1463
1526
|
try:
|
|
1464
1527
|
libcuda_check = subprocess.run(["ldconfig", "-p"], capture_output=True, text=True, timeout=10)
|
|
1465
1528
|
if libcuda_check.returncode == 0 and "libcuda.so.1" in libcuda_check.stdout:
|
|
1466
|
-
self.logger.
|
|
1529
|
+
self.logger.debug("libcuda.so.1 found in system libraries")
|
|
1467
1530
|
else:
|
|
1468
|
-
self.logger.
|
|
1469
|
-
self.logger.
|
|
1531
|
+
self.logger.debug("libcuda.so.1 NOT found - may need nvidia/cuda:*-devel image")
|
|
1532
|
+
self.logger.info(" ✗ NVENC not available (missing CUDA libraries)")
|
|
1470
1533
|
return False
|
|
1471
1534
|
except Exception as e:
|
|
1472
|
-
self.logger.
|
|
1535
|
+
self.logger.debug(f"Failed to check for libcuda.so.1: {e}")
|
|
1536
|
+
self.logger.info(" ✗ NVENC not available")
|
|
1473
1537
|
return False
|
|
1474
1538
|
|
|
1475
|
-
#
|
|
1476
|
-
self.
|
|
1477
|
-
|
|
1478
|
-
self.logger.debug(f"Running test command: {test_cmd}")
|
|
1539
|
+
# Test h264_nvenc encoder
|
|
1540
|
+
test_cmd = f"{self.ffmpeg_base_command} -hide_banner -loglevel error -f lavfi -i testsrc=duration=1:size=320x240:rate=1 -c:v h264_nvenc -f null -"
|
|
1541
|
+
self.logger.debug(f"Testing NVENC: {test_cmd}")
|
|
1479
1542
|
|
|
1480
1543
|
try:
|
|
1481
1544
|
result = subprocess.run(test_cmd, shell=True, capture_output=True, text=True, timeout=30)
|
|
1482
1545
|
|
|
1483
1546
|
if result.returncode == 0:
|
|
1484
|
-
self.logger.info("
|
|
1485
|
-
self.logger.info(f"Test command succeeded. Output: {result.stderr if result.stderr else '...'}")
|
|
1547
|
+
self.logger.info(" ✓ NVENC encoding available")
|
|
1486
1548
|
return True
|
|
1487
1549
|
else:
|
|
1488
|
-
self.logger.
|
|
1489
|
-
|
|
1490
|
-
self.logger.warning(f"Error output: {result.stderr}")
|
|
1491
|
-
if "Cannot load libcuda.so.1" in result.stderr:
|
|
1492
|
-
self.logger.warning("💡 Root cause: libcuda.so.1 cannot be loaded by NVENC")
|
|
1493
|
-
self.logger.warning("💡 Solution: Use nvidia/cuda:*-devel-* image instead of runtime")
|
|
1550
|
+
self.logger.debug(f"NVENC test failed (exit code {result.returncode}): {result.stderr}")
|
|
1551
|
+
self.logger.info(" ✗ NVENC not available")
|
|
1494
1552
|
return False
|
|
1495
1553
|
|
|
1496
1554
|
except subprocess.TimeoutExpired:
|
|
1497
|
-
self.logger.
|
|
1555
|
+
self.logger.debug("NVENC test timed out")
|
|
1556
|
+
self.logger.info(" ✗ NVENC not available (timeout)")
|
|
1498
1557
|
return False
|
|
1499
1558
|
|
|
1500
1559
|
except Exception as e:
|
|
1501
|
-
self.logger.
|
|
1560
|
+
self.logger.debug(f"Failed to detect NVENC support: {e}")
|
|
1561
|
+
self.logger.info(" ✗ NVENC not available (error)")
|
|
1502
1562
|
return False
|
|
1503
1563
|
|
|
1504
1564
|
def configure_hardware_acceleration(self):
|
|
@@ -1509,12 +1569,12 @@ class KaraokeFinalise:
|
|
|
1509
1569
|
# Remove -hwaccel_output_format cuda as it causes pixel format conversion issues
|
|
1510
1570
|
self.hwaccel_decode_flags = "-hwaccel cuda"
|
|
1511
1571
|
self.scale_filter = "scale" # Use CPU scaling for complex filter chains
|
|
1512
|
-
self.logger.info("
|
|
1572
|
+
self.logger.info("🚀 Using NVENC hardware acceleration for video encoding")
|
|
1513
1573
|
else:
|
|
1514
1574
|
self.video_encoder = "libx264"
|
|
1515
1575
|
self.hwaccel_decode_flags = ""
|
|
1516
1576
|
self.scale_filter = "scale"
|
|
1517
|
-
self.logger.info("
|
|
1577
|
+
self.logger.info("🔧 Using software encoding (libx264) for video")
|
|
1518
1578
|
|
|
1519
1579
|
def get_nvenc_quality_settings(self, quality_mode="high"):
|
|
1520
1580
|
"""Get NVENC settings based on quality requirements."""
|
|
@@ -1619,25 +1679,46 @@ class KaraokeFinalise:
|
|
|
1619
1679
|
if self.dry_run:
|
|
1620
1680
|
self.logger.warning("Dry run enabled. No actions will be performed.")
|
|
1621
1681
|
|
|
1682
|
+
self.logger.info("=" * 60)
|
|
1683
|
+
self.logger.info("Starting KaraokeFinalise processing pipeline")
|
|
1684
|
+
self.logger.info("=" * 60)
|
|
1685
|
+
|
|
1622
1686
|
# Check required input files and parameters exist, get user to confirm features before proceeding
|
|
1623
1687
|
self.validate_input_parameters_for_features()
|
|
1624
1688
|
|
|
1625
1689
|
with_vocals_file = self.find_with_vocals_file()
|
|
1626
1690
|
base_name, artist, title = self.get_names_from_withvocals(with_vocals_file)
|
|
1627
|
-
|
|
1628
|
-
|
|
1691
|
+
|
|
1692
|
+
self.logger.info(f"Processing: {artist} - {title}")
|
|
1693
|
+
|
|
1694
|
+
# Use the selected instrumental file if provided, otherwise search for one
|
|
1695
|
+
if self.selected_instrumental_file:
|
|
1696
|
+
if not os.path.isfile(self.selected_instrumental_file):
|
|
1697
|
+
raise Exception(f"Selected instrumental file not found: {self.selected_instrumental_file}")
|
|
1698
|
+
instrumental_audio_file = self.selected_instrumental_file
|
|
1699
|
+
self.logger.info(f"Using pre-selected instrumental file: {instrumental_audio_file}")
|
|
1700
|
+
else:
|
|
1701
|
+
self.logger.info("No instrumental file pre-selected, searching for instrumental files...")
|
|
1702
|
+
instrumental_audio_file = self.choose_instrumental_audio_file(base_name)
|
|
1629
1703
|
|
|
1630
1704
|
input_files = self.check_input_files_exist(base_name, with_vocals_file, instrumental_audio_file)
|
|
1631
1705
|
output_files = self.prepare_output_filenames(base_name)
|
|
1632
1706
|
|
|
1633
1707
|
if self.enable_cdg:
|
|
1708
|
+
self.logger.info("Creating CDG package...")
|
|
1634
1709
|
self.create_cdg_zip_file(input_files, output_files, artist, title)
|
|
1710
|
+
self.logger.info("CDG package created successfully")
|
|
1635
1711
|
|
|
1636
1712
|
if self.enable_txt:
|
|
1713
|
+
self.logger.info("Creating TXT package...")
|
|
1637
1714
|
self.create_txt_zip_file(input_files, output_files)
|
|
1715
|
+
self.logger.info("TXT package created successfully")
|
|
1638
1716
|
|
|
1717
|
+
self.logger.info("Starting video encoding (this is the longest step, ~15-20 minutes)...")
|
|
1639
1718
|
self.remux_and_encode_output_video_files(with_vocals_file, input_files, output_files)
|
|
1719
|
+
self.logger.info("Video encoding completed successfully")
|
|
1640
1720
|
|
|
1721
|
+
self.logger.info("Executing distribution features (YouTube, Dropbox, Discord)...")
|
|
1641
1722
|
self.execute_optional_features(artist, title, base_name, input_files, output_files, replace_existing)
|
|
1642
1723
|
|
|
1643
1724
|
result = {
|