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)