From 8181bebaa0c170a641ba7755e0889454c05b3b52 Mon Sep 17 00:00:00 2001 From: David Kifer Date: Mon, 12 Jan 2026 08:52:38 -0500 Subject: [PATCH] Added revisions to the translation app --- .../__pycache__/__init__.cpython-313.pyc | Bin 197 -> 0 bytes .../__pycache__/diarizer.cpython-313.pyc | Bin 3179 -> 0 bytes .../__pycache__/extractor.cpython-313.pyc | Bin 3629 -> 0 bytes .../__pycache__/transcriber.cpython-313.pyc | Bin 8168 -> 0 bytes .../__pycache__/translator.cpython-313.pyc | Bin 5013 -> 0 bytes .../__pycache__/utils.cpython-313.pyc | Bin 1152 -> 0 bytes .../README.md | 0 .../__init__.py | 0 .../diagnostic_scan.py | 0 .../diarizer.py | 0 .../{ => ai_transcriber_v1}/extract_audio.sh | 0 .../extractor.py | 0 .../main.py | 0 .../ai_transcriber_v1/mount_truenas.sh | 69 +++++ .../recover_and_fix.py | 9 +- .../requirements.txt | 0 .../{ => ai_transcriber_v1}/run_wizard.py | 6 +- .../tracker.py | 0 .../transcriber.py | 0 .../translator.py | 0 .../utils.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 200 -> 0 bytes .../__pycache__/diarizer.cpython-313.pyc | Bin 3179 -> 0 bytes .../__pycache__/extractor.cpython-313.pyc | Bin 3629 -> 4529 bytes .../__pycache__/main.cpython-313.pyc | Bin 0 -> 17926 bytes .../recover_and_fix_v2.cpython-313.pyc | Bin 0 -> 11252 bytes .../__pycache__/run_v2.cpython-313.pyc | Bin 0 -> 8192 bytes .../__pycache__/run_wizard_v2.cpython-313.pyc | Bin 0 -> 8199 bytes .../__pycache__/transcriber.cpython-313.pyc | Bin 8168 -> 0 bytes .../__pycache__/translator.cpython-313.pyc | Bin 6617 -> 11777 bytes .../__pycache__/utils.cpython-313.pyc | Bin 1155 -> 12405 bytes .../ai_transcriber_v2/extractor.py | 18 +- .../ai_transcriber_v2/install_local_llm.sh | 57 ++++ video_transcription/ai_transcriber_v2/main.py | 166 +++++++---- .../{ => ai_transcriber_v2}/mount_truenas.sh | 12 +- .../ai_transcriber_v2/recover_and_fix_v2.py | 254 +++++++++++++++++ .../ai_transcriber_v2/requirements.txt | 4 + .../{ => ai_transcriber_v2}/run_v2.bat | 0 .../{ => ai_transcriber_v2}/run_v2.py | 11 +- .../{ => ai_transcriber_v2}/run_wizard_v2.py | 11 +- .../ai_transcriber_v2/transcriber.py | 57 ++-- .../ai_transcriber_v2/translator.py | 175 ++++++++++-- .../ai_transcriber_v2/utils.py | 265 ++++++++++++++++++ video_transcription/recover_and_fix_v2.py | 254 ----------------- 44 files changed, 993 insertions(+), 375 deletions(-) delete mode 100644 video_transcription/ai_transcriber/__pycache__/__init__.cpython-313.pyc delete mode 100644 video_transcription/ai_transcriber/__pycache__/diarizer.cpython-313.pyc delete mode 100644 video_transcription/ai_transcriber/__pycache__/extractor.cpython-313.pyc delete mode 100644 video_transcription/ai_transcriber/__pycache__/transcriber.cpython-313.pyc delete mode 100644 video_transcription/ai_transcriber/__pycache__/translator.cpython-313.pyc delete mode 100644 video_transcription/ai_transcriber/__pycache__/utils.cpython-313.pyc rename video_transcription/{ai_transcriber => ai_transcriber_v1}/README.md (100%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/__init__.py (100%) rename video_transcription/{ => ai_transcriber_v1}/diagnostic_scan.py (100%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/diarizer.py (100%) rename video_transcription/{ => ai_transcriber_v1}/extract_audio.sh (100%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/extractor.py (100%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/main.py (100%) create mode 100755 video_transcription/ai_transcriber_v1/mount_truenas.sh rename video_transcription/{ => ai_transcriber_v1}/recover_and_fix.py (94%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/requirements.txt (100%) rename video_transcription/{ => ai_transcriber_v1}/run_wizard.py (97%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/tracker.py (100%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/transcriber.py (100%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/translator.py (100%) rename video_transcription/{ai_transcriber => ai_transcriber_v1}/utils.py (100%) delete mode 100644 video_transcription/ai_transcriber_v2/__pycache__/__init__.cpython-313.pyc delete mode 100644 video_transcription/ai_transcriber_v2/__pycache__/diarizer.cpython-313.pyc create mode 100644 video_transcription/ai_transcriber_v2/__pycache__/main.cpython-313.pyc create mode 100644 video_transcription/ai_transcriber_v2/__pycache__/recover_and_fix_v2.cpython-313.pyc create mode 100644 video_transcription/ai_transcriber_v2/__pycache__/run_v2.cpython-313.pyc create mode 100644 video_transcription/ai_transcriber_v2/__pycache__/run_wizard_v2.cpython-313.pyc delete mode 100644 video_transcription/ai_transcriber_v2/__pycache__/transcriber.cpython-313.pyc create mode 100755 video_transcription/ai_transcriber_v2/install_local_llm.sh rename video_transcription/{ => ai_transcriber_v2}/mount_truenas.sh (76%) create mode 100755 video_transcription/ai_transcriber_v2/recover_and_fix_v2.py rename video_transcription/{ => ai_transcriber_v2}/run_v2.bat (100%) rename video_transcription/{ => ai_transcriber_v2}/run_v2.py (94%) rename video_transcription/{ => ai_transcriber_v2}/run_wizard_v2.py (94%) delete mode 100755 video_transcription/recover_and_fix_v2.py diff --git a/video_transcription/ai_transcriber/__pycache__/__init__.cpython-313.pyc b/video_transcription/ai_transcriber/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index a5a3b713a103c8d2c32b3ef814bd2450e29a33a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 197 zcmX|*K?(vf3`JW41<=P9lD0Lb%Fw>N@Q}7@j!Yj?z6Ubb^7Sz8;Uh+RA zbK7oI(fe$v_E#-G=`YO70uOanTYY*eS(It6*5n{tfg2A9&Sr9O<|J< zWN&-pFk{c5noyF+CW-a26AFpggQfrCf&#{nr)117+^6+^U^n)7kX1QapNbXUjn-P4 FLLb27H(LMz diff --git a/video_transcription/ai_transcriber/__pycache__/diarizer.cpython-313.pyc b/video_transcription/ai_transcriber/__pycache__/diarizer.cpython-313.pyc deleted file mode 100644 index e6bb14df85a549fe718cc157f59c9939d4e5d95c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3179 zcmZ`*&2JmW6`v({NosdVDiS3*lx&VwB@!)(l;YZri8h67DUl;f6}V=jRt<=4A}I(E^hBR*6*a0{8r_=y3q1vJ(6a=oFtc?51IeW~88VEbx%AC)m!vIa0CHyD zypP{|Z|1!{4TnVpBk-xJZ-x>2j%~c*a~G#0uy}+Nq;Ly}HfN4<3a|L$==U7;DgHR3 z{xvQs{D+Aw75uIwn}YCT`_n6+?-9BLS`^;``j9jH80CCpj0c7$BecqRouLTVxaJC@ zHNJx$XwxsDoIl&;3Uo|LFy~W3H+%*UmiPt15OP8mw7)*G(bp*mU2{nuDitG?rxB__ zG%NQFF&E4ZoaM}gvKP*!#?PUON;K7hWpNrI2|-qZBj_?(fH9eL=GNq^J10fZRbSit1X6fInvvG{=3L}FP02UZakv8FGNDD2mMv^m zh`K>2)^wHXJ8D%o4P3KyV+~ifRl_i=B&F6g-Asw%3|+G_B8K5m@)fnZj>oJjO=j>i zTUX8I2`=g-l5#kWS~>5Uu<)2}R41@mVMf#vNG;XM_rSJU^k7(6FX9cu{KROAuNU)G zbAuS3nfcn<8en^0Ef73Y1Cw{zX_HsXl4^|_F2s^IbWfqN>xaT#=z3TIt?%ZS-0})rm6#w60v<%(++54Q!fx()T@MoNMXaW11coN z&?qZ*l+|myYi_GRoa>pCZLxjJDXXA%$h(bARH2G{A!s){l{#FpgOpgcQq{6~b|q~t zPbJ2~5sOt!_Jq7)Yw1murq|6fNo(q+uBB(qLe06X=}oI(Y9w7D)M5`N4?d8RSz(81 z;3Q@qJTa^S)g8=fRc{gCS*4qR2u)SC?XbJaJ5EsyD456AAQXeo(a8l~i2XY>{!&D8 z>}e=`5K)dpcN@{(@A<9+dFhy6KJg6-v1h%5PkP4>d&e7cs?ifa5m0R61o>lPJ#;Fg zuD*uU{UCEc^F$gxl!l*3BZtyRLykRIxWBL`d?|k+@BjMf!o;yW`F$w-P~6MxP3#vA zB3F(>K>>^{`+EvL-N2W@(E3+ZXbs} z{5INqA^_os7qf8K*E;OJ^40WLKRJk`jzj6E(cT{%rvF(<2+|({xBAduZ%*EtLXZ3U zZcX|hPjKviN}TQSKh8vE!~So=0;~&OYX_lrgKog)5z291^V5JUheqHBjpw&e%6-sS zIRUr~KdgMtidX!G0L43yW9{jea2Cu3U9J$Unp|D0kiO9||kCTU4pd ztMDs-pM>4bQim#5JPOYn*3-_qO17%78J*WhKWi3ur$co-+p`v4f$ll=ik0b> zm8sMXSi5D88B&(#W)|kM`KhVV&psBGUV3yd-_LzpQS-pKc_>c5hp0%P~84p1V*x8HXK&iFHD@XEj^CM=T&UQ)Ww`JMX5* z7PO`WOic;RkqEozE>-fjOk20Nfzt1MXISuMjJ!JN%rpTM`*NAxrhF$-*0x)9b?Ljl zWPPsDKe+e7u2KJ>5qop*t=&8IxfcO+5$^|&hNkO_PvaN&$WeT}zVI|Lyg$9aarpMk zQR3!-JoGG@_-AyoaVhmsY9vPXe{+~f|51KEfcmeT#L>`LT|Ww4cs>poPEtrt9PpP; z0U=8ImX?yd?K3UgZ*5yn{dJu98=_a?yyfB*mh diff --git a/video_transcription/ai_transcriber/__pycache__/extractor.cpython-313.pyc b/video_transcription/ai_transcriber/__pycache__/extractor.cpython-313.pyc deleted file mode 100644 index cdcfd4bf42ccc90936ddfb3fedc59ad53c2468b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3629 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 K|1=#Y*#8^Rb(fU@ diff --git a/video_transcription/ai_transcriber/__pycache__/translator.cpython-313.pyc b/video_transcription/ai_transcriber/__pycache__/translator.cpython-313.pyc deleted file mode 100644 index 5fa0c24e7f629812452ebbe76d7ac6430adb3ac8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5013 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 diff --git a/video_transcription/ai_transcriber/__pycache__/utils.cpython-313.pyc b/video_transcription/ai_transcriber/__pycache__/utils.cpython-313.pyc deleted file mode 100644 index c82c19324aa83bb00cb37d0fa3b7492d66e4b8f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmZ8g&2JM&6o30+XT8C8T9SeQ!L$*Qw;$e$6d_q9Qcyt%q=4)MkaBP*yJKgi_3mnB z?1Gg;k4-P(f|MgyZk#x9gTEjZkw8msaN!ooJyh+bZ`NKCc+%|qocDh3x4WZq*#?w8 zqrtBRz&qvSqEC#`DKs8I3tHMu;A5?Et)-s=u6H%Z_@D}xJ#A92Q~?k6gV7m`K7utB z%cPli%>$^Ls`@iog^QDZwMoWDzpJfH<%KPM6)Mm&7L2@-z*$(c@_aMfEceFx8#wG6 zX{%bZ{Apf!hZO{lOfwIsbPHd@S|z_Z1@t69q#vm*uId$7&<}5%gVt1}FCklF-O)O5 z**FIs&9R2h?K?CKd@7kBG$O6$3h4wPL)#}*O4duHATlPLB{bk9kfbkys7sOuqQ5RK z)NOl-cSXY{D1ys&Lgfaj3CW!XxvgxxD>vB3#`UbyZZg^D(d6EG9ET0E!uyN_9WqdO z-0?i-e^0K_P-F>@2Qu(zNEqiauiMv#GZX9z5F&4op;52bhl^^9tPPbx9Fc?y!F*E7 z7US#(K?msm79ct==A3zwbYkAab+M6CUm23Slt*YcGT28tbtrP~j8 z+5JFBkromjL^7S4aFd#0O#ReUXA`yNXa9LGqloGDBqi>bzh`d*R3l)3}T-Ho`% zT%Qh*h+kr_-(!&!?m&34&)kG@5l1v^`)t6%I8jL#D_Pu@oJIm^%I3H<_!wAc-0jOC z6!qjmdSbE=YvW69kBM$0B0hr{Ogv%}AHT!jvv91keQE2`_Ttv!PVM#d{9n`aZ>qEJ z&C>3%GduToZansO?Qi$Y?+$dkF!#Rt)%NC}n>*KERnPxn9u%Qe-Y#vG9@h4()B9F= zx3auv-Fj0!{?GpO>s>KY&6Rp8x;= diff --git a/video_transcription/ai_transcriber/README.md b/video_transcription/ai_transcriber_v1/README.md similarity index 100% rename from video_transcription/ai_transcriber/README.md rename to video_transcription/ai_transcriber_v1/README.md diff --git a/video_transcription/ai_transcriber/__init__.py b/video_transcription/ai_transcriber_v1/__init__.py similarity index 100% rename from video_transcription/ai_transcriber/__init__.py rename to video_transcription/ai_transcriber_v1/__init__.py diff --git a/video_transcription/diagnostic_scan.py b/video_transcription/ai_transcriber_v1/diagnostic_scan.py similarity index 100% rename from video_transcription/diagnostic_scan.py rename to video_transcription/ai_transcriber_v1/diagnostic_scan.py diff --git a/video_transcription/ai_transcriber/diarizer.py b/video_transcription/ai_transcriber_v1/diarizer.py similarity index 100% rename from video_transcription/ai_transcriber/diarizer.py rename to video_transcription/ai_transcriber_v1/diarizer.py diff --git a/video_transcription/extract_audio.sh b/video_transcription/ai_transcriber_v1/extract_audio.sh similarity index 100% rename from video_transcription/extract_audio.sh rename to video_transcription/ai_transcriber_v1/extract_audio.sh diff --git a/video_transcription/ai_transcriber/extractor.py b/video_transcription/ai_transcriber_v1/extractor.py similarity index 100% rename from video_transcription/ai_transcriber/extractor.py rename to video_transcription/ai_transcriber_v1/extractor.py diff --git a/video_transcription/ai_transcriber/main.py b/video_transcription/ai_transcriber_v1/main.py similarity index 100% rename from video_transcription/ai_transcriber/main.py rename to video_transcription/ai_transcriber_v1/main.py diff --git a/video_transcription/ai_transcriber_v1/mount_truenas.sh b/video_transcription/ai_transcriber_v1/mount_truenas.sh new file mode 100755 index 0000000..e4b5ebf --- /dev/null +++ b/video_transcription/ai_transcriber_v1/mount_truenas.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Configuration +MOUNT_POINT="/mnt/truenas_isolation" +SHARE="//truenas.local/isolation" + +echo "--- SMB Mount Tool (V1) ---" + +# Determine privilege escalation method +PRIV_CMD="" +if [ "$EUID" -eq 0 ]; then + echo "Running as root." +else + if command -v sudo &> /dev/null; then + PRIV_CMD="sudo" + elif command -v flatpak-spawn &> /dev/null; then + echo "Detected Flatpak environment. Attempting to use host permissions via sudo..." + # We need to run sudo ON THE HOST. + # flatpak-spawn --host runs as the current user on the host. + # So we run 'sudo' inside that host shell. + PRIV_CMD="flatpak-spawn --host sudo" + # Note: This requires the flatpak to have permission to talk to the host + else + echo "❌ Error: This script requires root privileges to mount drives." + echo " 'sudo' was not found." + echo " Please run this script as root: su -c ./mount_truenas.sh" + exit 1 + fi +fi + +# 1. Create mount point if it doesn't exist +if [ ! -d "$MOUNT_POINT" ]; then + echo "Creating directory $MOUNT_POINT..." + # We try to create it. If it fails (e.g. inside read-only flatpak mount namespace), warn user. + $PRIV_CMD mkdir -p "$MOUNT_POINT" + if [ $? -ne 0 ]; then + echo "Error creating directory. If you are in a Flatpak, you might not have access to host /mnt." + exit 1 + fi +fi + +# 2. Get Credentials +read -p "Enter SMB Username [guest]: " SMB_USER +SMB_USER=${SMB_USER:-guest} + +# 3. Mount +echo "Mounting $SHARE to $MOUNT_POINT..." + +# IMPORTANT: We force the mount to be owned by the current user (UID 1000 usually) +# This fixes "Permission Denied" errors when writing to the share. +# We also set file_mode/dir_mode to 0777 as a fallback to ensure full access. +MOUNT_OPTS="vers=3.0,uid=$(id -u),gid=$(id -g),file_mode=0777,dir_mode=0777,noperm" + +if [ "$SMB_USER" == "guest" ]; then + $PRIV_CMD mount -t cifs "$SHARE" "$MOUNT_POINT" -o "guest,$MOUNT_OPTS" +else + # This will prompt for the SMB password + $PRIV_CMD mount -t cifs "$SHARE" "$MOUNT_POINT" -o "username=$SMB_USER,$MOUNT_OPTS" +fi + +# 4. Check result +if [ $? -eq 0 ]; then + echo "✅ Success! Share is now available at $MOUNT_POINT" + echo "Files are now owned by $(id -un):$(id -gn) with full write access." + echo "The mapping will disappear automatically after you reboot." +else + echo "❌ Error: Failed to mount the share." + echo "Ensure 'cifs-utils' is installed and the server is reachable." +fi \ No newline at end of file diff --git a/video_transcription/recover_and_fix.py b/video_transcription/ai_transcriber_v1/recover_and_fix.py similarity index 94% rename from video_transcription/recover_and_fix.py rename to video_transcription/ai_transcriber_v1/recover_and_fix.py index 1214a8b..d05fb45 100755 --- a/video_transcription/recover_and_fix.py +++ b/video_transcription/ai_transcriber_v1/recover_and_fix.py @@ -7,17 +7,14 @@ from dotenv import load_dotenv # Load config script_dir = os.path.dirname(os.path.abspath(__file__)) -env_path = os.path.abspath(os.path.join(script_dir, '../.env_files/.env.aitranscribe')) +env_path = os.path.abspath(os.path.join(script_dir, '../../.env_files/.env.aitranscribe')) if os.path.exists(env_path): load_dotenv(env_path) else: load_dotenv() -# Add ai_transcriber to path so we can import modules -sys.path.append(os.path.join(script_dir, 'ai_transcriber')) - -from ai_transcriber.translator import translate_srt -from ai_transcriber.utils import validate_and_repair_srt +from translator import translate_srt +from utils import validate_and_repair_srt import pysubs2 from deep_translator import GoogleTranslator from datetime import datetime diff --git a/video_transcription/ai_transcriber/requirements.txt b/video_transcription/ai_transcriber_v1/requirements.txt similarity index 100% rename from video_transcription/ai_transcriber/requirements.txt rename to video_transcription/ai_transcriber_v1/requirements.txt diff --git a/video_transcription/run_wizard.py b/video_transcription/ai_transcriber_v1/run_wizard.py similarity index 97% rename from video_transcription/run_wizard.py rename to video_transcription/ai_transcriber_v1/run_wizard.py index b2752c2..7630472 100755 --- a/video_transcription/run_wizard.py +++ b/video_transcription/ai_transcriber_v1/run_wizard.py @@ -35,7 +35,7 @@ def main(): try: from dotenv import load_dotenv script_dir = os.path.dirname(os.path.abspath(__file__)) - env_path = os.path.abspath(os.path.join(script_dir, '../.env_files/.env.aitranscribe')) + env_path = os.path.abspath(os.path.join(script_dir, '../../.env_files/.env.aitranscribe')) if os.path.exists(env_path): load_dotenv(env_path) except ImportError: @@ -107,9 +107,9 @@ def main(): print("Using HF_TOKEN from environment.") # 5. Build Command - # script is in ai_transcriber/main.py relative to this script + # script is in main.py relative to this script script_dir = os.path.dirname(os.path.abspath(__file__)) - main_script = os.path.join(script_dir, "ai_transcriber", "main.py") + main_script = os.path.join(script_dir, "main.py") cmd = [sys.executable, main_script] # Add all inputs diff --git a/video_transcription/ai_transcriber/tracker.py b/video_transcription/ai_transcriber_v1/tracker.py similarity index 100% rename from video_transcription/ai_transcriber/tracker.py rename to video_transcription/ai_transcriber_v1/tracker.py diff --git a/video_transcription/ai_transcriber/transcriber.py b/video_transcription/ai_transcriber_v1/transcriber.py similarity index 100% rename from video_transcription/ai_transcriber/transcriber.py rename to video_transcription/ai_transcriber_v1/transcriber.py diff --git a/video_transcription/ai_transcriber/translator.py b/video_transcription/ai_transcriber_v1/translator.py similarity index 100% rename from video_transcription/ai_transcriber/translator.py rename to video_transcription/ai_transcriber_v1/translator.py diff --git a/video_transcription/ai_transcriber/utils.py b/video_transcription/ai_transcriber_v1/utils.py similarity index 100% rename from video_transcription/ai_transcriber/utils.py rename to video_transcription/ai_transcriber_v1/utils.py diff --git a/video_transcription/ai_transcriber_v2/__pycache__/__init__.cpython-313.pyc b/video_transcription/ai_transcriber_v2/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index c7756f3ef513c917be0a425fbccf86748df2ddf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 200 zcmX|*K?=e!5JeNKAVLq~#sSyj%C%PrX*1M7n+cON;6d~b-o|yeo=4A}I(E^hBR*6*a0{8r_=y3q1vJ(6a=oFtc?51IeW~88VEbx%AC)m!vIa0CHyD zypP{|Z|1!{4TnVpBk-xJZ-x>2j%~c*a~G#0uy}+Nq;Ly}HfN4<3a|L$==U7;DgHR3 z{xvQs{D+Aw75uIwn}YCT`_n6+?-9BLS`^;``j9jH80CCpj0c7$BecqRouLTVxaJC@ zHNJx$XwxsDoIl&;3Uo|LFy~W3H+%*UmiPt15OP8mw7)*G(bp*mU2{nuDitG?rxB__ zG%NQFF&E4ZoaM}gvKP*!#?PUON;K7hWpNrI2|-qZBj_?(fH9eL=GNq^J10fZRbSit1X6fInvvG{=3L}FP02UZakv8FGNDD2mMv^m zh`K>2)^wHXJ8D%o4P3KyV+~ifRl_i=B&F6g-Asw%3|+G_B8K5m@)fnZj>oJjO=j>i zTUX8I2`=g-l5#kWS~>5Uu<)2}R41@mVMf#vNG;XM_rSJU^k7(6FX9cu{KROAuNU)G zbAuS3nfcn<8en^0Ef73Y1Cw{zX_HsXl4^|_F2s^IbWfqN>xaT#=z3TIt?%ZS-0})rm6#w60v<%(++54Q!fx()T@MoNMXaW11coN z&?qZ*l+|myYi_GRoa>pCZLxjJDXXA%$h(bARH2G{A!s){l{#FpgOpgcQq{6~b|q~t zPbJ2~5sOt!_Jq7)Yw1murq|6fNo(q+uBB(qLe06X=}oI(Y9w7D)M5`N4?d8RSz(81 z;3Q@qJTa^S)g8=fRc{gCS*4qR2u)SC?XbJaJ5EsyD456AAQXeo(a8l~i2XY>{!&D8 z>}e=`5K)dpcN@{(@A<9+dFhy6KJg6-v1h%5PkP4>d&e7cs?ifa5m0R61o>lPJ#;Fg zuD*uU{UCEc^F$gxl!l*3BZtyRLykRIxWBL`d?|k+@BjMf!o;yW`F$w-P~6MxP3#vA zB3F(>K>>^{`+EvL-N2W@(E3+ZXbs} z{5INqA^_os7qf8K*E;OJ^40WLKRJk`jzj6E(cT{%rvF(<2+|({xBAduZ%*EtLXZ3U zZcX|hPjKviN}TQSKh8vE!~So=0;~&OYX_lrgKog)5z291^V5JUheqHBjpw&e%6-sS zIRUr~KdgMtidX!G0L43yW9{jea2Cu3U9J$Unp|D0kiO9||kCTU4pd ztMDs-pM>4bQim#5JPOYn*3-_qO17%78J*WhKWi3ur$co-+p`v4f$ll=ik0b> zm8sMXSi5D88B&(#W)|kM`KhVV&psBGUV3yd-_LzpQS-pKc_>c5hp0%P~84p1V*x8HXK&iFHD@XEj^CM=T&UQ)Ww`JMX5* z7PO`WOic;RkqEozE>-fjOk20Nfzt1MXISuMjJ!JN%rpTM`*NAxrhF$-*0x)9b?Ljl zWPPsDKe+e7u2KJ>5qop*t=&8IxfcO+5$^|&hNkO_PvaN&$WeT}zVI|Lyg$9aarpMk zQR3!-JoGG@_-AyoaVhmsY9vPXe{+~f|51KEfcmeT#L>`LT|Ww4cs>poPEtrt9PpP; z0U=8ImX?yd?K3UgZ*5yn{dJu98=_a?yyfB*mh 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 cdcfd4bf42ccc90936ddfb3fedc59ad53c2468b2..958760b4305f612dfc28f644383fa0217189fdd9 100644 GIT binary patch delta 1842 zcmbVNO>Y}T7@qNZ?Oofu8-K=j*73&qXx-AZp^Ye|Bovw;D5|2m7D7(zy7tyx>MzZ% z)7F=i8&ses8VU8-D+iFE|DZ_@9FS6qROAMULqUSmN&zA9A@PpaF0I4?G1Bus&pz|c zcz5QV@u%wNdwC*5x=pzP@Ml9h7sO;@)%w*y$|pMptxVT)JMoWYqLxqiGfOdds?AcCr3`lYlwE?q~EB?;Gp)+SpGo;53%# zjb6NiU#33`8vP50>17<6tN0Nrga@N@(OK+h9T(VCy_SpRv5XG827MaJ;|Vl|3No#^ z)I~2UO(WEBKQ&-D?|ybbAvT!oOnjjJ;S9>f>4z?LsSp~N|1xRfe@!}xlXt&x{eb6Q zUR!CU?h{D0UyB>Zkp zz%SlY=U-FNh8kIja2tmvPA0g`gamqXYGOg*wiF(8nSIQl@gRC-By)F9cw2b&Aj*kL zIOhZS0f8l)M}5Y?5=(&r)IARq(?Lp~220Ym{Yz;K`AzzIMSFal5kDZnTo4dYx( zxIupurmkqYu@_z1Q{|d}Qj&Z7FWxKX=-?}InL)`yIeVYD<9U~iaubKSL9d3YE>_Ni zj59HMLY%~tv?^v?g$O+*j`SY(mH|DM*%j-hD|)e`L*2nqMI^uiJDO(+d*9C7e<|w~qoOyRx>n%Ec&4yF@}GS> zK!R*qcKQgr3vN_=Ig`~|hR?|JU zJxG*nFF2r-grfxJiOds_GVCZH5*6FeJRb_i92H=m#Jm9WdP-)V!aSLIpjJKh5SuW_ z%ps&i9Tj4UVdjNUFzKiW5=ZPP^CBoU<)|1EllCz4Vo2TRs5tWy%!?!SB}XNZsM#aT zOQOuAqf{iO>~e~kDoXFOduo)uOqzKbQ)7-&VvfonF=CH{cjI+?ugK3ldw=Xab$^7_ z?bNvk;q%+l`Ny;^&2n&m&~-^$WtYRo(fN}{(dN;}qMO?a$O~z1D=jhooV+m4ZO!wb zv-ot@>l8_E|3_e4mv37}&2-9u-ED;J<|k+uA|e?GUEz4WS!vhwIWh~6?61XCA=n(o e_z@Bx#ZmAP@_mQ){Ng@>Bfr-0F+4+#K*rxNrJV)< delta 1073 zcmZ8gO=ufO6rS1Lkya~dZOM{l*|McLN>dTyl@l4#pX87T+FnePi4H||#abHMN+hkA zT}f%7Nlgziz0_tdhEmA2heC^cErr;ZLJw^&5$jw-=*{5vl2hN%C>X>t|R}zI& z1wAe)qNu;S^P(qAJSV==&jsvpgsNfjDbC`s_!?ggM@B`rOz|AQK24JqY@*A`5b*AW zc&^oV>rR7NUB+FfW4CGz)@LoJD}xqfGNoiH&OrYJ z)J=;=fe&NG!@=g@;r-3~+jpM^=YI>%3lglv10?ZGkAG=?VQvo|HJ<1fo{7(c*WWrK z3Q4?lr=a|CF@Gb8_LGTnO5HEy%W?G}PQb3q`Hv(G#>qaGmA#_g#U&b|Mg@Ul8m1A@ zD5y!t=s1m42#wPTnxIM0Nt&WlG+j}ww3su_FHO^#Hr(g5fJF`#qYePIEU2WBNnXk$&LcoNY+-{xE$*L@Xs{`a-3D4!g^T$|wl{IRR?KQm?bNNNM zErGfVSp016t|U+>Hq;|qcO+~Kb#t5i8zNh4(hUvu>D~8+dV2R7z^^lJLbfbeALA{hT$>J#u_9yZ;uAu!ZiE>^&h?wQ9dXObx7w~d{oKEXx z5RwU8QtzbQZ_qKAi01%&{q45X=(X52UIbVEeXh)gM=(A_p+giqM8+T5MV$EaBYX)j Ia?OJ9UpsjGh5!Hn diff --git a/video_transcription/ai_transcriber_v2/__pycache__/main.cpython-313.pyc b/video_transcription/ai_transcriber_v2/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb494c424d0b645aa338d203a6b4420420311e1a GIT binary patch literal 17926 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 o=C3-!c@o|41Np2(()?sjO2{J42jg%I{ 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 I-vjagFE<|K!2kdN literal 0 HcmV?d00001 diff --git a/video_transcription/ai_transcriber_v2/__pycache__/run_v2.cpython-313.pyc b/video_transcription/ai_transcriber_v2/__pycache__/run_v2.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..799fb5b5301a939d63fcc76a05f2edee12b75291 GIT binary patch literal 8192 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 literal 0 HcmV?d00001 diff --git a/video_transcription/ai_transcriber_v2/__pycache__/run_wizard_v2.cpython-313.pyc b/video_transcription/ai_transcriber_v2/__pycache__/run_wizard_v2.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebc1575010c7f0aced297f7f70cf0360cccaad8c GIT binary patch literal 8199 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 K|1=#Y*#8^Rb(fU@ diff --git a/video_transcription/ai_transcriber_v2/__pycache__/translator.cpython-313.pyc b/video_transcription/ai_transcriber_v2/__pycache__/translator.cpython-313.pyc index 5a97144db4850a4c1ef8c207ade33e855f9a5ca2..0940c9251a7c54a0a4e9e790028e6d4ad88f6381 100644 GIT binary patch literal 11777 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 TrydLcxHO7`Gm8oeHA$upE}LcW1U| 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 G4&pb;QFaFa diff --git a/video_transcription/ai_transcriber_v2/extractor.py b/video_transcription/ai_transcriber_v2/extractor.py index 986e38b..00fb93f 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 +from utils import verify_file_not_empty def extract_audio(video_path, output_path=None): """ @@ -22,7 +23,7 @@ def extract_audio(video_path, output_path=None): output_path = f"{base_name}.wav" # Check if output file already exists to avoid redundant processing - if os.path.exists(output_path): + if verify_file_not_empty(output_path): print(f"Audio file already exists: {output_path}") return output_path @@ -43,11 +44,18 @@ def extract_audio(video_path, output_path=None): try: subprocess.run(command, check=True) + + if not verify_file_not_empty(output_path): + raise Exception("FFmpeg command succeeded but output file is empty or missing.") + print(f"Audio extracted to: {output_path}") return output_path except subprocess.CalledProcessError as e: print(f"Error extracting audio: {e}") sys.exit(1) + except Exception as e: + print(f"Error: {e}") + sys.exit(1) def embed_subtitles(video_path, srt_path, output_path=None): """ @@ -80,6 +88,7 @@ def embed_subtitles(video_path, srt_path, output_path=None): command = [ "ffmpeg", + "-ignore_editlist", "1", "-i", video_path, "-i", srt_path, "-map", "0:v", @@ -90,6 +99,8 @@ def embed_subtitles(video_path, srt_path, output_path=None): "-disposition:s:0", "default", "-metadata:s:s:0", "language=eng", "-metadata:s:s:0", "title=English (AI Translated)", + "-max_interleave_delta", "0", + "-avoid_negative_ts", "make_zero", "-y", "-v", "error", output_path @@ -97,6 +108,11 @@ def embed_subtitles(video_path, srt_path, output_path=None): try: subprocess.run(command, check=True) + if not verify_file_not_empty(output_path): + raise Exception("FFmpeg command succeeded but output video is empty or missing.") + print(f"Subtitles embedded successfully: {output_path} (Set as primary)") except subprocess.CalledProcessError as e: print(f"Error embedding subtitles: {e}") + except Exception as e: + print(f"Error embedding subtitles: {e}") diff --git a/video_transcription/ai_transcriber_v2/install_local_llm.sh b/video_transcription/ai_transcriber_v2/install_local_llm.sh new file mode 100755 index 0000000..4890d11 --- /dev/null +++ b/video_transcription/ai_transcriber_v2/install_local_llm.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# install_local_llm.sh +# Installs Ollama and a translation-capable model on Linux (Bazzite/Fedora/Debian compatible) + +set -e + +echo "=================================================" +echo " Local LLM Setup for AI Transcriber (Ollama)" +echo "=================================================" + +# 1. Check if Ollama is already installed +if command -v ollama &> /dev/null; then + echo "✅ Ollama is already installed." +else + echo "⬇️ Installing Ollama..." + # Standard Ollama install script (Works on Bazzite/Silverblue as /usr/local is writable) + curl -fsSL https://ollama.com/install.sh | sh +fi + +# 2. Check GPU availability for Ollama +echo "-------------------------------------------------" +if command -v nvidia-smi &> /dev/null; then + echo "✅ Nvidia GPU detected. Ollama should run efficiently." +else + echo "⚠️ Nvidia GPU not found (or drivers missing)." + echo " Ollama will run on CPU, which might be slow for translation." +fi +echo "-------------------------------------------------" + +# 3. Start Ollama Server (Background) +# In some dev containers, systemd isn't available, so we try to start it manually if not running. +if ! pgrep -x "ollama" > /dev/null; then + echo "🚀 Starting Ollama server in the background..." + nohup ollama serve > ollama.log 2>&1 & + PID=$! + echo " (PID: $PID) - Waiting 5 seconds for initialization..." + sleep 5 +else + echo "✅ Ollama server is already running." +fi + +# 4. Pull a Model +# 'llama3' (8B) is a great balance of speed and quality for translation. +# 'gemma:7b' is also good. +MODEL="llama3" + +echo "⬇️ Pulling model: $MODEL (This may take a few minutes)..." +ollama pull $MODEL + +echo "-------------------------------------------------" +echo "✅ Installation Complete!" +echo "" +echo "You can test it manually with: ollama run $MODEL 'Translate this to Spanish: Hello World'" +echo "" +echo "The AI Transcriber scripts will now detect and use this as a fallback." +echo "=================================================" diff --git a/video_transcription/ai_transcriber_v2/main.py b/video_transcription/ai_transcriber_v2/main.py index 9fa6a5d..64b65bc 100644 --- a/video_transcription/ai_transcriber_v2/main.py +++ b/video_transcription/ai_transcriber_v2/main.py @@ -19,9 +19,9 @@ else: load_dotenv() from extractor import extract_audio, embed_subtitles -from transcriber import transcribe_audio, save_as_srt -from translator import translate_srt, translate_fallback_free -from utils import validate_and_repair_srt +from transcriber import transcribe_audio, save_as_srt, load_whisper_model +from translator import translate_with_auto_fallback +from utils import validate_and_repair_srt, check_srt_duration_match, GracefulKiller, ensure_ollama_running, check_service_availability, check_path_permissions from diarizer import diarize_audio, merge_diarization_with_transcript import tracker from tracker import JobStatus @@ -51,7 +51,7 @@ def save_srt_with_speakers(segments, output_path): f.write(f"{text}\n\n") print(f"SRT saved to: {output_path}") -def process_file(file_path, args, source_lang=None): +def process_file(file_path, args, source_lang=None, loaded_model=None, service_status=None): tracker.logger.info(f"=== Processing: {file_path} ===") # Initialize Job @@ -81,7 +81,8 @@ def process_file(file_path, args, source_lang=None): with open(transcript_file, "r", encoding="utf-8") as f: srt_content = f.read() else: - result = transcribe_audio(audio_path, model_size=args.model, language=source_lang) + # Use loaded_model if available + result = transcribe_audio(audio_path, model_size=args.model, language=source_lang, loaded_model=loaded_model) segments = result["segments"] if args.diarize: @@ -110,66 +111,75 @@ def process_file(file_path, args, source_lang=None): base_translated = os.path.splitext(file_path)[0] + f".{args.lang}.srt" deep_translated = os.path.splitext(file_path)[0] + f".{args.lang}.deep_translate.srt" + local_translated = os.path.splitext(file_path)[0] + f".{args.lang}.local_llm.srt" - translated_file = base_translated # Default + # Determine output path logic + target_path_gemini = base_translated + target_path_deep = deep_translated + target_path_local = local_translated + + translated_file = None translation_success = False method_used = "None" - if (os.path.exists(base_translated) or os.path.exists(deep_translated)) and not args.force: - if os.path.exists(deep_translated): + # Check existing + if (os.path.exists(base_translated) or os.path.exists(deep_translated) or os.path.exists(local_translated)) and not args.force: + if os.path.exists(local_translated): + translated_file = local_translated + method_used = "Local LLM (Existing)" + elif os.path.exists(deep_translated): translated_file = deep_translated method_used = "DeepTranslate (Existing)" else: + translated_file = base_translated method_used = "Gemini (Existing)" + tracker.logger.info(f"Translation exists: {translated_file} ({method_used}). Skipping translation.") final_srt_path = translated_file translation_success = True else: if srt_content: - # Helper functions - def try_gemini(): - res = translate_srt(srt_content, target_language=args.lang) - if res: - with open(base_translated, "w", encoding="utf-8") as f: - f.write(res) - return True, base_translated, "Gemini" - return False, None, None - - def try_deep(): - lang_map = { - "English": "en", "French": "fr", "Spanish": "es", "German": "de", - "Italian": "it", "Portuguese": "pt", "Russian": "ru", - "Japanese": "ja", "Chinese": "zh-CN" - } - target_code = lang_map.get(args.lang, "en") - res = translate_fallback_free(srt_content, target_language=target_code) - if res: - with open(deep_translated, "w", encoding="utf-8") as f: - f.write(res) - return True, deep_translated, "DeepTranslate" - return False, None, None - - success = False + res_content, method = translate_with_auto_fallback( + srt_content, + target_language=args.lang, + prefer_deep=args.prefer_deep, + prefer_local=args.prefer_local, + available_services=service_status + ) - if args.prefer_deep: - success, path, method = try_deep() - if not success: - tracker.logger.info("DeepTranslate failed. Attempting Gemini...") - success, path, method = try_gemini() + if res_content: + # Save based on method used + if "DeepTranslate" in method: + save_path = target_path_deep + elif "Local LLM" in method: + save_path = target_path_local + else: + save_path = target_path_gemini + + with open(save_path, "w", encoding="utf-8") as f: + f.write(res_content) + + tracker.logger.info(f"Translation saved to: {save_path} ({method})") + validate_and_repair_srt(save_path) + + # Duration Check + is_valid_duration, msg = check_srt_duration_match(transcript_file, save_path) + if is_valid_duration: + tracker.logger.info(f"Validation: {msg}") + final_srt_path = save_path + translation_success = True + method_used = method + else: + tracker.logger.error(f"VALIDATION FAILED: {msg}") + tracker.logger.error("Marking translation as failed due to incomplete coverage.") + + redo_file = os.path.join(os.path.dirname(file_path), "redo_queue.txt") + with open(redo_file, "a", encoding="utf-8") as rf: + rf.write(f"{file_path} | {msg}\n") + + translation_success = False else: - success, path, method = try_gemini() - if not success: - tracker.logger.warning("Gemini failed. Attempting DeepTranslate...") - success, path, method = try_deep() - - if success: - tracker.logger.info(f"Translation saved to: {path} ({method})") - validate_and_repair_srt(path) - final_srt_path = path - translation_success = True - method_used = method - else: - tracker.logger.error("TRANSLATION FAILED.") + tracker.logger.error("TRANSLATION FAILED (All methods attempted).") tracker.update_step(file_path, "step_translate", "failed") translation_success = False @@ -237,6 +247,7 @@ def main(): parser.add_argument("--delete-source", action="store_true", help="Delete original file after embedding") parser.add_argument("--retry-failed", action="store_true", help="Retry FAILED jobs from DB") parser.add_argument("--prefer-deep", action="store_true", help="Prefer DeepTranslate (Free) over Gemini") + parser.add_argument("--prefer-local", action="store_true", help="Prefer Local LLM (Ollama) over cloud APIs") args = parser.parse_args() @@ -257,9 +268,13 @@ def main(): user_input = input("Enter source language (e.g. 'French'). Enter for Auto: ").strip() source_lang = user_input if user_input else None + # Load model for retries too + loaded_model = load_whisper_model(args.model) + service_status = check_service_availability() + for file_path in failed_files: if os.path.exists(file_path): - process_file(file_path, args, source_lang) + process_file(file_path, args, source_lang, loaded_model=loaded_model, service_status=service_status) else: print(f"Skipping missing file: {file_path}") return @@ -274,22 +289,65 @@ def main(): source_lang = user_input if user_input else None print(f"Selected: {source_lang if source_lang else 'Auto-detect'}") + # --- Ensure Ollama is Running --- + ensure_ollama_running() + # -------------------------------- + + # --- Check Service Health --- + service_status = check_service_availability() + # ---------------------------- + + # --- Check Path Permissions --- + valid_inputs = [] + print("Checking Input Permissions...") + for inp in args.inputs: + ok, msg = check_path_permissions(inp) + print(msg) + if ok: + valid_inputs.append(inp) + + if not valid_inputs: + print("\n❌ Error: No valid inputs with read/write permissions found. Exiting.") + return + # ------------------------------ + + # --- Load Model Once --- + loaded_model = load_whisper_model(args.model) + # ----------------------- + + # Initialize Graceful Exit Handler + killer = GracefulKiller() + video_extensions = ('.mp4', '.mkv', '.mov', '.avi', '.webm', '.flv', '.wmv', '.m4v') - for input_path in args.inputs: + for input_path in valid_inputs: + if killer.kill_now: + break + if os.path.isfile(input_path): - process_file(input_path, args, source_lang) + process_file(input_path, args, source_lang, loaded_model=loaded_model, service_status=service_status) elif os.path.isdir(input_path): found = False for root, dirs, files in os.walk(input_path): + if killer.kill_now: + break + for file in files: + if killer.kill_now: + break + if file.lower().endswith(video_extensions): found = True - process_file(os.path.join(root, file), args, source_lang) + process_file(os.path.join(root, file), args, source_lang, loaded_model=loaded_model, service_status=service_status) if not found: print(f"No video files found in {input_path}") else: print(f"Error: Invalid input path '{input_path}'") + + if killer.kill_now: + print("\n🛑 Process stopped by user. Progress saved in database.") + else: + print("\n✅ All jobs finished.") if __name__ == "__main__": main() \ No newline at end of file diff --git a/video_transcription/mount_truenas.sh b/video_transcription/ai_transcriber_v2/mount_truenas.sh similarity index 76% rename from video_transcription/mount_truenas.sh rename to video_transcription/ai_transcriber_v2/mount_truenas.sh index 34c9991..e634deb 100755 --- a/video_transcription/mount_truenas.sh +++ b/video_transcription/ai_transcriber_v2/mount_truenas.sh @@ -4,7 +4,7 @@ MOUNT_POINT="/mnt/truenas_isolation" SHARE="//truenas.local/isolation" -echo "--- SMB Mount Tool ---" +echo "--- SMB Mount Tool (V2) ---" # Determine privilege escalation method PRIV_CMD="" @@ -46,16 +46,22 @@ SMB_USER=${SMB_USER:-guest} # 3. Mount echo "Mounting $SHARE to $MOUNT_POINT..." +# IMPORTANT: We force the mount to be owned by the current user (UID 1000 usually) +# This fixes "Permission Denied" errors when writing to the share. +# We also set file_mode/dir_mode to 0777 as a fallback to ensure full access. +MOUNT_OPTS="vers=3.0,uid=$(id -u),gid=$(id -g),file_mode=0777,dir_mode=0777,noperm" + if [ "$SMB_USER" == "guest" ]; then - $PRIV_CMD mount -t cifs "$SHARE" "$MOUNT_POINT" -o guest,vers=3.0 + $PRIV_CMD mount -t cifs "$SHARE" "$MOUNT_POINT" -o "guest,$MOUNT_OPTS" else # This will prompt for the SMB password - $PRIV_CMD mount -t cifs "$SHARE" "$MOUNT_POINT" -o username="$SMB_USER",vers=3.0 + $PRIV_CMD mount -t cifs "$SHARE" "$MOUNT_POINT" -o "username=$SMB_USER,$MOUNT_OPTS" fi # 4. Check result if [ $? -eq 0 ]; then echo "✅ Success! Share is now available at $MOUNT_POINT" + echo "Files are now owned by $(id -un):$(id -gn) with full write access." echo "The mapping will disappear automatically after you reboot." else echo "❌ Error: Failed to mount the share." diff --git a/video_transcription/ai_transcriber_v2/recover_and_fix_v2.py b/video_transcription/ai_transcriber_v2/recover_and_fix_v2.py new file mode 100755 index 0000000..1ef30c3 --- /dev/null +++ b/video_transcription/ai_transcriber_v2/recover_and_fix_v2.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +import os +import sys +import argparse +import subprocess +from dotenv import load_dotenv +from datetime import datetime +import pysubs2 + +# Load config +script_dir = os.path.dirname(os.path.abspath(__file__)) +env_path = os.path.abspath(os.path.join(script_dir, '../../.env_files/.env.aitranscribe')) +if os.path.exists(env_path): + load_dotenv(env_path) +else: + load_dotenv() + +from translator import translate_with_auto_fallback +from utils import validate_and_repair_srt, check_srt_duration_match, GracefulKiller, ensure_ollama_running, detect_file_encoding, check_service_availability, check_path_permissions +from extractor import embed_subtitles + +def process_recovery(folder_path, target_lang="English", prefer_deep=False, prefer_local=False): + print(f"Scanning {folder_path} for incomplete translations (V2)...") + + # Check Permissions + perm_ok, perm_msg = check_path_permissions(folder_path) + print(perm_msg) + if not perm_ok: + print("Aborting due to permission errors.") + return + + if prefer_local: + print("Preference: Local LLM (Ollama) > Gemini/Deep") + elif prefer_deep: + print("Preference: DeepTranslate (Google Translate Free) > Gemini") + else: + print("Preference: Gemini (API) > DeepTranslate") + + recovery_log_file = os.path.join(folder_path, "recovery_status.log") + print(f"Logging actions to: {recovery_log_file}") + + # Ensure Ollama is ready + ensure_ollama_running() + + # Check Service Health + service_status = check_service_availability() + + # Initialize Graceful Exit + killer = GracefulKiller() + + count_fixed = 0 + video_extensions = ('.mp4', '.mkv', '.mov', '.avi') + + for root, dirs, files in os.walk(folder_path): + if killer.kill_now: + break + + for file in files: + if killer.kill_now: + break + + if file.endswith(".srt") and \ + not file.endswith(f".{target_lang}.srt") and \ + not file.endswith(f".{target_lang}.deep_translate.srt") and \ + not file.endswith(f".{target_lang}.local_llm.srt"): + + source_srt_path = os.path.join(root, file) + base_name = os.path.splitext(file)[0] + + path_gemini = os.path.join(root, f"{base_name}.{target_lang}.srt") + path_deep = os.path.join(root, f"{base_name}.{target_lang}.deep_translate.srt") + path_local = os.path.join(root, f"{base_name}.{target_lang}.local_llm.srt") + + needs_translation = False + existing_translation_path = None + + # Check if translation exists + if os.path.exists(path_gemini): + existing_translation_path = path_gemini + elif os.path.exists(path_deep): + existing_translation_path = path_deep + elif os.path.exists(path_local): + existing_translation_path = path_local + + if existing_translation_path: + # Validate duration + is_valid, msg = check_srt_duration_match(source_srt_path, existing_translation_path) + if not is_valid: + print(f"\n⚠️ Found partial/broken translation: {existing_translation_path}") + print(f" Reason: {msg}") + print(" -> Queueing for re-translation...") + needs_translation = True + else: + # Missing translation + print(f"\nFound untranslated transcript: {file}") + needs_translation = True + + if not needs_translation: + continue + + # --- Proceed with Translation --- + + content = None + + # 1. Try automatic detection + detected_enc = detect_file_encoding(source_srt_path) + try: + with open(source_srt_path, "r", encoding=detected_enc) as f: + content = f.read() + except Exception: + # 2. Fallback to brute force if chardet was wrong + encodings_to_try = ['utf-8', 'shift_jis', 'euc_jp', 'latin-1', 'cp1252', 'utf-16'] + for enc in encodings_to_try: + try: + with open(source_srt_path, "r", encoding=enc) as f: + content = f.read() + break # Success + except UnicodeDecodeError: + continue + + if content is None: + print(f"❌ Error: Could not decode {file}. Skipping.") + continue + + # Use shared translation logic + res_content, method_used = translate_with_auto_fallback( + content, + target_language=target_lang, + prefer_deep=prefer_deep, + prefer_local=prefer_local, + available_services=service_status + ) + + final_srt_path = None + + # --- Helper to save result --- + def save_translation(text, method): + path = None + if "DeepTranslate" in method: + path = path_deep + elif "Local LLM" in method: + path = path_local + else: + path = path_gemini + + with open(path, "w", encoding="utf-8") as f: + f.write(text) + return path + + if res_content: + final_srt_path = save_translation(res_content, method_used) + + if final_srt_path: + # Validate the NEW translation immediately + is_valid_new, msg_new = check_srt_duration_match(source_srt_path, final_srt_path) + + if not is_valid_new: + print(f"❌ New translation ({method_used}) failed validation: {msg_new}") + + # --- RETRY WITH LOCAL LLM --- + # Only retry if we haven't already used Local LLM and it is available + if "Local LLM" not in method_used and service_status.get("Ollama", False): + print(" -> Retrying with Local LLM (Ollama) as fallback strategy...") + + # Force try Ollama + from translator import translate_via_ollama + retry_content = translate_via_ollama(content, target_language=target_lang) + + if retry_content: + retry_path = path_local + with open(retry_path, "w", encoding="utf-8") as f: + f.write(retry_content) + + # Validate Retry + valid_retry, msg_retry = check_srt_duration_match(source_srt_path, retry_path) + if valid_retry: + print(f" ✅ Local LLM Retry Succeeded! Using: {os.path.basename(retry_path)}") + # Rename/Cleanup the previous failed attempt + invalid_path = final_srt_path + ".invalid" + os.replace(final_srt_path, invalid_path) + + final_srt_path = retry_path + method_used = "Local LLM (Retry)" + is_valid_new = True # Mark as valid so we proceed to embedding + else: + print(f" ❌ Local LLM Retry also failed validation: {msg_retry}") + # Cleanup retry attempt + os.replace(retry_path, retry_path + ".invalid") + + if not is_valid_new: + # Rename the invalid file so it doesn't sit there as a "fake" good translation + invalid_path = final_srt_path + ".invalid" + if os.path.exists(final_srt_path): + os.replace(final_srt_path, invalid_path) + print(f" -> Moved failed attempt to: {os.path.basename(invalid_path)}") + + with open(recovery_log_file, "a", encoding="utf-8") as log: + log.write(f"{datetime.now().isoformat()} | {method_used} | FAILED_VALIDATION | {file}\n") + continue + + # Log result + with open(recovery_log_file, "a", encoding="utf-8") as log: + log.write(f"{datetime.now().isoformat()} | {method_used} | FIXED | {file} -> {os.path.basename(final_srt_path)}\n") + + validate_and_repair_srt(final_srt_path) + + video_candidates = [ + os.path.join(root, base_name + ".mp4"), + os.path.join(root, base_name + ".mkv"), + os.path.join(root, base_name + ".subbed.mp4"), + ] + + found_video = None + for v in video_candidates: + if os.path.exists(v): + found_video = v + break + + if found_video: + print(f"Found video to fix: {found_video}") + temp_video_out = found_video + ".temp_fix.mp4" + try: + embed_subtitles(found_video, final_srt_path, output_path=temp_video_out) + os.replace(temp_video_out, found_video) + print(f"✅ Fixed: {found_video}") + count_fixed += 1 + except Exception as e: + print(f"Error re-embedding: {e}") + if os.path.exists(temp_video_out): + os.remove(temp_video_out) + else: + print("Warning: Could not find a corresponding video file to embed into.") + else: + print("❌ All translation methods failed. Skipping.") + + if killer.kill_now: + print("\n🛑 Recovery process stopped by user.") + else: + print(f"\nRecovery Complete. Fixed {count_fixed} files.") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Recover and Fix Translations (V2)") + parser.add_argument("folders", nargs='+', help="One or more paths to folders to scan") + parser.add_argument("--lang", default="English", help="Target language (default: English)") + parser.add_argument("--prefer-deep", action="store_true", help="Prefer DeepTranslate (Free) over Gemini API") + parser.add_argument("--prefer-local", action="store_true", help="Prefer Local LLM (Ollama) over cloud APIs") + + args = parser.parse_args() + + for folder in args.folders: + if os.path.exists(folder): + process_recovery(folder, args.lang, args.prefer_deep, args.prefer_local) + else: + print(f"Error: Folder '{folder}' does not exist. Skipping.") \ No newline at end of file diff --git a/video_transcription/ai_transcriber_v2/requirements.txt b/video_transcription/ai_transcriber_v2/requirements.txt index 9856a52..30881b2 100644 --- a/video_transcription/ai_transcriber_v2/requirements.txt +++ b/video_transcription/ai_transcriber_v2/requirements.txt @@ -6,3 +6,7 @@ numpy tenacity pysubs2 pyannote.audio +deep-translator +ollama +chardet +tqdm diff --git a/video_transcription/run_v2.bat b/video_transcription/ai_transcriber_v2/run_v2.bat similarity index 100% rename from video_transcription/run_v2.bat rename to video_transcription/ai_transcriber_v2/run_v2.bat diff --git a/video_transcription/run_v2.py b/video_transcription/ai_transcriber_v2/run_v2.py similarity index 94% rename from video_transcription/run_v2.py rename to video_transcription/ai_transcriber_v2/run_v2.py index 560d011..46b4f67 100755 --- a/video_transcription/run_v2.py +++ b/video_transcription/ai_transcriber_v2/run_v2.py @@ -36,7 +36,7 @@ def main(): try: from dotenv import load_dotenv script_dir = os.path.dirname(os.path.abspath(__file__)) - env_path = os.path.abspath(os.path.join(script_dir, '../.env_files/.env.aitranscribe')) + env_path = os.path.abspath(os.path.join(script_dir, '../../.env_files/.env.aitranscribe')) if os.path.exists(env_path): load_dotenv(env_path) except ImportError: @@ -59,7 +59,7 @@ def main(): continue # Clean up input - input_path = input_path.strip("\'"") + input_path = input_path.strip('"\'') input_path = input_path.replace(r'\ ', ' ') # Expand user (~) and resolve absolute path @@ -98,6 +98,7 @@ def main(): do_delete_source = get_yes_no("Delete original source files after embedding?", default="n") do_prefer_deep = get_yes_no("Prefer DeepTranslate (Free) over Gemini API?", default="n") + do_prefer_local = get_yes_no("Prefer Local LLM (Ollama) over all cloud options?", default="n") hf_token = None if do_diarize: @@ -110,7 +111,7 @@ def main(): # 5. Build Command # Point to v2 main script script_dir = os.path.dirname(os.path.abspath(__file__)) - main_script = os.path.join(script_dir, "ai_transcriber_v2", "main.py") + main_script = os.path.join(script_dir, "main.py") cmd = [sys.executable, main_script] cmd.extend(input_paths) @@ -138,6 +139,9 @@ def main(): if do_prefer_deep: cmd.append("--prefer-deep") + if do_prefer_local: + cmd.append("--prefer-local") + # 6. Confirmation and Execution clear_screen() print_header() @@ -154,6 +158,7 @@ def main(): print(f"Delete Src: {do_delete_source}") print(f"Diarization: {do_diarize}") print(f"Prefer Deep: {do_prefer_deep}") + print(f"Prefer Local: {do_prefer_local}") print("-" * 30) if not get_yes_no("Run this job now?", default="y"): diff --git a/video_transcription/run_wizard_v2.py b/video_transcription/ai_transcriber_v2/run_wizard_v2.py similarity index 94% rename from video_transcription/run_wizard_v2.py rename to video_transcription/ai_transcriber_v2/run_wizard_v2.py index 560d011..46b4f67 100755 --- a/video_transcription/run_wizard_v2.py +++ b/video_transcription/ai_transcriber_v2/run_wizard_v2.py @@ -36,7 +36,7 @@ def main(): try: from dotenv import load_dotenv script_dir = os.path.dirname(os.path.abspath(__file__)) - env_path = os.path.abspath(os.path.join(script_dir, '../.env_files/.env.aitranscribe')) + env_path = os.path.abspath(os.path.join(script_dir, '../../.env_files/.env.aitranscribe')) if os.path.exists(env_path): load_dotenv(env_path) except ImportError: @@ -59,7 +59,7 @@ def main(): continue # Clean up input - input_path = input_path.strip("\'"") + input_path = input_path.strip('"\'') input_path = input_path.replace(r'\ ', ' ') # Expand user (~) and resolve absolute path @@ -98,6 +98,7 @@ def main(): do_delete_source = get_yes_no("Delete original source files after embedding?", default="n") do_prefer_deep = get_yes_no("Prefer DeepTranslate (Free) over Gemini API?", default="n") + do_prefer_local = get_yes_no("Prefer Local LLM (Ollama) over all cloud options?", default="n") hf_token = None if do_diarize: @@ -110,7 +111,7 @@ def main(): # 5. Build Command # Point to v2 main script script_dir = os.path.dirname(os.path.abspath(__file__)) - main_script = os.path.join(script_dir, "ai_transcriber_v2", "main.py") + main_script = os.path.join(script_dir, "main.py") cmd = [sys.executable, main_script] cmd.extend(input_paths) @@ -138,6 +139,9 @@ def main(): if do_prefer_deep: cmd.append("--prefer-deep") + if do_prefer_local: + cmd.append("--prefer-local") + # 6. Confirmation and Execution clear_screen() print_header() @@ -154,6 +158,7 @@ def main(): print(f"Delete Src: {do_delete_source}") print(f"Diarization: {do_diarize}") print(f"Prefer Deep: {do_prefer_deep}") + print(f"Prefer Local: {do_prefer_local}") print("-" * 30) if not get_yes_no("Run this job now?", default="y"): diff --git a/video_transcription/ai_transcriber_v2/transcriber.py b/video_transcription/ai_transcriber_v2/transcriber.py index f202022..2a3148f 100644 --- a/video_transcription/ai_transcriber_v2/transcriber.py +++ b/video_transcription/ai_transcriber_v2/transcriber.py @@ -115,7 +115,28 @@ def save_as_srt(result, output_path): f.write(f"{text}\n\n") print(f"SRT saved to: {output_path}") -def transcribe_audio(audio_path, model_size="auto", language=None): +def load_whisper_model(model_size="auto"): + """ + Loads and returns the Whisper model. + """ + check_gpu_health() + + if model_size == "auto": + model_size = get_optimal_model_size() + print(f"Auto-selected model: '{model_size}'") + + print(f"Loading Whisper model ('{model_size}')...") + device = "cuda" if torch.cuda.is_available() else "cpu" + print(f"Using device: {device}") + + try: + model = whisper.load_model(model_size, device=device) + return model + except Exception as e: + print(f"Error loading model: {e}") + sys.exit(1) + +def transcribe_audio(audio_path, model_size="auto", language=None, loaded_model=None): """ Transcribes an audio file using OpenAI's Whisper model. @@ -123,6 +144,7 @@ def transcribe_audio(audio_path, model_size="auto", language=None): audio_path (str): Path to the input audio file. model_size (str): Size of the Whisper model to use. If "auto", selects based on VRAM. language (str, optional): Language code (e.g., "en", "fr", "es"). If None, auto-detects. + loaded_model (object, optional): Pre-loaded Whisper model object. Returns: dict: The full transcription result containing segments and text. @@ -130,37 +152,14 @@ def transcribe_audio(audio_path, model_size="auto", language=None): if not os.path.exists(audio_path): raise FileNotFoundError(f"Audio file not found: {audio_path}") - # Run health check once - check_gpu_health() - - # Determine model size if auto - if model_size == "auto": - model_size = get_optimal_model_size() - print(f"Auto-selected model: '{model_size}'") - - print(f"Loading Whisper model ('{model_size}')...") - - # Check for GPU availability - device = "cuda" if torch.cuda.is_available() else "cpu" - print(f"Using device: {device}") - - try: - model = whisper.load_model(model_size, device=device) - except RuntimeError as e: - if "out of memory" in str(e).lower(): - print("Error: GPU Out of Memory. Try using a smaller model size.") - else: - print(f"Error loading model: {e}") - sys.exit(1) - except Exception as e: - print(f"Error loading model: {e}") - sys.exit(1) + model = loaded_model + if model is None: + model = load_whisper_model(model_size) print(f"Transcribing {audio_path}...") try: - # fp16=False is needed for CPU, but we can let whisper handle defaults usually. - # language=None allows auto-detection. - result = model.transcribe(audio_path, language=language) + # Enable verbose=True to show progress in terminal + result = model.transcribe(audio_path, language=language, verbose=True) print("Transcription complete.") return result except Exception as e: diff --git a/video_transcription/ai_transcriber_v2/translator.py b/video_transcription/ai_transcriber_v2/translator.py index b548a88..0419891 100644 --- a/video_transcription/ai_transcriber_v2/translator.py +++ b/video_transcription/ai_transcriber_v2/translator.py @@ -4,11 +4,71 @@ from google import genai from google.genai import types from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type import pysubs2 -from deep_translator import GoogleTranslator +from deep_translator import GoogleTranslator, MyMemoryTranslator +import ollama +from tqdm import tqdm # Define a retry decorator # ... (retry_policy remains) +def translate_via_ollama(source_srt_content, target_language="English", model="llama3"): + """ + Translates SRT content using a local Ollama model (Line-by-Line for progress). + """ + try: + subs = pysubs2.SSAFile.from_string(source_srt_content) + + # Using tqdm for progress bar + for line in tqdm(subs, desc=" Ollama Progress", unit="line"): + text = line.text.strip() + if text: + prompt = ( + f"Translate this subtitle text to {target_language}. Output ONLY the translation.\n" + f"Text: {text}" + ) + try: + response = ollama.chat(model=model, messages=[{'role': 'user', 'content': prompt}]) + translated_text = response['message']['content'].strip() + if translated_text: + line.text = translated_text + except Exception as e: + # Silent fail on line, logs would be too spammy in progress bar + pass + + return subs.to_string(format_="srt") + + except Exception as e: + print(f" [Local LLM] Error: {e}") + return None + +def translate_fallback_mymemory(source_srt_content, target_language="en"): + """ + Fallback translation using MyMemory (via deep-translator). + Limit: 1000 words/day roughly for anonymous usage. Good last resort. + """ + try: + subs = pysubs2.SSAFile.from_string(source_srt_content) + # MyMemory uses ISO 639-1 usually + translator = MyMemoryTranslator(source='auto', target=target_language) + + for line in tqdm(subs, desc=" MyMemory Progress", unit="line"): + text = line.text.strip() + if text: + if len(text) > 500: # MyMemory has stricter limits often + continue + try: + original_text = text.replace(r"\N", " ") + translated_text = translator.translate(original_text) + if translated_text: + line.text = translated_text + except Exception: + pass + + return subs.to_string(format_="srt") + except Exception as e: + print(f" [MyMemory Fallback] Critical Error: {e}") + return None + def translate_fallback_free(source_srt_content, target_language="en"): """ Fallback translation using deep-translator (free Google Translate). @@ -20,19 +80,17 @@ def translate_fallback_free(source_srt_content, target_language="en"): Returns: str: Translated SRT content, or None if failed. """ - print(f" [Free Fallback] Translating via Google Translate (deep-translator)...") try: # Load from string subs = pysubs2.SSAFile.from_string(source_srt_content) translator = GoogleTranslator(source='auto', target=target_language) # Simple line-by-line translation - for line in subs: + for line in tqdm(subs, desc=" DeepTranslate Progress", unit="line"): text = line.text.strip() if text: # Sanity check: Skip lines that are too long if len(text) > 4000: - print(f" Warning: Skipping line with excessive length ({len(text)} chars).") continue try: @@ -41,8 +99,8 @@ def translate_fallback_free(source_srt_content, target_language="en"): translated_text = translator.translate(original_text) if translated_text: line.text = translated_text - except Exception as e: - print(f" Warning: Failed to translate line: {e}") + except Exception: + pass # Return as string return subs.to_string(format_="srt") @@ -53,25 +111,26 @@ def translate_fallback_free(source_srt_content, target_language="en"): # Define a retry decorator # Waits 2^x * 1 seconds between retries (1s, 2s, 4s...) # Stop after 15 attempts +# before_sleep logic can print a simple message +def log_retry_attempt(retry_state): + if retry_state.attempt_number > 1: + print(f" [Gemini] Rate limit hit. Retrying in {retry_state.next_action.sleep}s...", end='\r') + retry_policy = retry( stop=stop_after_attempt(15), wait=wait_exponential(multiplier=1, min=2, max=60), retry=retry_if_exception_type(Exception), - reraise=True + reraise=True, + before_sleep=log_retry_attempt ) @retry_policy def _generate_with_retry(client, model_name, prompt): """Internal function to wrap the API call with retry logic.""" - try: - return client.models.generate_content( - model=model_name, - contents=prompt - ) - except Exception as e: - if "429" in str(e) or "Resource has been exhausted" in str(e): - print(f" [Rate Limit Hit] Waiting for quota reset... ({e})") - raise e + return client.models.generate_content( + model=model_name, + contents=prompt + ) def get_best_available_model(client): """ @@ -126,7 +185,6 @@ def translate_srt(srt_content, target_language="English", api_key=None): # Automatically select the best model # Note: v2 SDK might use 'gemini-1.5-flash' directly without 'models/' prefix usually model_name = "gemini-2.0-flash" - print(f"Using Gemini Model (v2): {model_name}") prompt = ( "You are a professional subtitle translator. Your task is to translate the following SRT subtitle file " @@ -140,11 +198,9 @@ def translate_srt(srt_content, target_language="English", api_key=None): f"{srt_content}" ) - print(f"Translating subtitles to {target_language} (with retries)...") try: # Call the retried internal function response = _generate_with_retry(client, model_name, prompt) - print("Translation complete.") # Cleanup: sometimes models wrap output in ```srt ... ``` or ``` ... ``` cleaned_text = response.text.strip() @@ -171,3 +227,84 @@ def translate_srt(srt_content, target_language="English", api_key=None): print(f"Fallback failed: {inner_e}") return None + +def translate_with_auto_fallback(srt_content, target_language="English", prefer_deep=False, prefer_local=False, available_services=None): + """ + Attempts to translate SRT content using Gemini, DeepTranslate, and Local LLM with fallback logic. + + Args: + srt_content (str): The source SRT content. + target_language (str): Target language name (e.g., "English", "French"). + prefer_deep (bool): If True, try DeepTranslate first (among cloud services). + prefer_local (bool): If True, try Local LLM (Ollama) first. + available_services (dict, optional): Result of check_service_availability(). + + Returns: + tuple: (translated_content, method_name) or (None, None) if all failed. + """ + + # Map full language name to code for DeepTranslate + lang_map = { + "English": "en", "French": "fr", "Spanish": "es", "German": "de", + "Italian": "it", "Portuguese": "pt", "Russian": "ru", + "Japanese": "ja", "Chinese": "zh-CN" + } + target_code = lang_map.get(target_language, "en") + + # Determine which services to even try + def is_ok(name): + if available_services is None: return True + return available_services.get(name, True) + + def try_gemini(): + if not is_ok("Gemini"): return None, None + res = translate_srt(srt_content, target_language=target_language) + if res: return res, "Gemini" + return None, None + + def try_deep(): + if not is_ok("DeepTranslate"): return None, None + res = translate_fallback_free(srt_content, target_language=target_code) + if res: return res, "DeepTranslate" + return None, None + + def try_ollama(): + if not is_ok("Ollama"): return None, None + res = translate_via_ollama(srt_content, target_language=target_language) + if res: return res, "Local LLM (Ollama)" + return None, None + + def try_mymemory(): + res = translate_fallback_mymemory(srt_content, target_language=target_code) + if res: return res, "MyMemory" + return None, None + + # Logic flow + attempts = [] + + if prefer_local: + attempts.append(try_ollama) + if prefer_deep: + attempts.extend([try_deep, try_gemini]) + else: + attempts.extend([try_gemini, try_deep]) + else: + if prefer_deep: + attempts.extend([try_deep, try_gemini]) + else: + attempts.extend([try_gemini, try_deep]) + attempts.append(try_ollama) + + # Final last resort + attempts.append(try_mymemory) + + # Execute attempts + for i, method_func in enumerate(attempts): + if i > 0: + print(f" Attempt {i} failed. Trying next fallback...") + + content, method = method_func() + if content: + return content, method + + return None, None diff --git a/video_transcription/ai_transcriber_v2/utils.py b/video_transcription/ai_transcriber_v2/utils.py index 8c452bf..f28e092 100644 --- a/video_transcription/ai_transcriber_v2/utils.py +++ b/video_transcription/ai_transcriber_v2/utils.py @@ -1,5 +1,220 @@ import pysubs2 import os +import signal +import sys +import subprocess +import time +import socket +import shutil +import chardet +from tqdm import tqdm + +class GracefulKiller: + """ + Handles SIGINT (Ctrl+C) and SIGTERM signals. + Allows the application to finish the current task before exiting. + """ + kill_now = False + + def __init__(self): + signal.signal(signal.SIGINT, self.exit_gracefully) + signal.signal(signal.SIGTERM, self.exit_gracefully) + + def exit_gracefully(self, signum, frame): + if not self.kill_now: + self.kill_now = True + print("\n\n[STOP REQUESTED] The script will exit after the current file finishes processing.") + print("Press Ctrl+C again to force quit immediately (not recommended).\n") + else: + print("\n[FORCE QUIT] Exiting immediately...") + sys.exit(1) + +def is_port_open(host, port): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(1) + return s.connect_ex((host, port)) == 0 + +def detect_file_encoding(file_path): + """ + Robustly detects the encoding of a file using chardet. + Returns 'utf-8' if detection fails or confidence is low, as a safe default. + """ + try: + with open(file_path, 'rb') as f: + raw_data = f.read(10000) # Read first 10KB + result = chardet.detect(raw_data) + + encoding = result['encoding'] + confidence = result['confidence'] + + if encoding and confidence > 0.7: + # Shift-JIS is often detected as other Japanese variants, which is fine, + # but sometimes we want to be specific. Chardet is usually good. + return encoding + return 'utf-8' + except Exception: + return 'utf-8' + +def verify_file_not_empty(file_path): + """ + Checks if a file exists and is larger than 0 bytes. + """ + if os.path.exists(file_path) and os.path.getsize(file_path) > 0: + return True + return False + +def ensure_ollama_running(model_name="llama3"): + """ + Checks if Ollama is running. If not, attempts to start it. + Supports Flatpak by escaping to host via flatpak-spawn. + """ + in_flatpak = os.path.exists("/.flatpak-info") + + def run_cmd(cmd_list, capture=False): + if in_flatpak: + full_cmd = ["flatpak-spawn", "--host"] + cmd_list + else: + full_cmd = cmd_list + + try: + if capture: + return subprocess.run(full_cmd, capture_output=True, text=True) + else: + # For serve, we use Popen + return subprocess.Popen( + full_cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True + ) + except Exception: + return None + + # 1. Start Server if port is closed + if not is_port_open("127.0.0.1", 11434): + print("Starting Ollama server (Local LLM)...") + run_cmd(["ollama", "serve"]) + + print(" Waiting for Ollama to initialize...", end="", flush=True) + for _ in range(10): + if is_port_open("127.0.0.1", 11434): + print(" Done.") + break + time.sleep(1) + print(".", end="", flush=True) + else: + print("\n Warning: Ollama server failed to start or binary not found.") + return False + + # 2. Check Model Presence + try: + result = run_cmd(["ollama", "list"], capture=True) + if result and result.returncode == 0: + if model_name not in result.stdout: + print(f" Model '{model_name}' not found. Pulling now (this may take a while)...") + # Pulling can take a long time, so we don't capture but we want to wait + pull_cmd = ["flatpak-spawn", "--host", "ollama", "pull", model_name] if in_flatpak else ["ollama", "pull", model_name] + subprocess.run(pull_cmd, check=True) + print(" Model pulled successfully.") + else: + # If we can't run list, but port is open, we assume it's okay and let the library handle it + pass + except Exception as e: + print(f" Warning: Could not verify/pull Ollama model: {e}") + + return True + +def check_service_availability(prefer_deep=False): + """ + Checks availability of configured translation services by performing tiny tests. + Returns a dictionary of status. + """ + status = { + "Gemini": False, + "DeepTranslate": False, + "Ollama": False + } + + print("Checking Services...") + + # 1. Check Gemini (Real Test) + gemini_key = os.getenv("GEMINI_API_KEY") + if gemini_key: + try: + # We import here to avoid global import issues if dependencies are missing + from google import genai + client = genai.Client(api_key=gemini_key) + # Try a very cheap call + client.models.generate_content( + model="gemini-2.0-flash", + contents="Hi" + ) + status["Gemini"] = True + except Exception as e: + # Check for rate limit in string representation + if "429" in str(e) or "RESOURCE_EXHAUSTED" in str(e): + # It is technically 'configured' but currently useless + status["Gemini"] = False + else: + status["Gemini"] = False + + # 2. Check DeepTranslate (Real Test) + try: + from deep_translator import GoogleTranslator + GoogleTranslator(source='auto', target='en').translate("hola") + status["DeepTranslate"] = True + except Exception: + status["DeepTranslate"] = False + + # 3. Check Ollama + if is_port_open("127.0.0.1", 11434): + status["Ollama"] = True + + # Print Report + gemini_msg = "[READY]" if status['Gemini'] else "[UNAVAILABLE] (Rate Limited or Key Invalid)" + if not gemini_key: gemini_msg = "[UNAVAILABLE] (Key missing)" + + print(f"1. Gemini API: {gemini_msg}") + print(f"2. DeepTranslate: {'[READY]' if status['DeepTranslate'] else '[UNAVAILABLE] (Network/Block)'}") + print(f"3. Local Ollama: {'[READY]' if status['Ollama'] else '[OFFLINE]'}") + + return status + +def check_path_permissions(directory_path): + """ + Checks if the script has read and write permissions for the given directory. + Returns: (bool, message) + """ + if not os.path.exists(directory_path): + return False, f"Path not found: {directory_path}" + + # If it's a file, check parent directory + if os.path.isfile(directory_path): + directory_path = os.path.dirname(directory_path) + + test_file = os.path.join(directory_path, ".perm_test_tmp") + + try: + # Test Write + with open(test_file, "w") as f: + f.write("test") + + # Test Read + with open(test_file, "r") as f: + content = f.read() + + # Cleanup + os.remove(test_file) + + if content == "test": + return True, f" [Permissions] Read/Write OK: {directory_path}" + else: + return False, f" [Permissions] Read check failed (content mismatch): {directory_path}" + + except PermissionError: + return False, f" ❌ [Permissions] DENIED: Cannot write to {directory_path}. Check ownership/mount options." + except Exception as e: + return False, f" ❌ [Permissions] Error checking {directory_path}: {e}" def validate_and_repair_srt(srt_path): """ @@ -26,3 +241,53 @@ def validate_and_repair_srt(srt_path): except Exception as e: print(f"Warning: SRT validation failed: {e}") return False + +def check_srt_duration_match(source_srt_path, target_srt_path, tolerance_seconds=30.0, tolerance_percent=0.10): + """ + Compares the duration of two SRT files to ensure they cover roughly the same timeframe. + Useful for detecting partial translations. + + Args: + source_srt_path (str): Path to the original language SRT. + target_srt_path (str): Path to the translated SRT. + tolerance_seconds (float): Max allowed difference in seconds. + tolerance_percent (float): Max allowed difference as a percentage of source duration. + + Returns: + tuple: (bool, str) -> (passed, message) + """ + if not os.path.exists(source_srt_path) or not os.path.exists(target_srt_path): + return False, "One or both SRT files missing." + + try: + source_subs = pysubs2.load(source_srt_path) + target_subs = pysubs2.load(target_srt_path) + except Exception as e: + return False, f"Error parsing SRTs: {e}" + + if not source_subs: + return False, "Source SRT is empty." + if not target_subs: + return False, "Target SRT is empty." + + # Get the end timestamp of the last event in each file (in milliseconds) + source_end = source_subs[-1].end + target_end = target_subs[-1].end + + # Convert to seconds + source_duration = source_end / 1000.0 + target_duration = target_end / 1000.0 + + diff = abs(source_duration - target_duration) + + # Check absolute difference + if diff > tolerance_seconds: + # Also check percentage (for very long videos, 30s might be negligible) + if source_duration > 0 and (diff / source_duration) > tolerance_percent: + return False, f"Duration mismatch: Source={source_duration:.1f}s, Target={target_duration:.1f}s (Diff={diff:.1f}s)" + + # For short videos, if percentage is high, fail + if source_duration < 300 and (diff / source_duration) > 0.20: + return False, f"Duration mismatch (short video): Source={source_duration:.1f}s, Target={target_duration:.1f}s" + + return True, f"Duration match verified (Diff={diff:.1f}s)" diff --git a/video_transcription/recover_and_fix_v2.py b/video_transcription/recover_and_fix_v2.py deleted file mode 100755 index ee89e4f..0000000 --- a/video_transcription/recover_and_fix_v2.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import argparse -import subprocess -from dotenv import load_dotenv -from datetime import datetime -import pysubs2 -from deep_translator import GoogleTranslator - -# Load config -script_dir = os.path.dirname(os.path.abspath(__file__)) -env_path = os.path.abspath(os.path.join(script_dir, '../.env_files/.env.aitranscribe')) -if os.path.exists(env_path): - load_dotenv(env_path) -else: - load_dotenv() - -# Add ai_transcriber_v2 to path so we can import modules -sys.path.append(os.path.join(script_dir, 'ai_transcriber_v2')) - -from ai_transcriber_v2.translator import translate_srt -from ai_transcriber_v2.utils import validate_and_repair_srt - -def translate_fallback_free(source_srt_path, output_srt_path, target_lang="en"): - """ - Fallback translation using deep-translator (free Google Translate). - Parses SRT, translates text line-by-line, and saves new SRT. - """ - print(f" [Free Fallback] Translating {source_srt_path}...") - - subs = None - encodings_to_try = ['utf-8', 'shift_jis', 'euc_jp', 'latin-1', 'cp1252', 'utf-16'] - - for enc in encodings_to_try: - try: - subs = pysubs2.load(source_srt_path, encoding=enc) - break - except Exception: - continue - - if subs is None: - print(f" [Free Fallback] Critical Error: Could not decode file with standard encodings.") - return False - - try: - translator = GoogleTranslator(source='auto', target=target_lang) - - for line in subs: - text = line.text.strip() - if text: - # Sanity check: Skip lines that are too long (likely garbage/corruption) - if len(text) > 4000: - print(f" Warning: Skipping line with excessive length ({len(text)} chars). Likely corrupted.") - continue - - try: - original_text = text.replace(r"\N", " ") - translated_text = translator.translate(original_text) - if translated_text: - line.text = translated_text - except Exception as e: - print(f" Warning: Failed to translate line: {e}") - - subs.save(output_srt_path) - print(f" [Free Fallback] Saved to {output_srt_path}") - return True - except Exception as e: - print(f" [Free Fallback] Critical Error: {e}") - return False - -def re_embed_subtitles(video_path, srt_path, output_path=None): - """ - Re-embeds subtitles into an EXISTING video file, replacing the old tracks. - Uses robust flags to handle bad metadata. - """ - if not os.path.exists(video_path) or not os.path.exists(srt_path): - print("Error: Video or SRT file not found.") - return False - - temp_output = video_path + ".temp.mp4" - print(f"Re-embedding subtitles into: {video_path}...") - - sub_codec = "mov_text" if video_path.lower().endswith(".mp4") else "srt" - - command = [ - "ffmpeg", - "-ignore_editlist", "1", - "-i", video_path, - "-i", srt_path, - "-map", "0:v", - "-map", "0:a", - "-map", "1:0", - "-c", "copy", - "-c:s", sub_codec, - "-disposition:s:0", "default", - "-metadata:s:s:0", "language=eng", - "-metadata:s:s:0", "title=English (AI Translated)", - "-max_interleave_delta", "0", - "-avoid_negative_ts", "make_zero", - "-y", - "-v", "error", - temp_output - ] - - try: - subprocess.run(command, check=True) - os.replace(temp_output, video_path) - print(f"✅ Fixed: {video_path}") - return True - except subprocess.CalledProcessError as e: - print(f"Error re-embedding: {e}") - if os.path.exists(temp_output): - os.remove(temp_output) - return False - -def process_recovery(folder_path, target_lang="English", prefer_deep=False): - print(f"Scanning {folder_path} for incomplete translations (V2)...") - if prefer_deep: - print("Preference: DeepTranslate (Google Translate Free) > Gemini") - else: - print("Preference: Gemini (API) > DeepTranslate") - - recovery_log_file = os.path.join(folder_path, "recovery_status.log") - print(f"Logging actions to: {recovery_log_file}") - - count_fixed = 0 - video_extensions = ('.mp4', '.mkv', '.mov', '.avi') - - for root, dirs, files in os.walk(folder_path): - for file in files: - if file.endswith(".srt") and \ - not file.endswith(f".{target_lang}.srt") and \ - not file.endswith(f".{target_lang}.deep_translate.srt"): - - source_srt_path = os.path.join(root, file) - base_name = os.path.splitext(file)[0] - - path_gemini = os.path.join(root, f"{base_name}.{target_lang}.srt") - path_deep = os.path.join(root, f"{base_name}.{target_lang}.deep_translate.srt") - - if os.path.exists(path_gemini) or os.path.exists(path_deep): - continue - - print(f"\nFound untranslated transcript: {file}") - - content = None - # extended list to include common Japanese encodings - encodings_to_try = ['utf-8', 'shift_jis', 'euc_jp', 'latin-1', 'cp1252', 'utf-16'] - - for enc in encodings_to_try: - try: - with open(source_srt_path, "r", encoding=enc) as f: - content = f.read() - break # Success - except UnicodeDecodeError: - continue - - if content is None: - print(f"❌ Error: Could not decode {file} with any standard encoding. Skipping.") - continue - - # Helpers for translation attempts - def try_gemini(): - res = translate_srt(content, target_language=target_lang) - if res: - with open(path_gemini, "w", encoding="utf-8") as f: - f.write(res) - return True, path_gemini, "Gemini" - return False, None, None - - def try_deep(): - lang_map = { - "English": "en", "French": "fr", "Spanish": "es", - "German": "de", "Italian": "it", "Portuguese": "pt", - "Russian": "ru", "Japanese": "ja", "Chinese": "zh-CN" - } - target_code = lang_map.get(target_lang, "en") - if translate_fallback_free(source_srt_path, path_deep, target_lang=target_code): - return True, path_deep, "DeepTranslate" - return False, None, None - - success = False - method_used = "None" - final_srt_path = None - - if prefer_deep: - # 1. Try DeepTranslate - success, final_srt_path, method_used = try_deep() - if not success: - print("❌ DeepTranslate failed. Attempting Gemini fallback...") - success, final_srt_path, method_used = try_gemini() - else: - # 1. Try Gemini - success, final_srt_path, method_used = try_gemini() - if not success: - print("❌ Gemini API failed. Attempting Free Fallback...") - success, final_srt_path, method_used = try_deep() - - if success and final_srt_path: - # Log result - with open(recovery_log_file, "a", encoding="utf-8") as log: - log.write(f"{datetime.now().isoformat()} | {method_used} | {file} -> {os.path.basename(final_srt_path)}\n") - - validate_and_repair_srt(final_srt_path) - - video_candidates = [ - os.path.join(root, base_name + ".mp4"), - os.path.join(root, base_name + ".mkv"), - os.path.join(root, base_name + ".subbed.mp4"), - ] - - found_video = None - for v in video_candidates: - if os.path.exists(v): - found_video = v - break - - if found_video: - print(f"Found video to fix: {found_video}") - if re_embed_subtitles(found_video, final_srt_path): - count_fixed += 1 - else: - print("Warning: Could not find a corresponding video file to embed into.") - else: - print("❌ All translation methods failed. Skipping.") - - print(f"\nRecovery Complete. Fixed {count_fixed} files.") - -if __name__ == "__main__": - - parser = argparse.ArgumentParser(description="Recover and Fix Translations (V2)") - - parser.add_argument("folders", nargs='+', help="One or more paths to folders to scan") - - parser.add_argument("--lang", default="English", help="Target language (default: English)") - - parser.add_argument("--prefer-deep", action="store_true", help="Prefer DeepTranslate (Free) over Gemini API") - - - - args = parser.parse_args() - - - - for folder in args.folders: - - if os.path.exists(folder): - - process_recovery(folder, args.lang, args.prefer_deep) - - else: - - print(f"Error: Folder '{folder}' does not exist. Skipping.") \ No newline at end of file