# gpl: authors Carlos Padial, Turi Scandurra import bpy import os from bpy.types import ( Operator, Panel, ) import subprocess from . import functions proxy_qualities = [ ("1", "25%", ""), ("2", "50%", ""), ("3", "75%", ""), ("4", "100%", "")] # # ls *.sh | parallel -j 8 sh {} # # functions def createsyncfile(filename): if not os.path.isfile(bpy.path.abspath(filename)): f = open(bpy.path.abspath(filename), "w") data = [] try: f.writelines(data) # Write a sequence of strings to a file finally: f.close() def readsyncfile(filename): try: file = open(bpy.path.abspath(filename)) data = file.readlines() file.close() return data except IOError: pass def writesyncfile(filename, data): try: f = open(bpy.path.abspath(filename), "w") try: for line in data: f.writelines(line) # Write a sequence of strings to a file finally: f.close() except IOError: pass # classes class ExtractWavOperator(Operator): bl_idname = "sequencer.extract_wav_operator" bl_label = "Extract Wav from movie strip Operator" bl_description = "Use ffmpeg to extract audio from video and import it synced" @staticmethod def has_sequencer(context): return (context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}) @classmethod def poll(self, context): strip = functions.act_strip(context) scn = context.scene if scn and scn.sequence_editor and scn.sequence_editor.active_strip: return strip.type in ('MOVIE') else: return False def execute(self, context): preferences = context.preferences audio_dir = preferences.addons[__package__].preferences.audio_dir functions.create_folder(bpy.path.abspath(audio_dir)) for strip in context.selected_editable_sequences: # get filename if strip.type == "MOVIE": filename = bpy.path.abspath(strip.filepath) newfilename = bpy.path.abspath(strip.filepath).rpartition( "/")[2] fileoutput = os.path.join( bpy.path.abspath(audio_dir), newfilename) + ".wav" # check for wav existing file if not os.path.isfile(fileoutput): # if not, extract the file extract_audio = "ffmpeg -i '{}' -acodec pcm_s16le -ac 2 {}".\ format(filename, fileoutput) print(extract_audio) os.system(extract_audio) else: print("The audio File exists") if strip.type == "MOVIE": # import the file and trim in the same way the original bpy.ops.sequencer.sound_strip_add( filepath=fileoutput, frame_start=strip.frame_start, channel=strip.channel + 1, replace_sel=True, overlap=False, cache=False ) # Update scene context.scene.update() newstrip = context.scene.sequence_editor.active_strip # deselect all other strips for i in context.selected_editable_sequences: if i.name != newstrip.name: i.select = False # Update scene context.scene.update() # Match the original clip's length newstrip.frame_start = strip.frame_start - strip.animation_offset_start functions.triminout(newstrip, strip.frame_start + strip.frame_offset_start, strip.frame_start + strip.frame_offset_start + strip.frame_final_duration) return {'FINISHED'} class ExternalAudioSetSyncOperator(Operator): bl_idname = "sequencer.external_audio_set_sync" bl_label = "set sync info" bl_description = ("Get sync info from selected audio and video strip " "and store it into a text file") @staticmethod def has_sequencer(context): return (context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}) @classmethod def poll(cls, context): if cls.has_sequencer(context): if len(context.selected_editable_sequences) == 2: types = [] for i in context.selected_editable_sequences: types.append(i.type) if 'MOVIE' and 'SOUND' in types: return True else: return False def execute(self, context): preferences = context.preferences filename = preferences.addons[__package__].preferences.audio_external_filename for strip in context.selected_editable_sequences: if strip.type == "MOVIE": moviestrip = strip elif strip.type == "SOUND": soundstrip = strip offset = str(moviestrip.frame_start - soundstrip.frame_start) data1 = readsyncfile(filename) data2 = [] newline = moviestrip.filepath + " " + soundstrip.filepath + " " + offset + "\n" if data1 is not None: repeated = False for line in data1: if line.split()[0] == moviestrip.filepath and line.split()[1] == soundstrip.filepath: data2.append(newline) repeated = True else: data2.append(line) if not repeated: data2.append(newline) else: data2.append(newline) createsyncfile(filename) writesyncfile(filename, data2) return {'FINISHED'} class ExternalAudioReloadOperator(Operator): bl_idname = "sequencer.external_audio_reload" bl_label = "Reload External audio" bl_description = ("Reload external audio synced to selected movie strip " "according to info from a text file") @staticmethod def has_sequencer(context): return (context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}) @classmethod def poll(cls, context): if cls.has_sequencer(context): if len(context.selected_editable_sequences) == 1: if context.selected_editable_sequences[0].type == 'MOVIE': return True else: return False def execute(self, context): preferences = context.preferences filename = preferences.addons[__package__].preferences.audio_external_filename data = readsyncfile(filename) for strip in context.selected_editable_sequences: sounds = [] for line in data: if line.split()[0] == strip.filepath: moviefile = bpy.path.abspath(line.split()[0]) soundfile = bpy.path.abspath(line.split()[1]) offset = int(line.split()[2]) sounds.append((soundfile, offset)) for soundfile, offset in sounds: print(soundfile, offset) print(strip.filepath) # find start frame for sound strip (using offset from file) sound_frame_start = strip.frame_start - strip.animation_offset_start - offset # import the file and trim in the same way the original bpy.ops.sequencer.sound_strip_add( filepath=soundfile, frame_start=sound_frame_start, channel=strip.channel + 1, replace_sel=True, overlap=False, cache=False ) # Update scene context.scene.update() newstrip = context.scene.sequence_editor.active_strip # deselect all other strips for i in context.selected_editable_sequences: if i.name != newstrip.name: i.select = False # Update scene context.scene.update() # trim sound strip like original one functions.triminout(newstrip, strip.frame_start + strip.frame_offset_start, strip.frame_start + strip.frame_offset_start + strip.frame_final_duration ) return {'FINISHED'} class AudioToolPanel(Panel): bl_label = "Audio Tools" bl_idname = "OBJECT_PT_AudioTool" bl_space_type = 'SEQUENCE_EDITOR' bl_region_type = 'UI' @classmethod def poll(self, context): if context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}: scn = context.scene preferences = context.preferences prefs = preferences.addons[__package__].preferences if scn and scn.sequence_editor and scn.sequence_editor.active_strip: if prefs.use_audio_tools: return True else: return False def draw_header(self, context): layout = self.layout layout.label(text="", icon="PLAY_AUDIO") def draw(self, context): preferences = context.preferences prefs = preferences.addons[__package__].preferences strip = functions.act_strip(context) if strip.type == "MOVIE": layout = self.layout layout.prop(prefs, "audio_dir", text="Path for Audio files") layout.operator("sequencer.extract_wav_operator", text="Extract Wav") layout = self.layout layout.prop(prefs, "audio_scripts") if prefs.audio_scripts: layout = self.layout layout.prop(prefs, "audio_scripts_path", text="Path for scripts") layout = self.layout layout.prop(prefs, "audio_use_external_links", text="External Audio sync") if prefs.audio_use_external_links: layout = self.layout layout.prop(prefs, "audio_external_filename", text="Sync data") row = layout.row(align=True) row.operator("sequencer.external_audio_set_sync", text="Set sync") row.operator("sequencer.external_audio_reload", text="Reload Audio") layout = self.layout row = layout.row() row.prop(prefs, "metertype", text="") row.operator("sequencer.openmeterbridge", text="Launch Audio Meter", icon="SOUND") class OpenMeterbridgeOperator(Operator): bl_idname = "sequencer.openmeterbridge" bl_label = "External VU meter" bl_description = "Open external VU meter to work with Jack" @staticmethod def has_sequencer(context): return (context.space_data.view_type in {'SEQUENCER', 'SEQUENCER_PREVIEW'}) @classmethod def poll(cls, context): if cls.has_sequencer(context): if len(context.selected_editable_sequences) == 1: return True def execute(self, context): preferences = context.preferences prefs = preferences.addons[__package__].preferences command = "meterbridge -t {} 'PulseAudio JACK Sink:front-left' " \ "'PulseAudio JACK Sink:front-right' &".format(prefs.metertype.lower()) p = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) return {'FINISHED'}