Skip to content

Commit b207ee7

Browse files
authored
chore(build): formatting, use pathlib, use dotenv (#164)
1 parent baf4778 commit b207ee7

File tree

4 files changed

+340
-243
lines changed

4 files changed

+340
-243
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ release/
1515
# Python
1616
.venv
1717
uv.lock
18+
19+
# Environment variables
20+
.env

voice-gen-elevenlabs.py

Lines changed: 92 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
#!/usr/bin/env python3
12
import csv
23
import os
34
import subprocess
45
import sys
56
from pathlib import Path
7+
68
from dotenv import load_dotenv
79
from elevenlabs.client import ElevenLabs
8-
from elevenlabs import play
910

1011
from rich.console import Console, Group
1112
from rich.live import Live
@@ -20,14 +21,15 @@
2021
from rich.text import Text
2122

2223
load_dotenv()
23-
client = ElevenLabs()
2424

2525
# Getting API key from environment variable
2626
# set the variable by:
2727
# export ELEVENLABS_API_KEY="<Api key>"
2828
api_key = os.environ.get("ELEVENLABS_API_KEY")
2929
if not api_key:
30-
raise RuntimeError("Environment variable ELEVENLABS_API_KEY not set. Use command:\nexport ELEVENLABS_API_KEY=""<Api key>""")
30+
raise RuntimeError(
31+
'Environment variable ELEVENLABS_API_KEY not set. Use command:\nexport ELEVENLABS_API_KEY="<Api key>"'
32+
)
3133

3234
# Init ElevenLabs
3335
client = ElevenLabs(api_key=api_key)
@@ -42,7 +44,14 @@
4244
in_ci = os.environ.get("GITHUB_ACTIONS", "").lower() == "true"
4345
total_files = len(languages)
4446

45-
def process_csv_file(csv_file: str, voice_name: str, output_dir: str, processed_files: int, total_files: int) -> None:
47+
48+
def process_csv_file(
49+
csv_file: str,
50+
voice_name: str,
51+
output_dir: str,
52+
processed_files: int,
53+
total_files: int,
54+
) -> None:
4655
"""Process a single CSV file."""
4756
console = Console(force_terminal=not in_ci, no_color=in_ci)
4857
progress = Progress(
@@ -72,7 +81,10 @@ def __rich_console__(self, console, options):
7281

7382
print(f"\nProcessing file {csv_file}")
7483

75-
with open(csv_file, newline="", encoding="utf-8") as f, Live(layout, console=console, refresh_per_second=10, transient=False):
84+
csv_path = Path(csv_file)
85+
with csv_path.open(newline="", encoding="utf-8") as f, Live(
86+
layout, console=console, refresh_per_second=10, transient=False
87+
):
7688
reader = csv.DictReader(f)
7789
rows = list(reader)
7890
total_rows = len(rows)
@@ -89,64 +101,85 @@ def report(msg: str) -> None:
89101
line_count = 0
90102

91103
try:
104+
fail_streak = 0
92105
for row in rows:
93106
line_count += 1
94-
if not row.get("Filename") or row.get("String ID", "").startswith("#"):
107+
try:
108+
if not row.get("Filename") or row.get("String ID", "").startswith(
109+
"#"
110+
):
111+
progress.update(task_id, advance=1)
112+
processed_count += 1
113+
continue
114+
115+
name = Path(row["Filename"]).stem
116+
tr = row.get("Translation", "")
117+
subd = row.get("Path", "")
118+
skip = row.get("Skip") or "0.0"
119+
120+
full_dir = Path("SOUNDS") / output_dir / subd
121+
full_dir.mkdir(parents=True, exist_ok=True)
122+
output_mp3 = full_dir / f"{name}.mp3"
123+
output_wav = full_dir / f"{name}.wav"
124+
125+
# To save free tokens available on Elevenlabs - skip existing files to avoid double generating
126+
if output_wav.exists():
127+
report(
128+
f'[{line_count}/{total_rows}] Skipping "{name}.wav" as already exists.'
129+
)
130+
progress.update(task_id, advance=1)
131+
processed_count += 1
132+
fail_streak = 0
133+
continue
134+
135+
report(
136+
f"[{line_count}/{total_rows}] Generating MP3 file: {output_mp3} ..."
137+
)
138+
139+
audio_generator = client.text_to_speech.convert(
140+
text=tr,
141+
voice_id=voice_name,
142+
model_id="eleven_multilingual_v2",
143+
output_format="mp3_44100_128",
144+
)
145+
146+
audio_bytes = b"".join(audio_generator)
147+
output_mp3.write_bytes(audio_bytes)
148+
149+
ffmpeg_cmd = [
150+
"ffmpeg",
151+
"-ss",
152+
skip,
153+
"-y",
154+
"-i",
155+
str(output_mp3),
156+
"-ar",
157+
"32000",
158+
"-ac",
159+
"1",
160+
"-sample_fmt",
161+
"s16",
162+
str(output_wav),
163+
]
164+
165+
subprocess.run(ffmpeg_cmd, check=True)
166+
167+
# Remove temporary mp3 file
168+
if output_mp3.exists():
169+
output_mp3.unlink()
170+
95171
progress.update(task_id, advance=1)
96172
processed_count += 1
97-
continue
98-
99-
name = row["Filename"].split('.')[0]
100-
tr = row.get("Translation", "")
101-
subd = row.get("Path", "") # Subdirectory
102-
103-
full_dir = os.path.join("SOUNDS", output_dir, subd)
104-
os.makedirs(full_dir, exist_ok=True)
105-
output_mp3 = os.path.join(full_dir, f"{name}.mp3")
106-
output_wav = os.path.join(full_dir, f"{name}.wav")
107-
108-
# To save free tokens available on Elevenlabs - skip existing files to avoid double generating
109-
if os.path.exists(output_wav):
110-
report(f"[{line_count}/{total_rows}] Skipping \"{name}.wav\" as already exists.")
173+
fail_streak = 0
174+
except Exception as e:
175+
report(f"[{line_count}/{total_rows}] Error processing row: {e}")
111176
progress.update(task_id, advance=1)
112177
processed_count += 1
178+
fail_streak += 1
179+
if fail_streak >= 3:
180+
report("Aborting after 3 consecutive failures")
181+
raise SystemExit(1)
113182
continue
114-
115-
report(f"[{line_count}/{total_rows}] Generating MP3 file: {output_mp3} ...")
116-
117-
audio_generator = client.text_to_speech.convert(
118-
text=tr,
119-
voice_id=voice_name,
120-
model_id="eleven_multilingual_v2",
121-
output_format="mp3_44100_128"
122-
)
123-
124-
audio_bytes = b''.join(audio_generator)
125-
126-
with open(output_mp3, "wb") as out_file:
127-
out_file.write(audio_bytes)
128-
129-
# Conversion MP3 -> WAV using ffmpeg command
130-
skip = row.get("Skip") or "0.0"
131-
ffmpeg_cmd = [
132-
"ffmpeg",
133-
"-ss", skip, # skip beginning in words that can be interpreted in a wrong lanuage
134-
"-y", # overwrite existing file
135-
"-i", output_mp3,
136-
"-ar", "32000", # sample rate 32 kHz
137-
"-ac", "1", # mono
138-
"-sample_fmt", "s16", # 16-bit PCM
139-
output_wav
140-
]
141-
142-
subprocess.run(ffmpeg_cmd, check=True)
143-
144-
# Remove temporary mp3 file
145-
if os.path.exists(output_mp3):
146-
os.remove(output_mp3)
147-
148-
progress.update(task_id, advance=1)
149-
processed_count += 1
150183
except KeyboardInterrupt:
151184
report(
152185
f"Interrupted. Processed {processed_files}/{total_files} files; {processed_count}/{total_rows} entries in current file."
@@ -155,7 +188,9 @@ def report(msg: str) -> None:
155188
raise SystemExit(1)
156189

157190
report(
158-
f'Finished processing {processed_files}/{total_files} files ({processed_count}/{total_rows} entries) in "{csv_file}".')
191+
f'Finished processing {processed_files}/{total_files} files ({processed_count}/{total_rows} entries) in "{csv_file}".'
192+
)
193+
159194

160195
for idx, (csv_file, voice_name, output_dir) in enumerate(languages, 1):
161196
try:
@@ -165,5 +200,3 @@ def report(msg: str) -> None:
165200
print("\nProcessing interrupted by user.")
166201
sys.exit(1)
167202
raise
168-
169-

0 commit comments

Comments
 (0)