diff --git a/video_transcription/ai_transcriber_v2/__pycache__/extractor.cpython-313.pyc b/video_transcription/ai_transcriber_v2/__pycache__/extractor.cpython-313.pyc index 4899a45..ff2d352 100644 Binary files a/video_transcription/ai_transcriber_v2/__pycache__/extractor.cpython-313.pyc and b/video_transcription/ai_transcriber_v2/__pycache__/extractor.cpython-313.pyc differ diff --git a/video_transcription/ai_transcriber_v2/diagnostic_check.py b/video_transcription/ai_transcriber_v2/diagnostic_check.py new file mode 100755 index 0000000..737dc0b --- /dev/null +++ b/video_transcription/ai_transcriber_v2/diagnostic_check.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +import sys +import shutil +import subprocess +import torch +import os + +def check_flatpak(): + return os.path.exists("/.flatpak-info") + +def run_cmd(cmd_list): + in_flatpak = check_flatpak() + if in_flatpak: + full_cmd = ["flatpak-spawn", "--host"] + cmd_list + else: + full_cmd = cmd_list + + try: + result = subprocess.run(full_cmd, capture_output=True, text=True) + return result.returncode == 0, result.stdout.strip(), result.stderr.strip() + except FileNotFoundError: + return False, "", "Command not found" + except Exception as e: + return False, "", str(e) + +def main(): + print("========================================") + print(" AI Transcriber Diagnostic Tool") + print("========================================") + + in_flatpak = check_flatpak() + print(f"Environment: {'Flatpak Sandbox' if in_flatpak else 'Native Host'}") + print("-" * 40) + + # 1. GPU Check (PyTorch) + print("\n[1] GPU Availability (Internal PyTorch)") + if torch.cuda.is_available(): + print(f"✅ GPU Detected: {torch.cuda.get_device_name(0)}") + print(f" VRAM: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB") + print(f" CUDA Version: {torch.version.cuda}") + else: + print("❌ GPU NOT Detected by PyTorch.") + print(" Whisper will run on CPU (Slow).") + + # 2. Nvidia Driver Check (System) + print("\n[2] Nvidia Driver Check (System)") + ok, out, err = run_cmd(["nvidia-smi", "--query-gpu=name,driver_version", "--format=csv,noheader"]) + if ok: + print(f"✅ Driver Active: {out}") + else: + print("❌ 'nvidia-smi' failed. Drivers might be missing or inaccessible.") + if err: print(f" Error: {err}") + + # 3. FFmpeg Check + print("\n[3] FFmpeg Check") + ok, out, err = run_cmd(["ffmpeg", "-version"]) + if ok: + version_line = out.split('\n')[0] + print(f"✅ FFmpeg Ready: {version_line}") + else: + print("❌ FFmpeg NOT found.") + print(" On Bazzite, install it with: 'brew install ffmpeg'") + + # 4. FFprobe Check + print("\n[4] FFprobe Check (Required for duration checks)") + ok, out, err = run_cmd(["ffprobe", "-version"]) + if ok: + version_line = out.split('\n')[0] + print(f"✅ FFprobe Ready: {version_line}") + else: + print("❌ FFprobe NOT found.") + + # 5. Ollama Check + print("\n[5] Local LLM (Ollama)") + ok, out, err = run_cmd(["ollama", "--version"]) + if ok: + print(f"✅ Ollama Installed: {out}") + + # Check server + import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(1) + result = sock.connect_ex(('127.0.0.1', 11434)) + if result == 0: + print("✅ Ollama Server is RUNNING.") + else: + print("⚠️ Ollama Server is NOT running (Scripts will try to auto-start it).") + else: + print("❌ Ollama NOT found.") + print(" Run: ./ai_transcriber_v2/install_local_llm.sh") + + print("\n========================================") + +if __name__ == "__main__": + main() diff --git a/video_transcription/ai_transcriber_v2/extractor.py b/video_transcription/ai_transcriber_v2/extractor.py index d39f7e2..202541c 100644 --- a/video_transcription/ai_transcriber_v2/extractor.py +++ b/video_transcription/ai_transcriber_v2/extractor.py @@ -1,6 +1,7 @@ import os import subprocess import sys +import json from utils import verify_file_not_empty def run_ffmpeg(args): @@ -20,6 +21,27 @@ def run_ffmpeg(args): print("Error: 'ffmpeg' command not found. Please ensure it is installed on your host system.") return False +def get_video_duration(file_path): + """ + Gets video duration in seconds using ffprobe. + """ + in_flatpak = os.path.exists("/.flatpak-info") + cmd_base = ["flatpak-spawn", "--host"] if in_flatpak else [] + + cmd = cmd_base + [ + "ffprobe", + "-v", "error", + "-show_entries", "format=duration", + "-of", "default=noprint_wrappers=1:nokey=1", + file_path + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + return float(result.stdout.strip()) + except Exception: + return 0.0 + def extract_audio(video_path, output_path=None): """ Extracts audio from a video file using ffmpeg. @@ -58,10 +80,19 @@ def extract_audio(video_path, output_path=None): def embed_subtitles(video_path, srt_path, output_path=None): """ Embeds subtitles into the video file (Soft Subs) and sets them as primary. + Includes strict safety checks (Size & Duration) to prevent replacing videos with corrupted files. """ if not os.path.exists(video_path) or not os.path.exists(srt_path): print("Error: Video or SRT file not found for embedding.") - return + return False + + # Capture original stats + original_size = os.path.getsize(video_path) + original_duration = get_video_duration(video_path) + + if original_size == 0: + print("Error: Source video is 0 bytes.") + return False if output_path is None: base, ext = os.path.splitext(video_path) @@ -91,8 +122,39 @@ def embed_subtitles(video_path, srt_path, output_path=None): ] if run_ffmpeg(args): - if not verify_file_not_empty(output_path): - raise Exception("FFmpeg succeeded but output video is empty.") + # --- Safety Checks --- + if not os.path.exists(output_path): + print("Error: Output file was not created.") + return False + + new_size = os.path.getsize(output_path) + new_duration = get_video_duration(output_path) + + # 1. Zero Byte Check + if new_size == 0: + print("❌ CRITICAL: Output file is 0 bytes. Deleting corrupted output.") + os.remove(output_path) + raise Exception("Embedding failed: Output is empty.") + + # 2. Significant Size Drop Check + if new_size < (original_size * 0.8): + print(f"❌ CRITICAL: Output file is significantly smaller than source!") + print(f" Original: {original_size/1024/1024:.2f} MB") + print(f" New: {new_size/1024/1024:.2f} MB") + os.remove(output_path) + raise Exception("Embedding failed: Suspicious file size reduction.") + + # 3. Duration Mismatch Check (New) + if abs(original_duration - new_duration) > 1.0: + print(f"❌ CRITICAL: Duration mismatch detected!") + print(f" Original: {original_duration:.2f}s") + print(f" New: {new_duration:.2f}s") + print(" This indicates truncated video stream. Aborting.") + os.remove(output_path) + raise Exception("Embedding failed: Duration mismatch > 1s.") + print(f"Subtitles embedded successfully: {output_path}") + return True else: print("Error: Embedding failed.") + return False diff --git a/video_transcription/ai_transcriber_v2/main.py b/video_transcription/ai_transcriber_v2/main.py index 64b65bc..3555b8f 100644 --- a/video_transcription/ai_transcriber_v2/main.py +++ b/video_transcription/ai_transcriber_v2/main.py @@ -195,23 +195,20 @@ def process_file(file_path, args, source_lang=None, loaded_model=None, service_s should_embed = False if should_embed: - embed_subtitles(file_path, final_srt_path) + success_embed = embed_subtitles(file_path, final_srt_path) - if args.delete_source: - if args.embed: - base, ext = os.path.splitext(file_path) - expected_output = f"{base}.subbed{ext}" - - if os.path.exists(expected_output): - try: - os.remove(file_path) - tracker.logger.info(f"SOURCE DELETED: {file_path}") - except OSError as e: - tracker.logger.error(f"Error deleting source: {e}") - else: - tracker.logger.error(f"SAFETY ABORT: Output '{expected_output}' not found.") + if success_embed and args.delete_source: + base, ext = os.path.splitext(file_path) + expected_output = f"{base}.subbed{ext}" + + if os.path.exists(expected_output): + try: + os.remove(file_path) + tracker.logger.info(f"SOURCE DELETED: {file_path}") + except OSError as e: + tracker.logger.error(f"Error deleting source: {e}") else: - tracker.logger.warning("SAFETY ABORT: Enable --embed to delete source.") + tracker.logger.error(f"SAFETY ABORT: Output '{expected_output}' not found.") tracker.update_step(file_path, "step_embed", "done") # 5. Cleanup