import os import bpy from contextlib import contextmanager from typing import List import wave from .speaker_utils import solo_speakers, unmute_all_speakers @contextmanager def adm_object_rendering_context(scene: bpy.types.Scene): old_ff = scene.render.image_settings.file_format old_codec = scene.render.ffmpeg.audio_codec old_chans = scene.render.ffmpeg.audio_channels scene = bpy.context.scene scene.render.image_settings.file_format = 'FFMPEG' scene.render.ffmpeg.audio_codec = 'PCM' scene.render.ffmpeg.audio_channels = 'MONO' yield scene scene.render.image_settings.file_format = old_ff scene.render.ffmpeg.audio_codec = old_codec scene.render.ffmpeg.audio_channels = old_chans class ObjectMix: def __init__(self, sources: List[bpy.types.Object], scene: bpy.types.Scene, base_dir: str): self.sources = sources self.intermediate_filename = None self.base_dir = base_dir self.scene = scene self._mixdown_file_handle = None self._mixdown_reader = None @property def frame_start(self): return self.scene.frame_start @property def frame_end(self): return self.scene.frame_end @property def sample_rate(self) -> int: with wave.open(self.mixdown_filename, "rb") as f: return f.getframerate() @property def bits_per_sample(self) -> int: with wave.open(self.mixdown_filename, "rb") as f: return f.getsampwidth() * 8 @property def frames_length(self) -> int: with wave.open(self.mixdown_filename, "rb") as f: return f.getnframes() @property def mixdown_filename(self) -> str: if self.intermediate_filename is None: self.mixdown() assert self.intermediate_filename return self.intermediate_filename @property def object_name(self): return self.sources[0].name def mixdown(self): with adm_object_rendering_context(self.scene) as scene: solo_speakers(scene, self.sources) scene_name = bpy.path.clean_name(scene.name) speaker_name = bpy.path.clean_name(self.object_name) self.intermediate_filename = os.path.join( self.base_dir, "%s_%s.wav" % (scene_name, speaker_name)) bpy.ops.sound.mixdown(filepath=self.intermediate_filename, container='WAV', codec='PCM', format='S24') print("Created mixdown named {}" .format(self.intermediate_filename)) unmute_all_speakers(scene) def rm_mixdown(self): if self._mixdown_reader is not None: self._mixdown_reader = None if self._mixdown_file_handle is not None: self._mixdown_file_handle.close() self._mixdown_file_handle = None if self.intermediate_filename is not None: os.remove(self.intermediate_filename) self.intermediate_filename = None class ObjectMixPool: def __init__(self, object_mixes: List[ObjectMix]): self.object_mixes = object_mixes def __enter__(self): return self def __exit__(self, _exc_type, _exc_val, _exc_tb): for mix in self.object_mixes: mix.rm_mixdown() @property def shortest_file_length(self): lengths = map(lambda f: len(f.mixdown_reader), self.object_mixes) return min(lengths) def object_mixes_from_source_groups(groups: List[List[bpy.types.Object]], scene: bpy.types.Scene, base_dir: str): mixes = [] for group in groups: mixes.append(ObjectMix(sources=group, scene=scene, base_dir=base_dir)) return mixes