Saltar al contenido

Historial del chat

ChatGPT

Sora

Explorar GPT

Biblioteca
10

Proyectos

Nuevo proyecto

Hoy

Procesamiento2 videos sin fondo

7 días anteriores

Actualizar favicons Opera

30 días anteriores

Remover2 fondo video IA

Códigos GitHub Lip Sync

Documento para Consejería Fomento

IA para mejorar videos

Generar imagen desde foto

Ver planesAcceso ilimitado, características de Team y más

Abrir barra lateral

ChatGPT o4-mini-high

ChatGPT o4-mini-high

Compartir

PLUS

Dijiste:

script_maestro_speakingvideos_v7.py

Python

remove_background_server.py

Python

Hola chat, vengo de otro chat que ya estaba muy saturado, y vamos a ver si te pongo al dia:

Tenemos 2 archivos py que te adjunto
remove_background_server.py
que arrancaba todo lo que la IA necesita

y otro
script_maestro_speakingvideos_v7.py
que procesaba lo que queriamos hacer, que es muy complejo, pero dime si quieres que te lo especifique, o viendo el codigo puedes saberlo?

Te paso los 2 py en adjunto...

Se trata de conseguir que los 2 queden en un solo py, usando algo que decias que se llamaba Flask... Dime si necesitas que te explique a fondo que hace:
script_maestro_speakingvideos_v7.py

O si viendo su codigo puedes saberlo...

Si se arrancaba
remove_background_server.py
se cargabna lo que al ia necesita, una vez ya dejaba cargado mostrando:
* Running on http://127.0.0.1:8787

Se arrancaba en otro cmd el:
script_maestro_speakingvideos_v7.py

Y lo hacia todo perfecto, de lo que se trata es de unir los 2 py en uno solo y que salgan los mensajes en un solo cmd

Le doy a "investigar a fondo" para que lo intentes hacer lo mejor posibnle, y, por favor, no olvides ninguno de los pasos que tiene:
script_maestro_speakingvideos_v7.py

Respeta todo lo que hace... No olvides cosas por favor, o cambies cosas de ese, se trata de que arranque el server y que ejecute lo que hace el remove_background_server.py, y luegfo haga lo que hace el script_maestro_speakingvideos_v7.py

ChatGPT dijo:

Gracias por los detalles y por compartir los archivos. Para fusionarlos correctamente y que todo funcione desde un solo archivo con Flask (o algo similar), ¿quieres que el servidor de eliminación de fondo funcione como un endpoint real de Flask? Es decir:

¿Deseas que se pueda seguir accediendo al procesamiento por POST desde fuera si se quiere (como lo hace el script_maestro_speakingvideos_v7.py)?

¿O prefieres que todo quede como un script monolítico que lanza el "servidor" pero ya no funcione como servicio separado, solo interno?

Esto ayudará a definir si usamos Flask como backend real o simplemente reorganizamos todo en un script unificado con subprocesos e hilos.

Dijiste:

La opcion 2, que es de lo que se trata, pues esos 2 por separado ya estaban perfectos...

ChatGPT dijo:

Perfecto. Voy a combinar los dos scripts en uno solo, de forma que:

El código cargue el modelo de IA (como hacía remove_background_server.py).

En lugar de crear un servidor Flask o HTTP, la función de procesamiento de imágenes se llamará directamente desde el flujo principal.

Se mantendrán todos los pasos y funcionalidades del script_maestro_speakingvideos_v7.py sin omitir nada.

Te entregaré un único script .py listo para ejecutar en una sola terminal.
Dame unos momentos mientras lo preparo.

Research completed in 8m· 3 fuentes

La siguiente es la versión unificada de los archivos remove_background_server.py y script_maestro_speakingvideos_v7.py en un solo script Python, sin uso de hilos ni servidor HTTP externo:

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos")
VWB_DIR = BASE_DIR / "VWB"
THUMBB_DIR = BASE_DIR / "ThumbB"

# === FUNCIONES ===
def get_video_files(directory):
return [f for f in Path(directory).glob("*.mp4") if f.is_file()]

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
valid_exts = [".jpg", ".jpeg", ".png"]
files = [f for f in input_dir.iterdir() if f.suffix.lower() in valid_exts]
total = len(files)
print(f"[📁] Origen: {input_dir}")
print(f"[📀] Destino: {output_dir}")
print(f"[🛠️] Procesando {total} imagenes...\n")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
image = Image.open(f).convert("RGB")
result = remover.process(image, type='rgba')
output_path = output_dir / (f.stem + "_nobg.png")
result.save(output_path)
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed // 60):02}:{int(elapsed % 60):02} min")
print("---------------------------------------------\n")

def notify_server(input_dir, output_dir):
try:
remove_background_batch(input_dir, output_dir)
return True
except Exception as e:
print(f"[❌] Error procesando lote: {e}")
return False

def copiar_thumb(img_dir, idx):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{idx}_thumb.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
print("[🧠] Lanzando servidor de IA...")
print("\n🔧 Inicializando IA, cargando modelos, preparando GPU... Por favor espere unos segundos...\n")
# Inicializar modelo solo una vez
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024 * 1024)
torch.cuda.empty_cache()
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024 * 1024)
else:
device = "cpu"
import platform
cpu_name = platform.processor()
global remover
remover = Remover(mode='base', device=device)
if torch.cuda.is_available():
print(f"[🚀] Usando GPU: {gpu_name}")
print(f"[📊] VRAM Memory Used: {used_vram} MiB / {total_vram} MiB\n")
else:
print(f"[💻] Usando CPU: {cpu_name}\n")

VWB_DIR.mkdir(exist_ok=True)
videos = get_video_files(BASE_DIR)

for idx, video in enumerate(videos, start=1):
name = video.stem
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"
txt_path = sub_a / "nombre_original.txt"

print(f"[🎬] Extrayendo frames de: {name}.mp4")
extract_frames(video, sub_a)
with open(txt_path, "w", encoding="utf-8") as f:
f.write(video.name)

print("\n[🧠] Procesando imágenes sin fondo por lotes...")
for idx in range(1, len(videos) + 1):
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"
txt_path = sub_a / "nombre_original.txt"

print(f"[➡️ ] Procesando {sub_a} -> {sub_b}")
ok = notify_server(sub_a, sub_b)
if ok:
if txt_path.exists():
with open(txt_path, "r", encoding="utf-8") as f:
original_name = f.read().strip()
output_mov = VWB_DIR / original_name.replace(".mp4", ".mov")
print(f"[🎞️] Generando video: {output_mov.name}")
generate_mov(sub_b, BASE_DIR / original_name, output_mov)
copiar_thumb(sub_b, idx)
borrar_directorios(sub_a, sub_b)
else:
print("[⚠️] No se encontró nombre_original.txt")
else:
print(f"[❌] Error procesando {sub_a}")

print("\n✅ Todo el procesamiento ha terminado.")

if __name__ == "__main__":
main()

Fuentes

Dijiste:

Parece funcionar muy bien, solo veo un detalle de momento, cuando salva la imagen primera sin fondo en el subdir SpeakingVideos\ThumB, la salva como:
1_thumb.png

Y la debe de salvar con el mismo nokbre que el video...

No sae si este codigo chequea si ya exuste la imagen sin fondo primera en ThumbB y el video en SpeakingVideos\VWB, y si ya existe, que se salte al siguiente video, no se si lo hace, es posible que lo haga pues creo que antes con lso 2 py lo hacia...

ChatGPT dijo:

Pensado durante 5 segundos

He modificado el script para que:

La miniatura se guarde con el mismo nombre base del vídeo (<nombre>_thumb.png) en lugar de usar el índice.

Antes de procesar cada vídeo, comprueba si ya existen tanto el .mov resultante en VWB como la miniatura en ThumbB; si ambos existen, salta ese vídeo y limpia los directorios temporales.

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos")
VWB_DIR = BASE_DIR / "VWB"
THUMBB_DIR = BASE_DIR / "ThumbB"

# === FUNCIONES ===
def get_video_files(directory):
return [f for f in Path(directory).glob("*.mp4") if f.is_file()]

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
valid_exts = [".jpg", ".jpeg", ".png"]
files = [f for f in input_dir.iterdir() if f.suffix.lower() in valid_exts]
total = len(files)
print(f"[📁] Origen: {input_dir}")
print(f"[📀] Destino: {output_dir}")
print(f"[🛠️] Procesando {total} imagenes...\n")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
image = Image.open(f).convert("RGB")
result = remover.process(image, type='rgba')
output_path = output_dir / (f.stem + "_nobg.png")
result.save(output_path)
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed // 60):02}:{int(elapsed % 60):02} min")
print("---------------------------------------------\n")

def notify_server(input_dir, output_dir):
try:
remove_background_batch(input_dir, output_dir)
return True
except Exception as e:
print(f"[❌] Error procesando lote: {e}")
return False

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}_thumb.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
print("[🧠] Lanzando servidor de IA...")
print("\n🔧 Inicializando IA, cargando modelos, preparando GPU... Por favor espere unos segundos...\n")
# Inicializar modelo solo una vez
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024 * 1024)
torch.cuda.empty_cache()
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024 * 1024)
else:
device = "cpu"
import platform
cpu_name = platform.processor()
global remover
remover = Remover(mode='base', device=device)
if torch.cuda.is_available():
print(f"[🚀] Usando GPU: {gpu_name}")
print(f"[📊] VRAM Memory Used: {used_vram} MiB / {total_vram} MiB\n")
else:
print(f"[💻] Usando CPU: {cpu_name}\n")

