import os import re import subprocess import bpy from vse_toolbox import bl_utils def detect_scene_change(strip, movie_type="ANIMATED", threshold=0.5, frame_start=None, frame_end=None, crop=None): """Launch ffmpeg command to detect changing frames from a movie strip. Args: movie_strip (bpy.types.Sequence): blender sequence strip to detect changes. threshold (float): value of the detection factor (from 0 to 1). frame_start (int, optional): first frame to detect. Defaults to None. frame_end (int, optional): last frame to detect. Defaults to None. Returns: str: ffmpeg command log. """ path = bl_utils.abspath(strip.filepath) fps = bpy.context.scene.render.fps if frame_start is None: frame_start = 0 if frame_end is None: frame_end = strip.frame_duration frame_start = frame_start - strip.frame_start start_time = frame_start/fps end_time = frame_end/fps #frame_start += strip.frame_offset_start #frame_end -= strip.frame_offset_end if movie_type == 'ANIMATED': return select_generator(path, start_time=start_time, end_time=end_time, threshold=threshold, crop=crop) elif movie_type == 'STILL': return freeze_detect_generator(path, start_time=start_time, end_time=end_time, threshold=threshold, crop=crop) else: raise Exception(f'movie_type: {movie_type} not implemented') def freeze_detect_generator(path, start_time, end_time, threshold=0.005, crop=None): """Generate the ffmpeg command which detect change from a movie. Args: path (_type_): path to detect changes. threshold (_type_): value of the detection factor (from 0 to 1). frame_start (_type_): first frame to detect. frame_end (_type_): last frame to detect. fps (_type_): framerate of the movie. Returns: list: ffmpeg command as list for subprocess module. """ if crop is None: crop = [0, 0, 0, 0] crop_expr = f"crop=iw-{crop[0]}-{crop[1]}:ih-{crop[2]}-{crop[3]}:{crop[0]}:{crop[-1]}" command = [ 'ffmpeg', '-nostats', '-i', str(path), '-ss', str(start_time), '-t', str(end_time), '-vf', f"{crop_expr}, freezedetect=n={threshold}:d=0.01,metadata=print", #, '-f', 'null', '-' ] print(command) process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) def parse_split_time(log): print(log) timecodes = re.findall(r'freeze_end: ([\d.]+)', log) if timecodes: return round(float(timecodes[0]) * bpy.context.scene.render.fps) return filter(None, (parse_split_time(x) for x in process.stdout)) def select_generator(path, start_time, end_time, threshold, crop=None): """Generate the ffmpeg command which detect change from a movie. Args: path (_type_): path to detect changes. threshold (_type_): value of the detection factor (from 0 to 1). frame_start (_type_): first frame to detect. frame_end (_type_): last frame to detect. fps (_type_): framerate of the movie. Returns: list: ffmpeg command as list for subprocess module. """ if crop is None: crop = [0, 0, 0, 0] crop_expr = f"crop=iw-{crop[0]}-{crop[1]}:ih-{crop[2]}-{crop[3]}:{crop[0]}:{crop[-1]}" command = [ 'ffmpeg', '-nostats', '-i', str(path), '-ss', str(start_time), '-t', str(end_time), '-vf', f"{crop_expr}, select='gt(scene, {threshold})', showinfo", '-f', 'null', '-' ] print(command) process = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) def parse_split_time(log): print(log) timecodes = re.findall(r'pts_time:([\d.]+)', log) if timecodes: return round(float(timecodes[0]) * bpy.context.scene.render.fps) return filter(None, (parse_split_time(x) for x in process.stdout))