diff --git a/app/main.py b/app/main.py index d60513e..e7a4eba 100755 --- a/app/main.py +++ b/app/main.py @@ -3,7 +3,7 @@ from modules import file_manager, renamer, encoder st.set_page_config(page_title="PyMedia Manager", layout="wide", page_icon="🎬") -st.title("🎬 PyMedia Manager - Central de Controle") +st.title("🎬 PyMedia Manager Clei - Central de Controle") # CSS para melhorar visual st.markdown(""" diff --git a/app/modules/encoder.py b/app/modules/encoder.py index fe00b6c..78fa252 100755 --- a/app/modules/encoder.py +++ b/app/modules/encoder.py @@ -13,45 +13,33 @@ ROOT_DIR = "/downloads" OUTPUT_BASE = "/downloads/finalizados" STATUS_FILE = "/app/data/encoder_status.json" -# --- GERENCIAMENTO DE ESTADO EM DISCO --- +# --- STATUS --- def save_status(data): - """Salva o estado atual no disco para sobreviver ao F5""" try: - with open(STATUS_FILE, 'w') as f: - json.dump(data, f) + with open(STATUS_FILE, 'w') as f: json.dump(data, f) except: pass def load_status(): - """Lê o estado do disco""" - if not os.path.exists(STATUS_FILE): - return None + if not os.path.exists(STATUS_FILE): return None try: - with open(STATUS_FILE, 'r') as f: - return json.load(f) - except: - return None + with open(STATUS_FILE, 'r') as f: return json.load(f) + except: return None def reset_status(): - """Reseta o status para 'parado'""" save_status({ - "is_running": False, - "current_file": "", - "progress": 0, - "total_progress": 0, - "log": "Aguardando...", - "stop_requested": False + "is_running": False, "current_file": "", "progress": 0, + "total_progress": 0, "log": "Aguardando...", "stop_requested": False }) -# --- WORKER EM BACKGROUD --- +# --- WORKER --- class BackgroundWorker(threading.Thread): def __init__(self, input_folder, delete_orig): super().__init__() self.input_folder = input_folder self.delete_orig = delete_orig - self.daemon = True # Daemon threads rodam em background independente da sessão + self.daemon = True def run(self): - # 1. Preparação prepare_driver_environment() files_to_process = [] @@ -62,36 +50,26 @@ class BackgroundWorker(threading.Thread): files_to_process.append(os.path.join(r, file)) total = len(files_to_process) - - # Salva estado inicial state = { - "is_running": True, - "current_file": "Iniciando...", - "progress": 0, - "total_progress": 0, - "log": "Preparando fila...", + "is_running": True, "current_file": "Iniciando...", + "progress": 0, "total_progress": 0, "log": "Preparando fila...", "stop_requested": False } save_status(state) - # 2. Loop de Arquivos for i, fpath in enumerate(files_to_process): - # Verifica se pediram pra parar current_state = load_status() - if current_state and current_state.get("stop_requested"): - break + if current_state and current_state.get("stop_requested"): break fname = os.path.basename(fpath) duration = get_video_duration(fpath) - # Atualiza estado para arquivo atual state["current_file"] = fname state["progress"] = 0 state["total_progress"] = int((i / total) * 100) state["log"] = "Convertendo..." save_status(state) - # Caminhos rel_path = os.path.relpath(fpath, self.input_folder) folder_name = os.path.basename(self.input_folder) if self.input_folder == ROOT_DIR: @@ -101,7 +79,7 @@ class BackgroundWorker(threading.Thread): os.makedirs(os.path.dirname(out_full_path), exist_ok=True) - # Comando FFmpeg + # --- COMANDO FFMPEG OTIMIZADO (CQP) --- cmd = [ "ffmpeg", "-y", "-hwaccel", "vaapi", @@ -111,20 +89,28 @@ class BackgroundWorker(threading.Thread): ] cmd += get_streams_map(fpath) cmd += [ - "-c:v", "h264_vaapi", "-b:v", "4500k", "-compression_level", "1", + "-c:v", "h264_vaapi", + + # MUDANÇA AQUI: Modo CQP (Qualidade Constante) + # QP 25 é similar ao RF 22-23 do HandBrake. + # Menor número = Mais Qualidade/Maior Arquivo. + # Maior número = Menos Qualidade/Menor Arquivo. + "-qp", "25", + + # Compressão 0 é mais lenta mas gera melhor qualidade por bit + "-compression_level", "0", + "-c:a", "copy", "-c:s", "copy", out_full_path ] try: - # Executa process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True, env=os.environ ) for line in process.stdout: - # Checa stop a cada linha lida (para resposta rápida) chk_state = load_status() if chk_state and chk_state.get("stop_requested"): process.terminate() @@ -135,31 +121,27 @@ class BackgroundWorker(threading.Thread): if match: secs = parse_time_to_seconds(match.group(1)) pct = int((secs / duration) * 100) - if pct > 100: pct = 100 + state["progress"] = min(pct, 100) - # Atualiza status no disco - state["progress"] = pct - - # Pega velocidade speed_match = re.search(r"speed=\s*(\S+)", line) if speed_match: state["log"] = f"Velocidade: {speed_match.group(1)}" - save_status(state) process.wait() - if process.returncode == 0: if self.delete_orig: os.remove(fpath) except Exception as e: state["log"] = f"Erro: {str(e)}" save_status(state) - - # Fim + reset_status() -# --- FUNÇÕES AUXILIARES --- +# --- AUXILIARES --- +@st.cache_resource +def get_manager(): return None # Placeholder se precisar + def prepare_driver_environment(): os.environ["LIBVA_DRIVER_NAME"] = "i965" drivers_ruins = ["/usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so", "/usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so.1"] @@ -173,8 +155,7 @@ def get_all_subfolders(root_path): try: for root, dirs, files in os.walk(root_path): if "finalizados" in root or "temp" in root: continue - for d in dirs: - folder_list.append(os.path.join(root, d)) + for d in dirs: folder_list.append(os.path.join(root, d)) except: pass return sorted(folder_list) @@ -209,15 +190,11 @@ def get_streams_map(filepath): map_args.extend(["-map", f"0:{s['index']}"]) return map_args -# --- RENDERIZAÇÃO --- +# --- RENDER --- def render(): - st.header("⚙️ Encoder (Persistente)") - st.info("O processo continua rodando mesmo se você fechar a página.") - - # 1. Lê estado do disco + st.header("⚙️ Encoder (Persistente - CQP)") status = load_status() - # Se não existe ou não está rodando if not status or not status.get("is_running"): all_folders = get_all_subfolders(ROOT_DIR) idx = 0 @@ -233,32 +210,23 @@ def render(): if not os.path.exists(input_folder): st.error("Pasta inválida") else: - # Inicia Thread t = BackgroundWorker(input_folder, delete_orig) t.start() - time.sleep(1) # Dá tempo de criar o arquivo json + time.sleep(1) st.rerun() - else: - # MODO MONITORAMENTO st.success("🔄 Sistema Rodando...") - col1, col2 = st.columns([3, 1]) with col1: st.write(f"📁 **Processando:** `{status.get('current_file')}`") st.progress(status.get('progress', 0), text=f"Arquivo: {status.get('progress')}%") st.progress(status.get('total_progress', 0), text=f"Total: {status.get('total_progress')}%") st.caption(status.get('log')) - with col2: - if st.button("🛑 Parar", type="secondary"): + if st.button("🛑 Parar"): status["stop_requested"] = True save_status(status) st.warning("Parando...") - - if st.button("🔄 Refresh"): - st.rerun() - - # Atualiza a tela a cada 5s + if st.button("🔄 Refresh"): st.rerun() time.sleep(5) st.rerun() \ No newline at end of file