use modal and display shots created one after other

pull/6/head
florentin.luce 2024-05-27 14:40:23 +02:00
parent b1bde01b57
commit 940cd1100f
4 changed files with 133 additions and 40 deletions

View File

@ -1,36 +1,102 @@
import os
import re import re
import subprocess import subprocess
from vse_toolbox.constants import AUTO_SPLITTER_LOG import bpy
from vse_toolbox import file_utils
from vse_toolbox import bl_utils
class AutoSplitter(object): def launch_split(movie_strip, threshold, frame_start=None, frame_end=None):
"""Launch ffmpeg command to detect changing frames from a movie strip.
def __init__(self, path, fps=None): 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.
self.path = path Returns:
self.fps = fps str: ffmpeg command log.
"""
self.log_path = AUTO_SPLITTER_LOG path = bl_utils.abspath(movie_strip.filepath)
fps = bpy.context.scene.render.fps
def launch_analysis(self, threshold=0.6): if frame_start is None:
frame_start = movie_strip.frame_final_start
if frame_end is None:
frame_end = movie_strip.frame_final_end
ffmpeg_cmd = f"ffmpeg -i {str(self.path)} -filter:v \"select='gt(scene,{threshold})',showinfo\" -f null - 2> {str(self.log_path)}" frame_start -= movie_strip.frame_final_start
print(ffmpeg_cmd) frame_end -= movie_strip.frame_final_start
subprocess.call(ffmpeg_cmd, shell=True) # Launch ffmpeg command to split
ffmpeg_cmd = get_command(str(path), threshold, frame_start, frame_end, fps)
def get_split_times(self, as_frame=True): print(ffmpeg_cmd)
log = file_utils.read_file(self.log_path) process = subprocess.Popen(
ffmpeg_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True)
timecodes = re.findall(r'pts_time:([\d.]+)', log) return process
if as_frame:
# convert timecode to frame number
if not self.fps:
self.fps = float(re.findall(r'([\d]+) fps', log)[0])
return [round(float(time) * self.fps) for time in timecodes] def get_command(path, threshold, frame_start, frame_end, fps):
"""Generate the ffmpeg command which detect change from a movie.
return timecodes 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.
"""
start_time = frame_start/fps
end_time = frame_end/fps
return [
'ffmpeg',
'-i',
str(path),
'-vf',
f"trim=start={start_time}:end={end_time}, select='gt(scene, {threshold})',showinfo",
'-f',
'null',
'-'
]
def get_split_time(log, as_frame=True, fps=None):
"""Parse ffmpeg command lines to detect the timecode
Args:
log (str): log to parse.
as_frame (bool, optional): if wanted the timecode as frame number.
Defaults to True.
fps (_type_, optional): framerate of the movie (mandatory if as_frame used).
Defaults to None.
Returns:
_type_: _description_
"""
timecodes = re.findall(r'pts_time:([\d.]+)', log)
if not timecodes:
return
timecode = timecodes[0]
if as_frame:
# convert timecode to frame number
return round(float(timecode) * fps)
return timecode

View File

@ -37,5 +37,3 @@ ASSET_PREVIEWS = bpy.utils.previews.new()
CASTING_BUFFER = CONFIG_DIR / 'casting.json' CASTING_BUFFER = CONFIG_DIR / 'casting.json'
SPREADSHEET = [] SPREADSHEET = []
AUTO_SPLITTER_LOG = CONFIG_DIR / 'auto_splitter.log'

View File

@ -2,14 +2,14 @@ from os.path import expandvars, abspath
from pathlib import Path from pathlib import Path
import bpy import bpy
from bpy.types import Operator from bpy.types import Operator
from bpy.props import (BoolProperty, StringProperty, EnumProperty, FloatProperty) from bpy.props import (BoolProperty, StringProperty, FloatProperty)
from vse_toolbox.sequencer_utils import (get_strips, rename_strips, set_channels, from vse_toolbox.sequencer_utils import (get_strips, rename_strips, set_channels,
get_channel_index, new_text_strip, get_strip_at, get_channel_name) get_channel_index, new_text_strip, get_strip_at, get_channel_name)
from vse_toolbox import auto_splitter
from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings from vse_toolbox.bl_utils import get_scene_settings, get_strip_settings
from vse_toolbox.auto_splitter import AutoSplitter from vse_toolbox.sequencer_utils import create_shot_strip
from vse_toolbox.constants import REVIEW_TEMPLATE_BLEND
from shutil import copy2 from shutil import copy2
@ -199,8 +199,10 @@ class VSETB_OT_auto_split(Operator):
col.prop(self, 'selected_only') col.prop(self, 'selected_only')
def execute(self, context): def execute(self, context):
scn = context.scene context.window_manager.modal_handler_add(self)
return {'PASS_THROUGH'}
def modal(self, context, event):
strips = get_strips('Movie') strips = get_strips('Movie')
if self.selected_only: if self.selected_only:
strips = context.selected_sequences strips = context.selected_sequences
@ -210,23 +212,35 @@ class VSETB_OT_auto_split(Operator):
if strip.type != 'MOVIE': if strip.type != 'MOVIE':
continue continue
splitter = AutoSplitter(bpy.path.abspath(strip.filepath)) process = auto_splitter.launch_split(strip, self.threshold)
splitter.launch_analysis(self.threshold)
split_frames = [0] i = 1
split_frames += splitter.get_split_times(as_frame=True) frame_start = 0
for line in process.stdout:
frame_end = auto_splitter.get_split_time(line, fps=24)
for i, frame in enumerate(split_frames): if not frame_end:
continue
shot_strip = scn.sequence_editor.sequences.new_effect( create_shot_strip(
f'tmp_shot_{str(i).zfill(3)}', f'tmp_shot_{str(i).zfill(3)}',
'COLOR', start=frame_start+strip.frame_final_start,
get_channel_index('Shots'), end=frame_end+strip.frame_final_start
frame_start=frame + strip.frame_final_start, )
frame_end=split_frames[i+1] + strip.frame_final_start if i+1 < len(split_frames) else strip.frame_final_end
i += 1
frame_start = frame_end
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
process.wait()
# last strip:
create_shot_strip(
f'tmp_shot_{str(i).zfill(3)}',
start=frame_start+strip.frame_final_start,
end=strip.frame_final_end
) )
shot_strip.blend_alpha = 0
shot_strip.color = (0.5, 0.5, 0.5)
return {'FINISHED'} return {'FINISHED'}

View File

@ -582,4 +582,19 @@ def update_text_strips(scene):
strip['text_pattern'] = strip.text strip['text_pattern'] = strip.text
if 'text_pattern' in strip.keys(): if 'text_pattern' in strip.keys():
strip.text = strip['text_pattern'].format_map(MissingKey(**format_data)) strip.text = strip['text_pattern'].format_map(MissingKey(**format_data))
def create_shot_strip(name, start, end):
shot_strip = bpy.context.scene.sequence_editor.sequences.new_effect(
name,
'COLOR',
get_channel_index('Shots'),
frame_start=start,
frame_end=end
)
shot_strip.blend_alpha = 0
shot_strip.color = (0.5, 0.5, 0.5)
return shot_strip