Created
October 3, 2025 00:16
-
-
Save AARP41298/103810325ee55611c9fb94d28c4a598f to your computer and use it in GitHub Desktop.
Collage from video
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import cv2 | |
| from PIL import Image | |
| import numpy as np | |
| import argparse | |
| from datetime import timedelta, datetime | |
| def format_time(segundos: float) -> str: | |
| td = timedelta(seconds=segundos) | |
| h, m, s = str(td).split(':') | |
| if '.' not in s: | |
| s = s + '.000' | |
| else: | |
| secs, ms = s.split('.') | |
| s = f"{secs}.{ms[:3]}" | |
| return f"{int(h):02d}:{int(m):02d}:{s}" | |
| def parse_time_to_seconds(time_str: str) -> float: | |
| """ | |
| Convierte 'mm:ss' o 'hh:mm:ss' a segundos float | |
| """ | |
| parts = time_str.strip().split(':') | |
| parts = [float(p) for p in parts] | |
| if len(parts) == 3: | |
| h, m, s = parts | |
| return h * 3600 + m * 60 + s | |
| elif len(parts) == 2: | |
| m, s = parts | |
| return m * 60 + s | |
| else: | |
| raise ValueError("Formato de tiempo inválido, use mm:ss o hh:mm:ss") | |
| def generar_collage(video_path, x, y, inicio=0, fin=100, salida='', timestamps=[]): | |
| """ | |
| video_path : ruta al video | |
| x, y : columnas y filas del collage (x columnas, y filas) | |
| inicio : porcentaje de inicio (0-100) | |
| fin : porcentaje de fin (0-100) | |
| salida : nombre del archivo de salida (si vacío se genera automático) | |
| timestamps : lista de timestamps manuales en segundos | |
| """ | |
| if not salida: | |
| now = datetime.now().strftime('%Y-%m-%d_%H-%M') | |
| salida = f'collage_{now}.jpg' | |
| cap = cv2.VideoCapture(video_path) | |
| if not cap.isOpened(): | |
| raise Exception("No se pudo abrir el video") | |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| fps = cap.get(cv2.CAP_PROP_FPS) | |
| duracion = total_frames / fps # segundos | |
| total_capturas = x * y | |
| porcentajes = np.linspace(inicio, fin, total_capturas) | |
| segundos_auto = [duracion * (p / 100.0) for p in porcentajes] | |
| # Reemplazar con timestamps manuales | |
| if timestamps: | |
| for ts in timestamps: | |
| ts_seg = parse_time_to_seconds(ts) | |
| # Encontrar índice más cercano | |
| idx = min(range(len(segundos_auto)), key=lambda i: abs(segundos_auto[i]-ts_seg)) | |
| old_seg = segundos_auto[idx] | |
| segundos_auto[idx] = ts_seg | |
| print(f"> Reemplazado frame #{idx+1} {format_time(old_seg)} → {format_time(ts_seg)}") | |
| frames = [] | |
| for idx, segundo in enumerate(segundos_auto, start=1): | |
| frame_num = int(segundo * fps) | |
| cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num) | |
| ret, frame = cap.read() | |
| if not ret: | |
| continue | |
| frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| frames.append(Image.fromarray(frame)) | |
| porcentaje = (segundo / duracion) * 100 | |
| print(f"#{idx} - {format_time(segundo)} - {porcentaje:.2f}%") | |
| cap.release() | |
| if not frames: | |
| raise Exception("No se pudieron capturar frames") | |
| ancho, alto = frames[0].size | |
| collage = Image.new('RGB', (ancho * x, alto * y)) | |
| i = 0 | |
| for fila in range(y): | |
| for col in range(x): | |
| if i < len(frames): | |
| collage.paste(frames[i], (col * ancho, fila * alto)) | |
| i += 1 | |
| collage.save(salida) | |
| print(f"Collage guardado en {salida}") | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description='Generar collage de capturas de un video') | |
| parser.add_argument('video', help='Ruta al video') | |
| parser.add_argument('-x', type=int, required=True, help='Columnas del collage') | |
| parser.add_argument('-y', type=int, required=True, help='Filas del collage') | |
| parser.add_argument('-i', '--inicio', type=float, default=0, help='Porcentaje inicio (0-100)') | |
| parser.add_argument('-f', '--fin', type=float, default=100, help='Porcentaje fin (0-100)') | |
| parser.add_argument('-o', '--output', default='', help='Archivo de salida (opcional)') | |
| parser.add_argument('-t', '--timestamps', type=str, default='', | |
| help='Timestamps manuales separados por coma (ej. 01:35,03:10)') | |
| args = parser.parse_args() | |
| ts_list = [t for t in args.timestamps.split(',') if t.strip()] if args.timestamps else [] | |
| generar_collage( | |
| args.video, | |
| x=args.x, | |
| y=args.y, | |
| inicio=args.inicio, | |
| fin=args.fin, | |
| salida=args.output, | |
| timestamps=ts_list | |
| ) | |
| # py video_collage.py -x 4 -y 6 -t 01:35,03:47,02:34,02:44,03:20,03:34.50 'Gorillaz - El Mañana.webm' | |
| # py video_collage.py -x 4 -y 6 's:/Daft Punk - Something About Us (Official Video).webm' -t 03:46 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment