Last active
October 27, 2023 22:41
-
-
Save tin2tin/ac965f236676b54d249b171fc8185c22 to your computer and use it in GitHub Desktop.
Find audio offset
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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