Download app.py from xplhack1/fixedxet: direct link, hf CLI and curl.
- Browser
- Download file 10 kB
-
https://huggingface.co/spaces/xplhack1/fixedxet/resolve/main/app.py
- Command line
-
hf download hf://spaces/xplhack1/fixedxet/app.py
-
curl -L -o app.py https://huggingface.co/spaces/xplhack1/fixedxet/resolve/main/app.py
10 kB
| import gradio as gr | |
| import os | |
| import subprocess | |
| import requests | |
| from huggingface_hub import HfApi, hf_hub_download | |
| import spaces | |
| api = HfApi() | |
| 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) |