Split auto (UI + Core) - 1st version
parent
b68b43e3e1
commit
a30e16606b
|
@ -0,0 +1,36 @@
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from vse_toolbox.constants import AUTO_SPLITTER_LOG
|
||||||
|
from vse_toolbox import file_utils
|
||||||
|
|
||||||
|
|
||||||
|
class AutoSplitter(object):
|
||||||
|
|
||||||
|
def __init__(self, paths, fps=None):
|
||||||
|
|
||||||
|
self.paths = paths
|
||||||
|
self.fps = fps
|
||||||
|
|
||||||
|
self.log_path = AUTO_SPLITTER_LOG
|
||||||
|
|
||||||
|
def launch_analysis(self, threshold=0.6):
|
||||||
|
|
||||||
|
ffmpeg_cmd = f"ffmpeg -i {str(self.paths[0])} -filter:v \"select='gt(scene,{threshold})',showinfo\" -f null - 2> {str(self.log_path)}"
|
||||||
|
print(ffmpeg_cmd)
|
||||||
|
|
||||||
|
subprocess.call(ffmpeg_cmd, shell=True)
|
||||||
|
|
||||||
|
def get_split_times(self, as_frame=True):
|
||||||
|
log = file_utils.read_file(self.log_path)
|
||||||
|
|
||||||
|
timecodes = re.findall(r'pts_time:([\d.]+)', log)
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
return timecodes
|
|
@ -36,4 +36,6 @@ 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'
|
||||||
|
|
|
@ -89,9 +89,9 @@ def norm_name(string, separator='_', format=str.lower, padding=0):
|
||||||
return string
|
return string
|
||||||
|
|
||||||
def read_file(path):
|
def read_file(path):
|
||||||
'''Read a file with an extension in (json, yaml, yml, txt)'''
|
'''Read a file with an extension in (json, yaml, yml, txt, log)'''
|
||||||
|
|
||||||
exts = ('.json', '.yaml', '.yml', '.txt')
|
exts = ('.json', '.yaml', '.yml', '.txt', '.log')
|
||||||
|
|
||||||
if not path:
|
if not path:
|
||||||
print('Try to read empty file')
|
print('Try to read empty file')
|
||||||
|
|
|
@ -2,12 +2,13 @@ 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)
|
from bpy.props import (BoolProperty, StringProperty, EnumProperty, 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.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.constants import REVIEW_TEMPLATE_BLEND
|
from vse_toolbox.constants import REVIEW_TEMPLATE_BLEND
|
||||||
from shutil import copy2
|
from shutil import copy2
|
||||||
|
|
||||||
|
@ -172,6 +173,61 @@ class VSETB_OT_set_sequencer(Operator):
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class VSETB_OT_auto_split(Operator):
|
||||||
|
"""Launch subprocess with ffmpeg and python to find and create each
|
||||||
|
shots strips from video source"""
|
||||||
|
|
||||||
|
bl_idname = "vse_toolbox.auto_split"
|
||||||
|
bl_label = "Auto Split"
|
||||||
|
bl_description = "Generate shots strips"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
threshold: FloatProperty(name="Threshold", default=0.6, min=0, max=1)
|
||||||
|
selected_only: BoolProperty(name="Selected Only", default=True)
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
col = layout.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
|
||||||
|
col.prop(self, 'threshold')
|
||||||
|
col.prop(self, 'selected_only')
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
scn = context.scene
|
||||||
|
first_frame = int(scn.frame_start)
|
||||||
|
|
||||||
|
strips = get_strips('Movie')
|
||||||
|
if self.selected_only:
|
||||||
|
strips = context.selected_sequences
|
||||||
|
|
||||||
|
sources = list(set([bpy.path.abspath(strip.filepath) for strip in strips if strip.type == 'MOVIE']))
|
||||||
|
|
||||||
|
splitter = AutoSplitter(sources)
|
||||||
|
splitter.launch_analysis(self.threshold)
|
||||||
|
|
||||||
|
split_frames = [0]
|
||||||
|
split_frames += splitter.get_split_times(as_frame=True)
|
||||||
|
|
||||||
|
for i, frame in enumerate(split_frames):
|
||||||
|
strip = scn.sequence_editor.sequences.new_effect(
|
||||||
|
f'tmp_shot_{str(i).zfill(3)}',
|
||||||
|
'COLOR',
|
||||||
|
get_channel_index('Shots'),
|
||||||
|
frame_start=frame + first_frame,
|
||||||
|
frame_end=split_frames[i+1] + first_frame if i < len(split_frames) else strips[0].frame_final_end
|
||||||
|
)
|
||||||
|
strip.blend_alpha = 0
|
||||||
|
strip.color = (0.5, 0.5, 0.5)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class VSETB_OT_set_stamps(Operator):
|
class VSETB_OT_set_stamps(Operator):
|
||||||
bl_idname = "vse_toolbox.set_stamps"
|
bl_idname = "vse_toolbox.set_stamps"
|
||||||
bl_label = "Set Stamps"
|
bl_label = "Set Stamps"
|
||||||
|
@ -625,6 +681,7 @@ def unregister_keymaps():
|
||||||
classes = (
|
classes = (
|
||||||
VSETB_OT_rename,
|
VSETB_OT_rename,
|
||||||
VSETB_OT_set_sequencer,
|
VSETB_OT_set_sequencer,
|
||||||
|
VSETB_OT_auto_split,
|
||||||
VSETB_OT_set_stamps,
|
VSETB_OT_set_stamps,
|
||||||
VSETB_OT_show_waveform,
|
VSETB_OT_show_waveform,
|
||||||
VSETB_OT_previous_shot,
|
VSETB_OT_previous_shot,
|
||||||
|
|
|
@ -179,6 +179,7 @@ class VSETB_PT_sequencer(VSETB_main, Panel):
|
||||||
|
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.operator('vse_toolbox.set_sequencer', text='Set-Up Sequencer', icon='SEQ_SEQUENCER')
|
col.operator('vse_toolbox.set_sequencer', text='Set-Up Sequencer', icon='SEQ_SEQUENCER')
|
||||||
|
col.operator('vse_toolbox.auto_split', text='Auto Split Shots')
|
||||||
col.operator('vse_toolbox.strips_rename', text=f'Rename {channel}', icon='SORTALPHA')
|
col.operator('vse_toolbox.strips_rename', text=f'Rename {channel}', icon='SORTALPHA')
|
||||||
col.operator('vse_toolbox.set_stamps', text='Set Stamps', icon='COLOR')
|
col.operator('vse_toolbox.set_stamps', text='Set Stamps', icon='COLOR')
|
||||||
col.operator("vse_toolbox.collect_files", text='Collect Files', icon='PACKAGE')
|
col.operator("vse_toolbox.collect_files", text='Collect Files', icon='PACKAGE')
|
||||||
|
|
Loading…
Reference in New Issue