spantrack
This commit is contained in:
+8
-5
@@ -27,19 +27,22 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"load-dotenv>=0.1.0",
|
"audioop-lts; python_version >= '3.13'",
|
||||||
"beautifulsoup4",
|
"beautifulsoup4",
|
||||||
"ebooklib",
|
"ebooklib",
|
||||||
"kokoro>=0.9.4",
|
"kokoro>=0.9.4",
|
||||||
|
"load-dotenv>=0.1.0",
|
||||||
"lxml",
|
"lxml",
|
||||||
"mutagen",
|
"mutagen",
|
||||||
"nltk",
|
"nltk",
|
||||||
"numpy",
|
"numpy", # --index-url https://download.pytorch.org/whl/cu132
|
||||||
"pillow",
|
"pillow", # --index-url https://download.pytorch.org/whl/cu132
|
||||||
"pydub",
|
"pydub",
|
||||||
"soundfile",
|
"soundfile",
|
||||||
"tqdm",
|
"torch", # --index-url https://download.pytorch.org/whl/cu132
|
||||||
"audioop-lts; python_version >= '3.13'",
|
"torchaudio", # --index-url https://download.pytorch.org/whl/cu132
|
||||||
|
"torchcodec", # PyTorch 2.9+ # --index-url https://download.pytorch.org/whl/cu132
|
||||||
|
"tqdm", # --index-url https://download.pytorch.org/whl/cu132
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
logger = logging.get_logger()
|
||||||
|
os.environ["NLTK_DATA" = 'C:\\Users\\renat\\AppData\\Roaming\\nltk_data'
|
||||||
|
os.environ["HF_TOKEN"] = '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'
|
||||||
|
from spantrack import cli
|
||||||
|
root = 'D:\DATA\EBOOKS\_SEM_DRM\'
|
||||||
|
globs = [
|
||||||
|
"Night School*.epub",
|
||||||
|
"The Midnight*.epub",
|
||||||
|
"Past Tense*.epub",
|
||||||
|
"Blue Moon*.epub",
|
||||||
|
"The Sentinel",
|
||||||
|
"Better Off Dead",
|
||||||
|
"No Plan B",
|
||||||
|
]
|
||||||
|
for glob in globs:
|
||||||
|
f = root.glob(glob)
|
||||||
|
if len(f) > 1:
|
||||||
|
logger.error("Glob pattern found more than 1 file: %s", f)
|
||||||
|
continue
|
||||||
|
cli.main([f.resolve(), "--voice", "am_liam"])
|
||||||
@@ -33,7 +33,8 @@ from argparse import ArgumentParser
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
# Local imports
|
# Local imports
|
||||||
from . import tts_aedocw, tts_generic, tts_kokoro
|
from . import tts_aedocw, tts_generic
|
||||||
|
#from . import tts_kokoro
|
||||||
from . import log, PathLike
|
from . import log, PathLike
|
||||||
from .utils import check_env
|
from .utils import check_env
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ BACKENDS = {
|
|||||||
"epub2tts-chatterbox": tts_aedocw.AedocwChatterbox,
|
"epub2tts-chatterbox": tts_aedocw.AedocwChatterbox,
|
||||||
"epub2tts-kokoro": tts_aedocw.AedocwKokoro,
|
"epub2tts-kokoro": tts_aedocw.AedocwKokoro,
|
||||||
"generic-epub2tts": tts_aedocw.Epub2TTS,
|
"generic-epub2tts": tts_aedocw.Epub2TTS,
|
||||||
"kokoro": tts_kokoro.KokoroBackend,
|
#"kokoro": tts_kokoro.KokoroBackend,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ No vendored repository needed, kokoro is a pure Python package that can be insta
|
|||||||
|
|
||||||
# stdlib modules
|
# stdlib modules
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
# Automatically enable MPS fallback on Apple Silicon macOS
|
# Automatically enable MPS fallback on Apple Silicon macOS
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
os.environ['PYTORCH_ENABLE_MPS_FALLBACK'] = '1'
|
os.environ['PYTORCH_ENABLE_MPS_FALLBACK'] = '1'
|
||||||
|
|
||||||
|
|
||||||
# pip installed packages
|
# pip installed packages
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import soundfile
|
import soundfile
|
||||||
@@ -203,3 +205,109 @@ class KokoroBackend(GenericTTSBackend):
|
|||||||
os.remove(file)
|
os.remove(file)
|
||||||
segments.append(partname)
|
segments.append(partname)
|
||||||
return segments
|
return segments
|
||||||
|
|
||||||
|
# ***
|
||||||
|
|
||||||
|
# READ
|
||||||
|
def read_from_txt_to_wav(text_file:PathLike,
|
||||||
|
speaker:str="af_heart") -> Path:
|
||||||
|
if not isinstance(text_file,Path):
|
||||||
|
text_file = Path(text_file).resolve()
|
||||||
|
else:
|
||||||
|
text_file = text_file.resolve()
|
||||||
|
|
||||||
|
# Check if text file exists
|
||||||
|
if not text_file.exists():
|
||||||
|
print(f"Error: Text file '{text_file}' not found.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Read the text from the file
|
||||||
|
with open(text_file, 'r', encoding='utf-8') as f:
|
||||||
|
text_contents = f.read()
|
||||||
|
|
||||||
|
# Generate output filename (replace .txt extension with .wav)
|
||||||
|
output_file = text_file.with_suffix('.wav')
|
||||||
|
|
||||||
|
# Check for CUDA GPU
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
print('CUDA GPU available')
|
||||||
|
torch.set_default_device('cuda')
|
||||||
|
|
||||||
|
print(f"Generating audio for speaker '{speaker}' from '{text_file}'...")
|
||||||
|
|
||||||
|
# Create pipeline with language code (first character of speaker name)
|
||||||
|
pipeline = KPipeline(lang_code=speaker[0])
|
||||||
|
|
||||||
|
# Generate audio segments
|
||||||
|
audio_segments = []
|
||||||
|
for gs, ps, audio in pipeline(text_contents, voice=speaker, speed=1, split_pattern=r'\n\n\n'):
|
||||||
|
audio_segments.append(audio)
|
||||||
|
|
||||||
|
# Concatenate all audio segments
|
||||||
|
final_audio = np.concatenate(audio_segments)
|
||||||
|
|
||||||
|
# Write to wav file
|
||||||
|
soundfile.write(output_file, final_audio, 24000)
|
||||||
|
|
||||||
|
print(f"Audio saved to '{output_file}'")
|
||||||
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_speakers(self) -> list[str]:
|
||||||
|
"""Return list of available speakers.
|
||||||
|
See https://huggingface.co/hexgrad/Kokoro-82M/blob/main/VOICES.md"""
|
||||||
|
speakers = [
|
||||||
|
# 🇺🇸 American English: 11F 9M
|
||||||
|
# Overall Grade 'A'
|
||||||
|
"af_heart",
|
||||||
|
# Overall Grade 'C+'
|
||||||
|
"af_aoede","af_kore","af_sarah",
|
||||||
|
"am_fenrir","am_michael","am_puck",
|
||||||
|
# Other
|
||||||
|
"af_alloy", "af_bella", "af_jessica", "af_nicole", "af_nova", "af_river", "af_sky", "am_adam", "am_echo", "am_eric", "am_liam", "am_onyx", "am_santa", "bf_alice",
|
||||||
|
# 🇬🇧 British English: 4F 4M
|
||||||
|
"bf_emma", "bf_isabella", "bf_lily", "bm_daniel", "bm_fable", "bm_george", "bm_lewis",
|
||||||
|
# 🇧🇷 Brazilian Portuguese: 1F 2M
|
||||||
|
"pf_dora",
|
||||||
|
"pm_alex",
|
||||||
|
"pm_santa"]
|
||||||
|
# Old list:
|
||||||
|
# ["af_heart", "af_joy", "af_sad", "af_angry", "af_fear", "af_surprise"]
|
||||||
|
return speakers
|
||||||
|
|
||||||
|
def gen_speaker_samples(
|
||||||
|
self,
|
||||||
|
samples: list =None,
|
||||||
|
output_path: PathLike=None,
|
||||||
|
) -> list[str]:
|
||||||
|
"""Generate sample speakers audio in output_path."""
|
||||||
|
result = self._build_command("gen_samples.py", *samples, output_path=output_path)
|
||||||
|
return [str(self._normalize_path(s)) for s in samples]
|
||||||
|
|
||||||
|
def gen_speaker_samples():
|
||||||
|
|
||||||
|
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
print('CUDA GPU available')
|
||||||
|
torch.set_default_device('cuda')
|
||||||
|
|
||||||
|
for speaker in speakers:
|
||||||
|
file = speaker + "_sample.wav"
|
||||||
|
if os.path.exists(file):
|
||||||
|
print(f"Sample for {speaker} already exists.")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
print(f"Creating {speaker}")
|
||||||
|
pipeline = KPipeline(lang_code=speaker[0])
|
||||||
|
sentence = f"Hello, this voice is {speaker[3:]}. The quick brown fox jumped over the lazy dog. The fish twisted and turned on the bent hook. Press the pants and sew a button on the vest. The swan dive was far short of perfect."
|
||||||
|
audio_segments = []
|
||||||
|
for gs, ps, audio in pipeline(
|
||||||
|
sentence,
|
||||||
|
repo_id='hexgrad/Kokoro-82M',
|
||||||
|
voice=speaker,
|
||||||
|
speed=1,
|
||||||
|
split_pattern=r'\n\n\n'):
|
||||||
|
audio_segments.append(audio)
|
||||||
|
final_audio = np.concatenate(audio_segments)
|
||||||
|
soundfile.write(file, final_audio, 24000)
|
||||||
|
|||||||
Reference in New Issue
Block a user