diff options
Diffstat (limited to 'kinoraw_tools/audio_tools.py')
-rw-r--r-- | kinoraw_tools/audio_tools.py | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/kinoraw_tools/audio_tools.py b/kinoraw_tools/audio_tools.py new file mode 100644 index 00000000..d0278de0 --- /dev/null +++ b/kinoraw_tools/audio_tools.py @@ -0,0 +1,360 @@ +import bpy, os +from bpy.props import IntProperty, StringProperty, BoolProperty +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(bpy.types.Operator): + """ Use ffmpeg to extract audio from video and import it synced""" + bl_idname = "sequencer.extract_wav_operator" + bl_label = "Extract Wav from movie strip Operator" + + @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.user_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("ya existe") + + 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 + + #print(newstrip.name) + 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(bpy.types.Operator): + """get sync info from selected audio and video strip and store it into a text file """ + bl_idname = "sequencer.external_audio_set_sync" + bl_label = "set sync info" + + @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.user_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 != 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(bpy.types.Operator): + """ reload external audio synced to selected movie strip acording to info from a text file """ + bl_idname = "sequencer.external_audio_reload" + bl_label = "reload external audio" + + @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.user_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(bpy.types.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'}: + strip = functions.act_strip(context) + scn = context.scene + preferences = context.user_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.user_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(bpy.types.Operator): + """ """ + bl_idname = "sequencer.openmeterbridge" + bl_label = "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.user_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'} + + |