Skip to content

Instantly share code, notes, and snippets.

@tin2tin
Last active October 27, 2023 22:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tin2tin/ac965f236676b54d249b171fc8185c22 to your computer and use it in GitHub Desktop.
Save tin2tin/ac965f236676b54d249b171fc8185c22 to your computer and use it in GitHub Desktop.
Find audio offset
import bpy
import librosa
import numpy as np
from scipy import signal
def normalize_audio(audio):
max_amplitude = np.max(np.abs(audio))
if max_amplitude > 0:
audio /= max_amplitude
return audio
def find_audio_offset(within_file, find_file, window=10):
y_within, sr_within = librosa.load(within_file, sr=None)
y_find, _ = librosa.load(find_file, sr=sr_within)
# Normalize both audio files
y_within = normalize_audio(y_within)
y_find = normalize_audio(y_find)
c = signal.correlate(y_within, y_find[:int(sr_within * window)], mode='valid', method='fft')
peak = np.argmax(c)
offset = round(peak / sr_within, 2)
return offset
def calculate_offset(within_file, find_file, window):
# Normalize both input audio in memory
y_within, sr_within = librosa.load(within_file, sr=None)
y_find, _ = librosa.load(find_file, sr=sr_within)
# Normalize the audio
peak_amplitude_within = max(abs(y_within))
peak_amplitude_find = max(abs(y_find))
scaling_factor_within = 1.0 / peak_amplitude_within
scaling_factor_find = 1.0 / peak_amplitude_find
y_within = y_within * scaling_factor_within
y_find = y_find * scaling_factor_find
# Calculate the offset
c = signal.correlate(y_within, y_find[:sr_within * window], mode='valid', method='fft')
peak = np.argmax(c)
offset = round(peak / sr_within, 2)
return offset
class SEQUENCER_OT_AudioOffsetOperator(bpy.types.Operator):
bl_idname = "sequencer.audio_offset"
bl_label = "Calculate Audio Offset"
def execute(self, context):
active_strip = context.scene.sequence_editor.active_strip
if active_strip and active_strip.type == 'SOUND':
find_file = bpy.path.abspath(active_strip.sound.filepath)
for strip in context.selected_sequences:
if strip.type == 'SOUND' and strip != active_strip:
within_file = bpy.path.abspath(strip.sound.filepath)
offset = find_audio_offset(within_file, find_file, 5)
#self.report({'INFO'}, f"Offset between {active_strip.name} and {strip.name}: {offset} seconds")
# Calculate the frame offset based on the offset in seconds
frame_offset = round(offset * (context.scene.render.fps/context.scene.render.fps_base))
strip.frame_start = active_strip.frame_start + frame_offset
#strip.frame_final_end += frame_offset
return {'FINISHED'}
def draw_func(self, context):
layout = self.layout
layout.operator("sequencer.audio_offset")
def register():
bpy.utils.register_class(SEQUENCER_OT_AudioOffsetOperator)
bpy.types.SEQUENCER_MT_strip.append(draw_func)
def unregister():
bpy.utils.unregister_class(SEQUENCER_OT_AudioOffsetOperator)
bpy.types.SEQUENCER_MT_strip.remove(draw_func)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment