/
diger
/
fb2tts
Обзор
Документация
Войти
/
diger
/
fb2tts
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
app_cli.py
156 строк
7 KB
diger
fix style and args in cli version
05 авг 2026, 08:07
05 авг 2026, 08:07
609ab9e
Код
Авторство
О чём код?
#!/usr/bin/env python3 import argparse import random from pathlib import Path from lxml import etree from pydub import AudioSegment, effects from tqdm import tqdm from libs.tts import synth synth.load(5) now_dir = Path.cwd() sound_dir = now_dir / 'sound' def add_reverb(audio, room_size=0.3, # 0.1 — маленькая комната, 0.8 — большой зал wet_gain=-15, # громкость ревера (обычно тише) decay_rate=0.4): # скорость затухания # Определяем количество отражений по размеру комнаты if len(audio) == 0: return audio num_reflections = int(4 + room_size * 12) # от 4 до ~14 output = audio wet = audio + wet_gain # Генерируем случайные отражения for _ in range(num_reflections): delay = random.uniform(20, 300) additional_decay = min(1, delay / 1000) * 10 echo = wet - additional_decay if delay > 100: echo = echo.low_pass_filter(3000).high_pass_filter(100) output = output.overlay(echo, position=int(delay)) return output def tts(args): args.repl = getattr(args, 'repl', False) args.use_sound_effect = getattr(args, 'use_sound_effect', True) args.debug = getattr(args, 'debug', False) args.noise_lvl = getattr(args, 'noise_lvl', 16) args.sp_rate = getattr(args, 'sp_rate', 1.1) work_dir = Path(args.ab_path) xml_path = work_dir / 'xml' cover_file = work_dir / 'cover.jpg' if not cover_file.exists(): print(f"Ошибка: Обложка не найдена не найдена: {cover_file}") return if not xml_path.exists(): print(f"Ошибка: папка XML не найдена: {xml_path}") return mp3_path = work_dir / 'mp3' mp3_path.mkdir(parents=True, exist_ok=True) files = [x.stem for x in xml_path.glob('*.xml')] for file in sorted(files, key=float): mp3_file = mp3_path / f'{file}.mp3' if mp3_file.exists() and not args.repl: print(f'{file}.mp3 уже есть') else: xml_file = xml_path / f'{file}.xml' root = etree.parse(str(xml_file)).getroot() out_audio = AudioSegment.silent(duration=1000) autor = root.get('autor') album = root.get('album') for i, line in enumerate(tqdm(root, desc=f"Обработка файла {file}.xml")): audio = AudioSegment.empty() if line.tag == 'sound' and args.use_sound_effect: sound_file = sound_dir / 'events' / f'{line.get("value")}.wav' audio = AudioSegment.from_wav(str(sound_file)) audio = effects.normalize(audio) if line.tag == 'break': slt = int(line.get('time')) * 100 audio = AudioSegment.silent(duration=slt) elif line.text: if args.debug and line.text: print(line.text) np_audio, sr = synth.synth_audio( line.text, speaker_id=args.spk_sel, speed=args.sp_rate, noise=args.noise_lvl ) audio = AudioSegment( np_audio.tobytes(), frame_rate=sr, sample_width=2, channels=1 ) audio = effects.normalize(audio) if line.tag == 'cite' and args.use_sound_effect: audio = add_reverb(audio) if line.get('position') == 'start': pr_audio = AudioSegment.from_wav(sound_dir / 'pause' / 'min_cite.wav') pr_audio = effects.normalize(pr_audio) audio = pr_audio + audio if line.tag == 'empty-line' and args.use_sound_effect: pr_audio = AudioSegment.from_wav(sound_dir / 'pause' / 'empty.wav') pr_audio = effects.normalize(pr_audio) audio = pr_audio + audio out_audio = out_audio + audio if args.back_sound: back_sound_file = sound_dir / 'back' / args.back_sound pr_audio = AudioSegment.from_wav(str(back_sound_file)) duration = pr_audio.duration_seconds # это float back_duration_ms = int(duration * 1000) clip_length_ms = 16000 if back_duration_ms <= clip_length_ms: st_poz = 0 else: max_start = back_duration_ms - clip_length_ms st_poz = random.randint(0, max_start) pr_audio = pr_audio[st_poz:st_poz + clip_length_ms] pr_audio = pr_audio.fade_out(8000).fade_in(3000) - 10 out_audio = out_audio.overlay(pr_audio, position=0) tags = { 'artist': autor, 'title': f'Глава {file}', 'track': f'{file}', 'album': album } out_audio.export( str(mp3_file), format='mp3', bitrate=f'{args.bitrate}', cover=str(cover_file), tags=tags ) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Генерация аудиокниги из XML-разметки с синтезом речи.") parser.add_argument("--ab_path", required=True, type=Path, help="Рабочая директория (содержит /xml, /mp3, обложку)") parser.add_argument("--debug", action="store_true", default=None, help="Режим отладки") parser.add_argument("--back_sound", help="Путь к фоновой дорожке (WAV)") parser.add_argument("--repl", action="store_true", help="Перезаписывать существующие файлы") parser.add_argument("--spk_sel", type=str, default="2", help="Номер диктора") parser.add_argument("--sp_rate", type=float, default=1.1, help="Скорость речи (по умолчанию: 1.1)") parser.add_argument("--bitrate", type=int, default=128, help="Параметр битрейта для mp3") parser.add_argument("--noise_lvl", type=int, default=16, help="Качество (по умолчанию: 16)") parser.add_argument("--use_sound_effect", action="store_true", default=True, help="Использовать звуковые эффекты") args = parser.parse_args() tts(args)