-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfn.py
198 lines (158 loc) · 6.94 KB
/
fn.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
import bpy
import tempfile
from pathlib import Path
from time import time
import math
from .preferences import get_addon_prefs
## context manager
class attr_set():
'''Receive a list of tuple [(data_path, "attribute" [, wanted value)] ]
entering with-statement : Store existing values, assign wanted value (if any)
exiting with-statement: Restore values to their old values
'''
def __init__(self, attrib_list):
self.store = []
# item = (prop, attr, [new_val])
for item in attrib_list:
prop, attr = item[:2]
self.store.append( (prop, attr, getattr(prop, attr)) )
if len(item) >= 3:
setattr(prop, attr, item[2])
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
for prop, attr, old_val in self.store:
setattr(prop, attr, old_val)
## -- Mixdown ops reference
# bpy.ops.sound.mixdown(
# mixdown()
# bpy.ops.sound.mixdown(filepath="", check_existing=True,
# filemode=9, relative_path=True,
# accuracy=1024,
# container='FLAC', # ('AC3', 'FLAC', 'MATROSKA', 'MP2', 'MP3', 'OGG', 'WAV')
# codec='FLAC', # ('AAC', 'AC3', 'FLAC', 'MP2', 'MP3', 'PCM', 'VORBIS')
# format='S16', # ('U8', 'S16', 'S24', 'S32', 'F32', 'F64')
# bitrate=192,
# split_channels=False)
def get_sound_strip_in_scene_range(vse=None):
vse = vse or bpy.context.scene.sequence_editor
scn = bpy.context.scene
strips = [s for s in vse.sequences if s.type == 'SOUND' \
and not s.mute \
and not (s.frame_final_end <= scn.frame_start or s.frame_final_start >= scn.frame_end)]
return strips
def round_to_second(start, end):
'''get a new end so duration is up to one second'''
fps = bpy.context.scene.render.fps
frame_count = end - start
round_count = math.ceil(frame_count / fps) * fps
end = start + round_count
return start, end
def get_start_end(strip_list):
start = min([s.frame_final_start for s in strip_list])
end = max([s.frame_final_end for s in strip_list])
return round_to_second(start, end)
def mixdown(filepath, source='ALL', vse_tgt='SELECTED'):
''' mixdon audio at filepath accoding to filters
source in (ALL, SEQUENCER, SPEAKER)
vse_tgt in (SELECTED, UNMUTED, SCENE)
'''
prefs = get_addon_prefs()
scn = bpy.context.scene
vse = scn.sequence_editor
# print('source: ', source)#Dbg
# print('vse_tgt: ', vse_tgt)#Dbg
## Unmute playback (also mute mixdown)
if bpy.app.version < (4,1,0):
## Old name in API name meant the opposite (using 'Mute' as label)
temp_changes = [(scn, 'use_audio', False)]
else:
temp_changes = [(scn, 'use_audio', True)]
## default to scene range
start, end = scn.frame_start, scn.frame_end
if source == 'ALL':
# simplest, nothing to change... mixdown scene as it is
pass
elif source == 'SPEAKERS':
## Mute every audible vse strips
temp_changes += [(s, 'mute', True) for s in vse.sequences if s.type == 'SOUND' and not s.mute]
elif source == 'SEQUENCER':
## mute every speakers
## muting speaker data NOT working, need to use speaker -> NLA tracks -> strips
# temp_changes = [(s, 'muted', True) for s in bpy.data.speakers if not s.muted]
## muting NLA tracks of speaker object
for o in [o for o in scn.objects if o.type == 'SPEAKER' and not o.data.muted and not o.hide_viewport]:
if not o.animation_data or not o.animation_data.nla_tracks:
continue
temp_changes += [(s, 'mute', True) for t in o.animation_data.nla_tracks for s in t.strips]
if vse_tgt == 'SCENE':
## Optimise by reducing range to first/last audible strips if inside SCENE
strips = get_sound_strip_in_scene_range(vse)
strips_start, strips_end = get_start_end(strips)
# override start/end if within range
if strips_start > scn.frame_start:
start = strips_start
if strips_end < scn.frame_end:
end = strips_end
elif vse_tgt == 'UNMUTED':
# unmuted range (no need to render the whole )
unmuted = [s for s in vse.sequences if s.type == 'SOUND' and not s.mute]
start, end = get_start_end(unmuted)
else: # SELECTED or LIST
if vse_tgt == 'SELECTED':
selected_strips = [s for s in vse.sequences if s.type == 'SOUND' and (s.select or s == vse.active_strip)]
else: # LIST
selected_strips = [vse.sequences[scn.swd_settings.seq_idx]]
# get range
start, end = get_start_end(selected_strips)
# temp_changes += [(scn, 'use_preview_range', False) # not affected by preview range]
# one-liner : temp_changes += [(s, 'mute', not s.select) for s in vse.sequences if s.type == 'SOUND']
## mute non selected strips
unselected_strips = [s for s in vse.sequences if s.type == 'SOUND' and not s.select]
temp_changes += [(s, 'mute', True) for s in unselected_strips]
## unmute selected strips (can be counter-logic to some...)
temp_changes += [(s, 'mute', False) for s in selected_strips]
if start < 0:
# Clamp start to zero (can't mixdown in negative)
start = 0
if end < 0:
return None, 'Sound is before frame 0'
start, end = round_to_second(start, end) # round to second to avoid lost in precision with ffmpeg
temp_changes += [
(scn, 'frame_start', start),
(scn, 'frame_end', end),
]
# for i in temp_changes: print(i) # Dbg
with attr_set(temp_changes):
t0 = time()
## Fastest container-codec to write seem to be wav... need further testing
# WAV-PCM = 0.310
# FLAC-FLAC = 0.450,
# MP3-MP3 = 0.8
# OGG-VORBIS = 1.114 ~ 1.313,
ret = bpy.ops.sound.mixdown(filepath=str(filepath), check_existing=False, relative_path=False,
accuracy=128, # lower than 32 crash blender # default 1024
container='WAV', # ('AC3', 'FLAC', 'MATROSKA', 'MP2', 'MP3', 'OGG', 'WAV')
codec='PCM', # ('AAC', 'AC3', 'FLAC', 'MP2', 'MP3', 'PCM', 'VORBIS')
# format='S16', # ('U8', 'S16', 'S24', 'S32', 'F32', 'F64')
bitrate=32, # default 192 [32, 512]
split_channels=False)
if prefs.debug: print(f'Mixdown time: {time() - t0:.3f}s')
if ret != {'FINISHED'}:
print(ret)
return None, 'Problem mixing down sound to load waveform'
return start, end
def hex_to_rgb(hex, base_one=True) -> tuple:
rgb = []
for i in (0, 2, 4):
decimal = int(hex[i:i+2], 16)
if base_one:
decimal /= 255
rgb.append(decimal)
return tuple(rgb)
def rgb_to_hex(rgb) -> str:
r, g, b = rgb[:3]
if isinstance(r, float):
return ('{:X}{:X}{:X}').format(int(r*255), int(g*255), int(b*255))
else:
return ('{:X}{:X}{:X}').format(r, g, b)