1use ilass::{TimeDelta as AlgTimeDelta, TimePoint as AlgTimePoint, TimeSpan as AlgTimeSpan};
2use encoding_rs::Encoding;
3use failure::ResultExt;
4use pbr::ProgressBar;
5use std::cmp::{max, min};
6use std::ffi::OsStr;
7use std::fs::File;
8use std::io::{Read, Write};
9use std::path::{Path, PathBuf};
10use std::result::Result;
11
12use errors::*;
13
14pub mod errors;
15pub mod video_decoder;
16
17use subparse::timetypes::*;
18use subparse::{get_subtitle_format_err, parse_bytes, SubtitleFile};
19
20pub const PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
21pub const PKG_NAME: Option<&'static str> = option_env!("CARGO_PKG_NAME");
22pub const PKG_DESCRIPTION: Option<&'static str> = option_env!("CARGO_PKG_DESCRIPTION");
23
24pub struct NoProgressInfo {}
31
32impl ilass::ProgressHandler for NoProgressInfo {
33 fn init(&mut self, _steps: i64) {}
34 fn inc(&mut self) {}
35 fn finish(&mut self) {}
36}
37
38impl video_decoder::ProgressHandler for NoProgressInfo {
39 fn init(&mut self, _steps: i64) {}
40 fn inc(&mut self) {}
41 fn finish(&mut self) {}
42}
43
44pub struct ProgressInfo {
45 init_msg: Option<String>,
46 prescaler: i64,
47 counter: i64,
48 progress_bar: Option<ProgressBar<std::io::Stdout>>,
49}
50
51impl ProgressInfo {
52 pub fn new(prescaler: i64, init_msg: Option<String>) -> ProgressInfo {
53 ProgressInfo {
54 init_msg: init_msg,
55 prescaler,
56 counter: 0,
57 progress_bar: None,
58 }
59 }
60}
61
62impl ProgressInfo {
63 fn init(&mut self, steps: i64) {
64 self.progress_bar = Some(ProgressBar::new((steps / self.prescaler) as u64));
65 if let Some(init_msg) = &self.init_msg {
66 println!("{}", init_msg);
67 }
68 }
69
70 fn inc(&mut self) {
71 self.counter = self.counter + 1;
72 if self.counter == self.prescaler {
73 self.progress_bar.as_mut().unwrap().inc();
74 self.counter = 0;
75 }
76 }
77
78 fn finish(&mut self) {
79 self.progress_bar.as_mut().unwrap().finish_println("\n");
80 }
81}
82
83impl ilass::ProgressHandler for ProgressInfo {
84 fn init(&mut self, steps: i64) {
85 self.init(steps)
86 }
87 fn inc(&mut self) {
88 self.inc()
89 }
90 fn finish(&mut self) {
91 self.finish()
92 }
93}
94
95impl video_decoder::ProgressHandler for ProgressInfo {
96 fn init(&mut self, steps: i64) {
97 self.init(steps)
98 }
99 fn inc(&mut self) {
100 self.inc()
101 }
102 fn finish(&mut self) {
103 self.finish()
104 }
105}
106
107pub fn read_file_to_bytes(path: &Path) -> std::result::Result<Vec<u8>, FileOperationError> {
108 let mut file = File::open(path).with_context(|_| FileOperationErrorKind::FileOpen {
109 path: path.to_path_buf(),
110 })?;
111 let mut v = Vec::new();
112 file.read_to_end(&mut v)
113 .with_context(|_| FileOperationErrorKind::FileRead {
114 path: path.to_path_buf(),
115 })?;
116 Ok(v)
117}
118
119pub fn write_data_to_file(path: &Path, d: Vec<u8>) -> std::result::Result<(), FileOperationError> {
120 let mut file = File::create(path).with_context(|_| FileOperationErrorKind::FileOpen {
121 path: path.to_path_buf(),
122 })?;
123 file.write_all(&d).with_context(|_| FileOperationErrorKind::FileWrite {
124 path: path.to_path_buf(),
125 })?;
126 Ok(())
127}
128
129pub fn timing_to_alg_timepoint(t: TimePoint, interval: i64) -> AlgTimePoint {
130 assert!(interval > 0);
131 AlgTimePoint::from(t.msecs() / interval)
132}
133
134pub fn alg_delta_to_delta(t: AlgTimeDelta, interval: i64) -> TimeDelta {
135 assert!(interval > 0);
136 let time_int: i64 = t.into();
137 TimeDelta::from_msecs(time_int * interval)
138}
139
140pub fn timings_to_alg_timespans(v: &[TimeSpan], interval: i64) -> Vec<AlgTimeSpan> {
141 v.iter()
142 .cloned()
143 .map(|timespan| {
144 AlgTimeSpan::new_safe(
145 timing_to_alg_timepoint(timespan.start, interval),
146 timing_to_alg_timepoint(timespan.end, interval),
147 )
148 })
149 .collect()
150}
151
152pub fn alg_deltas_to_timing_deltas(v: &[AlgTimeDelta], interval: i64) -> Vec<TimeDelta> {
153 v.iter().cloned().map(|x| alg_delta_to_delta(x, interval)).collect()
154}
155
156pub fn get_subtitle_delta_groups(mut v: Vec<(AlgTimeDelta, TimeSpan)>) -> Vec<(AlgTimeDelta, Vec<TimeSpan>)> {
158 v.sort_by_key(|t| min((t.1).start, (t.1).end));
159
160 let mut result: Vec<(AlgTimeDelta, Vec<TimeSpan>)> = Vec::new();
161
162 for (delta, original_timespan) in v {
163 let mut new_block = false;
164
165 if let Some(last_tuple_ref) = result.last_mut() {
166 if delta == last_tuple_ref.0 {
167 last_tuple_ref.1.push(original_timespan);
168 } else {
169 new_block = true;
170 }
171 } else {
172 new_block = true;
173 }
174
175 if new_block {
176 result.push((delta, vec![original_timespan]));
177 }
178 }
179
180 result
181}
182
183pub enum InputFileHandler {
184 Subtitle(SubtitleFileHandler),
185 Video(VideoFileHandler),
186}
187
188pub struct SubtitleFileHandler {
189 file_format: subparse::SubtitleFormat,
190 subtitle_file: SubtitleFile,
191 subparse_timespans: Vec<subparse::timetypes::TimeSpan>,
192}
193
194impl SubtitleFileHandler {
195 pub fn open_sub_file(
196 file_path: &Path,
197 sub_encoding: Option<&'static Encoding>,
198 sub_fps: f64,
199 ) -> Result<SubtitleFileHandler, InputSubtitleError> {
200 let sub_data = read_file_to_bytes(file_path.as_ref())
201 .with_context(|_| InputSubtitleErrorKind::ReadingSubtitleFileFailed(file_path.to_path_buf()))?;
202
203 let file_format = get_subtitle_format_err(file_path.extension(), &sub_data)
204 .with_context(|_| InputSubtitleErrorKind::UnknownSubtitleFormat(file_path.to_path_buf()))?;
205
206 let parsed_subtitle_data: SubtitleFile = parse_bytes(file_format, &sub_data, sub_encoding, sub_fps)
207 .with_context(|_| InputSubtitleErrorKind::ParsingSubtitleFailed(file_path.to_path_buf()))?;
208
209 let subparse_timespans: Vec<subparse::timetypes::TimeSpan> = parsed_subtitle_data
210 .get_subtitle_entries()
211 .with_context(|_| InputSubtitleErrorKind::RetreivingSubtitleLinesFailed(file_path.to_path_buf()))?
212 .into_iter()
213 .map(|subentry| subentry.timespan)
214 .map(|timespan: subparse::timetypes::TimeSpan| {
215 TimeSpan::new(min(timespan.start, timespan.end), max(timespan.start, timespan.end))
216 })
217 .collect();
218
219 Ok(SubtitleFileHandler {
220 file_format: file_format,
221 subparse_timespans,
222 subtitle_file: parsed_subtitle_data,
223 })
224 }
225
226 pub fn file_format(&self) -> subparse::SubtitleFormat {
227 self.file_format
228 }
229
230 pub fn timespans(&self) -> &[subparse::timetypes::TimeSpan] {
231 self.subparse_timespans.as_slice()
232 }
233
234 pub fn into_subtitle_file(self) -> subparse::SubtitleFile {
235 self.subtitle_file
236 }
237}
238
239pub struct VideoFileHandler {
240 subparse_timespans: Vec<subparse::timetypes::TimeSpan>,
242 }
244
245impl VideoFileHandler {
246 pub fn from_cache(timespans: Vec<subparse::timetypes::TimeSpan>) -> VideoFileHandler {
247 VideoFileHandler {
248 subparse_timespans: timespans,
249 }
250 }
251
252 pub fn open_video_file(
253 file_path: &Path,
254 audio_index: Option<usize>,
255 video_decode_progress: impl video_decoder::ProgressHandler,
256 ) -> Result<VideoFileHandler, InputVideoError> {
257 use webrtc_vad::*;
259
260 struct WebRtcFvad {
261 fvad: Vad,
262 vad_buffer: Vec<bool>,
263 }
264
265 impl video_decoder::AudioReceiver for WebRtcFvad {
266 type Output = Vec<bool>;
267 type Error = InputVideoError;
268
269 fn push_samples(&mut self, samples: &[i16]) -> Result<(), InputVideoError> {
270 assert!(samples.len() == 80);
272
273 let is_voice = self
274 .fvad
275 .is_voice_segment(samples)
276 .map_err(|_| InputVideoErrorKind::VadAnalysisFailed)?;
277
278 self.vad_buffer.push(is_voice);
279
280 Ok(())
281 }
282
283 fn finish(self) -> Result<Vec<bool>, InputVideoError> {
284 Ok(self.vad_buffer)
285 }
286 }
287
288 let vad_processor = WebRtcFvad {
289 fvad: Vad::new_with_rate(SampleRate::Rate8kHz),
290 vad_buffer: Vec::new(),
291 };
292
293 let chunk_processor = video_decoder::ChunkedAudioReceiver::new(80, vad_processor);
294
295 let vad_buffer = video_decoder::VideoDecoder::decode(file_path, audio_index, chunk_processor, video_decode_progress)
296 .with_context(|_| InputVideoErrorKind::FailedToDecode {
297 path: PathBuf::from(file_path),
298 })?;
299
300 let mut voice_segments: Vec<(i64, i64)> = Vec::new();
301 let mut voice_segment_start: i64 = 0;
302
303 let combine_with_distance_lower_than = 0 / 10;
304
305 let mut last_segment_end: i64 = 0;
306 let mut already_saved_span = true;
307
308 for (i, is_voice_segment) in vad_buffer.into_iter().chain(std::iter::once(false)).enumerate() {
309 let i = i as i64;
310
311 if is_voice_segment {
312 last_segment_end = i;
313 if already_saved_span {
314 voice_segment_start = i;
315 already_saved_span = false;
316 }
317 } else {
318 if i - last_segment_end >= combine_with_distance_lower_than && !already_saved_span {
320 voice_segments.push((voice_segment_start, last_segment_end));
321 already_saved_span = true;
322 }
323 }
324 }
325
326 let subparse_timespans: Vec<subparse::timetypes::TimeSpan> = voice_segments
327 .into_iter()
328 .map(|(start, end)| {
329 subparse::timetypes::TimeSpan::new(
330 subparse::timetypes::TimePoint::from_msecs(start * 10),
331 subparse::timetypes::TimePoint::from_msecs(end * 10),
332 )
333 })
334 .collect();
335
336 Ok(VideoFileHandler {
337 subparse_timespans,
339 })
340 }
341
342 pub fn filter_with_min_span_length_ms(&mut self, min_vad_span_length_ms: i64) {
343 self.subparse_timespans = self
344 .subparse_timespans
345 .iter()
346 .filter(|ts| ts.len() >= TimeDelta::from_msecs(min_vad_span_length_ms))
347 .cloned()
348 .collect();
349 }
350
351 pub fn timespans(&self) -> &[subparse::timetypes::TimeSpan] {
352 self.subparse_timespans.as_slice()
353 }
354}
355
356impl InputFileHandler {
357 pub fn open(
358 file_path: &Path,
359 audio_index: Option<usize>,
360 sub_encoding: Option<&'static Encoding>,
361 sub_fps: f64,
362 video_decode_progress: impl video_decoder::ProgressHandler,
363 ) -> Result<InputFileHandler, InputFileError> {
364 let known_subitle_endings: [&str; 6] = ["srt", "vob", "idx", "ass", "ssa", "sub"];
365
366 let extension: Option<&OsStr> = file_path.extension();
367
368 for &subtitle_ending in known_subitle_endings.iter() {
369 if extension == Some(OsStr::new(subtitle_ending)) {
370 return Ok(SubtitleFileHandler::open_sub_file(file_path, sub_encoding, sub_fps)
371 .map(|v| InputFileHandler::Subtitle(v))
372 .with_context(|_| InputFileErrorKind::SubtitleFile(file_path.to_path_buf()))?);
373 }
374 }
375
376 return Ok(VideoFileHandler::open_video_file(file_path, audio_index, video_decode_progress)
377 .map(|v| InputFileHandler::Video(v))
378 .with_context(|_| InputFileErrorKind::VideoFile(file_path.to_path_buf()))?);
379 }
380
381 pub fn into_subtitle_file(self) -> Option<SubtitleFile> {
382 match self {
383 InputFileHandler::Video(_) => None,
384 InputFileHandler::Subtitle(sub_handler) => Some(sub_handler.subtitle_file),
385 }
386 }
387
388 pub fn timespans(&self) -> &[subparse::timetypes::TimeSpan] {
389 match self {
390 InputFileHandler::Video(video_handler) => video_handler.timespans(),
391 InputFileHandler::Subtitle(sub_handler) => sub_handler.timespans(),
392 }
393 }
394
395 pub fn filter_video_with_min_span_length_ms(&mut self, min_vad_span_length_ms: i64) {
396 if let InputFileHandler::Video(video_handler) = self {
397 video_handler.filter_with_min_span_length_ms(min_vad_span_length_ms);
398 }
399 }
400}
401
402pub fn guess_fps_ratio(
403 ref_spans: &[ilass::TimeSpan],
404 in_spans: &[ilass::TimeSpan],
405 ratios: &[f64],
406 mut progress_handler: impl ilass::ProgressHandler,
407) -> (Option<usize>, ilass::TimeDelta) {
408 progress_handler.init(ratios.len() as i64);
409 let (delta, score) = ilass::align_nosplit(
410 ref_spans,
411 in_spans,
412 ilass::overlap_scoring,
413 ilass::NoProgressHandler,
414 );
415 progress_handler.inc();
416
417 let (mut opt_idx, mut opt_delta, mut opt_score) = (None, delta, score);
421
422 for (scale_factor_idx, scaling_factor) in ratios.iter().cloned().enumerate() {
423 let stretched_in_spans: Vec<ilass::TimeSpan> =
424 in_spans.iter().map(|ts| ts.scaled(scaling_factor)).collect();
425
426 let (delta, score) = ilass::align_nosplit(
427 ref_spans,
428 &stretched_in_spans,
429 ilass::overlap_scoring,
430 ilass::NoProgressHandler,
431 );
432 progress_handler.inc();
433
434 if score > opt_score {
437 opt_score = score;
438 opt_idx = Some(scale_factor_idx);
439 opt_delta = delta;
440 }
441 }
442
443 progress_handler.finish();
444
445 (opt_idx, opt_delta)
446}
447
448pub fn print_error_chain(error: failure::Error) {
449 let show_bt_opt = std::env::vars()
450 .find(|(key, _)| key == "RUST_BACKTRACE")
451 .map(|(_, value)| value);
452 let show_bt = show_bt_opt != None && show_bt_opt != Some("0".to_string());
453
454 println!("error: {}", error);
455 if show_bt {
456 println!("stack trace: {}", error.backtrace());
457 }
458
459 for cause in error.as_fail().iter_causes() {
460 println!("caused by: {}", cause);
461 if show_bt {
462 if let Some(backtrace) = cause.backtrace() {
463 println!("stack trace: {}", backtrace);
464 }
465 }
466 }
467
468 if !show_bt {
469 println!("");
470 println!("not: run with environment variable 'RUST_BACKTRACE=1' for detailed stack traces");
471 }
472}