xplhack1 commited on
Commit
608cdab
·
verified ·
1 Parent(s): 1a721d3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +127 -52
app.py CHANGED
@@ -1,7 +1,8 @@
1
  import gradio as gr
2
  import os
3
  import subprocess
4
- from huggingface_hub import HfApi, hf_hub_url, list_repo_files, hf_hub_download
 
5
  import spaces
6
 
7
  api = HfApi()
@@ -19,95 +20,172 @@ def process_full_migration(token, source_dataset, target_dataset, folder_path):
19
  print(msg, flush=True)
20
  yield log_output
21
 
 
 
 
 
22
  try:
23
  folder_path = folder_path.strip().rstrip('/')
24
- yield from log(f"🔍 Escaneando archivos de video (.mp4 y .mkv) en toda la ruta: {folder_path}...")
25
 
26
- all_files = list_repo_files(repo_id=source_dataset, repo_type="dataset", token=token)
 
27
 
28
- video_files = [
29
- f for f in all_files
30
- if f.startswith(folder_path) and (f.endswith('.mp4') or f.endswith('.mkv'))
 
 
 
 
 
 
 
 
 
 
 
31
  ]
32
 
33
- total_files = len(video_files)
34
  if total_files == 0:
35
  yield from log(f"⚠️ No se encontraron archivos de video en la ruta: {folder_path}")
36
  return
37
 
38
- yield from log(f"📁 ¡Se encontraron {total_files} videos en total para verificar y migrar!\n")
39
 
40
- for idx, file_path in enumerate(video_files, 1):
 
 
 
 
41
  yield from log(f"==================================================")
42
  yield from log(f"🎬 Procesando [{idx}/{total_files}]: {file_path}")
 
43
  yield from log(f"==================================================")
44
 
 
 
 
 
 
 
 
45
  is_mkv = file_path.endswith('.mkv')
46
- temp_local_path = "temp_download.mkv" if is_mkv else "temp_download.mp4"
47
  output_ext = ".mkv" if is_mkv else ".mp4"
48
  output_path = f"video_arreglado{output_ext}"
49
 
50
- file_url = hf_hub_url(repo_id=source_dataset, filename=file_path, repo_type="dataset")
51
-
52
- # Descarga estándar por caché de Hugging Face (se aloja temporalmente en el disco local de la VPS)
53
  yield from log("📥 Descargando archivo al disco local de la VPS...")
54
- temp_local_path = hf_hub_download(
55
- repo_id=source_dataset,
56
- filename=file_path,
57
- repo_type="dataset",
58
- token=token
59
- )
 
 
 
 
60
 
61
  # Procesar con FFmpeg
62
  if is_mkv:
63
  yield from log("⚙️ Verificando y limpiando contenedor MKV con FFmpeg (-c copy)...")
64
- command = [
65
- "ffmpeg", "-y", "-i", temp_local_path,
66
- "-c", "copy", output_path
67
- ]
68
  else:
69
- yield from log("⚙️ Aplicando fix moov atom a MP4/TS con FFmpeg (-c copy -movflags +faststart)...")
70
- command = [
71
- "ffmpeg", "-y", "-i", temp_local_path,
72
- "-c", "copy", "-movflags", "+faststart", output_path
73
- ]
74
 
 
75
  try:
76
- subprocess.run(command, check=True)
77
  except subprocess.CalledProcessError:
78
- yield from log("⚠️ ¡Aviso! El copiado directo falló debido a corrupción interna en el archivo. Intentando re-codificación de emergencia...")
79
  if is_mkv:
80
  command_fallback = ["ffmpeg", "-y", "-i", temp_local_path, output_path]
81
  else:
82
  command_fallback = ["ffmpeg", "-y", "-i", temp_local_path, "-movflags", "+faststart", output_path]
83
- subprocess.run(command_fallback, check=True)
84
- yield from log("✅ Re-codificación de emergencia completada con éxito.")
 
 
 
 
 
 
 
 
85
 
86
- # Subir el archivo resultante limpio al dataset de destino
 
 
 
87
  yield from log(f"☁️ Subiendo video optimizado a {target_dataset} ({file_path})...")