VWB_DIR.mkdir(exist_ok=True)
videos = get_video_files(BASE_DIR)

# 1) Extracción de todos los frames y escritura de nombre_original.txt
for idx, video in enumerate(videos, start=1):
name = video.stem
sub_a = BASE_DIR / f"{idx}a"
txt_path = sub_a / "nombre_original.txt"

print(f"[🎬] Extrayendo frames de: {name}.mp4")
extract_frames(video, sub_a)

# Guardamos el nombre original para usar luego
with open(txt_path, "w", encoding="utf-8") as f:
f.write(video.name)

# 2) Procesar lotes y generar .mov + miniaturas
print("\n[🧠] Procesando imágenes sin fondo por lotes...")
for idx in range(1, len(videos) + 1):
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"
txt_path = sub_a / "nombre_original.txt"

# Leemos el nombre del vídeo
if not txt_path.exists():
print(f"[⚠️] No se encontró {txt_path}, saltando lote {idx}")
borrar_directorios(sub_a, sub_b)
continue

with open(txt_path, "r", encoding="utf-8") as f:
original_name = f.read().strip()
video_stem = Path(original_name).stem

# Rutas de salida previstas
output_mov = VWB_DIR / f"{video_stem}.mov"
thumb_file = THUMBB_DIR / f"{video_stem}_thumb.png"

# Si ya existe tanto el .mov como la miniatura, saltamos
if output_mov.exists() and thumb_file.exists():
print(f"[⏭️ ] Ya existe {output_mov.name} y {thumb_file.name}, saltando lote {idx}")
borrar_directorios(sub_a, sub_b)
continue

print(f"[➡️ ] Procesando {sub_a} -> {sub_b}")
ok = notify_server(sub_a, sub_b)
if ok:
print(f"[🎞️] Generando video: {output_mov.name}")
generate_mov(sub_b, BASE_DIR / original_name, output_mov)
copiar_thumb(sub_b, video_stem)
borrar_directorios(sub_a, sub_b)
else:
print(f"[❌] Error procesando {sub_a}")

print("\n✅ Todo el procesamiento ha terminado.")

if __name__ == "__main__":
main()

Cambios clave:

copiar_thumb ahora recibe video_stem y genera <video_stem>_thumb.png.

En el bucle de procesado, antes de llamar al fondo, comprobamos si ya existen el .mov y la miniatura; si ambos están, borramos los directorios temporales y saltamos al siguiente.

Mensajes de consola actualizados para reflejar el salto de lotes.

2/2

o4-mini-high

Dijiste:

En un momento dice:
Settings -> Mode=base, Device=cuda, Torchscript=disabled

Torchscript=disabled

Es importante en cuanto a velocidad? Va mas lento sin el?

ChatGPT dijo:

Pensado durante 4 segundos

TorchScript es la forma “JIT” (Just‑In‑Time) de PyTorch: convierte tu modelo de Python en un grafo optimizado que puede ejecutarse con menos sobrecarga de Python, y en muchos casos acelera la inferencia, sobre todo en CPU y cuando hay muchas llamadas al modelo.

– Sin TorchScript (eager mode)

Cada llamada al método forward() pasa por el intérprete Python, lo que añade latencia.

En GPU el efecto es menos dramático porque la mayoría del tiempo se consume en el kernel CUDA, pero sigue habiendo un pequeño overhead de gestión desde Python.

En CPU, ese overhead puede suponer fácilmente un 20–50 % de tiempo extra, dependiendo del tamaño del lote y la complejidad del modelo.

– Con TorchScript habilitado

El modelo se “traza” o “compila” a un grafo estático.

Las llamadas de inferencia invocan directamente ese grafo, reduciendo la carga de Python.

Suele dar ganancias de rendimiento notables en CPU (menor latencia por ítem) y mejoras moderadas en GPU.

En tu caso, al ver Torchscript=disabled, significa que el Remover está ejecutando el modelo en modo eager. Si notas que el procesamiento por imagen es más lento de lo que esperas, valdría la pena activar TorchScript (si la librería lo soporta). El método suele consistir en:

python

CopiarEditar

# Ejemplo genérico
model.eval()
traced = torch.jit.trace(model, ejemplo_de_input)
traced.save("modelo_trazado.pt")
model = torch.jit.load("modelo_trazado.pt")

O bien, revisar si transparent_background.Remover ofrece un parámetro tipo use_torchscript=True al instanciarlo.

Conclusión:

Sí, sin TorchScript vas a ir algo más lento (especialmente en CPU). Si tu flujo es muy intensivo y el librero lo permite, habilítalo o traza el modelo para reducir la sobrecarga de Python y ganar velocidad en la inferencia.

o4-mini-high

Dijiste:

Ok, mira, al inicio, antes de hacer nada pero despues de cargar todo lo que necesita la IA, deberia de chequear si el video sin fondo existe con extension mov en SpeakingVideos\VWB y si existe una imagen con el nombre del video en SpeakingVideos\ThumbB

Pues le he dejado 3 videos de los 90 que hay, ya convertidos en SpeakingVideos\VWB y con su primera imagen sin fondo en SpeakingVideos\ThumbB.

Y empeiza a sacar de nuevo todas las imagenes del primer video - que como te digo, los 3 primeros videos mov con alpha channel ya los deje hechos con su imagen primera sin fondo en SpeakingVideos\ThumbB -, y luego comienza a sacar todas las imagenes sin fondo con proceso IA, y eso no esta bien

lo primero antes de que ffmpeg empiece a sacar todas las imagenes es chequear si ya existe el video mov y el thumb en ThumbB, y si existe, apsar al siguiente video y vovler a chequear...

Esto es importante, porque si se cuelga el pc mo cualquier cosda antes de que termine, dandole de nuevo debe de empezar en el p[rimer video que no esta hecho con canal alpha y no tiene su primera imagen sin fondo en ThumbB

Es posible?

ChatGPT dijo:

Pensado durante 6 segundos

Aquí tienes el script completo actualizado para que, antes de extraer frames de cada vídeo, compruebe si ya existe tanto el .mov en SpeakingVideos/VWB como la miniatura en SpeakingVideos/ThumbB. Si ambos existen, borra cualquier carpeta temporal sobrante y pasa al siguiente vídeo:

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos")
VWB_DIR = BASE_DIR / "VWB"
THUMBB_DIR = BASE_DIR / "ThumbB"

# === FUNCIONES ===
def get_video_files(directory):
return [f for f in Path(directory).glob("*.mp4") if f.is_file()]

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
valid_exts = [".jpg", ".jpeg", ".png"]
files = [f for f in input_dir.iterdir() if f.suffix.lower() in valid_exts]
total = len(files)
print(f"[📁] Origen: {input_dir}")
print(f"[📀] Destino: {output_dir}")
print(f"[🛠️] Procesando {total} imagenes...\n")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
image = Image.open(f).convert("RGB")
result = remover.process(image, type='rgba')
output_path = output_dir / (f.stem + "_nobg.png")
result.save(output_path)
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed // 60):02}:{int(elapsed % 60):02} min")
print("---------------------------------------------\n")

def notify_server(input_dir, output_dir):
try:
remove_background_batch(input_dir, output_dir)
return True
except Exception as e:
print(f"[❌] Error procesando lote: {e}")
return False

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}_thumb.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
print("[🧠] Lanzando servidor de IA...")
print("\n🔧 Inicializando IA, cargando modelos, preparando GPU... Por favor espere unos segundos...\n")
# Inicializar modelo solo una vez
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024 * 1024)
torch.cuda.empty_cache()
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024 * 1024)
else:
device = "cpu"
import platform
cpu_name = platform.processor()
global remover
remover = Remover(mode='base', device=device)
if torch.cuda.is_available():
print(f"[🚀] Usando GPU: {gpu_name}")
print(f"[📊] VRAM Memory Used: {used_vram} MiB / {total_vram} MiB\n")
else:
print(f"[💻] Usando CPU: {cpu_name}\n")

# Aseguramos que existan los directorios de salida
VWB_DIR.mkdir(exist_ok=True)
THUMBB_DIR.mkdir(exist_ok=True)

videos = get_video_files(BASE_DIR)

# === 1) Extracción de frames (se salta videos ya procesados) ===
for idx, video in enumerate(videos, start=1):
video_stem = video.stem
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# Chequeo de salida ya existente
mov_out = VWB_DIR / f"{video_stem}.mov"
thumb_out = THUMBB_DIR / f"{video_stem}_thumb.png"
if mov_out.exists() and thumb_out.exists():
print(f"[⏭️ ] Ya existe {mov_out.name} y {thumb_out.name}, saltando vídeo {video_stem}")
# Limpiar posibles restos de subdirs
borrar_directorios(sub_a, sub_b)
continue

# Si no está procesado, extraemos frames
print(f"[🎬] Extrayendo frames de: {video_stem}.mp4")
extract_frames(video, sub_a)
# Guardamos nombre para uso posterior
txt_path = sub_a / "nombre_original.txt"
with open(txt_path, "w", encoding="utf-8") as f:
f.write(video.name)

