fixedxet / app.py
xplhack1's picture
Update app.py
608cdab verified
Raw History Blame Contribute Delete
10 kB
import gradio as gr
import os
import subprocess
import requests
from huggingface_hub import HfApi, hf_hub_download
import spaces
api = HfApi()
@spaces.GPU
def process_full_migration(token, source_dataset, target_dataset, folder_path):
if not token or not source_dataset or not target_dataset or not folder_path:
yield "❌ Error: Faltan datos obligatorios."
return
log_output = ""
def log(msg):
nonlocal log_output
log_output += msg + "\n"
print(msg, flush=True)
yield log_output
size_damaged_files = [] # Archivos < 50MB detectados por API JSON
repaired_files = [] # Archivos que requirieron re-codificación de emergencia
successful_files = [] # Archivos migrados con éxito
try:
folder_path = folder_path.strip().rstrip('/')
yield from log(f"🔍 Consultando API de Hugging Face para obtener metadatos y tamaños en: {folder_path}...")
headers = {"Authorization": f"Bearer {token}"} if token else {}
api_url = f"https://huggingface.co/api/datasets/{source_dataset}/tree/main/{folder_path}?recursive=true"
response = requests.get(api_url, headers=headers)
if response.status_code != 200:
yield from log(f"❌ Error al consultar la API de HF: {response.status_code} - {response.text}")
return
items = response.json()
if not isinstance(items, list):
yield from log(f"❌ Respuesta inesperada de la API: {items}")
return
# Filtrar solo archivos de video (.mp4 y .mkv)
video_items = [
item for item in items
if item.get("type") == "file" and (item.get("path", "").endswith('.mp4') or item.get("path", "").endswith('.mkv'))
]
total_files = len(video_items)
if total_files == 0:
yield from log(f"⚠️ No se encontraron archivos de video en la ruta: {folder_path}")
return
yield from log(f"📁 Se encontraron {total_files} videos en el JSON de la API. Analizando tamaños...\n")
for idx, item in enumerate(video_items, 1):
file_path = item["path"]
file_size_bytes = item.get("size", 0)
file_size_mb = round(file_size_bytes / (1024 * 1024), 2)
yield from log(f"==================================================")
yield from log(f"🎬 Procesando [{idx}/{total_files}]: {file_path}")
yield from log(f" 📏 Tamaño detectado en API: {file_size_mb} MB ({file_size_bytes} bytes)")
yield from log(f"==================================================")
# Validación directa por metadata de API (< 50 Megas) -> Omitir descarga, fix y subida
if file_size_bytes < (50 * 1024 * 1024):
yield from log(f"⚠️ Archivo DAÑADO O INCOMPLETO POR TAMAÑO: {file_size_mb} MB (< 50 MB). Omitiendo descarga y fix.")
size_damaged_files.append({"path": file_path, "size_mb": file_size_mb})
yield from log(f"⏭️ Archivo [{idx}/{total_files}] omitido.\n")
continue
is_mkv = file_path.endswith('.mkv')
output_ext = ".mkv" if is_mkv else ".mp4"
output_path = f"video_arreglado{output_ext}"
# Descargar archivo válido a la VPS
yield from log("📥 Descargando archivo al disco local de la VPS...")
try:
temp_local_path = hf_hub_download(
repo_id=source_dataset,
filename=file_path,
repo_type="dataset",
token=token
)
except Exception as e:
yield from log(f"❌ Error al descargar el archivo desde HF: {str(e)}")
continue
# Procesar con FFmpeg
if is_mkv:
yield from log("⚙️ Verificando y limpiando contenedor MKV con FFmpeg (-c copy)...")
command = ["ffmpeg", "-y", "-i", temp_local_path, "-c", "copy", output_path]
else:
yield from log("⚙️ Aplicando fix moov atom a MP4 con FFmpeg (-c copy -movflags +faststart)...")
command = ["ffmpeg", "-y", "-i", temp_local_path, "-c", "copy", "-movflags", "+faststart", output_path]
repaired_flag = False
try:
subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
yield from log("⚠️ ¡Aviso! El copiado directo falló (corrupción interna / streams dañados). Aplicando re-codificación de emergencia...")
if is_mkv:
command_fallback = ["ffmpeg", "-y", "-i", temp_local_path, output_path]
else:
command_fallback = ["ffmpeg", "-y", "-i", temp_local_path, "-movflags", "+faststart", output_path]
try:
subprocess.run(command_fallback, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
repaired_flag = True
yield from log("✅ Re-codificación de emergencia completada con éxito.")
except Exception as ex:
yield from log(f"❌ Error crítico procesando FFmpeg en fallback: {str(ex)}")
if os.path.exists(output_path):
os.remove(output_path)
continue
if repaired_flag:
repaired_files.append({"path": file_path, "ext": output_ext})
# Subir archivo optimizado al dataset de destino
yield from log(f"☁️ Subiendo video optimizado a {target_dataset} ({file_path})...")
try:
api.upload_file(
path_or_fileobj=output_path,
path_in_repo=file_path,
repo_id=target_dataset,
repo_type="dataset",
token=token
)
successful_files.append(file_path)
except Exception as e:
yield from log(f"❌ Error al subir a Hugging Face: {str(e)}")
# Limpieza local
if os.path.exists(output_path):
os.remove(output_path)
yield from log(f"🎉 ¡Video [{idx}/{total_files}] procesado y subido con éxito!\n")
yield from log("🏆 ¡Recorrido masivo total finalizado!")
except Exception as e:
yield from log(f"❌ Error crítico en el proceso: {str(e)}")
finally:
# Generar y subir el archivo de log obligatoriamente a la raíz del dataset de destino
yield from log("📝 Generando reporte final 'seriestransferencia.log'...")
log_content = "=" * 60 + "\n"
log_content += " 📊 REPORTE DE TRANSFERENCIA Y MIGRACIÓN DE SERIES\n"
log_content += "=" * 60 + "\n\n"
log_content += f"1. VIDEOS DAÑADOS POR TAMAÑO (< 50 MB) [OMITIDOS]: {len(size_damaged_files)}\n"
for item in size_damaged_files:
log_content += f" - Ruta: {item['path']} | Tamaño detectado: {item['size_mb']} MB\n"
log_content += f"\n2. VIDEOS REPARADOS POR RE-CODIFICACIÓN (Daños internos / Faltas de start): {len(repaired_files)}\n"
for item in repaired_files:
log_content += f" - Ruta: {item['path']} | Extensión real: {item['ext']}\n"
log_content += f"\n3. VIDEOS MIGRADOS Y SUBIDOS CON ÉXITO: {len(successful_files)}\n"
for item in successful_files:
log_content += f" - Ruta: {item}\n"
log_content += "=" * 60 + "\n"
log_filename = "seriestransferencia.log"
with open(log_filename, "w", encoding="utf-8") as f:
f.write(log_content)
try:
api.upload_file(
path_or_fileobj=log_filename,
path_in_repo=log_filename,
repo_id=target_dataset,
repo_type="dataset",
token=token
)
yield from log(f"✅ Archivo '{log_filename}' subido con éxito a la raíz del dataset {target_dataset}.")
except Exception as log_err:
yield from log(f"⚠️ No se pudo subir el log a Hugging Face: {str(log_err)}")
if os.path.exists(log_filename):
os.remove(log_filename)
with gr.Blocks() as demo:
gr.Markdown("# 🚀 Migrador Inteligente con Validación de Tamaño por API")
gr.Markdown("Lee los tamaños directamente del JSON de Hugging Face para descartar archivos corruptos/incompletos (<50MB) sin descargarlos, repara streams y genera `seriestransferencia.log` al terminar o detenerse.")
token_input = gr.Textbox(label="Tu HF_TOKEN (Con permisos de WRITE)", type="password")
with gr.Row():
source_input = gr.Textbox(label="Dataset ORIGEN", value="xplhack1/bylatamsrc2")
target_input = gr.Textbox(label="Dataset DESTINO", value="orglatamsrc/latamsrcfixed")
folder_input = gr.Textbox(
label="Carpeta o temporada a escanear",
value="SERIES/EN_EMISION/33_Dias_2026/TEMPORADA1"
)
with gr.Row():
btn = gr.Button("Iniciar Migración Inteligente 🔥", variant="primary")
stop_btn = gr.Button("Detener Proceso ⏹️", variant="stop")
output = gr.Textbox(label="Consola de Logs en Tiempo Real", lines=22)
watcher = btn.click(fn=process_full_migration, inputs=[token_input, source_input, target_input, folder_input], outputs=output)
stop_btn.click(fn=None, inputs=None, outputs=None, cancels=[watcher])
demo.launch(theme=gr.themes.Soft(), server_name="0.0.0.0", server_port=7860)