diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8879dbb --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +.venv/ +__pycache__/ \ No newline at end of file diff --git a/example.py b/example.py new file mode 100644 index 0000000..3531740 --- /dev/null +++ b/example.py @@ -0,0 +1,43 @@ +import random +from pydub import AudioSegment +from granules import Granule, Render + +# Загружаем аудиофайл +audio = AudioSegment.from_file("./mocart-lacrimosa-dies-illa.mp3") + +# Инициализируем класс рендера +render = Render() + +# Создаем случайное количество гранул от 5 до 30 +for _ in range(random.randint(5, 30)): + + # Определяем случайную позицию гранулы с 10мс до 100000мс, + # а так же ее длину от 50мс до 300мс + start = random.randint(10, 100000) + end = start + random.randint(50, 300) + + # Случайно выбираем длительность сглаживания начала и завершения гранулы + fade_start=random.randint(0, 10) + fade_end=random.randint(0, 10) + + # Случайно выбираем от 2 до 10 повторений гранулы + iterations = random.randint(2, 10) + + # Инициализируем класс гранулы и рендерим ее + granule = Granule( + audio, + iterations=iterations, + start=start, + end=end, + fade_start=fade_start, + fade_end=fade_end + ) + granule.render() + + # Добавляем рендер гранулы в очередь рендеринга + render.append(granule) + +# Запускаем рендер очереди в единый аудио-файл, сохраняем и проигрываем его +render.render() +render.export_mp3('./lacrimoza_mix.mp3') +render.play() \ No newline at end of file diff --git a/granules.py b/granules.py new file mode 100644 index 0000000..a9233f0 --- /dev/null +++ b/granules.py @@ -0,0 +1,108 @@ +from pydub import AudioSegment +from pydub.playback import play + +class Granule: + + def __init__(self, + audio:AudioSegment, + iterations:int=0, + start:int=0, + end:int=0, + fade_start:int=0, + fade_end:int=0, + ): + + self.audio:AudioSegment = audio + self.iterations:int = iterations + self.final_clip:AudioSegment = AudioSegment.empty() + self.start:int = start + self.end:int = end + self.fade_start:int = fade_start + self.fade_end:int = fade_end + self.clip = None + + if self.start > 0 and self.end > 0: + self.split(self.start, self.end) + + def split(self, start:int, end:int): + self.clip = self.audio[start:end] + + def render(self): + final_clip = AudioSegment.empty() + + for _ in range(self.iterations): + current_clip = self.clip + + if self.fade_start > 0: + current_clip = current_clip.fade_in(self.fade_start) + + if self.fade_end > 0: + current_clip = current_clip.fade_out(self.fade_end) + + final_clip += current_clip + + return final_clip + + def generate(self): + for _ in range(self.iterations): + current_clip = self.clip + + if self.fade_start > 0: + current_clip = current_clip.fade_in(self.fade_start) + + if self.fade_end > 0: + current_clip = current_clip.fade_out(self.fade_end) + + yield current_clip + +class Render: + + def __init__(self, granules:list[Granule]=[]): + self.granules = granules + self.final_clip = AudioSegment.empty() + + def append(self, granule:Granule): + self.granules.append(granule) + + def pop(self, index:int): + self.granules.pop(index) + + def insert(self, index:int, granule:Granule): + self.granules.pop(index, granule) + + def render(self): + self.final_clip = AudioSegment.empty() + + for granule in self.granules: + for clip in granule.generate(): + self.final_clip += clip + + def play(self): + play(self.final_clip) + + def export(self, file_path: str, format: str = "mp3", parameters: dict = None): + + if len(self.final_clip) == 0: + raise BaseException("Нет аудио для экспорта. Сначала выполните render().") + + try: + export_params = {"format": format} + + if parameters: + export_params.update(parameters) + + self.final_clip.export(file_path, **export_params) + return True + + except Exception as e: + raise BaseException(f"Ошибка при экспорте файла: {e}") + + def export_mp3(self, file_path: str, bitrate: str = "192k"): + return self.export(file_path, format="mp3", parameters={"bitrate": bitrate}) + + def export_wav(self, file_path: str): + return self.export(file_path, format="wav") + + def clear(self): + self.granules.clear() + self.final_clip = AudioSegment.empty() \ No newline at end of file diff --git a/lacrimoza_mix.mp3 b/lacrimoza_mix.mp3 new file mode 100644 index 0000000..462e99a --- /dev/null +++ b/lacrimoza_mix.mp3 Binary files differ diff --git a/mocart-lacrimosa-dies-illa.mp3 b/mocart-lacrimosa-dies-illa.mp3 new file mode 100644 index 0000000..f9fd9ee --- /dev/null +++ b/mocart-lacrimosa-dies-illa.mp3 Binary files differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..802fd1b --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pydub==0.25.1