Merge branch 'blender-v4.0-release'
[blender-addons.git] / storypencil / sound.py
blob1ebd2803ed480b8680e70f866e91a05a6b5fbaae
1 # SPDX-FileCopyrightText: 2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 from typing import List, Sequence, Tuple
7 from bpy.types import (
8 Operator,
12 def send_sound_strip(s, dest_scn):
13 '''recreate sound strip in another scene
14 :dest_scn: scene destination
15 :return: newly create sound strip
16 '''
18 if s.type != 'SOUND':
19 return
20 vse = dest_scn.sequence_editor
21 ns = vse.sequences.new_sound(name=s.name, filepath=s.sound.filepath, channel=s.channel, frame_start=int(s.frame_start))
22 ns.sound = s.sound # reget the same sound source
24 for attr in ('pitch',
25 'pan',
26 'show_waveform',
27 'speed_factor',
28 'volume',
29 'mute'):
30 if hasattr(s, attr):
31 setattr(ns, attr, getattr(s, attr))
32 if ns.volume == 0:
33 ns.volume = 1
34 return ns
36 def get_all_overlapping_sound_strip(scn_strip, skip_mute=True):
37 """return array of all sound strips for this strip"""
38 if scn_strip.type != 'SCENE':
39 return
41 src_scn = scn_strip.id_data
42 vse = src_scn.sequence_editor
43 overlapping_sounds = []
44 for s in vse.sequences:
45 if s.type != 'SOUND':
46 continue
47 if skip_mute and s.mute:
48 continue
50 if (s.frame_final_end - 1 < scn_strip.frame_final_start)\
51 or (s.frame_final_start - 1 > scn_strip.frame_final_end):
52 continue
54 overlapping_sounds.append(s)
56 return overlapping_sounds
58 def delete_sounds(scene):
59 for st in reversed(scene.sequence_editor.sequences):
60 if st.type == 'SOUND':
61 scene.sequence_editor.sequences.remove(st)
63 def get_scene_frame_from_sequencer_frame(scn_strip, sound) -> float:
64 """return frame in scene referential"""
65 return sound.frame_start - scn_strip.frame_start + scn_strip.scene.frame_start
67 def send_sound_to_strip_scene(scn_strip, clear_sequencer=True, skip_mute=True):
68 """Add sounds to strip scene"""
69 if scn_strip.type != 'SCENE':
70 return
71 tgt_scene = scn_strip.scene
73 sounds = get_all_overlapping_sound_strip(scn_strip, skip_mute=skip_mute)
74 if not sounds:
75 return
77 # Clear sounds if exists in scene vse already
78 if clear_sequencer:
79 delete_sounds(tgt_scene)
81 print(f'Duplicating sounds in {tgt_scene.name}:')
82 for s in sounds:
83 new_start = get_scene_frame_from_sequencer_frame(scn_strip, s)
84 ns = send_sound_strip(s, tgt_scene)
85 if ns:
86 ns.frame_start = new_start
87 ns.frame_offset_start = s.frame_offset_start
88 ns.frame_final_duration = s.frame_final_duration
90 return sounds
93 def dispatch_sounds_in_scenes(selected_scn_only=True, skip_mute=True):
94 """Main function to duplicate sounds in strip scenes"""
95 edit_scene = bpy.context.scene
96 edit = edit_scene.sequence_editor
98 ct = 0
99 for strip in edit.sequences:
100 if strip.type != 'SCENE':
101 continue
103 if selected_scn_only and not strip.select:
104 continue
106 sounds = send_sound_to_strip_scene(strip, skip_mute=skip_mute)
107 if sounds:
108 ct += 1
110 if ct:
111 print('INFO', f'Sound duplicated in {ct} scenes')
112 else:
113 print('ERROR', f'No duplication occured')
116 class STORYPENCIL_OT_duplicate_sound_in_edit_scene(Operator):
117 bl_idname = "storypencil.duplicate_sound_in_edit_scene"
118 bl_label = "Sounds To Scenes"
119 bl_description = "Copy sound strips from VSE to the source scenes"
120 bl_options = {'REGISTER', 'UNDO'}
122 @classmethod
123 def poll(cls, context):
124 if context.space_data.type != 'SEQUENCE_EDITOR':
125 return False
127 return True
129 def execute(self, context):
130 scene = context.scene
131 dispatch_sounds_in_scenes(
132 selected_scn_only=scene.storypencil_selected_scn_only,
133 skip_mute=scene.storypencil_skip_sound_mute)
135 bpy.ops.sequencer.reload()
137 return {'FINISHED'}