mudado a config do encoder para melhor qualidade e menor tamanho
This commit is contained in:
@@ -3,7 +3,7 @@ from modules import file_manager, renamer, encoder
|
|||||||
|
|
||||||
st.set_page_config(page_title="PyMedia Manager", layout="wide", page_icon="🎬")
|
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
|
# CSS para melhorar visual
|
||||||
st.markdown("""
|
st.markdown("""
|
||||||
|
|||||||
@@ -13,45 +13,33 @@ ROOT_DIR = "/downloads"
|
|||||||
OUTPUT_BASE = "/downloads/finalizados"
|
OUTPUT_BASE = "/downloads/finalizados"
|
||||||
STATUS_FILE = "/app/data/encoder_status.json"
|
STATUS_FILE = "/app/data/encoder_status.json"
|
||||||
|
|
||||||
# --- GERENCIAMENTO DE ESTADO EM DISCO ---
|
# --- STATUS ---
|
||||||
def save_status(data):
|
def save_status(data):
|
||||||
"""Salva o estado atual no disco para sobreviver ao F5"""
|
|
||||||
try:
|
try:
|
||||||
with open(STATUS_FILE, 'w') as f:
|
with open(STATUS_FILE, 'w') as f: json.dump(data, f)
|
||||||
json.dump(data, f)
|
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
def load_status():
|
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:
|
try:
|
||||||
with open(STATUS_FILE, 'r') as f:
|
with open(STATUS_FILE, 'r') as f: return json.load(f)
|
||||||
return json.load(f)
|
except: return None
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def reset_status():
|
def reset_status():
|
||||||
"""Reseta o status para 'parado'"""
|
|
||||||
save_status({
|
save_status({
|
||||||
"is_running": False,
|
"is_running": False, "current_file": "", "progress": 0,
|
||||||
"current_file": "",
|
"total_progress": 0, "log": "Aguardando...", "stop_requested": False
|
||||||
"progress": 0,
|
|
||||||
"total_progress": 0,
|
|
||||||
"log": "Aguardando...",
|
|
||||||
"stop_requested": False
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# --- WORKER EM BACKGROUD ---
|
# --- WORKER ---
|
||||||
class BackgroundWorker(threading.Thread):
|
class BackgroundWorker(threading.Thread):
|
||||||
def __init__(self, input_folder, delete_orig):
|
def __init__(self, input_folder, delete_orig):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.input_folder = input_folder
|
self.input_folder = input_folder
|
||||||
self.delete_orig = delete_orig
|
self.delete_orig = delete_orig
|
||||||
self.daemon = True # Daemon threads rodam em background independente da sessão
|
self.daemon = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# 1. Preparação
|
|
||||||
prepare_driver_environment()
|
prepare_driver_environment()
|
||||||
|
|
||||||
files_to_process = []
|
files_to_process = []
|
||||||
@@ -62,36 +50,26 @@ class BackgroundWorker(threading.Thread):
|
|||||||
files_to_process.append(os.path.join(r, file))
|
files_to_process.append(os.path.join(r, file))
|
||||||
|
|
||||||
total = len(files_to_process)
|
total = len(files_to_process)
|
||||||
|
|
||||||
# Salva estado inicial
|
|
||||||
state = {
|
state = {
|
||||||
"is_running": True,
|
"is_running": True, "current_file": "Iniciando...",
|
||||||
"current_file": "Iniciando...",
|
"progress": 0, "total_progress": 0, "log": "Preparando fila...",
|
||||||
"progress": 0,
|
|
||||||
"total_progress": 0,
|
|
||||||
"log": "Preparando fila...",
|
|
||||||
"stop_requested": False
|
"stop_requested": False
|
||||||
}
|
}
|
||||||
save_status(state)
|
save_status(state)
|
||||||
|
|
||||||
# 2. Loop de Arquivos
|
|
||||||
for i, fpath in enumerate(files_to_process):
|
for i, fpath in enumerate(files_to_process):
|
||||||
# Verifica se pediram pra parar
|
|
||||||
current_state = load_status()
|
current_state = load_status()
|
||||||
if current_state and current_state.get("stop_requested"):
|
if current_state and current_state.get("stop_requested"): break
|
||||||
break
|
|
||||||
|
|
||||||
fname = os.path.basename(fpath)
|
fname = os.path.basename(fpath)
|
||||||
duration = get_video_duration(fpath)
|
duration = get_video_duration(fpath)
|
||||||
|
|
||||||
# Atualiza estado para arquivo atual
|
|
||||||
state["current_file"] = fname
|
state["current_file"] = fname
|
||||||
state["progress"] = 0
|
state["progress"] = 0
|
||||||
state["total_progress"] = int((i / total) * 100)
|
state["total_progress"] = int((i / total) * 100)
|
||||||
state["log"] = "Convertendo..."
|
state["log"] = "Convertendo..."
|
||||||
save_status(state)
|
save_status(state)
|
||||||
|
|
||||||
# Caminhos
|
|
||||||
rel_path = os.path.relpath(fpath, self.input_folder)
|
rel_path = os.path.relpath(fpath, self.input_folder)
|
||||||
folder_name = os.path.basename(self.input_folder)
|
folder_name = os.path.basename(self.input_folder)
|
||||||
if self.input_folder == ROOT_DIR:
|
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)
|
os.makedirs(os.path.dirname(out_full_path), exist_ok=True)
|
||||||
|
|
||||||
# Comando FFmpeg
|
# --- COMANDO FFMPEG OTIMIZADO (CQP) ---
|
||||||
cmd = [
|
cmd = [
|
||||||
"ffmpeg", "-y",
|
"ffmpeg", "-y",
|
||||||
"-hwaccel", "vaapi",
|
"-hwaccel", "vaapi",
|
||||||
@@ -111,20 +89,28 @@ class BackgroundWorker(threading.Thread):
|
|||||||
]
|
]
|
||||||
cmd += get_streams_map(fpath)
|
cmd += get_streams_map(fpath)
|
||||||
cmd += [
|
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",
|
"-c:a", "copy", "-c:s", "copy",
|
||||||
out_full_path
|
out_full_path
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Executa
|
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
text=True, bufsize=1, universal_newlines=True, env=os.environ
|
text=True, bufsize=1, universal_newlines=True, env=os.environ
|
||||||
)
|
)
|
||||||
|
|
||||||
for line in process.stdout:
|
for line in process.stdout:
|
||||||
# Checa stop a cada linha lida (para resposta rápida)
|
|
||||||
chk_state = load_status()
|
chk_state = load_status()
|
||||||
if chk_state and chk_state.get("stop_requested"):
|
if chk_state and chk_state.get("stop_requested"):
|
||||||
process.terminate()
|
process.terminate()
|
||||||
@@ -135,20 +121,14 @@ class BackgroundWorker(threading.Thread):
|
|||||||
if match:
|
if match:
|
||||||
secs = parse_time_to_seconds(match.group(1))
|
secs = parse_time_to_seconds(match.group(1))
|
||||||
pct = int((secs / duration) * 100)
|
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)
|
speed_match = re.search(r"speed=\s*(\S+)", line)
|
||||||
if speed_match:
|
if speed_match:
|
||||||
state["log"] = f"Velocidade: {speed_match.group(1)}"
|
state["log"] = f"Velocidade: {speed_match.group(1)}"
|
||||||
|
|
||||||
save_status(state)
|
save_status(state)
|
||||||
|
|
||||||
process.wait()
|
process.wait()
|
||||||
|
|
||||||
if process.returncode == 0:
|
if process.returncode == 0:
|
||||||
if self.delete_orig: os.remove(fpath)
|
if self.delete_orig: os.remove(fpath)
|
||||||
|
|
||||||
@@ -156,10 +136,12 @@ class BackgroundWorker(threading.Thread):
|
|||||||
state["log"] = f"Erro: {str(e)}"
|
state["log"] = f"Erro: {str(e)}"
|
||||||
save_status(state)
|
save_status(state)
|
||||||
|
|
||||||
# Fim
|
|
||||||
reset_status()
|
reset_status()
|
||||||
|
|
||||||
# --- FUNÇÕES AUXILIARES ---
|
# --- AUXILIARES ---
|
||||||
|
@st.cache_resource
|
||||||
|
def get_manager(): return None # Placeholder se precisar
|
||||||
|
|
||||||
def prepare_driver_environment():
|
def prepare_driver_environment():
|
||||||
os.environ["LIBVA_DRIVER_NAME"] = "i965"
|
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"]
|
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:
|
try:
|
||||||
for root, dirs, files in os.walk(root_path):
|
for root, dirs, files in os.walk(root_path):
|
||||||
if "finalizados" in root or "temp" in root: continue
|
if "finalizados" in root or "temp" in root: continue
|
||||||
for d in dirs:
|
for d in dirs: folder_list.append(os.path.join(root, d))
|
||||||
folder_list.append(os.path.join(root, d))
|
|
||||||
except: pass
|
except: pass
|
||||||
return sorted(folder_list)
|
return sorted(folder_list)
|
||||||
|
|
||||||
@@ -209,15 +190,11 @@ def get_streams_map(filepath):
|
|||||||
map_args.extend(["-map", f"0:{s['index']}"])
|
map_args.extend(["-map", f"0:{s['index']}"])
|
||||||
return map_args
|
return map_args
|
||||||
|
|
||||||
# --- RENDERIZAÇÃO ---
|
# --- RENDER ---
|
||||||
def render():
|
def render():
|
||||||
st.header("⚙️ Encoder (Persistente)")
|
st.header("⚙️ Encoder (Persistente - CQP)")
|
||||||
st.info("O processo continua rodando mesmo se você fechar a página.")
|
|
||||||
|
|
||||||
# 1. Lê estado do disco
|
|
||||||
status = load_status()
|
status = load_status()
|
||||||
|
|
||||||
# Se não existe ou não está rodando
|
|
||||||
if not status or not status.get("is_running"):
|
if not status or not status.get("is_running"):
|
||||||
all_folders = get_all_subfolders(ROOT_DIR)
|
all_folders = get_all_subfolders(ROOT_DIR)
|
||||||
idx = 0
|
idx = 0
|
||||||
@@ -233,32 +210,23 @@ def render():
|
|||||||
if not os.path.exists(input_folder):
|
if not os.path.exists(input_folder):
|
||||||
st.error("Pasta inválida")
|
st.error("Pasta inválida")
|
||||||
else:
|
else:
|
||||||
# Inicia Thread
|
|
||||||
t = BackgroundWorker(input_folder, delete_orig)
|
t = BackgroundWorker(input_folder, delete_orig)
|
||||||
t.start()
|
t.start()
|
||||||
time.sleep(1) # Dá tempo de criar o arquivo json
|
time.sleep(1)
|
||||||
st.rerun()
|
st.rerun()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# MODO MONITORAMENTO
|
|
||||||
st.success("🔄 Sistema Rodando...")
|
st.success("🔄 Sistema Rodando...")
|
||||||
|
|
||||||
col1, col2 = st.columns([3, 1])
|
col1, col2 = st.columns([3, 1])
|
||||||
with col1:
|
with col1:
|
||||||
st.write(f"📁 **Processando:** `{status.get('current_file')}`")
|
st.write(f"📁 **Processando:** `{status.get('current_file')}`")
|
||||||
st.progress(status.get('progress', 0), text=f"Arquivo: {status.get('progress')}%")
|
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.progress(status.get('total_progress', 0), text=f"Total: {status.get('total_progress')}%")
|
||||||
st.caption(status.get('log'))
|
st.caption(status.get('log'))
|
||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
if st.button("🛑 Parar", type="secondary"):
|
if st.button("🛑 Parar"):
|
||||||
status["stop_requested"] = True
|
status["stop_requested"] = True
|
||||||
save_status(status)
|
save_status(status)
|
||||||
st.warning("Parando...")
|
st.warning("Parando...")
|
||||||
|
if st.button("🔄 Refresh"): st.rerun()
|
||||||
if st.button("🔄 Refresh"):
|
|
||||||
st.rerun()
|
|
||||||
|
|
||||||
# Atualiza a tela a cada 5s
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
st.rerun()
|
st.rerun()
|
||||||
Reference in New Issue
Block a user