Skip to content

Commit fa8d13a

Browse files
committed
feat: Implemented intelligent PyTorch type download for Whisper Setup to download PyTorch with GPU support for any computer. So if available GPU can be used for 10x-20x increased speed in AI Generated subtitles.
fix: Implement use of processpool for Encoder, fixed ffmpeg detection for GPU to set ffmpeg path if = none before checkng. fix: Implemented use of processpool for metadata searching, implemented parralell processing with multiple threads for processing multiple files at a time.
1 parent e34463c commit fa8d13a

8 files changed

Lines changed: 429 additions & 56 deletions

File tree

EncodeForge/src/main/java/com/encodeforge/controller/MainController.java

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,9 +1726,21 @@ private void handleStop() {
17261726

17271727
// Best-effort cancel in backend
17281728
try {
1729-
logger.info("Sending stop conversion command to Python bridge...");
1730-
pythonBridge.requestStopConversion();
1731-
logger.info("Stop conversion command sent successfully");
1729+
logger.info("Broadcasting stop conversion command to all workers...");
1730+
if (processPool != null) {
1731+
// Broadcast stop to all workers (conversion could be on any worker)
1732+
processPool.broadcastCommand("stop_conversion", new JsonObject())
1733+
.thenAccept(stopResult -> {
1734+
logger.info("Stop conversion result: {}", stopResult);
1735+
})
1736+
.exceptionally(ex -> {
1737+
logger.error("Stop conversion failed: {}", ex.getMessage());
1738+
return null;
1739+
});
1740+
logger.info("Stop conversion broadcast sent to all workers");
1741+
} else {
1742+
logger.warn("Process pool is null, cannot send stop command");
1743+
}
17321744
} catch (Exception e) {
17331745
logger.error("Stop command failed: {}", e.getMessage(), e);
17341746
}
@@ -4671,7 +4683,8 @@ private void handleRefreshMetadata() {
46714683
// Send to Python backend asynchronously
46724684
CompletableFuture.runAsync(() -> {
46734685
try {
4674-
com.google.gson.JsonObject jsonResponse = pythonBridge.sendCommand(request);
4686+
// Use process pool instead of pythonBridge
4687+
com.google.gson.JsonObject jsonResponse = processPool.submitQuickTask("preview_rename", request).join();
46754688
String status = jsonResponse.get("status").getAsString();
46764689

46774690
if ("success".equals(status)) {
@@ -6036,10 +6049,15 @@ private void processWhisperFile(ConversionJob job, int fileIndex, int totalFiles
60366049
progressUpdate.get("message").getAsString() : "Processing...";
60376050

60386051
Platform.runLater(() -> {
6039-
log(String.format("Progress: %.1f%% - %s", overallProgress, message));
6052+
if (fileProgress < 100) { // Only log non-completion progress
6053+
log(String.format("Progress: %.1f%% - %s", overallProgress, message));
6054+
}
60406055
if (subtitleTotalProgressBar != null) {
60416056
subtitleTotalProgressBar.setProgress(overallProgress / 100.0);
60426057
}
6058+
if (subtitleProgressStatusLabel != null) {
6059+
subtitleProgressStatusLabel.setText(message);
6060+
}
60436061
});
60446062
}
60456063

@@ -6138,13 +6156,21 @@ private void processWhisperFile(ConversionJob job, int fileIndex, int totalFiles
61386156
}
61396157
}
61406158
});
6141-
successCount.incrementAndGet();
61426159

6143-
// Update progress label
6160+
// Update counters and progress
6161+
int success = successCount.incrementAndGet();
61446162
final int completed = completedCount.incrementAndGet();
6163+
6164+
logger.info("🔢 Progress Update: {} / {} files completed ({} success, {} failed)",
6165+
completed, totalFiles, success, failedCount.get());
6166+
61456167
Platform.runLater(() -> {
61466168
if (subtitleProgressLabel != null) {
6147-
subtitleProgressLabel.setText(completed + " / " + totalFiles + " files");
6169+
String labelText = completed + " / " + totalFiles + " files";
6170+
subtitleProgressLabel.setText(labelText);
6171+
logger.info("📊 Updated progress label to: {}", labelText);
6172+
} else {
6173+
logger.warn("⚠️ subtitleProgressLabel is null!");
61486174
}
61496175
});
61506176
} else {
@@ -6282,17 +6308,30 @@ private void showWhisperCompletionSummary(int successCount, int failedCount, int
62826308
}
62836309

62846310
Platform.runLater(() -> {
6285-
log("AI subtitle generation complete: " + finalSuccess + " success, " + finalFailed + " failed");
6311+
log("AI subtitle generation complete: " + finalSuccess + " success, " + finalFailed + " failed");
62866312

6287-
// Reset progress UI
6313+
// Set progress to 100%
62886314
if (subtitleTotalProgressBar != null) {
6289-
subtitleTotalProgressBar.setProgress(1.0); // Show 100%
6315+
subtitleTotalProgressBar.setProgress(1.0);
62906316
}
6317+
6318+
// Update file counter to show completion
62916319
if (subtitleProgressLabel != null) {
62926320
subtitleProgressLabel.setText(totalFiles + " / " + totalFiles + " files");
62936321
}
6322+
6323+
// Show completion status
62946324
if (subtitleProgressStatusLabel != null) {
6295-
subtitleProgressStatusLabel.setText("Complete: " + finalSuccess + " succeeded, " + finalFailed + " failed");
6325+
subtitleProgressStatusLabel.setText("✅ Complete: " + finalSuccess + " succeeded, " + finalFailed + " failed");
6326+
}
6327+
6328+
// Refresh the Available Subtitles table if visible
6329+
if (availableSubtitlesTable != null && currentlySelectedFile != null) {
6330+
ObservableList<SubtitleItem> subs = subtitlesByFile.get(currentlySelectedFile);
6331+
if (subs != null) {
6332+
availableSubtitlesTable.setItems(subs);
6333+
availableSubtitlesTable.refresh();
6334+
}
62966335
}
62976336

62986337
// Don't show dialog if shutting down (double-check)
@@ -6302,14 +6341,17 @@ private void showWhisperCompletionSummary(int successCount, int failedCount, int
63026341
finalSuccess + finalFailed, finalSuccess, finalFailed));
63036342
}
63046343

6305-
// Reset progress bar after a short delay to let user see 100%
6344+
// Reset progress UI after delay
63066345
new Thread(() -> {
63076346
try {
6308-
Thread.sleep(2000);
6347+
Thread.sleep(3000); // 3 second delay to let user see completion
63096348
Platform.runLater(() -> {
63106349
if (subtitleTotalProgressBar != null) {
63116350
subtitleTotalProgressBar.setProgress(0.0);
63126351
}
6352+
if (subtitleProgressLabel != null) {
6353+
subtitleProgressLabel.setText("");
6354+
}
63136355
if (subtitleProgressStatusLabel != null) {
63146356
subtitleProgressStatusLabel.setText("");
63156357
}

EncodeForge/src/main/java/com/encodeforge/service/PythonProcessPool.java

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,71 @@ public int getAvailableWorkerCount() {
397397
return (int) workers.stream().filter(PythonWorker::isAvailable).count();
398398
}
399399

400+
/**
401+
* Broadcast a command to all workers (useful for stop_conversion)
402+
* Returns as soon as any worker reports success
403+
*/
404+
public CompletableFuture<JsonObject> broadcastCommand(String action, JsonObject params) {
405+
logger.info("Broadcasting command '{}' to all {} workers", action, workers.size());
406+
407+
JsonObject command = new JsonObject();
408+
command.addProperty("action", action);
409+
410+
// Merge params
411+
for (String key : params.keySet()) {
412+
command.add(key, params.get(key));
413+
}
414+
415+
List<CompletableFuture<JsonObject>> futures = new ArrayList<>();
416+
417+
for (PythonWorker worker : workers) {
418+
CompletableFuture<JsonObject> future = CompletableFuture.supplyAsync(() -> {
419+
try {
420+
logger.debug("Sending broadcast command '{}' to {}", action, worker.getWorkerId());
421+
JsonObject result = worker.sendCommand(command);
422+
logger.debug("Worker {} responded to '{}': {}", worker.getWorkerId(), action, result);
423+
return result;
424+
} catch (Exception e) {
425+
logger.warn("Worker {} failed to handle broadcast '{}': {}", worker.getWorkerId(), action, e.getMessage());
426+
JsonObject error = new JsonObject();
427+
error.addProperty("status", "error");
428+
error.addProperty("message", e.getMessage());
429+
return error;
430+
}
431+
}, taskExecutor);
432+
futures.add(future);
433+
}
434+
435+
// Wait for all workers to respond, then return the first success or last error
436+
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
437+
.thenApply(v -> {
438+
// Check if any worker reported success
439+
for (CompletableFuture<JsonObject> future : futures) {
440+
try {
441+
JsonObject result = future.get();
442+
if (result.has("status") && "success".equals(result.get("status").getAsString())) {
443+
logger.info("Broadcast '{}' succeeded on at least one worker", action);
444+
return result;
445+
}
446+
} catch (Exception e) {
447+
// Ignore individual failures
448+
}
449+
}
450+
451+
// No success, return first error
452+
try {
453+
JsonObject firstResult = futures.get(0).get();
454+
logger.warn("Broadcast '{}' did not succeed on any worker", action);
455+
return firstResult;
456+
} catch (Exception e) {
457+
JsonObject error = new JsonObject();
458+
error.addProperty("status", "error");
459+
error.addProperty("message", "All workers failed");
460+
return error;
461+
}
462+
});
463+
}
464+
400465
/**
401466
* Get pool statistics
402467
*/
@@ -413,24 +478,27 @@ public Map<String, Object> getStatistics() {
413478
* Shutdown all workers
414479
*/
415480
public void shutdown() {
481+
// Prevent double shutdown
482+
if (taskExecutor.isShutdown()) {
483+
logger.debug("Process pool already shut down");
484+
return;
485+
}
486+
416487
logger.info("Shutting down Python process pool");
417488
isRunning = false;
418489

419-
// Stop health monitoring
420-
healthMonitor.shutdown();
421-
422-
// Shutdown all workers in parallel
423-
List<CompletableFuture<Void>> shutdownFutures = new ArrayList<>();
424-
for (PythonWorker worker : workers) {
425-
CompletableFuture<Void> future = CompletableFuture.runAsync(worker::shutdown, taskExecutor);
426-
shutdownFutures.add(future);
490+
// Stop health monitoring first
491+
if (!healthMonitor.isShutdown()) {
492+
healthMonitor.shutdown();
427493
}
428494

429-
// Wait for all workers to shutdown
430-
try {
431-
CompletableFuture.allOf(shutdownFutures.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS);
432-
} catch (Exception e) {
433-
logger.warn("Not all workers shut down cleanly", e);
495+
// Shutdown all workers directly (synchronously to avoid executor issues during shutdown)
496+
for (PythonWorker worker : workers) {
497+
try {
498+
worker.shutdown();
499+
} catch (Exception e) {
500+
logger.warn("Error shutting down worker: {}", e.getMessage());
501+
}
434502
}
435503

436504
// Shutdown executors

EncodeForge/src/main/resources/python/encodeforge_core.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,31 @@ def _ensure_handlers_initialized(self):
8585
"""Lazy initialize handlers (called before first use)"""
8686
if not hasattr(self, 'file_handler'):
8787
logger.info("Initializing EncodeForge handlers")
88-
self.file_handler = FileHandler(self.settings, self.ffmpeg_mgr)
89-
self.subtitle_handler = SubtitleHandler(self.settings, self.whisper_mgr, self.subtitle_providers)
90-
self.renaming_handler = RenamingHandler(self.settings, self.renamer)
91-
self.conversion_handler = ConversionHandler(self.settings, self.ffmpeg_mgr)
88+
try:
89+
logger.info("Creating FileHandler...")
90+
self.file_handler = FileHandler(self.settings, self.ffmpeg_mgr)
91+
logger.info("FileHandler created successfully")
92+
93+
logger.info("Creating SubtitleHandler...")
94+
logger.info("About to call SubtitleHandler constructor...")
95+
# Pass None instead of self to avoid triggering lazy whisper_mgr loading during handler init
96+
# SubtitleHandler will get whisper_mgr from the parent later when actually needed
97+
self.subtitle_handler = SubtitleHandler(self.settings, None, self.subtitle_providers)
98+
logger.info("SubtitleHandler constructor returned")
99+
logger.info("SubtitleHandler created successfully")
100+
101+
logger.info("Creating RenamingHandler...")
102+
self.renaming_handler = RenamingHandler(self.settings, self.renamer)
103+
logger.info("RenamingHandler created successfully")
104+
105+
logger.info("Creating ConversionHandler...")
106+
self.conversion_handler = ConversionHandler(self.settings, self.ffmpeg_mgr)
107+
logger.info("ConversionHandler created successfully")
108+
109+
logger.info("All handlers initialized successfully")
110+
except Exception as e:
111+
logger.error(f"Error initializing handlers: {e}", exc_info=True)
112+
raise
92113

93114
# ======================
94115
# FFmpeg & Whisper Setup
@@ -274,8 +295,12 @@ def convert_file(self, input_path: str, output_path: Optional[str] = None,
274295

275296
def convert_files(self, file_paths: List[str], progress_callback: Optional[Callable] = None) -> Dict:
276297
"""Convert multiple files"""
298+
logger.info(f"EncodeForgeCore.convert_files called with {len(file_paths)} files")
277299
self._ensure_handlers_initialized()
278-
return self.conversion_handler.convert_files(file_paths, progress_callback)
300+
logger.info("Handlers initialized, calling conversion_handler.convert_files")
301+
result = self.conversion_handler.convert_files(file_paths, progress_callback)
302+
logger.info(f"conversion_handler.convert_files returned: {result}")
303+
return result
279304

280305
def cancel_current(self) -> Dict:
281306
"""Cancel current conversion"""

0 commit comments

Comments
 (0)