File size: 10,037 Bytes
1327076 608cdab 4888228 1327076 4888228 1a721d3 294eb7e 1a721d3 1327076 1a721d3 608cdab 1327076 294eb7e 608cdab 7a43783 608cdab 1327076 608cdab 1327076 608cdab 1a721d3 294eb7e 608cdab 1327076 608cdab 1a721d3 608cdab 1a721d3 608cdab 1a721d3 608cdab 1a721d3 608cdab 294eb7e 1a721d3 608cdab 1a721d3 608cdab 1a721d3 608cdab 1a721d3 608cdab 1a721d3 608cdab 1a721d3 608cdab 294eb7e 608cdab 1a721d3 608cdab 294eb7e 608cdab 294eb7e 1a721d3 294eb7e 608cdab 1327076 608cdab 1327076 9b6c842 608cdab 7a43783 1327076 294eb7e 1327076 294eb7e 608cdab 294eb7e 1327076 1a721d3 608cdab 1a721d3 1327076 1a721d3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | 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) |