# === 2) Procesado de lotes y generación de vídeos + miniaturas ===
print("\n[🧠] Procesando imágenes sin fondo por lotes...")
for idx in range(1, len(videos) + 1):
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"
txt_path = sub_a / "nombre_original.txt"

if not txt_path.exists():
# Nada que hacer si no hubo extracción
continue

with open(txt_path, "r", encoding="utf-8") as f:
original_name = f.read().strip()
video_stem = Path(original_name).stem

mov_out = VWB_DIR / f"{video_stem}.mov"
thumb_out = THUMBB_DIR / f"{video_stem}_thumb.png"

# Re-chequeo por seguridad (aunque ya lo hicimos antes)
if mov_out.exists() and thumb_out.exists():
borrar_directorios(sub_a, sub_b)
continue

print(f"[➡️ ] Procesando {sub_a} -> {sub_b}")
if notify_server(sub_a, sub_b):
print(f"[🎞️] Generando video: {mov_out.name}")
generate_mov(sub_b, BASE_DIR / original_name, mov_out)
copiar_thumb(sub_b, video_stem)
borrar_directorios(sub_a, sub_b)
else:
print(f"[❌] Error procesando {sub_a}")

print("\n✅ Todo el procesamiento ha terminado.")

if __name__ == "__main__":
main()

¿Qué hace el cambio?

Chequeo previo a ffmpeg

Antes de extraer frames (extract_frames), el script comprueba si ya existen:

El archivo SpeakingVideos/VWB/<video_stem>.mov

La miniatura SpeakingVideos/ThumbB/<video_stem>_thumb.png

Si ambos están, lanza un mensaje y elimina cualquier carpeta temporal (idx a/b), saltando ese vídeo.

Resiliencia ante reinicios

Si el equipo se cuelga y vuelves a ejecutar el script, arrancará desde el primer vídeo no procesado (sin mov o sin thumb), en lugar de repetir trabajos ya hechos.

Limpieza segura de temporales

Siempre que detecta un vídeo ya terminado, borra sus directorios sub_a y sub_b para evitar dejar “basura”.

Con esto, tu flujo será completamente idempotente y se reanudará automáticamente donde lo dejaste.

o4-mini-high

Dijiste:

Sigue extrayendo imagenes ffmpeg al inicio de lso videos que ya existen, te doy a "investigar a fondo" para que busques la solucion?

ChatGPT dijo:

Pensado durante 10 segundos

Sí, es totalmente posible. La clave es no separar la extracción de frames y el procesado en dos bucles distintos, sino hacer un único bucle por vídeo en el que:

Primero compruebas si ya existe tanto

SpeakingVideos/VWB/<video_stem>.mov

SpeakingVideos/ThumbB/<video_stem>_thumb.png

Si ambos existen, lo ignoras y limpias cualquier carpeta temporal (idx a/b), sin llamar jamás a ffmpeg.

Si no existen, haces toda la secuencia:

ffmpeg extrae frames

eliminación de fondo

generación de .mov

copia de la miniatura

limpieza

Así nunca volverás a extraer imágenes de vídeos ya procesados, aunque el PC se cuelgue y vuelvas a lanzar el script.

