1+ #!/usr/bin/env python3
12import csv
23import os
34import subprocess
45import sys
56from pathlib import Path
7+
68from dotenv import load_dotenv
79from elevenlabs .client import ElevenLabs
8- from elevenlabs import play
910
1011from rich .console import Console , Group
1112from rich .live import Live
2021from rich .text import Text
2122
2223load_dotenv ()
23- client = ElevenLabs ()
2424
2525# Getting API key from environment variable
2626# set the variable by:
2727# export ELEVENLABS_API_KEY="<Api key>"
2828api_key = os .environ .get ("ELEVENLABS_API_KEY" )
2929if not api_key :
30- raise RuntimeError ("Environment variable ELEVENLABS_API_KEY not set. Use command:\n export ELEVENLABS_API_KEY=" "<Api key>" "" )
30+ raise RuntimeError (
31+ 'Environment variable ELEVENLABS_API_KEY not set. Use command:\n export ELEVENLABS_API_KEY="<Api key>"'
32+ )
3133
3234# Init ElevenLabs
3335client = ElevenLabs (api_key = api_key )
4244in_ci = os .environ .get ("GITHUB_ACTIONS" , "" ).lower () == "true"
4345total_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"\n Processing 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
160195for idx , (csv_file , voice_name , output_dir ) in enumerate (languages , 1 ):
161196 try :
@@ -165,5 +200,3 @@ def report(msg: str) -> None:
165200 print ("\n Processing interrupted by user." )
166201 sys .exit (1 )
167202 raise
168-
169-
0 commit comments