Timelapse does compile

This commit is contained in:
Arnaud_Cayrol 2025-04-12 21:47:49 +02:00
parent 225728d624
commit e0d50cd496

View file

@ -1,7 +1,9 @@
import os import os
import subprocess import subprocess
import logging import logging
from typing import Callable, List import tempfile
import shutil
from typing import Callable
from pathlib import Path from pathlib import Path
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -16,52 +18,56 @@ def compile_timelapse(
Compile a timelapse video from a folder of images using ffmpeg. Compile a timelapse video from a folder of images using ffmpeg.
Args: Args:
image_folder: Path to folder containing timestamped JPEG images image_folder: Path to folder containing timestamped JPEG images.
output_path: Path where the output video should be saved output_path: Path where the output video should be saved.
framerate: Desired frames per second framerate: Desired frames per second.
update_progress: Callback function to report progress (current: int, total: int) update_progress: Callback function to report progress (current: int, total: int).
Returns: Returns:
True if successful, False otherwise True if successful, False otherwise.
""" """
try: try:
# Get list of image files and sort by timestamp # List and sort JPEG image files in the folder.
image_files = sorted([ image_files = sorted([
f for f in os.listdir(image_folder) f for f in os.listdir(image_folder)
if f.lower().endswith(('.jpg', '.jpeg')) if f.lower().endswith(('.jpg', '.jpeg'))
]) ])
if not image_files: total_frames = len(image_files)
if total_frames == 0:
logger.error("No image files found in folder") logger.error("No image files found in folder")
update_progress(0, 0) update_progress(0, 0)
return False return False
total_frames = len(image_files)
logger.info(f"Found {total_frames} images to process") logger.info(f"Found {total_frames} images to process")
# Create ffmpeg input file list # Create a temporary directory to hold sequential files.
input_file = os.path.join(image_folder, "input.txt") temp_dir = tempfile.mkdtemp()
frame_duration = 1.0 / framerate # Calculate duration based on requested framerate logger.info(f"Created temporary directory: {temp_dir}")
with open(input_file, "w") as f: try:
for img in image_files: for idx, img in enumerate(image_files):
f.write(f"file '{os.path.join(image_folder, img)}'\n") src = os.path.join(image_folder, img)
f.write(f"duration {frame_duration}\n") dst = os.path.join(temp_dir, f"{idx:06d}.jpg")
try:
os.symlink(src, dst)
except (AttributeError, NotImplementedError, OSError):
# Fallback to copying if symlinking is not supported.
shutil.copy2(src, dst)
# Build ffmpeg command input_pattern = os.path.join(temp_dir, "%06d.jpg")
cmd = [ cmd = [
"ffmpeg", "ffmpeg",
"-f", "concat",
"-safe", "0",
"-i", input_file,
"-framerate", str(framerate), "-framerate", str(framerate),
"-i", input_pattern,
"-c:v", "libx264", "-c:v", "libx264",
"-pix_fmt", "yuv420p", "-pix_fmt", "yuv420p",
"-y", # Overwrite output file if it exists "-y", # Overwrite output file if it exists
output_path output_path
] ]
logger.info("Running ffmpeg command: " + " ".join(cmd))
# Run ffmpeg with progress reporting # Run ffmpeg and capture the stderr for progress updates.
process = subprocess.Popen( process = subprocess.Popen(
cmd, cmd,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -69,32 +75,31 @@ def compile_timelapse(
universal_newlines=True universal_newlines=True
) )
# Monitor progress
frame_count = 0 frame_count = 0
for line in process.stderr: for line in process.stderr:
if "frame=" in line: if "frame=" in line:
try: try:
# Extract frame number from ffmpeg output
frame = int(line.split("frame=")[1].split()[0]) frame = int(line.split("frame=")[1].split()[0])
frame_count = min(frame, total_frames) # Ensure we don't exceed total frame_count = min(frame, total_frames)
update_progress(frame_count, total_frames) update_progress(frame_count, total_frames)
except (IndexError, ValueError): except (IndexError, ValueError):
continue continue
# Wait for process to complete
process.wait() process.wait()
# Clean up temporary file
os.remove(input_file)
if process.returncode == 0: if process.returncode == 0:
update_progress(total_frames, total_frames) update_progress(total_frames, total_frames)
logger.info("Timelapse video created successfully")
return True return True
else: else:
logger.error(f"ffmpeg failed with return code {process.returncode}") logger.error(f"ffmpeg failed with return code {process.returncode}")
update_progress(0, total_frames) update_progress(0, total_frames)
return False return False
finally:
shutil.rmtree(temp_dir)
logger.info(f"Removed temporary directory: {temp_dir}")
except Exception as e: except Exception as e:
logger.error(f"Error compiling timelapse: {str(e)}") logger.error(f"Error compiling timelapse: {str(e)}")
update_progress(0, total_frames if 'total_frames' in locals() else 0) update_progress(0, total_frames if 'total_frames' in locals() else 0)