2026-05-02 11:51:56 +03:00
"""
file_processor.py — JARVIS Universal File Processor
Supported types:
image → describe, ocr, resize, convert, compress, crop
pdf → summarize, extract_text, extract_pages, to_word
docx → summarize, extract_text, reformat, translate_hint
txt/md → summarize, reformat, translate_hint, word_count
csv → analyze, filter, sort, convert, stats
xlsx → analyze, filter, convert, stats
json → validate, format, extract, convert
code → explain, review, fix, run, document
audio → transcribe, trim, convert, info
video → trim, extract_audio, extract_frame, info, compress
zip → list, extract
pptx → summarize, extract_text, to_pdf
"""
import os
import re
import json
import shutil
import subprocess
import tempfile
from pathlib import Path
from datetime import datetime
2026-06-21 13:23:06 +03:00
def _get_api_key () -> str :
config_path = Path ( __file__ ) . resolve () . parent . parent / "config" / "api_keys.json"
with open ( config_path , "r" , encoding = "utf-8" ) as f :
return json . load ( f )[ "gemini_api_key" ]
2026-05-02 11:51:56 +03:00
def _gemini_client ():
2026-06-21 13:23:06 +03:00
from google import genai
_c = genai . Client ( api_key = _get_api_key ())
class _W :
def generate_content ( self , contents ):
return _c . models . generate_content ( model = "gemini-2.5-flash" , contents = contents )
return _W ()
2026-05-02 11:51:56 +03:00
def _detect_type ( path : Path ) -> str :
ext = path . suffix . lower () . lstrip ( "." )
image_exts = { "jpg" , "jpeg" , "png" , "gif" , "webp" , "bmp" , "tiff" , "svg" , "ico" }
video_exts = { "mp4" , "avi" , "mov" , "mkv" , "wmv" , "flv" , "webm" , "m4v" , "3gp" }
audio_exts = { "mp3" , "wav" , "ogg" , "m4a" , "aac" , "flac" , "wma" , "opus" }
code_exts = { "py" , "js" , "ts" , "jsx" , "tsx" , "html" , "css" , "java" , "c" ,
"cpp" , "cs" , "go" , "rs" , "rb" , "php" , "swift" , "kt" , "sh" ,
"bash" , "ps1" , "lua" , "r" , "m" , "sql" , "yaml" , "toml" }
archive_exts = { "zip" , "rar" , "tar" , "gz" , "7z" , "bz2" , "xz" }
if ext in image_exts : return "image"
if ext in video_exts : return "video"
if ext in audio_exts : return "audio"
if ext in code_exts : return "code"
if ext in archive_exts : return "archive"
if ext == "pdf" : return "pdf"
if ext in ( "docx" , "doc" ): return "docx"
if ext in ( "txt" , "md" , "rst" , "log" ): return "text"
if ext in ( "csv" , "tsv" ): return "csv"
if ext in ( "xlsx" , "xls" , "ods" ): return "excel"
if ext == "json" : return "json"
if ext == "xml" : return "xml"
if ext in ( "pptx" , "ppt" ): return "pptx"
return "unknown"
def _file_size_str ( path : Path ) -> str :
size = path . stat () . st_size
if size < 1024 : return f " { size } B"
if size < 1024 ** 2 : return f " { size / 1024 : .1f } KB"
if size < 1024 ** 3 : return f " { size / 1024 ** 2 : .1f } MB"
return f " { size / 1024 ** 3 : .1f } GB"
def _output_path ( src : Path , suffix : str , new_ext : str = None ) -> Path :
ext = new_ext or src . suffix
name = f " { src . stem } _ { suffix }{ ext } "
return src . parent / name
def _process_image ( path : Path , action : str , params : dict , speak = None ) -> str :
try :
from PIL import Image
except ImportError :
return "Pillow is not installed. Run: pip install Pillow"
action = action or "describe"
if action in ( "describe" , "ocr" , "analyze" , "read" , "extract_text" ):
try :
model = _gemini_client ()
img = Image . open ( path )
prompt = {
"describe" : "Describe this image in detail." ,
"ocr" : "Extract all text visible in this image. Return only the text, formatted clearly." ,
"analyze" : "Analyze this image thoroughly: objects, colors, composition, any text, context." ,
"read" : "Read all text in this image, preserving structure and formatting." ,
"extract_text" : "Extract all text from this image." ,
} . get ( action , "Describe this image." )
if params . get ( "instruction" ):
prompt = params [ "instruction" ]
response = model . generate_content ([ prompt , img ])
result = response . text . strip ()
if len ( result ) > 500 and params . get ( "save" , True ):
out = _output_path ( path , "result" , ".txt" )
out . write_text ( result , encoding = "utf-8" )
return f " { result [: 300 ] } ... \n\n Full result saved to: { out } "
return result
except Exception as e :
return f "AI image analysis failed: { e } "
if action == "resize" :
width = int ( params . get ( "width" , 0 ))
height = int ( params . get ( "height" , 0 ))
scale = float ( params . get ( "scale" , 0 ))
try :
img = Image . open ( path )
w , h = img . size
if scale :
new_size = ( int ( w * scale ), int ( h * scale ))
elif width and height :
new_size = ( width , height )
elif width :
new_size = ( width , int ( h * width / w ))
elif height :
new_size = ( int ( w * height / h ), height )
else :
return "Please specify width, height, or scale."
out = _output_path ( path , f "resized_ { new_size [ 0 ] } x { new_size [ 1 ] } " )
img . resize ( new_size , Image . LANCZOS ) . save ( out )
return f "Resized from { w } x { h } to { new_size [ 0 ] } x { new_size [ 1 ] } . Saved: { out . name } "
except Exception as e :
return f "Resize failed: { e } "
if action == "convert" :
fmt = params . get ( "format" , "png" ) . lower () . strip ( "." )
fmt_map = { "jpg" : "JPEG" , "jpeg" : "JPEG" , "png" : "PNG" ,
"webp" : "WEBP" , "bmp" : "BMP" , "tiff" : "TIFF" }
pil_fmt = fmt_map . get ( fmt , fmt . upper ())
try :
img = Image . open ( path ) . convert ( "RGB" ) if fmt == "jpg" else Image . open ( path )
out = _output_path ( path , "converted" , f ". { fmt } " )
img . save ( out , pil_fmt )
return f "Converted to { fmt . upper () } . Saved: { out . name } "
except Exception as e :
return f "Convert failed: { e } "
if action == "compress" :
quality = int ( params . get ( "quality" , 70 ))
try :
img = Image . open ( path ) . convert ( "RGB" )
out = _output_path ( path , f "compressed_q { quality } " , ".jpg" )
img . save ( out , "JPEG" , quality = quality , optimize = True )
before = _file_size_str ( path )
after = _file_size_str ( out )
return f "Compressed: { before } → { after } . Saved: { out . name } "
except Exception as e :
return f "Compress failed: { e } "
if action == "info" :
try :
img = Image . open ( path )
return ( f "Image info: { img . format } , { img . size [ 0 ] } x { img . size [ 1 ] } px, "
f "mode: { img . mode } , size: { _file_size_str ( path ) } " )
except Exception as e :
return f "Info failed: { e } "
return _process_image ( path , "describe" , { "instruction" : f " { action } : { params } " })
def _process_pdf ( path : Path , action : str , params : dict , speak = None ) -> str :
action = action or "summarize"
def _extract_pdf_text ( max_chars = 50000 ) -> str :
text = ""
try :
import pdfplumber
with pdfplumber . open ( path ) as pdf :
for page in pdf . pages :
text += ( page . extract_text () or "" ) + " \n "
except ImportError :
try :
import PyPDF2
with open ( path , "rb" ) as f :
reader = PyPDF2 . PdfReader ( f )
for page in reader . pages :
text += page . extract_text () + " \n "
except ImportError :
return ""
return text [: max_chars ]
if action in ( "summarize" , "extract_text" , "translate_hint" , "analyze" , "reformat" ):
text = _extract_pdf_text ()
if not text . strip ():
return "Could not extract text from PDF (may be scanned/image-based)."
if action == "extract_text" :
out = _output_path ( path , "text" , ".txt" )
out . write_text ( text , encoding = "utf-8" )
return f "Text extracted ( { len ( text ) } chars). Saved: { out . name } "
prompt_map = {
"summarize" : f "Summarize this PDF document concisely: \n\n { text } " ,
"analyze" : f "Analyze this document thoroughly: \n\n { text } " ,
"translate_hint" : f "What language is this document in and what does it say? Summarize: \n\n { text } " ,
"reformat" : f "Reformat this text cleanly with proper structure: \n\n { text } " ,
}
try :
model = _gemini_client ()
response = model . generate_content ( prompt_map . get ( action , f "Analyze: \n\n { text } " ))
result = response . text . strip ()
if len ( result ) > 600 and params . get ( "save" , True ):
out = _output_path ( path , action , ".txt" )
out . write_text ( result , encoding = "utf-8" )
return f " { result [: 400 ] } ... \n\n Full result saved: { out . name } "
return result
except Exception as e :
return f "AI analysis failed: { e } "
if action == "info" :
try :
import pdfplumber
with pdfplumber . open ( path ) as pdf :
pages = len ( pdf . pages )
return f "PDF: { pages } pages, size: { _file_size_str ( path ) } "
except Exception :
return f "PDF size: { _file_size_str ( path ) } "
if action == "to_word" :
text = _extract_pdf_text ()
if not text :
return "Could not extract text to convert."
try :
from docx import Document
doc = Document ()
doc . add_heading ( path . stem , 0 )
for para in text . split ( " \n\n " ):
if para . strip ():
doc . add_paragraph ( para . strip ())
out = _output_path ( path , "converted" , ".docx" )
doc . save ( out )
return f "Converted to Word document. Saved: { out . name } "
except ImportError :
return "python-docx not installed. Run: pip install python-docx"
return f "Unknown PDF action: ' { action } '. Try: summarize, extract_text, info, to_word"
def _process_text_doc ( path : Path , file_type : str , action : str ,
params : dict , speak = None ) -> str :
action = action or "summarize"
def _read_content () -> str :
if file_type == "docx" :
try :
from docx import Document
doc = Document ( path )
return " \n " . join ( p . text for p in doc . paragraphs )
except ImportError :
return "python-docx not installed."
except Exception as e :
return f "Read failed: { e } "
else :
return path . read_text ( encoding = "utf-8" , errors = "ignore" )
content = _read_content ()
if not content . strip ():
return "File appears to be empty."
if action == "word_count" :
words = len ( content . split ())
chars = len ( content )
lines = content . count ( " \n " )
return f "Word count: { words } words, { chars } characters, { lines } lines."
if action == "extract_text" :
if file_type != "txt" :
out = _output_path ( path , "extracted" , ".txt" )
out . write_text ( content , encoding = "utf-8" )
return f "Text extracted. Saved: { out . name } "
return content [: 2000 ]
instruction = params . get ( "instruction" , "" )
prompt_map = {
"summarize" : f "Summarize this document concisely: \n\n { content [: 40000 ] } " ,
"analyze" : f "Analyze this document: \n\n { content [: 40000 ] } " ,
"reformat" : f "Reformat this text with clean structure, proper headings and paragraphs: \n\n { content [: 40000 ] } " ,
"fix" : f "Fix grammar, spelling and style issues in this text: \n\n { content [: 40000 ] } " ,
"translate_hint" : f "What language is this and what does it say? Summarize: \n\n { content [: 10000 ] } " ,
"to_bullet" : f "Convert this text into a clear bullet-point summary: \n\n { content [: 40000 ] } " ,
"custom" : f " { instruction } \n\n { content [: 40000 ] } " ,
}
if action not in prompt_map :
action = "custom"
instruction = action
try :
model = _gemini_client ()
response = model . generate_content ( prompt_map [ action ])
result = response . text . strip ()
if len ( result ) > 600 and params . get ( "save" , True ):
out = _output_path ( path , action , ".txt" )
out . write_text ( result , encoding = "utf-8" )
return f " { result [: 400 ] } ... \n\n Full result saved: { out . name } "
return result
except Exception as e :
return f "AI processing failed: { e } "
def _process_data ( path : Path , file_type : str , action : str ,
params : dict , speak = None ) -> str :
try :
import pandas as pd
except ImportError :
return "pandas not installed. Run: pip install pandas openpyxl"
action = action or "analyze"
try :
if file_type == "csv" :
df = pd . read_csv ( path , encoding = "utf-8" , errors = "replace" )
else :
df = pd . read_excel ( path )
except Exception as e :
return f "Could not read file: { e } "
if action == "info" :
return ( f "Rows: { len ( df ) } , Columns: { len ( df . columns ) } \n "
f "Columns: { ', ' . join ( df . columns . tolist ()) } \n "
f "Size: { _file_size_str ( path ) } " )
if action == "stats" :
try :
desc = df . describe ( include = "all" ) . to_string ()
return f "Statistics: \n { desc [: 2000 ] } "
except Exception as e :
return f "Stats failed: { e } "
if action == "analyze" :
preview = df . head ( 50 ) . to_string ()
prompt = ( f "Analyze this dataset. Columns: { list ( df . columns ) } \n "
f "Rows: { len ( df ) } \n Preview: \n { preview } \n\n "
f "Give insights, patterns, and notable findings." )
try :
model = _gemini_client ()
response = model . generate_content ( prompt )
return response . text . strip ()
except Exception as e :
return f "AI analysis failed: { e } "
if action in ( "convert" , "to_csv" , "to_excel" , "to_json" ):
fmt = { "to_csv" : "csv" , "to_excel" : "xlsx" , "to_json" : "json" ,
"convert" : params . get ( "format" , "csv" )} . get ( action , "csv" )
try :
if fmt == "csv" :
out = _output_path ( path , "converted" , ".csv" )
df . to_csv ( out , index = False , encoding = "utf-8" )
elif fmt == "xlsx" :
out = _output_path ( path , "converted" , ".xlsx" )
df . to_excel ( out , index = False )
elif fmt == "json" :
out = _output_path ( path , "converted" , ".json" )
df . to_json ( out , orient = "records" , force_ascii = False , indent = 2 )
return f "Converted to { fmt . upper () } . Saved: { out . name } "
except Exception as e :
return f "Convert failed: { e } "
if action == "filter" :
col = params . get ( "column" , "" )
value = params . get ( "value" , "" )
condition = params . get ( "condition" , "equals" )
if not col or col not in df . columns :
return f "Column ' { col } ' not found. Available: { ', ' . join ( df . columns ) } "
try :
if condition == "equals" : filtered = df [ df [ col ] == value ]
elif condition == "contains" : filtered = df [ df [ col ] . astype ( str ) . str . contains ( str ( value ), case = False )]
elif condition == "gt" : filtered = df [ df [ col ] > float ( value )]
elif condition == "lt" : filtered = df [ df [ col ] < float ( value )]
else : filtered = df [ df [ col ] == value ]
out = _output_path ( path , "filtered" , ".csv" )
filtered . to_csv ( out , index = False )
return f "Filtered: { len ( filtered ) } rows match. Saved: { out . name } "
except Exception as e :
return f "Filter failed: { e } "
if action == "sort" :
col = params . get ( "column" , df . columns [ 0 ])
asc = params . get ( "ascending" , True )
try :
sorted_df = df . sort_values ( col , ascending = asc )
out = _output_path ( path , "sorted" , path . suffix )
sorted_df . to_csv ( out , index = False )
return f "Sorted by ' { col } '. Saved: { out . name } "
except Exception as e :
return f "Sort failed: { e } "
preview = df . head ( 30 ) . to_string ()
try :
model = _gemini_client ()
response = model . generate_content (
f "Task: { action } \n Dataset ( { len ( df ) } rows, cols: { list ( df . columns ) } ): \n { preview } "
)
return response . text . strip ()
except Exception as e :
return f "Processing failed: { e } "
def _process_json ( path : Path , action : str , params : dict , speak = None ) -> str :
action = action or "analyze"
try :
content = path . read_text ( encoding = "utf-8" )
data = json . loads ( content )
except Exception as e :
return f "Invalid JSON: { e } "
if action == "validate" :
return f "Valid JSON. Type: { type ( data ) . __name__ } , size: { _file_size_str ( path ) } "
if action == "format" :
out = _output_path ( path , "formatted" , ".json" )
out . write_text ( json . dumps ( data , indent = 2 , ensure_ascii = False ), encoding = "utf-8" )
return f "Formatted JSON saved: { out . name } "
if action in ( "analyze" , "summarize" , "extract" ):
preview = json . dumps ( data , indent = 2 , ensure_ascii = False )[: 8000 ]
prompt = f "Task: { action } this JSON data: \n { preview } "
if params . get ( "instruction" ):
prompt = f " { params [ 'instruction' ] } \n\n JSON data: \n { preview } "
try :
model = _gemini_client ()
response = model . generate_content ( prompt )
return response . text . strip ()
except Exception as e :
return f "AI processing failed: { e } "
if action == "to_csv" :
try :
import pandas as pd
if isinstance ( data , list ):
df = pd . DataFrame ( data )
out = _output_path ( path , "converted" , ".csv" )
df . to_csv ( out , index = False )
return f "Converted to CSV. Saved: { out . name } "
return "JSON must be an array of objects to convert to CSV."
except ImportError :
return "pandas not installed."
return _process_json ( path , "analyze" , { "instruction" : action })
def _process_code ( path : Path , action : str , params : dict , speak = None ) -> str :
action = action or "explain"
content = path . read_text ( encoding = "utf-8" , errors = "ignore" )
ext = path . suffix . lstrip ( "." )
if action == "run" :
if ext == "py" :
try :
result = subprocess . run (
[ "python" , str ( path )],
capture_output = True , text = True , timeout = 30
)
out = result . stdout or result . stderr
return f "Output: \n { out [: 2000 ] } " if out else "No output."
except subprocess . TimeoutExpired :
return "Execution timed out (30s)."
except Exception as e :
return f "Run failed: { e } "
return f "Direct execution not supported for . { ext } files."
if action == "info" :
lines = content . count ( " \n " )
words = len ( content . split ())
return f "Code file: { lines } lines, { words } words, { _file_size_str ( path ) } "
prompt_map = {
"explain" : f "Explain this { ext } code clearly: \n\n ``` { ext } \n { content [: 30000 ] } \n ```" ,
"review" : f "Review this { ext } code for bugs, issues, and improvements: \n\n ``` { ext } \n { content [: 30000 ] } \n ```" ,
"fix" : f "Fix any bugs in this { ext } code and return the corrected version: \n\n ``` { ext } \n { content [: 30000 ] } \n ```" ,
"optimize" : f "Optimize this { ext } code for performance and readability: \n\n ``` { ext } \n { content [: 30000 ] } \n ```" ,
"document" : f "Add proper documentation/comments to this { ext } code: \n\n ``` { ext } \n { content [: 30000 ] } \n ```" ,
"summarize" : f "Summarize what this { ext } code does: \n\n ``` { ext } \n { content [: 30000 ] } \n ```" ,
"test" : f "Write unit tests for this { ext } code: \n\n ``` { ext } \n { content [: 30000 ] } \n ```" ,
}
instruction = params . get ( "instruction" , "" )
if action not in prompt_map :
prompt = f " { action } \n\n ``` { ext } \n { content [: 30000 ] } \n ```"
if instruction :
prompt = f " { instruction } \n\n ``` { ext } \n { content [: 30000 ] } \n ```"
else :
prompt = prompt_map [ action ]
try :
model = _gemini_client ()
response = model . generate_content ( prompt )
result = response . text . strip ()
if action in ( "fix" , "optimize" , "document" ) and params . get ( "save" , True ):
out = _output_path ( path , action )
code_match = re . search ( r "```(?:\w+)?\n(.*?)```" , result , re . DOTALL )
code_to_save = code_match . group ( 1 ) if code_match else result
out . write_text ( code_to_save , encoding = "utf-8" )
return f " { result [: 400 ] } ... \n\n Saved: { out . name } "
return result
except Exception as e :
return f "AI processing failed: { e } "
def _process_audio ( path : Path , action : str , params : dict , speak = None ) -> str :
action = action or "transcribe"
if action == "info" :
try :
from pydub import AudioSegment
audio = AudioSegment . from_file ( path )
duration = len ( audio ) / 1000
mins , secs = divmod ( int ( duration ), 60 )
return ( f "Audio: { mins } m { secs } s, "
f " { audio . channels } ch, "
f " { audio . frame_rate } Hz, "
f " { _file_size_str ( path ) } " )
except ImportError :
return f "Audio file: { _file_size_str ( path ) } (install pydub for more info)"
except Exception as e :
return f "Info failed: { e } "
if action == "transcribe" :
try :
model = _gemini_client ()
content = path . read_bytes ()
mime = {
"mp3" : "audio/mp3" , "wav" : "audio/wav" ,
"ogg" : "audio/ogg" , "m4a" : "audio/mp4" ,
"aac" : "audio/aac" , "flac" : "audio/flac" ,
} . get ( path . suffix . lstrip ( "." ) . lower (), "audio/mpeg" )
response = model . generate_content ([
"Transcribe all speech in this audio file accurately." ,
{ "mime_type" : mime , "data" : content }
])
result = response . text . strip ()
if params . get ( "save" , True ):
out = _output_path ( path , "transcript" , ".txt" )
out . write_text ( result , encoding = "utf-8" )
return f "Transcription saved: { out . name } \n\n Preview: { result [: 300 ] } "
return result
except Exception as e :
return f "Transcription failed: { e } "
if action == "convert" :
fmt = params . get ( "format" , "mp3" ) . lstrip ( "." )
try :
from pydub import AudioSegment
audio = AudioSegment . from_file ( path )
out = _output_path ( path , "converted" , f ". { fmt } " )
audio . export ( out , format = fmt )
return f "Converted to { fmt . upper () } . Saved: { out . name } "
except ImportError :
return "pydub not installed. Run: pip install pydub"
except Exception as e :
return f "Convert failed: { e } "
if action == "trim" :
start = float ( params . get ( "start" , 0 ))
end = float ( params . get ( "end" , 0 ))
try :
from pydub import AudioSegment
audio = AudioSegment . from_file ( path )
end_ms = int ( end * 1000 ) if end else len ( audio )
trimmed = audio [ int ( start * 1000 ): end_ms ]
out = _output_path ( path , f "trim_ { int ( start ) } s_ { int ( end ) } s" )
trimmed . export ( out , format = path . suffix . lstrip ( "." ))
return f "Trimmed audio ( { int ( start ) } s– { int ( end ) } s). Saved: { out . name } "
except ImportError :
return "pydub not installed."
except Exception as e :
return f "Trim failed: { e } "
return f "Unknown audio action: ' { action } '. Try: transcribe, info, convert, trim"
def _process_video ( path : Path , action : str , params : dict , speak = None ) -> str :
action = action or "info"
def _ffmpeg_available () -> bool :
try :
subprocess . run ([ "ffmpeg" , "-version" ], capture_output = True , timeout = 3 )
return True
except Exception :
return False
if action == "info" :
try :
result = subprocess . run (
[ "ffprobe" , "-v" , "quiet" , "-print_format" , "json" ,
"-show_format" , "-show_streams" , str ( path )],
capture_output = True , text = True , timeout = 10
)
data = json . loads ( result . stdout )
fmt = data . get ( "format" , {})
duration = float ( fmt . get ( "duration" , 0 ))
mins , secs = divmod ( int ( duration ), 60 )
size = _file_size_str ( path )
streams = data . get ( "streams" , [])
video_s = next (( s for s in streams if s [ "codec_type" ] == "video" ), {})
w = video_s . get ( "width" , "?" )
h = video_s . get ( "height" , "?" )
fps = video_s . get ( "r_frame_rate" , "?" )
return f "Video: { mins } m { secs } s, { w } x { h } , { fps } fps, { size } "
except Exception :
return f "Video file: { _file_size_str ( path ) } "
if action == "extract_audio" :
if not _ffmpeg_available ():
return "ffmpeg not found. Install ffmpeg to extract audio."
out = _output_path ( path , "audio" , ".mp3" )
try :
subprocess . run (
[ "ffmpeg" , "-i" , str ( path ), "-q:a" , "0" , "-map" , "a" , str ( out ), "-y" ],
capture_output = True , timeout = 300
)
return f "Audio extracted. Saved: { out . name } "
except Exception as e :
return f "Extract audio failed: { e } "
if action == "trim" :
start = params . get ( "start" , "00:00:00" )
end = params . get ( "end" , "" )
if not _ffmpeg_available ():
return "ffmpeg not found."
out = _output_path ( path , f "trim" , path . suffix )
try :
cmd = [ "ffmpeg" , "-i" , str ( path ), "-ss" , str ( start )]
if end :
cmd += [ "-to" , str ( end )]
cmd += [ "-c" , "copy" , str ( out ), "-y" ]
subprocess . run ( cmd , capture_output = True , timeout = 600 )
return f "Trimmed video saved: { out . name } "
except Exception as e :
return f "Trim failed: { e } "
if action == "extract_frame" :
timestamp = params . get ( "timestamp" , "00:00:01" )
if not _ffmpeg_available ():
return "ffmpeg not found."
out = _output_path ( path , f "frame_ { timestamp . replace ( ':' , '' ) } " , ".jpg" )
try :
subprocess . run (
[ "ffmpeg" , "-i" , str ( path ), "-ss" , timestamp ,
"-vframes" , "1" , str ( out ), "-y" ],
capture_output = True , timeout = 30
)
return f "Frame extracted at { timestamp } . Saved: { out . name } "
except Exception as e :
return f "Extract frame failed: { e } "
if action == "compress" :
crf = int ( params . get ( "quality" , 28 ))
if not _ffmpeg_available ():
return "ffmpeg not found."
out = _output_path ( path , f "compressed_crf { crf } " , ".mp4" )
try :
subprocess . run (
[ "ffmpeg" , "-i" , str ( path ),
"-c:v" , "libx264" , "-crf" , str ( crf ),
"-preset" , "medium" , "-c:a" , "copy" ,
str ( out ), "-y" ],
capture_output = True , timeout = 1800
)
before = _file_size_str ( path )
after = _file_size_str ( out )
return f "Compressed: { before } → { after } . Saved: { out . name } "
except Exception as e :
return f "Compress failed: { e } "
if action == "transcribe" :
if not _ffmpeg_available ():
return "ffmpeg not found. Needed for video transcription."
tmp_audio = Path ( tempfile . mktemp ( suffix = ".mp3" ))
try :
subprocess . run (
[ "ffmpeg" , "-i" , str ( path ), "-q:a" , "0" , "-map" , "a" ,
str ( tmp_audio ), "-y" ],
capture_output = True , timeout = 300
)
result = _process_audio ( tmp_audio , "transcribe" , params , speak )
return result
except Exception as e :
return f "Video transcription failed: { e } "
finally :
if tmp_audio . exists ():
tmp_audio . unlink ()
if action == "convert" :
fmt = params . get ( "format" , "mp4" ) . lstrip ( "." )
if not _ffmpeg_available ():
return "ffmpeg not found."
out = _output_path ( path , "converted" , f ". { fmt } " )
try :
subprocess . run (
[ "ffmpeg" , "-i" , str ( path ), str ( out ), "-y" ],
capture_output = True , timeout = 1800
)
return f "Converted to { fmt . upper () } . Saved: { out . name } "
except Exception as e :
return f "Convert failed: { e } "
return f "Unknown video action: ' { action } '. Try: info, trim, extract_audio, extract_frame, compress, transcribe, convert"
def _process_archive ( path : Path , action : str , params : dict , speak = None ) -> str :
action = action or "list"
if action == "list" :
try :
import zipfile , tarfile
ext = path . suffix . lower ()
if ext == ".zip" :
with zipfile . ZipFile ( path ) as z :
names = z . namelist ()
elif ext in ( ".tar" , ".gz" , ".bz2" , ".xz" ):
with tarfile . open ( path ) as t :
names = t . getnames ()
else :
return f "Unsupported archive format: { ext } "
preview = " \n " . join ( names [: 30 ])
suffix = f " \n ... and { len ( names ) - 30 } more" if len ( names ) > 30 else ""
return f "Archive contains { len ( names ) } files: \n { preview }{ suffix } "
except Exception as e :
return f "List failed: { e } "
if action == "extract" :
dest = Path ( params . get ( "destination" , str ( path . parent / path . stem )))
dest . mkdir ( parents = True , exist_ok = True )
try :
shutil . unpack_archive ( path , dest )
return f "Extracted to: { dest } "
except Exception as e :
return f "Extract failed: { e } "
return f "Unknown archive action: ' { action } '. Try: list, extract"
def _process_pptx ( path : Path , action : str , params : dict , speak = None ) -> str :
action = action or "summarize"
def _read_pptx_text () -> str :
try :
from pptx import Presentation
prs = Presentation ( path )
text = []
for i , slide in enumerate ( prs . slides , 1 ):
slide_text = f " \n --- Slide { i } --- \n "
for shape in slide . shapes :
if hasattr ( shape , "text" ) and shape . text . strip ():
slide_text += shape . text . strip () + " \n "
text . append ( slide_text )
return " \n " . join ( text )
except ImportError :
return "python-pptx not installed."
if action in ( "summarize" , "extract_text" , "analyze" ):
text = _read_pptx_text ()
if action == "extract_text" :
out = _output_path ( path , "text" , ".txt" )
out . write_text ( text , encoding = "utf-8" )
return f "Text extracted. Saved: { out . name } "
try :
model = _gemini_client ()
prompt = f " { 'Summarize' if action == 'summarize' else 'Analyze' } this presentation: \n { text [: 30000 ] } "
response = model . generate_content ( prompt )
return response . text . strip ()
except Exception as e :
return f "AI processing failed: { e } "
return f "Unknown PPTX action: ' { action } '. Try: summarize, extract_text, analyze"
def file_processor ( parameters : dict , player = None , speak = None ) -> str :
file_path_str = parameters . get ( "file_path" , "" ) . strip ()
if not file_path_str :
return "No file path provided."
path = Path ( file_path_str )
if not path . exists ():
return f "File not found: { file_path_str } "
if not path . is_file ():
return f "Path is not a file: { file_path_str } "
file_type = _detect_type ( path )
action = ( parameters . get ( "action" ) or "" ) . lower () . strip ()
instruction = parameters . get ( "instruction" , "" )
params = { ** parameters , "instruction" : instruction }
log_msg = f "[FileProcessor] { file_type . upper () } | { path . name } | action= { action or 'auto' } "
print ( log_msg )
if player :
player . write_log ( log_msg )
if file_type == "unknown" :
try :
content = path . read_text ( encoding = "utf-8" , errors = "ignore" )[: 10000 ]
model = _gemini_client ()
prompt = f "File: { path . name } \n Content preview: \n { content } \n\n Task: { action or instruction or 'Describe what this file contains and what can be done with it.' } "
response = model . generate_content ( prompt )
return response . text . strip ()
except Exception as e :
return f "Unknown file type ( { path . suffix } ). Could not process: { e } "
dispatch = {
"image" : _process_image ,
"pdf" : _process_pdf ,
"docx" : lambda p , a , pm , s : _process_text_doc ( p , "docx" , a , pm , s ),
"text" : lambda p , a , pm , s : _process_text_doc ( p , "text" , a , pm , s ),
"csv" : lambda p , a , pm , s : _process_data ( p , "csv" , a , pm , s ),
"excel" : lambda p , a , pm , s : _process_data ( p , "excel" , a , pm , s ),
"json" : _process_json ,
"xml" : lambda p , a , pm , s : _process_json ( p , a , pm , s ),
"code" : _process_code ,
"audio" : _process_audio ,
"video" : _process_video ,
"archive" : _process_archive ,
"pptx" : _process_pptx ,
}
handler = dispatch . get ( file_type )
if not handler :
return f "Unsupported file type: { file_type } "
try :
result = handler ( path , action , params , speak )
return result or "Done."
except Exception as e :
import traceback
traceback . print_exc ()
return f "Processing failed: { e } "