27
27
28
28
29
29
import argparse
30
+ import sys
31
+ from typing import TYPE_CHECKING
30
32
31
33
import httpx
32
34
import trio
33
35
34
36
from subtitle_translate import extricate , subtitle_parser , translate
35
37
38
+ if TYPE_CHECKING :
39
+ from collections .abc import Iterable
40
+
36
41
37
42
async def translate_texts (
38
43
texts : dict [int , tuple [str , ...]],
@@ -61,16 +66,12 @@ async def translate_texts(
61
66
62
67
63
68
async def translate_subtitles (
64
- source_file : str ,
65
- dest_file : str ,
69
+ generator : Iterable [tuple [int , subtitle_parser .Subtitle ]],
66
70
source_language : str = "auto" ,
67
71
dest_language : str = "en" ,
68
- ) -> None :
72
+ ) -> tuple [ dict [ int , subtitle_parser . Subtitle ], dict [ int , tuple [ str , ...]]] :
69
73
"""Translate subtitles file asynchronously."""
70
- print (f"Loading subtitles file { source_file !r} ..." )
71
- subs , texts = subtitle_parser .convert_text (
72
- subtitle_parser .parse_file (source_file ),
73
- )
74
+ subs , texts = subtitle_parser .convert_text (generator )
74
75
75
76
print (f"Parsed { len (subs )} subtitles" )
76
77
@@ -80,11 +81,76 @@ async def translate_subtitles(
80
81
sentence_count = sum (map (len , texts .values ()))
81
82
print (f"Translated { sentence_count } sentences." )
82
83
84
+ return subs , new_texts
85
+
86
+
87
+ async def translate_subtitles_srt (
88
+ source_file : str ,
89
+ dest_file : str | None = None ,
90
+ source_language : str = "auto" ,
91
+ dest_language : str = "en" ,
92
+ ) -> None :
93
+ """Translate subtitles file asynchronously."""
94
+ # Set destination if not provided
95
+ if dest_file is None :
96
+ name , ext = source_file .rsplit ("." , 1 )
97
+ dest_file = f"{ name } .{ dest_language } .{ ext } "
98
+
99
+ print (f"Loading subtitles file { source_file !r} ..." )
100
+ subs , new_texts = await translate_subtitles (
101
+ subtitle_parser .parse_file_srt (source_file ),
102
+ )
103
+
83
104
print ("Updating subtitle texts..." )
84
105
subs = subtitle_parser .modify_subtitles (subs , new_texts )
85
106
86
107
print ("Saving..." )
87
- subtitle_parser .write_subtitles_file (dest_file , subs )
108
+ subtitle_parser .write_subtitles_srt_file (dest_file , subs )
109
+ print ("Save complete." )
110
+ print (f"Saved to { dest_file !r} " )
111
+
112
+
113
+ async def translate_subtitles_vtt (
114
+ source_file : str ,
115
+ dest_file : str | None = None ,
116
+ source_language : str = "auto" ,
117
+ dest_language : str = "en" ,
118
+ ) -> None :
119
+ """Translate subtitles file asynchronously."""
120
+ print (f"Loading subtitles file { source_file !r} ..." )
121
+ gen = subtitle_parser .parse_file_vtt (source_file )
122
+ header = next (gen )
123
+
124
+ new_header = []
125
+ for line in header :
126
+ assert isinstance (line , str )
127
+ if source_language == "auto" and line .startswith ("Language: " ):
128
+ key , value = line .split (" " , 1 )
129
+ source_language = value
130
+ line = f"{ key } { dest_language } "
131
+ new_header .append (line )
132
+
133
+ # Set destination if not provided
134
+ if dest_file is None :
135
+ name , ext = source_file .rsplit ("." , 1 )
136
+ source_lang_ext = f".{ source_language } "
137
+ if source_lang_ext in name :
138
+ name = name .removesuffix (source_lang_ext )
139
+ dest_file = f"{ name } .{ dest_language } .{ ext } "
140
+
141
+ subs , new_texts = await translate_subtitles (
142
+ enumerate (x for x in gen if isinstance (x , subtitle_parser .Subtitle )),
143
+ )
144
+
145
+ print ("Updating subtitle texts..." )
146
+ subs = subtitle_parser .modify_subtitles_plain (subs , new_texts )
147
+
148
+ print ("Saving..." )
149
+ subtitle_parser .write_subtitles_vtt_file (
150
+ dest_file ,
151
+ new_header ,
152
+ subs .values (),
153
+ )
88
154
print ("Save complete." )
89
155
print (f"Saved to { dest_file !r} " )
90
156
@@ -119,6 +185,15 @@ async def run_async() -> None:
119
185
"Must be a ISO 639-1:2002 language code or 'auto' to guess."
120
186
),
121
187
)
188
+ parser .add_argument (
189
+ "--source-type" ,
190
+ type = str ,
191
+ default = "auto" ,
192
+ help = (
193
+ "Subtitle source type (default: 'auto').\n "
194
+ "Must be either 'srt' or 'vtt', or 'auto' to guess from filename."
195
+ ),
196
+ )
122
197
parser .add_argument (
123
198
"--dest-lang" ,
124
199
type = str ,
@@ -136,17 +211,27 @@ async def run_async() -> None:
136
211
137
212
args = parser .parse_args ()
138
213
139
- # Set destination if not provided
140
- if args .dest_file is None :
214
+ if args .source_type == "auto" :
141
215
name , ext = args .source_file .rsplit ("." , 1 )
142
- args .dest_file = f"{ name } .{ args .dest_lang } .{ ext } "
143
-
144
- await translate_subtitles (
145
- args .source_file ,
146
- args .dest_file ,
147
- args .source_lang ,
148
- args .dest_lang ,
149
- )
216
+ args .source_type = ext
217
+
218
+ if args .source_type == "srt" :
219
+ await translate_subtitles_srt (
220
+ args .source_file ,
221
+ args .dest_file ,
222
+ args .source_lang ,
223
+ args .dest_lang ,
224
+ )
225
+ elif args .source_type == "vtt" :
226
+ await translate_subtitles_vtt (
227
+ args .source_file ,
228
+ args .dest_file ,
229
+ args .source_lang ,
230
+ args .dest_lang ,
231
+ )
232
+ else :
233
+ print (f"Unhandled source type { args .source_type !r} ." )
234
+ sys .exit (1 )
150
235
151
236
152
237
def cli_run () -> None :
0 commit comments