88
- api.upload_file(
89
- path_or_fileobj=output_path,
90
- path_in_repo=file_path,
91
- repo_id=target_dataset,
92
- repo_type="dataset",
93
- token=token
94
- )
 
 
 
 
95
 
96
- # Limpieza local exhaustiva del disco temporal generado
97
  if os.path.exists(output_path):
98
  os.remove(output_path)
99
 
100
  yield from log(f"🎉 ¡Video [{idx}/{total_files}] procesado y subido con éxito!\n")
101
 
102
- yield from log("🏆 ¡Recorrido masivo total finalizado! Ningún archivo fue omitido y todos están limpios.")
103
 
104
  except Exception as e:
105
- yield from log(f"❌ Error crítico en el recorrido: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- # Interfaz gráfica de control total con botón de Cancelar/Detener
108
  with gr.Blocks() as demo:
109
- gr.Markdown("# 🛠️ Migrador y Reparador Masivo Total (.MP4, .MKV y Fakes TS)")
110
- gr.Markdown("Recorre de forma recursiva todas las temporadas, descarga al disco local, repara fallos y migra limpio a tu organización.")
111
 
112
  token_input = gr.Textbox(label="Tu HF_TOKEN (Con permisos de WRITE)", type="password")
113
 
@@ -116,20 +194,17 @@ with gr.Blocks() as demo:
116
  target_input = gr.Textbox(label="Dataset DESTINO", value="orglatamsrc/latamsrcfixed")
117
 
118
  folder_input = gr.Textbox(
119
- label="Carpeta principal a escanear",
120
- value="SERIES/EN_EMISION/Rivales_2024"
121
  )
122
 
123
  with gr.Row():
124
- btn = gr.Button("Iniciar Migración Masiva Total 🔥", variant="primary")
125
  stop_btn = gr.Button("Detener Proceso ⏹️", variant="stop")
126
 
127
  output = gr.Textbox(label="Consola de Logs en Tiempo Real", lines=22)
128
 
129
- # Evento de inicio del proceso
130
  watcher = btn.click(fn=process_full_migration, inputs=[token_input, source_input, target_input, folder_input], outputs=output)
131
-
132
- # Evento del botón de detener para cancelar la ejecución al instante si lo deseas
133
  stop_btn.click(fn=None, inputs=None, outputs=None, cancels=[watcher])
134
 
135
  demo.launch(theme=gr.themes.Soft(), server_name="0.0.0.0", server_port=7860)
 
1
  import gradio as gr
2
  import os
3
  import subprocess
4
+ import requests
5
+ from huggingface_hub import HfApi, hf_hub_download
6
  import spaces
7
 
8
  api = HfApi()
 
20
  print(msg, flush=True)
21
  yield log_output
22
 
23
+ size_damaged_files = [] # Archivos < 50MB detectados por API JSON
24
+ repaired_files = [] # Archivos que requirieron re-codificación de emergencia
25
+ successful_files = [] # Archivos migrados con éxito
26
+
27
  try:
28
  folder_path = folder_path.strip().rstrip('/')
29
+ yield from log(f"🔍 Consultando API de Hugging Face para obtener metadatos y tamaños en: {folder_path}...")
30
 
31
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
32
+ api_url = f"https://huggingface.co/api/datasets/{source_dataset}/tree/main/{folder_path}?recursive=true"
33
 
34
+ response = requests.get(api_url, headers=headers)
35
+ if response.status_code != 200:
36
+ yield from log(f"❌ Error al consultar la API de HF: {response.status_code} - {response.text}")
37
+ return
38
+
39
+ items = response.json()
40
+ if not isinstance(items, list):
41
+ yield from log(f"❌ Respuesta inesperada de la API: {items}")
42
+ return
43
+
44
+ # Filtrar solo archivos de video (.mp4 y .mkv)
45
+ video_items = [
46
+ item for item in items
47
+ if item.get("type") == "file" and (item.get("path", "").endswith('.mp4') or item.get("path", "").endswith('.mkv'))
48
  ]
49
 
50
+ total_files = len(video_items)
51
  if total_files == 0:
52
  yield from log(f"⚠️ No se encontraron archivos de video en la ruta: {folder_path}")
53
  return
54
 
55
+ yield from log(f"📁 Se encontraron {total_files} videos en el JSON de la API. Analizando tamaños...\n")
56
 
57
+ for idx, item in enumerate(video_items, 1):
58
+ file_path = item["path"]
59
+ file_size_bytes = item.get("size", 0)
60
+ file_size_mb = round(file_size_bytes / (1024 * 1024), 2)
61
+
62
  yield from log(f"==================================================")
63
  yield from log(f"🎬 Procesando [{idx}/{total_files}]: {file_path}")
64
+ yield from log(f" 📏 Tamaño detectado en API: {file_size_mb} MB ({file_size_bytes} bytes)")
65
  yield from log(f"==================================================")
66
 
67
+ # Validación directa por metadata de API (< 50 Megas) -> Omitir descarga, fix y subida
68
+ if file_size_bytes < (50 * 1024 * 1024):
69
+ yield from log(f"⚠️ Archivo DAÑADO O INCOMPLETO POR TAMAÑO: {file_size_mb} MB (< 50 MB). Omitiendo descarga y fix.")
70
+ size_damaged_files.append({"path": file_path, "size_mb": file_size_mb})
71
+ yield from log(f"⏭️ Archivo [{idx}/{total_files}] omitido.\n")
72
+ continue
73
+
74
  is_mkv = file_path.endswith('.mkv')
 
75
  output_ext = ".mkv" if is_mkv else ".mp4"
76
  output_path = f"video_arreglado{output_ext}"
77
 
78
+ # Descargar archivo válido a la VPS
 
 
79
  yield from log("📥 Descargando archivo al disco local de la VPS...")
80
+ try:
81
+ temp_local_path = hf_hub_download(
82
+ repo_id=source_dataset,
83
+ filename=file_path,
84
+ repo_type="dataset",
85
+ token=token
86
+ )
87
+ except Exception as e:
88
+ yield from log(f"❌ Error al descargar el archivo desde HF: {str(e)}")
89
+ continue
90
 
91
  # Procesar con FFmpeg
92
  if is_mkv:
93
  yield from log("⚙️ Verificando y limpiando contenedor MKV con FFmpeg (-c copy)...")
94
+ command = ["ffmpeg", "-y", "-i", temp_local_path, "-c", "copy", output_path]
 
 
 
95
  else:
96
+ yield from log("⚙️ Aplicando fix moov atom a MP4 con FFmpeg (-c copy -movflags +faststart)...")
97
+ command = ["ffmpeg", "-y", "-i", temp_local_path, "-c", "copy", "-movflags", "+faststart", output_path]
 
 
 
98
 
99
+ repaired_flag = False
100
  try:
101
+ subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
102
  except subprocess.CalledProcessError:
103
+ yield from log("⚠️ ¡Aviso! El copiado directo falló (corrupción interna / streams dañados). Aplicando re-codificación de emergencia...")
104
  if is_mkv:
105
  command_fallback = ["ffmpeg", "-y", "-i", temp_local_path, output_path]
106
  else:
107
  command_fallback = ["ffmpeg", "-y", "-i", temp_local_path, "-movflags", "+faststart", output_path]
108
+
109
+ try:
110
+ subprocess.run(command_fallback, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
111
+ repaired_flag = True
112
+ yield from log("✅ Re-codificación de emergencia completada con éxito.")
113
+ except Exception as ex:
114
+ yield from log(f"❌ Error crítico procesando FFmpeg en fallback: {str(ex)}")
115
+ if os.path.exists(output_path):
116
+ os.remove(output_path)
117
+ continue
118
 
119
+ if repaired_flag:
120
+ repaired_files.append({"path": file_path, "ext": output_ext})
121
+
122
+ # Subir archivo optimizado al dataset de destino
123
  yield from log(f"☁️ Subiendo video optimizado a {target_dataset} ({file_path})...")
124
+ try:
125
+ api.upload_file(
126
+ path_or_fileobj=output_path,
127
+ path_in_repo=file_path,
128
+ repo_id=target_dataset,
129
+ repo_type="dataset",
130
+ token=token
131
+ )
132
+ successful_files.append(file_path)
133
+ except Exception as e:
134
+ yield from log(f"❌ Error al subir a Hugging Face: {str(e)}")
135
 
136
+ # Limpieza local
137
  if os.path.exists(output_path):
138
  os.remove(output_path)
139
 
140
  yield from log(f"🎉 ¡Video [{idx}/{total_files}] procesado y subido con éxito!\n")
141
 
142
+ yield from log("🏆 ¡Recorrido masivo total finalizado!")
143
 
144
  except Exception as e:
145
+ yield from log(f"❌ Error crítico en el proceso: {str(e)}")
146
+
147
+ finally:
148
+ # Generar y subir el archivo de log obligatoriamente a la raíz del dataset de destino
149
+ yield from log("📝 Generando reporte final 'seriestransferencia.log'...")
150
+ log_content = "=" * 60 + "\n"
151
+ log_content += " 📊 REPORTE DE TRANSFERENCIA Y MIGRACIÓN DE SERIES\n"
152
+ log_content += "=" * 60 + "\n\n"
153
+
154
+ log_content += f"1. VIDEOS DAÑADOS POR TAMAÑO (< 50 MB) [OMITIDOS]: {len(size_damaged_files)}\n"
155
+ for item in size_damaged_files:
156
+ log_content += f" - Ruta: {item['path']} | Tamaño detectado: {item['size_mb']} MB\n"
157
+
158
+ log_content += f"\n2. VIDEOS REPARADOS POR RE-CODIFICACIÓN (Daños internos / Faltas de start): {len(repaired_files)}\n"
159
+ for item in repaired_files:
160
+ log_content += f" - Ruta: {item['path']} | Extensión real: {item['ext']}\n"
161
+
162
+ log_content += f"\n3. VIDEOS MIGRADOS Y SUBIDOS CON ÉXITO: {len(successful_files)}\n"
163
+ for item in successful_files:
164
+ log_content += f" - Ruta: {item}\n"
165
+ log_content += "=" * 60 + "\n"
166
+
167
+ log_filename = "seriestransferencia.log"
168
+ with open(log_filename, "w", encoding="utf-8") as f:
169
+ f.write(log_content)
170
+
171
+ try:
172
+ api.upload_file(
173
+ path_or_fileobj=log_filename,
174
+ path_in_repo=log_filename,
175
+ repo_id=target_dataset,
176
+ repo_type="dataset",
177
+ token=token
178
+ )
179
+ yield from log(f"✅ Archivo '{log_filename}' subido con éxito a la raíz del dataset {target_dataset}.")
180
+ except Exception as log_err:
181
+ yield from log(f"⚠️ No se pudo subir el log a Hugging Face: {str(log_err)}")
182
+
183
+ if os.path.exists(log_filename):
184
+ os.remove(log_filename)
185
 
 
186
  with gr.Blocks() as demo:
187
+ gr.Markdown("# 🚀 Migrador Inteligente con Validación de Tamaño por API")
188
+ 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.")
189
 
190
  token_input = gr.Textbox(label="Tu HF_TOKEN (Con permisos de WRITE)", type="password")
191
 
 
194
  target_input = gr.Textbox(label="Dataset DESTINO", value="orglatamsrc/latamsrcfixed")
195
 
196
  folder_input = gr.Textbox(
197
+ label="Carpeta o temporada a escanear",
198
+ value="SERIES/EN_EMISION/33_Dias_2026/TEMPORADA1"
199
  )
200
 
201
  with gr.Row():
202
+ btn = gr.Button("Iniciar Migración Inteligente 🔥", variant="primary")
203
  stop_btn = gr.Button("Detener Proceso ⏹️", variant="stop")
204
 
205
  output = gr.Textbox(label="Consola de Logs en Tiempo Real", lines=22)
206
 
 
207
  watcher = btn.click(fn=process_full_migration, inputs=[token_input, source_input, target_input, folder_input], outputs=output)
 
 
208
  stop_btn.click(fn=None, inputs=None, outputs=None, cancels=[watcher])
209
 
210
  demo.launch(theme=gr.themes.Soft(), server_name="0.0.0.0", server_port=7860)