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,86 +18,89 @@ 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")
# Build ffmpeg command
cmd = [
"ffmpeg",
"-f", "concat",
"-safe", "0",
"-i", input_file,
"-framerate", str(framerate),
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
"-y", # Overwrite output file if it exists
output_path
]
# Run ffmpeg with progress reporting
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True
)
# Monitor progress
frame_count = 0
for line in process.stderr:
if "frame=" in line:
try: try:
# Extract frame number from ffmpeg output os.symlink(src, dst)
frame = int(line.split("frame=")[1].split()[0]) except (AttributeError, NotImplementedError, OSError):
frame_count = min(frame, total_frames) # Ensure we don't exceed total # Fallback to copying if symlinking is not supported.
update_progress(frame_count, total_frames) shutil.copy2(src, dst)
except (IndexError, ValueError):
continue
# Wait for process to complete
process.wait()
# Clean up temporary file
os.remove(input_file)
if process.returncode == 0:
update_progress(total_frames, total_frames)
return True
else:
logger.error(f"ffmpeg failed with return code {process.returncode}")
update_progress(0, total_frames)
return False
input_pattern = os.path.join(temp_dir, "%06d.jpg")
cmd = [
"ffmpeg",
"-framerate", str(framerate),
"-i", input_pattern,
"-c:v", "libx264",
"-pix_fmt", "yuv420p",
"-y", # Overwrite output file if it exists
output_path
]
logger.info("Running ffmpeg command: " + " ".join(cmd))
# Run ffmpeg and capture the stderr for progress updates.
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True
)
frame_count = 0
for line in process.stderr:
if "frame=" in line:
try:
frame = int(line.split("frame=")[1].split()[0])
frame_count = min(frame, total_frames)
update_progress(frame_count, total_frames)
except (IndexError, ValueError):
continue
process.wait()
if process.returncode == 0:
update_progress(total_frames, total_frames)
logger.info("Timelapse video created successfully")
return True
else:
logger.error(f"ffmpeg failed with return code {process.returncode}")
update_progress(0, total_frames)
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)
return False return False