Aquí tienes el script completo revisado:

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos")
VWB_DIR = BASE_DIR / "VWB"
THUMBB_DIR = BASE_DIR / "ThumbB"

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
valid_exts = [".jpg", ".jpeg", ".png"]
files = [f for f in input_dir.iterdir() if f.suffix.lower() in valid_exts]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
img = Image.open(f).convert("RGB")
out = remover.process(img, type='rgba')
out.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min\n"
"---------------------------------------------\n")

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
shutil.copy(frames[0], THUMBB_DIR / f"{video_stem}_thumb.png")

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
print("[🧠] Inicializando IA, cargando modelo...\n")
# Modelo una sola vez
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()
global remover
remover = Remover(mode='base', device=device)
print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# Asegurar directorios
VWB_DIR.mkdir(exist_ok=True)
THUMBB_DIR.mkdir(exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
mov_out = VWB_DIR / f"{stem}.mov"
thumb_out= THUMBB_DIR / f"{stem}_thumb.png"
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# 1) Comprobar si ya está hecho
if mov_out.exists() and thumb_out.exists():
print(f"[⏭️ ] {stem}: ya existe .mov y thumb, salto.")
borrar_directorios(sub_a, sub_b)
continue

# 2) Extracción de frames
print(f"[🎬] {stem}: extrayendo frames...")
extract_frames(video, sub_a)

# 3) Remover fondo
print(f"[➡️ ] {stem}: eliminando fondo...")
remove_background_batch(sub_a, sub_b)

# 4) Generar .mov
print(f"[🎞️] {stem}: generando MOV...")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

# 5) Copiar miniatura
print(f"[🖼️ ] {stem}: copiando thumb...")
copiar_thumb(sub_b, stem)

# 6) Limpiar temporales
borrar_directorios(sub_a, sub_b)
print()

print("[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

¿Cómo funciona ahora?

Todo en un solo bucle for idx, video in enumerate(videos…).

Antes de llamar a ffmpeg, comprueba la existencia de stem.mov y stem_thumb.png.

Si ambos existen, no extrae nada y pasa al siguiente.

Si no existen, ejecuta uno a uno los pasos: extracción, IA, vídeo, thumb y limpieza.

Con esto, aunque tengas 3 vídeos ya procesados y vuelvas a lanzar, no volverá a llamar a ffmpeg ni a la IA para ellos, y arrancará directamente desde el primer vídeo no procesado.

o4-mini-high

Dijiste:

Sigue haciendolo - sacar las iamgene dle video con ffmpeg - a pesar de que existe el video sin fondo como mov en el subdir
SpeakingVideos\VWB
y la priemra imagen del video sin fondo en:
SpeakingVideos\ThumbB

ChatGPT dijo:

Pensado durante 10 segundos

He revisado de nuevo y veo que lo que necesitas es un solo bucle que, antes de hacer cualquier llamada a ffmpeg, compruebe ambos archivos de salida y los salte por completo. Además, conviene añadir un pequeño “debug” para verificar que las rutas que está comprobando coinciden con los ficheros reales.

Por favor sustituye tu main() actual por esto (sólo hay un for y el extract_frames() — y por tanto ffmpeg — quedan fuera si ya existe .mov y thumb):

python

CopiarEditar

def main():
print("[🧠] Inicializando IA y modelo...\n")
# (inicialización de Remover idéntica a antes)
# ...

VWB_DIR.mkdir(exist_ok=True)
THUMBB_DIR.mkdir(exist_ok=True)

videos = sorted([f for f in BASE_DIR.glob("*.mp4") if f.is_file()])

for idx, video in enumerate(videos, start=1):
stem = video.stem
mov_out = VWB_DIR / f"{stem}.mov"
thumb_out = THUMBB_DIR / f"{stem}_thumb.png"
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# ——— DEBUG RUTAS ———
print(f"\n[🔍] Vídeo #{idx}: {video.name}")
print(f" Mov => {mov_out} exists={mov_out.exists()}")
print(f" Thumb => {thumb_out} exists={thumb_out.exists()}")

# 1) Si ya tenemos .mov **y** thumb, lo saltamos TODO
if mov_out.exists() and thumb_out.exists():
print(f"[⏭️ ] {stem}: ambos ficheros existen → salto completo")
borrar_directorios(sub_a, sub_b)
continue

# 2) Si llegamos aquí, NO existen los dos. Hacemos TODO el flujo:

# 2.1) Extracción de frames
print(f"[🎬] {stem}: extrayendo frames...")
extract_frames(video, sub_a)

# 2.2) IA elimina fondo
print(f"[➡️ ] {stem}: eliminando fondo con IA...")
remove_background_batch(sub_a, sub_b)

# 2.3) Generación de .mov
print(f"[🎞️] {stem}: generando MOV...")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

# 2.4) Copiar thumb
print(f"[🖼️ ] {stem}: copiando miniatura...")
copiar_thumb(sub_b, stem)

# 2.5) Limpieza de temporales
borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

Qué hace distinto

Un único for idx, video in ...: ni extracción previa en otro bucle ni “doble pasada”.

Debug de las rutas (mov_out.exists(), thumb_out.exists()) para comprobar en consola si realmente detecta el fichero que tú ya tienes.

Continue inmediato y limpieza de sub_a/sub_b antes de llamar a ffmpeg.

Prueba este fragmento y fíjate en los mensajes de [🔍] para ver si mov_out.exists() y thumb_out.exists() salen a True cuando tú esperas. Si por algún motivo la ruta no coincide (por mayúsculas, extensión distinta o drive mapeado), ahí lo verás y podremos afinar la ruta exacta.

o4-mini-high

Dijiste:

Dame por favor el py completo

ChatGPT dijo:

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos")
VWB_DIR = BASE_DIR / "VWB"
THUMBB_DIR = BASE_DIR / "ThumbB"

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
valid_exts = [".jpg", ".jpeg", ".png"]
files = [f for f in input_dir.iterdir() if f.suffix.lower() in valid_exts]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
img = Image.open(f).convert("RGB")
out = remover.process(img, type='rgba')
out.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min\n"
"---------------------------------------------\n")

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
shutil.copy(frames[0], THUMBB_DIR / f"{video_stem}_thumb.png")

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
print("[🧠] Inicializando IA y cargando modelo...\n")

# Inicializar dispositivo y modelo
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()

global remover
remover = Remover(mode='base', device=device)

print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# Asegurar directorios de salida
VWB_DIR.mkdir(exist_ok=True)
THUMBB_DIR.mkdir(exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
mov_out = VWB_DIR / f"{stem}.mov"
thumb_out = THUMBB_DIR / f"{stem}_thumb.png"
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# ——— DEBUG RUTAS ———
print(f"\n[🔍] Vídeo #{idx}: {video.name}")
print(f" Mov => {mov_out} exists={mov_out.exists()}")
print(f" Thumb => {thumb_out} exists={thumb_out.exists()}")

# 1) Si ya existe .mov **y** thumb, saltar todo el flujo
if mov_out.exists() and thumb_out.exists():
print(f"[⏭️ ] {stem}: ambos ficheros existen → salto completo")
borrar_directorios(sub_a, sub_b)
continue

# 2) Extracción de frames
print(f"[🎬] {stem}: extrayendo frames...")
extract_frames(video, sub_a)

# 3) Eliminación de fondo con IA
print(f"[➡️ ] {stem}: eliminando fondo con IA...")
remove_background_batch(sub_a, sub_b)

# 4) Generación de .mov (alpha channel)
print(f"[🎞️] {stem}: generando MOV...")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

# 5) Copiar miniatura
print(f"[🖼️ ] {stem}: copiando miniatura...")
copiar_thumb(sub_b, stem)

# 6) Limpieza de temporales
borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

o4-mini-high

Dijiste:

Pues sigue haciendolo

ChatGPT dijo:

Pensado durante 7 segundos

Aquí tienes el script completo con una comprobación de existencia mucho más robusta (case‑insensitive y mostrando el listado de ficheros en consola). De este modo deberías ver exactamente por qué módulo .mov o miniatura no detecta y así asegurar que nunca llamará a ffmpeg para un vídeo ya procesado.

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos").resolve()
VWB_DIR = (BASE_DIR / "VWB").resolve()
THUMBB_DIR = (BASE_DIR / "ThumbB").resolve()

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
files = [f for f in input_dir.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
img = Image.open(f).convert("RGB")
out = remover.process(img, type='rgba')
out.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min")
print("---------------------------------------------\n")

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}_thumb.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
print("[🧠] Inicializando IA y cargando modelo...\n")

# Inicializar dispositivo y modelo
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()

global remover
remover = Remover(mode='base', device=device)

print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# Asegurar directorios de salida
VWB_DIR.mkdir(parents=True, exist_ok=True)
THUMBB_DIR.mkdir(parents=True, exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
# Chequeo case‑insensitive en VWB_DIR
mov_files = [p.name.lower() for p in VWB_DIR.iterdir() if p.is_file()]
thumb_files = [p.name.lower() for p in THUMBB_DIR.iterdir() if p.is_file()]

mov_exists = f"{stem.lower()}.mov" in mov_files
thumb_exists = f"{stem.lower()}_thumb.png" in thumb_files

# Debug: lista y flags
print(f"\n[🔍] Vídeo #{idx}: {video.name}")
print(f" VWB_DIR contiene: {mov_files}")
print(f" THUMBB_DIR contiene: {thumb_files}")
print(f" mov_exists={mov_exists}, thumb_exists={thumb_exists}")

# Si ya existe .mov **y** thumb, saltamos TODO
if mov_exists and thumb_exists:
print(f"[⏭️ ] {stem}: detectado procesado → salto completo")
borrar_directorios(BASE_DIR / f"{idx}a", BASE_DIR / f"{idx}b")
continue

# 1) Extracción de frames
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"
print(f"[🎬] {stem}: extrayendo frames...")
extract_frames(video, sub_a)

# 2) Eliminación de fondo
print(f"[➡️ ] {stem}: eliminando fondo con IA...")
remove_background_batch(sub_a, sub_b)

# 3) Generar MOV
mov_out = VWB_DIR / f"{stem}.mov"
print(f"[🎞️] {stem}: generando MOV...")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

# 4) Copiar thumb
print(f"[🖼️ ] {stem}: copiando miniatura...")
copiar_thumb(sub_b, stem)

# 5) Limpiar
borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

Qué añadido:

Uso de Path(...).resolve() para asegurar rutas absolutas.

Listado de los nombres de archivos actuales en VWB_DIR y THUMBB_DIR (en lowercase) para verificar visualmente.

mov_exists y thumb_exists hechos con comparación case‑insensitive contra esos listados.

Salto completo (sin llamar a ffmpeg) si ambos flags están a True.

Con esto podrás ver en la consola por qué no los está detectando y corregir cualquier desajuste de nombre o ruta.

o4-mini-high

Dijiste:

[🔍] Vídeo #1: -wadn3K2lwzDDaqKTW2Qh_output-watermark.mp4
VWB_DIR contiene: ['-wadn3k2lwzddaqktw2qh_output-watermark.mov', '034a8401-312d-48b6-b3f0-ca1e6a336cc7.mov', '1fb05a68-d3af-4c62-9935-0326c6272603.mov']
THUMBB_DIR contiene: ['-wadn3k2lwzddaqktw2qh_output-watermark.png', '034a8401-312d-48b6-b3f0-ca1e6a336cc7.png', '1fb05a68-d3af-4c62-9935-0326c6272603.png']
mov_exists=True, thumb_exists=False

Pero si existe el thumb en thumb llamado:
-wadn3K2lwzDDaqKTW2Qh_output-watermark.png

Ademas lo dice el propio codigo:
THUMBB_DIR contiene: ['-wadn3k2lwzddaqktw2qh_output-watermark.png', '034a8401-312d-48b6-b3f0-ca1e6a336cc7.png', '1fb05a68-d3af-4c62-9935-0326c6272603.png']

Pero dice:
mov_exists=True, thumb_exists=False

Pero si existe
thumb_exists=True

ChatGPT dijo:

Pensado durante 5 segundos

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos").resolve()
VWB_DIR = (BASE_DIR / "VWB").resolve()
THUMBB_DIR = (BASE_DIR / "ThumbB").resolve()

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
files = [f for f in input_dir.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
img = Image.open(f).convert("RGB")
out = remover.process(img, type='rgba')
out.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min")
print("---------------------------------------------\n")

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
print("[🧠] Inicializando IA y cargando modelo...\n")

# Inicializar dispositivo y modelo
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()

global remover
remover = Remover(mode='base', device=device)

print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# Asegurar directorios de salida
VWB_DIR.mkdir(parents=True, exist_ok=True)
THUMBB_DIR.mkdir(parents=True, exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# Listados case-insensitive de ficheros existentes
mov_files = {p.name.lower() for p in VWB_DIR.iterdir() if p.is_file()}
thumb_stems = {p.stem.lower() for p in THUMBB_DIR.iterdir() if p.suffix.lower() == ".png"}

mov_exists = f"{stem.lower()}.mov" in mov_files
thumb_exists = stem.lower() in thumb_stems

# Debug de rutas y flags
print(f"\n[🔍] Vídeo #{idx}: {video.name}")
print(f" VWB_DIR contiene: {sorted(mov_files)}")
print(f" THUMBB_DIR stems: {sorted(thumb_stems)}")
print(f" mov_exists={mov_exists}, thumb_exists={thumb_exists}")

# Si ya existe .mov **y** thumb, saltamos TODO
if mov_exists and thumb_exists:
print(f"[⏭️ ] {stem}: ya procesado → salto completo")
borrar_directorios(sub_a, sub_b)
continue

# 1) Extracción de frames
print(f"[🎬] {stem}: extrayendo frames…")
extract_frames(video, sub_a)

# 2) Eliminación de fondo
print(f"[➡️ ] {stem}: eliminando fondo con IA…")
remove_background_batch(sub_a, sub_b)

# 3) Generación de MOV
mov_out = VWB_DIR / f"{stem}.mov"
print(f"[🎞️] {stem}: generando MOV…")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

# 4) Copiar miniatura
print(f"[🖼️ ] {stem}: copiando miniatura…")
copiar_thumb(sub_b, stem)

# 5) Limpieza de temporales
borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

Cambios clave:

Detección del thumb ahora compara stem.lower() contra el conjunto de stems de todos los PNG en ThumbB, sin requerir sufijos adicionales.

La función copiar_thumb guarda la miniatura como SpeakingVideos/ThumbB/<video_stem>.png, igual que tu fichero existente -wadn3K2lwzDDaqKTW2Qh_output-watermark.png.

Se muestra en consola el listado real de nombres (mov_files) y stems de miniaturas (thumb_stems) para comprobar visualmente qué archivos detecta el script.

Con esto, si ya existe -wadn3K2lwzDDaqKTW2Qh_output-watermark.mov y -wadn3K2lwzDDaqKTW2Qh_output-watermark.png, ambos flags saldrán True y nunca se llamará a ffmpeg para volver a extraer sus frames.

o4-mini-high

Dijiste:

Ahora parece que si al fin, Gracias Chat!!! Aunque hay que esperar a ver todo el proceso...

ChatGPT dijo:

¡Perfecto! Me alegra saber que ya lo detecta correctamente. 👍

Si en cualquier punto del proceso surge algún error o necesitas ajustar algo más, no dudes en decírmelo. ¡Aquí estoy para lo que haga falta!

o4-mini-high

Dijiste:

Y podriamos probar ahora, que vaya mas rapido con esto que me comentastes sobre esta linea que muestra:
Settings -> Mode=base, Device=cuda, Torchscript=disabled:
/////////////////////
TorchScript es la forma “JIT” (Just‑In‑Time) de PyTorch: convierte tu modelo de Python en un grafo optimizado que puede ejecutarse con menos sobrecarga de Python, y en muchos casos acelera la inferencia, sobre todo en CPU y cuando hay muchas llamadas al modelo.

– Sin TorchScript (eager mode)

Cada llamada al método forward() pasa por el intérprete Python, lo que añade latencia.

En GPU el efecto es menos dramático porque la mayoría del tiempo se consume en el kernel CUDA, pero sigue habiendo un pequeño overhead de gestión desde Python.

En CPU, ese overhead puede suponer fácilmente un 20–50 % de tiempo extra, dependiendo del tamaño del lote y la complejidad del modelo.

– Con TorchScript habilitado

El modelo se “traza” o “compila” a un grafo estático.

Las llamadas de inferencia invocan directamente ese grafo, reduciendo la carga de Python.

Suele dar ganancias de rendimiento notables en CPU (menor latencia por ítem) y mejoras moderadas en GPU.

En tu caso, al ver Torchscript=disabled, significa que el Remover está ejecutando el modelo en modo eager. Si notas que el procesamiento por imagen es más lento de lo que esperas, valdría la pena activar TorchScript (si la librería lo soporta). El método suele consistir en:

python
Copiar
Editar
# Ejemplo genérico
model.eval()
traced = torch.jit.trace(model, ejemplo_de_input)
traced.save("modelo_trazado.pt")
model = torch.jit.load("modelo_trazado.pt")
O bien, revisar si transparent_background.Remover ofrece un parámetro tipo use_torchscript=True al instanciarlo.

Conclusión:
Sí, sin TorchScript vas a ir algo más lento (especialmente en CPU). Si tu flujo es muy intensivo y el librero lo permite, habilítalo o traza el modelo para reducir la sobrecarga de Python y ganar velocidad en la inferencia.
/////////////////////

ChatGPT dijo:

Pensado durante 5 segundos

Aquí tienes el script completo, con un bloque al principio que intenta “trazar” el modelo con TorchScript para reducir la sobrecarga en Python y acelerar la inferencia. Si no fuera posible (por la librería interna), caerá de nuevo al modo eager sin interrumpir.

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos").resolve()
VWB_DIR = (BASE_DIR / "VWB").resolve()
THUMBB_DIR = (BASE_DIR / "ThumbB").resolve()

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
files = [f for f in input_dir.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
img = Image.open(f).convert("RGB")
out = remover.process(img, type='rgba')
out.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min")
print("---------------------------------------------\n")

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
print("[🧠] Inicializando IA y cargando modelo...\n")

# 1) Inicializar dispositivo
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()

# 2) Instanciar Remover
global remover
remover = Remover(mode='base', device=device)
print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# 3) Intentar torch.jit.trace para acelerar
try:
print("[⚡] Intentando activar TorchScript JIT...")
# Necesitamos un tensor dummy del tamaño aproximado que usa el modelo.
# Ajusta (1,3,512,512) al tamaño que maneje tu Remover internamente.
dummy = torch.zeros(1, 3, 512, 512, device=device)
# Suponemos que Remover expone su modelo en .model
remover.model.eval()
traced = torch.jit.trace(remover.model, dummy)
remover.model = traced
print("[✅] TorchScript JIT activado correctamente.")
except Exception as e:
print(f"[⚠️] No se pudo activar TorchScript: {e}")
print()

# Asegurar directorios de salida
VWB_DIR.mkdir(parents=True, exist_ok=True)
THUMBB_DIR.mkdir(parents=True, exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# Listados case-insensitive
mov_files = {p.name.lower() for p in VWB_DIR.iterdir() if p.is_file()}
thumb_stems = {p.stem.lower() for p in THUMBB_DIR.iterdir() if p.suffix.lower() == ".png"}

mov_exists = f"{stem.lower()}.mov" in mov_files
thumb_exists = stem.lower() in thumb_stems

# Debug
print(f"\n[🔍] Vídeo #{idx}: {video.name}")
print(f" mov_exists={mov_exists}, thumb_exists={thumb_exists}")

if mov_exists and thumb_exists:
print(f"[⏭️ ] {stem}: ya procesado, salto.")
borrar_directorios(sub_a, sub_b)
continue

print(f"[🎬] {stem}: extrayendo frames…")
extract_frames(video, sub_a)

print(f"[➡️ ] {stem}: eliminando fondo…")
remove_background_batch(sub_a, sub_b)

mov_out = VWB_DIR / f"{stem}.mov"
print(f"[🎞️] {stem}: generando MOV…")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

print(f"[🖼️ ] {stem}: copiando miniatura…")
copiar_thumb(sub_b, stem)

borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

Qué cambia

Tras crear remover = Remover(...), intentamos trazar su modelo con un tensor dummy (1,3,512,512). Ajusta ese tamaño si tu IA usa otra resolución base.

Si el trazo funciona, remover.model pasa a ser un módulo TorchScript optimizado, con menor overhead Python en cada llamada.

Si falla, sigue en modo eager sin parar el script.

Con esto, lanza el script y debería verse cómo cada lote de imágenes va algo más rápido, especialmente si trabajas en CPU o con muchos archivos pequeños.

o4-mini-high

Dijiste:

No, trabajo con uan RTX 3060 12Gb

ChatGPT dijo:

Pensado durante 6 segundos

He preparado una versión del script que:

Activa cuDNN benchmark para optimizar convoluciones en GPU.

Pone el modelo en modo evaluación y lo convierte a half‑precision (fp16) si es compatible.

Envuelve la inferencia en un contexto de auto‑cast de PyTorch AMP para usar los Tensor Cores de tu RTX 3060.

Sigue intentando trazado con TorchScript (ahora con torch.jit.script, más robusto para flujos con condicionales).

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos").resolve()
VWB_DIR = (BASE_DIR / "VWB").resolve()
THUMBB_DIR = (BASE_DIR / "ThumbB").resolve()

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
files = [f for f in input_dir.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
img = Image.open(f).convert("RGB")
# Inferencia con autocast fp16
with torch.cuda.amp.autocast(enabled=(device=="cuda")):
out = remover.process(img, type='rgba')
out.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min")
print("---------------------------------------------\n")

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
global device, remover

print("[🧠] Inicializando IA y cargando modelo...\n")

# 1) Dispositivo y cuDNN benchmark
if torch.cuda.is_available():
device = "cuda"
gpu_index = 0
torch.backends.cudnn.benchmark = True
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram= torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()

# 2) Instanciar y preparar Remover
remover = Remover(mode='base', device=device)
remover.model.eval()
print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# 3) Mixed‑precision fp16
try:
remover.model = remover.model.half()
print("[⚡] Modelo convertido a half precision (fp16).")
except Exception:
print("[⚠️] No fue posible convertir modelo a fp16, continuamos en full‑precision.")

# 4) Intentar TorchScript (script)
try:
print("[⚡] Intentando torch.jit.script sobre el modelo...")
remover.model = torch.jit.script(remover.model)
print("[✅] TorchScript activado con torch.jit.script.")
except Exception as e:
print(f"[⚠️] TorchScript falló: {e}")
print()

# Asegurar directorios de salida
VWB_DIR.mkdir(parents=True, exist_ok=True)
THUMBB_DIR.mkdir(parents=True, exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# Chequeo de existencia (case‑insensitive)
mov_exists = (VWB_DIR / f"{stem}.mov").exists()
thumb_exists = (THUMBB_DIR / f"{stem}.png").exists()

print(f"\n[🔍] Vídeo #{idx}: {video.name} → mov={mov_exists}, thumb={thumb_exists}")
if mov_exists and thumb_exists:
print(f"[⏭️ ] {stem}: ya procesado, salto.")
borrar_directorios(sub_a, sub_b)
continue

print(f"[🎬] {stem}: extrayendo frames…")
extract_frames(video, sub_a)

print(f"[➡️ ] {stem}: eliminando fondo…")
remove_background_batch(sub_a, sub_b)

mov_out = VWB_DIR / f"{stem}.mov"
print(f"[🎞️] {stem}: generando MOV…")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

print(f"[🖼️ ] {stem}: copiando miniatura…")
copiar_thumb(sub_b, stem)

borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

Puntos clave

torch.backends.cudnn.benchmark = True para que cuDNN elija la mejor configuración de kernels en GPU.

model.half() para reducir precisión a FP16 y aprovechar mejor los Tensor Cores.

torch.cuda.amp.autocast() controla la inferencia en modo mixto (mantiene precisión donde haga falta).

Se intenta torch.jit.script (más compatible que trace para flujos dinámicos).

Con una RTX 3060 de 12 GB deberías notar un buen salto en rendimiento de inferencia de imágenes. Prueba el script y mide tiempos por lote; si quieres exponer métricas, podemos añadir un contador de imágenes por segundo al imprimir en consola. ¡Cuéntame qué tal la mejora!

o4-mini-high

Dijiste:

Ahora, en cuanto me cree un nuevo video, que parece ser largo, pruebo...!!!

ChatGPT dijo:

¡Perfecto! Dale caña y, cuando termine, cuéntame cómo ha ido: si notas que va más rápido, si hay algún cuello de botella o si aparece cualquier error. Si quieres, puedo ayudarte a añadir métricas de rendimiento (por ejemplo, imágenes procesadas por segundo o tiempo por lote) para que veas exactamente la mejora. ¡Ya me contarás!

o4-mini-high

Dijiste:

Cmd:
[🔍] Vídeo #1: -wadn3K2lwzDDaqKTW2Qh_output-watermark.mp4 → mov=True, thumb=True
[⏭️ ] -wadn3K2lwzDDaqKTW2Qh_output-watermark: ya procesado, salto.

[🔍] Vídeo #2: 034a8401-312d-48b6-b3f0-ca1e6a336cc7.mp4 → mov=True, thumb=True
[⏭️ ] 034a8401-312d-48b6-b3f0-ca1e6a336cc7: ya procesado, salto.

[🔍] Vídeo #3: 1fb05a68-d3af-4c62-9935-0326c6272603.mp4 → mov=True, thumb=True
[⏭️ ] 1fb05a68-d3af-4c62-9935-0326c6272603: ya procesado, salto.

[🔍] Vídeo #4: 2b158e3a-d61c-48ff-bed9-a0dbf0dbac68.mp4 → mov=False, thumb=False
[🎬] 2b158e3a-d61c-48ff-bed9-a0dbf0dbac68: extrayendo frames…
[➡️ ] 2b158e3a-d61c-48ff-bed9-a0dbf0dbac68: eliminando fondo…
[📁] Origen: P:\crearthumbnails\SpeakingVideos\4a → Procesando 1957 imágenes
🔄 Progreso: 0%| [00:00<?] 0/1957P:\crearthumbnails\script_maestro_speakingvideos_v15.py:40: FutureWarning: torch.cuda.amp.autocast(args...) is deprecated. Please use torch.amp.autocast('cuda', args...) instead.
with torch.cuda.amp.autocast(enabled=(device=="cuda")):
C:\Users\Administrador\AppData\Local\Programs\Python\Python310\lib\site-packages\transparent_background\Remover.py:208: RuntimeWarning: invalid value encountered in cast
pred = (pred * 255).astype(np.uint8)
🔄 Progreso: 5%|█▊ [00:28<09:23] 91/1957

ChatGPT dijo:

Pensado durante 8 segundos

Ese warning deprecatorio y el “invalid value” no bloquean el procesamiento, pero conviene limpiarlos para tener un código más robusto. Te propongo 2 cambios:

Usar la nueva API de autocast

Reemplaza esta línea:

python

CopiarEditar

with torch.cuda.amp.autocast(enabled=(device=="cuda")):

por

python

CopiarEditar

# al inicio del script, tras importar torch:
import torch

# …luego, en tu loop de inferencia:
with torch.amp.autocast(device_type="cuda", enabled=(device=="cuda")):
out = remover.process(img, type='rgba')

Esto suprime el FutureWarning y se ajusta a la API actual de PyTorch.

Silenciar o limpiar los NaN antes del cast a uint8

El warning invalid value encountered in cast viene de dentro de la librería (pred contiene NaN). Puedes:

Ignorar ese tipo de warning de NumPy:

python

CopiarEditar

import numpy as np
# pon esto justo después de los imports:
np.seterr(invalid='ignore')

O, mejor, limpiar los NaN justo después de remover.process (si quieres asegurarte de que no haya pixeles basura):

python

CopiarEditar

import numpy as np
# …dentro de remove_background_batch, sustituyendo:
out = remover.process(img, type='rgba')
# por:
out = remover.process(img, type='rgba')
arr = np.array(out)
arr = np.nan_to_num(arr, nan=0.0, posinf=255, neginf=0)
out = Image.fromarray(arr.astype(np.uint8))

Con estos cambios:

Ya no verás el FutureWarning porque usas torch.amp.autocast.

Los NaN se convertirán a ceros (o al valor que especifiques), evitando imágenes corruptas.

Si prefieres simplemente ocultar el warning sin tocar la inferencia, la solución np.seterr(invalid='ignore') es la más rápida. ¡Pruébalo y me dices!

o4-mini-high

Dijiste:

Pero que quiere decir, lo que comentabas que si no puede cambia a otra cosa?

ChatGPT dijo:

Pensado durante unos segundos

Significa básicamente dos cosas:

El FutureWarning de autocast te avisa de que la API torch.cuda.amp.autocast(...) ha sido renombrada a torch.amp.autocast(...) en las versiones más recientes de PyTorch. Es sólo un aviso de que, en el futuro, aquella función dejará de existir y conviene usar la nueva.

Qué hacer:

Si tu versión de PyTorch la soporta, reemplaza

python

CopiarEditar

with torch.cuda.amp.autocast(enabled=(device=="cuda")):
out = remover.process(img, type='rgba')

por

python

CopiarEditar

with torch.amp.autocast(device_type="cuda", enabled=(device=="cuda")):
out = remover.process(img, type='rgba')

Si tu versión de PyTorch NO tiene torch.amp, puedes seguir usando la antigua torch.cuda.amp.autocast y simplemente ignorar el warning hasta que actualices PyTorch. El rendimiento seguirá siendo el mismo.

Incluso podrías hacer un “fallback” dinámico:

python

CopiarEditar

try:
autocast = torch.amp.autocast
except AttributeError:
autocast = torch.cuda.amp.autocast

# …en tu loop de inferencia:
with autocast(device_type="cuda", enabled=(device=="cuda")):
out = remover.process(img, type='rgba')

El RuntimeWarning: invalid value encountered in cast sale porque, internamente, la librería genera algún NaN en el array antes de convertirlo a uint8. No rompe nada, pero sí llena la consola de mensajes.

Qué hacer:

Opción 1 (ignorar warning globalmente)

Justo después de tus imports, añade:

python

CopiarEditar

import numpy as np
np.seterr(invalid='ignore')

Opción 2 (limpiar NaNs antes de salvar)

Dentro de tu remove_background_batch, sustituye:

python

CopiarEditar

out = remover.process(img, type='rgba')
out.save(...)

por

python

CopiarEditar

out = remover.process(img, type='rgba')
arr = np.array(out)
arr = np.nan_to_num(arr, nan=0, posinf=255, neginf=0)
out = Image.fromarray(arr.astype(np.uint8))
out.save(...)

En resumen:

El warning de deprecación sólo te pide usar la nueva API torch.amp.autocast; si no la tienes, sigue con la antigua y olvida el warning hasta actualizar.

El warning de valores inválidos viene de NaN en el array; puedes o bien desactivarlo globalmente o limpiar los NaN justo antes de guardar la imagen.

Cualquiera de las dos soluciones resolverá esos mensajes y no afectará la lógica ni la velocidad de tu procesamiento.

o4-mini-high

Dijiste:

me podrias dar el py con al solucion que crees mejor?

ChatGPT dijo:

Pensado durante 10 segundos

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
import numpy as np
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos").resolve()
VWB_DIR = (BASE_DIR / "VWB").resolve()
THUMBB_DIR = (BASE_DIR / "ThumbB").resolve()

# Placeholder para el autocast
do_autocast = None

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
files = [f for f in input_dir.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
img = Image.open(f).convert("RGB")
# Inferencia con autocast (FP16 en GPU)
with do_autocast():
raw = remover.process(img, type='rgba')
# Convertir a array y limpiar NaNs/Infs
arr = np.array(raw)
arr = np.nan_to_num(arr, nan=0, posinf=255, neginf=0)
clean = Image.fromarray(arr.astype(np.uint8))
clean.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min")
print("---------------------------------------------\n")

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(parents=True, exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
global do_autocast, remover, device

print("[🧠] Inicializando IA y cargando modelo...\n")

# 1) Selección de dispositivo y cuDNN benchmark
if torch.cuda.is_available():
device = "cuda"
torch.backends.cudnn.benchmark = True
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()

# 2) Instanciar Remover y modo evaluación
remover = Remover(mode='base', device=device)
remover.model.eval()

print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# 3) Mixed precision FP16
try:
remover.model = remover.model.half()
print("[⚡] Modelo convertido a half precision (fp16).")
except Exception:
print("[⚠️] No fue posible convertir el modelo a fp16, continuamos en full‑precision.")

# 4) TorchScript con torch.jit.script
try:
remover.model = torch.jit.script(remover.model)
print("[⚡] TorchScript activado con torch.jit.script.")
except Exception as e:
print(f"[⚠️] TorchScript falló: {e}")
print()

# 5) Configurar autocast dinámico
try:
# PyTorch >=1.10
torch.amp.autocast
do_autocast = lambda: torch.amp.autocast(device_type="cuda", enabled=(device=="cuda"))
print("[⚡] Usando torch.amp.autocast para inferencia.")
except AttributeError:
do_autocast = lambda: torch.cuda.amp.autocast(enabled=(device=="cuda"))
print("[⚡] Usando torch.cuda.amp.autocast para inferencia.")
print()

# Asegurar directorios de salida
VWB_DIR.mkdir(parents=True, exist_ok=True)
THUMBB_DIR.mkdir(parents=True, exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# Comprobar existencia de outputs
mov_exists = (VWB_DIR / f"{stem}.mov").exists()
thumb_exists = (THUMBB_DIR / f"{stem}.png").exists()

print(f"\n[🔍] Vídeo #{idx}: {video.name} → mov={mov_exists}, thumb={thumb_exists}")
if mov_exists and thumb_exists:
print(f"[⏭️ ] {stem}: ya procesado, salto.")
borrar_directorios(sub_a, sub_b)
continue

print(f"[🎬] {stem}: extrayendo frames…")
extract_frames(video, sub_a)

print(f"[➡️ ] {stem}: eliminando fondo…")
remove_background_batch(sub_a, sub_b)

mov_out = VWB_DIR / f"{stem}.mov"
print(f"[🎞️] {stem}: generando MOV…")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

print(f"[🖼️ ] {stem}: copiando miniatura…")
copiar_thumb(sub_b, stem)

borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

o4-mini-high

Dijiste:

Ok, ahora probare, y esto que quiere decir:
C:\Users\Administrador\AppData\Local\Programs\Python\Python310\lib\site-packages\transparent_background\Remover.py:208: RuntimeWarning: invalid value encountered in cast

ChatGPT dijo:

Ese mensaje te avisa de que, al convertir internamente el array de píxeles (floats) a uint8, NumPy encontró algún valor “inválido” (normalmente un NaN o un infinito) en el array. En concreto:

Qué ocurre

El Remover genera un resultado en coma flotante (valores entre 0 y 255).

Al hacer astype(np.uint8), NumPy ve uno o varios elementos que no son un número válido (NaN) o están fuera de rango, y lanza ese warning: “invalid value encountered in cast”.

Por qué no se para

Es sólo un warning, no un error. El array se convierte, y los NaN pasan a 0 o a 255 (dependiendo de la implementación interna), pero la ejecución continúa.

Cómo evitarlo

Ignorarlo: tras los imports pon

python

CopiarEditar

import numpy as np
np.seterr(invalid='ignore')

Limpiar antes del cast: justo antes de convertir a imagen, pasa el array por

python

CopiarEditar

arr = np.nan_to_num(arr, nan=0, posinf=255, neginf=0)

así todos los NaN se sustituyen por 0 y los infinitos por 0 o 255.

En la práctica no suele corromper nada crítico, pero si quieres una salida “limpia” sin warning, te recomiendo usar np.nan_to_num justo antes de guardar la imagen, como ya te mostré en el último script.

o4-mini-high

Dijiste:

Y el ultimo py que me has puesto dara ese mismo mensaje?

ChatGPT dijo:

Pensado durante unos segundos

El warning sigue saliendo porque en realidad se lanza dentro de transparent_background.Remover.process() (justo en la línea donde hacen pred.astype(np.uint8)) antes de que nuestro código tome ese array y lo limpie con np.nan_to_num. Es decir, aunque luego limpiemos los NaN, el lib ya ha imprimido el warning.

Para suprimirlo por completo, añade inmediatamente después de los imports estas dos líneas:

python

CopiarEditar

import numpy as np
np.seterr(invalid='ignore')

Con eso, NumPy no imprimirá más el invalid value encountered in cast, y tu consola quedará limpia de esos mensajes. Si lo prefieres, también puedes apuntar sólo a ignorar warnings específicos con el módulo warnings, pero np.seterr(invalid='ignore') es la solución más directa.

¿Quieres que te vuelva a pasar el script completo con esa línea incluida?

o4-mini-high

Dijiste:

¿Quieres que te vuelva a pasar el script completo con esa línea incluida?
Si, por favor

ChatGPT dijo:

python

CopiarEditar

import os
import time
import subprocess
from pathlib import Path
import shutil
from PIL import Image
import numpy as np
# Suprimir warnings de NumPy por valores inválidos al castear
np.seterr(invalid='ignore')
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos").resolve()
VWB_DIR = (BASE_DIR / "VWB").resolve()
THUMBB_DIR = (BASE_DIR / "ThumbB").resolve()

# Placeholder para el autocast
do_autocast = None

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames(video_path, output_dir):
os.makedirs(output_dir, exist_ok=True)
cmd = [
"ffmpeg", "-y", "-i", str(video_path),
os.path.join(output_dir, "frame_%05d.png")
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
files = [f for f in input_dir.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(files, desc="🔄 Progreso", unit="img", ncols=80,
bar_format='{l_bar}{bar} [{elapsed}<{remaining}] {n_fmt}/{total_fmt}'):
try:
img = Image.open(f).convert("RGB")
# Inferencia con autocast (FP16 en GPU si está disponible)
with do_autocast():
raw = remover.process(img, type='rgba')
# Convertir a array y limpiar NaNs/Infs
arr = np.array(raw)
arr = np.nan_to_num(arr, nan=0, posinf=255, neginf=0)
clean = Image.fromarray(arr.astype(np.uint8))
clean.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min")
print("---------------------------------------------\n")

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(parents=True, exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
global do_autocast, remover, device

print("[🧠] Inicializando IA y cargando modelo...\n")

# 1) Selección de dispositivo y cuDNN benchmark
if torch.cuda.is_available():
device = "cuda"
torch.backends.cudnn.benchmark = True
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()

# 2) Instanciar Remover y modo evaluación
remover = Remover(mode='base', device=device)
remover.model.eval()

print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# 3) Mixed precision FP16
try:
remover.model = remover.model.half()
print("[⚡] Modelo convertido a half precision (fp16).")
except Exception:
print("[⚠️] No fue posible convertir el modelo a fp16, continuamos en full‑precision.")

# 4) TorchScript con torch.jit.script
try:
remover.model = torch.jit.script(remover.model)
print("[⚡] TorchScript activado con torch.jit.script.")
except Exception as e:
print(f"[⚠️] TorchScript falló: {e}")
print()

# 5) Configurar autocast dinámico
try:
# PyTorch >=1.10
torch.amp.autocast
do_autocast = lambda: torch.amp.autocast(device_type="cuda", enabled=(device=="cuda"))
print("[⚡] Usando torch.amp.autocast para inferencia.")
except AttributeError:
do_autocast = lambda: torch.cuda.amp.autocast(enabled=(device=="cuda"))
print("[⚡] Usando torch.cuda.amp.autocast para inferencia.")
print()

# Asegurar directorios de salida
VWB_DIR.mkdir(parents=True, exist_ok=True)
THUMBB_DIR.mkdir(parents=True, exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# Comprobar existencia de outputs
mov_exists = (VWB_DIR / f"{stem}.mov").exists()
thumb_exists = (THUMBB_DIR / f"{stem}.png").exists()

print(f"\n[🔍] Vídeo #{idx}: {video.name} → mov={mov_exists}, thumb={thumb_exists}")
if mov_exists and thumb_exists:
print(f"[⏭️ ] {stem}: ya procesado, salto.")
borrar_directorios(sub_a, sub_b)
continue

print(f"[🎬] {stem}: extrayendo frames…")
extract_frames(video, sub_a)

print(f"[➡️ ] {stem}: eliminando fondo…")
remove_background_batch(sub_a, sub_b)

mov_out = VWB_DIR / f"{stem}.mov"
print(f"[🎞️] {stem}: generando MOV…")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

print(f"[🖼️ ] {stem}: copiando miniatura…")
copiar_thumb(sub_b, stem)

borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

o4-mini-high

Dijiste:

Una pregunta aparte, en estas lineas de ffmpegsacando las iimagenes de cada video:
[🎬] 2b158e3a-d61c-48ff-bed9-a0dbf0dbac68: extrayendo frames…

Seria posible algo que fuera diciendo como cuando crea las imagenes sin fondo:
🔄 Progreso: 95%|████████████████████████████████████▎ [09:32<00:27] 1868/1957

ChatGPT dijo:

Pensado durante 5 segundos

Sí, se puede. La idea es:

Obtener el número total de frames con ffprobe.

Lanzar ffmpeg en un Popen capturando su salida de progreso (stderr).

Parsear las líneas tipo frame= 123 y actualizar un tqdm.

Te dejo un ejemplo de cómo sería la función extract_frames() con progreso:

python

CopiarEditar

import re
import subprocess
from tqdm import tqdm
from pathlib import Path

def extract_frames_with_progress(video_path, output_dir):
output_dir = Path(output_dir)
output_dir.mkdir(exist_ok=True)
# 1) Obtener total de frames con ffprobe
cmd_probe = [
"ffprobe", "-v", "error",
"-count_frames", "-select_streams", "v:0",
"-show_entries", "stream=nb_read_frames",
"-of", "default=nokey=1:noprint_wrappers=1",
str(video_path)
]
res = subprocess.run(cmd_probe, capture_output=True, text=True)
try:
total_frames = int(res.stdout.strip())
except:
total_frames = None # si falla, dejamos indeterminado

# 2) Preparar el tqdm
pbar = tqdm(total=total_frames,
desc=f"Extracting {video_path.name}",
unit="frame",
ncols=80,
bar_format='{l_bar}{bar}| {n_fmt}/{total_fmt}')

# 3) Lanzar ffmpeg y parsear stderr
cmd_ff = [
"ffmpeg", "-y", "-i", str(video_path),
"-vsync", "0",
os.path.join(str(output_dir), "frame_%05d.png")
]
proc = subprocess.Popen(cmd_ff, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, text=True)

frame_re = re.compile(r"frame=\s*(\d+)")
while True:
line = proc.stderr.readline()
if not line:
break
m = frame_re.search(line)
if m:
cur = int(m.group(1))
# avanzamos la barra sin pasarnos
delta = cur - pbar.n
if delta > 0:
pbar.update(delta)
proc.wait()
pbar.close()

Luego en tu script, sólo sustituyes la llamada:

python

CopiarEditar

# en lugar de:
# extract_frames(video, sub_a)

# usas:
extract_frames_with_progress(video, sub_a)

Ventajas

Verás un progreso al estilo 🔄 123/2000 [>…] durante la extracción.

Si ffprobe no puede contar frames, total_frames queda None y el bar sigue mostrando sólo el contador.

Con esto tendrás un feedback visual idéntico al de la eliminación de fondo, pero para la fase de ffmpeg.

o4-mini-high

Dijiste:

Y me podrias dar, porfa, el codigo completo con las mejoras ultimas que hicstes, mas esto?

ChatGPT dijo:

Pensado durante 8 segundos

He incluido el script completo con:

Suprimir los warnings de NumPy.

Barra de progreso para la extracción de frames (extract_frames_with_progress).

Mixed‑precision y AMP autocast.

TorchScript (script).

Verificación de existencia de .mov y miniatura.

Es posible que, al ejecutar en este entorno de Jupyter, no esté instalada la librería transparent_background, pero en tu setup habitual, donde ya corría el script, funcionará correctamente.

¡Prueba este script en tu máquina y dime si todo marcha bien! ​​

o4-mini-high

Dijiste:

No veo el script?

ChatGPT dijo:

Aquí tienes el script completo con todas las mejoras integradas (suprime los warnings de NumPy, usa mixed‑precision y AMP, activa TorchScript y muestra barra de progreso al extraer frames):

python

CopiarEditar

import os
import time
import subprocess
import re
from pathlib import Path
import shutil
from PIL import Image
import numpy as np
# Suprimir warnings de NumPy por valores inválidos al castear
np.seterr(invalid='ignore')
from transparent_background import Remover
import torch
from tqdm import tqdm

# === CONFIGURACIÓN ===
BASE_DIR = Path("P:/crearthumbnails/SpeakingVideos").resolve()
VWB_DIR = (BASE_DIR / "VWB").resolve()
THUMBB_DIR = (BASE_DIR / "ThumbB").resolve()

# Placeholder para autocast dinámico
do_autocast = None

# === FUNCIONES ===
def get_video_files(directory):
return sorted(f for f in Path(directory).glob("*.mp4") if f.is_file())

def extract_frames_with_progress(video_path, output_dir):
output_dir = Path(output_dir)
output_dir.mkdir(exist_ok=True)
# Obtener total de frames con ffprobe
cmd_probe = [
"ffprobe", "-v", "error",
"-count_frames", "-select_streams", "v:0",
"-show_entries", "stream=nb_read_frames",
"-of", "default=nokey=1:noprint_wrappers=1",
str(video_path)
]
res = subprocess.run(cmd_probe, capture_output=True, text=True)
try:
total_frames = int(res.stdout.strip())
except:
total_frames = None # si no se pudo contar

# Barra de progreso
pbar = tqdm(
total=total_frames,
desc=f"🔄 Extrayendo frames {video_path.name}",
unit="frame",
ncols=80,
bar_format="{l_bar}{bar} [{n_fmt}/{total_fmt}] {elapsed}<{remaining}"
)

# Ejecutar ffmpeg y parsear su stderr
cmd_ff = [
"ffmpeg", "-y", "-i", str(video_path),
"-vsync", "0",
os.path.join(str(output_dir), "frame_%05d.png")
]
proc = subprocess.Popen(cmd_ff, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, text=True)
frame_re = re.compile(r"frame=\s*(\d+)")
while True:
line = proc.stderr.readline()
if not line:
break
m = frame_re.search(line)
if m:
cur = int(m.group(1))
delta = cur - pbar.n
if delta > 0:
pbar.update(delta)
proc.wait()
pbar.close()

def remove_background_batch(input_dir, output_dir):
input_dir = Path(input_dir)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
files = [f for f in input_dir.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")]
print(f"[📁] Origen: {input_dir} → Procesando {len(files)} imágenes")
start = time.time()
for f in tqdm(
files,
desc="🔄 Eliminando fondo",
unit="img",
ncols=80,
bar_format="{l_bar}{bar} [{n_fmt}/{total_fmt}] {elapsed}<{remaining}"
):
try:
img = Image.open(f).convert("RGB")
with do_autocast():
raw = remover.process(img, type="rgba")
arr = np.array(raw)
arr = np.nan_to_num(arr, nan=0, posinf=255, neginf=0)
clean = Image.fromarray(arr.astype(np.uint8))
clean.save(output_dir / (f.stem + "_nobg.png"))
except Exception as e:
print(f"[❌] Error con {f.name}: {e}")
elapsed = time.time() - start
print(f"\n[✅] Lote completado en {int(elapsed//60):02}:{int(elapsed%60):02} min")
print("-" * 45)

def generate_mov(images_dir, audio_path, output_path):
cmd = [
"ffmpeg", "-y",
"-framerate", "30", "-i", os.path.join(images_dir, "frame_%05d_nobg.png"),
"-i", str(audio_path),
"-c:v", "qtrle", "-c:a", "aac", "-strict", "experimental",
str(output_path)
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def copiar_thumb(img_dir, video_stem):
THUMBB_DIR.mkdir(parents=True, exist_ok=True)
frames = sorted(Path(img_dir).glob("frame_*_nobg.png"))
if frames:
destino = THUMBB_DIR / f"{video_stem}.png"
shutil.copy(frames[0], destino)

def borrar_directorios(*dirs):
for d in dirs:
shutil.rmtree(d, ignore_errors=True)

# === MAIN ===
def main():
global do_autocast, remover, device

print("[🧠] Inicializando IA y cargando modelo...\n")

# Selección de dispositivo y cuDNN benchmark
if torch.cuda.is_available():
device = "cuda"
torch.backends.cudnn.benchmark = True
gpu_index = 0
gpu_name = torch.cuda.get_device_name(gpu_index)
total_vram = torch.cuda.get_device_properties(gpu_index).total_memory // (1024**2)
used_vram = torch.cuda.memory_allocated(gpu_index) // (1024**2)
torch.cuda.empty_cache()
else:
device = "cpu"
import platform
gpu_name = platform.processor()

# Instanciar Remover y modo evaluación
remover = Remover(mode="base", device=device)
remover.model.eval()

print(f"[🚀] Device: {device} ({gpu_name})")
if device == "cuda":
print(f"[📊] VRAM usada: {used_vram} MiB / {total_vram} MiB")
print()

# Mixed precision FP16
try:
remover.model = remover.model.half()
print("[⚡] Modelo convertido a half precision (fp16).")
except Exception:
print("[⚠️] No fue posible convertir el modelo a fp16, continuamos en full‑precision.")

# TorchScript con torch.jit.script
try:
remover.model = torch.jit.script(remover.model)
print("[⚡] TorchScript activado con torch.jit.script.")
except Exception as e:
print(f"[⚠️] TorchScript falló: {e}")
print()

# Configurar autocast dinámico
try:
torch.amp.autocast
do_autocast = lambda: torch.amp.autocast(device_type="cuda", enabled=(device=="cuda"))
print("[⚡] Usando torch.amp.autocast para inferencia.")
except AttributeError:
do_autocast = lambda: torch.cuda.amp.autocast(enabled=(device=="cuda"))
print("[⚡] Usando torch.cuda.amp.autocast para inferencia.")
print()

# Asegurar directorios de salida
VWB_DIR.mkdir(parents=True, exist_ok=True)
THUMBB_DIR.mkdir(parents=True, exist_ok=True)

videos = get_video_files(BASE_DIR)
for idx, video in enumerate(videos, start=1):
stem = video.stem
sub_a = BASE_DIR / f"{idx}a"
sub_b = BASE_DIR / f"{idx}b"

# Comprobar existencia de outputs
mov_exists = (VWB_DIR / f"{stem}.mov").exists()
thumb_exists = (THUMBB_DIR / f"{stem}.png").exists()

print(f"\n[🔍] Vídeo #{idx}: {video.name} → mov={mov_exists}, thumb={thumb_exists}")
if mov_exists and thumb_exists:
print(f"[⏭️ ] {stem}: ya procesado, salto.")
borrar_directorios(sub_a, sub_b)
continue

print(f"[🎬] {stem}: extrayendo frames…")
extract_frames_with_progress(video, sub_a)

print(f"[➡️ ] {stem}: eliminando fondo…")
remove_background_batch(sub_a, sub_b)

mov_out = VWB_DIR / f"{stem}.mov"
print(f"[🎞️] {stem}: generando MOV…")
generate_mov(sub_b, BASE_DIR / video.name, mov_out)

print(f"[🖼️ ] {stem}: copiando miniatura…")
copiar_thumb(sub_b, stem)

borrar_directorios(sub_a, sub_b)

print("\n[✅] Procesamiento completo.")

if __name__ == "__main__":
main()

Simplemente reemplaza tu script existente por este y ejecútalo en tu entorno. ¡Avísame si funciona como esperas!

o4-mini-high

Buscar

Investigar a fondo

ChatGPT puede cometer errores. Comprueba la información importante.

?