alass_cli/
lib.rs

1use alass_core::{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
24/*#[derive(Debug, Clone, PartialEq, Eq, Copy)]
25pub enum VideoFileFormat {
26    /// we don't need to differentiate between video file formats in current code
27    NotImplemented,
28}*/
29
30pub struct NoProgressInfo {}
31
32impl alass_core::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 alass_core::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
156/// Groups consecutive timespans with the same delta together.
157pub 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: &'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    //video_file_format: VideoFileFormat,
241    subparse_timespans: Vec<subparse::timetypes::TimeSpan>,
242    //aligner_timespans: Vec<alass_core::TimeSpan>,
243}
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        video_decode_progress: impl video_decoder::ProgressHandler,
255    ) -> Result<VideoFileHandler, InputVideoError> {
256        //video_decoder::VideoDecoder::decode(file_path, );
257        use webrtc_vad::*;
258
259        struct WebRtcFvad {
260            fvad: Vad,
261            vad_buffer: Vec<bool>,
262        }
263
264        impl video_decoder::AudioReceiver for WebRtcFvad {
265            type Output = Vec<bool>;
266            type Error = InputVideoError;
267
268            fn push_samples(&mut self, samples: &[i16]) -> Result<(), InputVideoError> {
269                // the chunked audio receiver should only provide 10ms of 8000kHz -> 80 samples
270                assert!(samples.len() == 80);
271
272                let is_voice = self
273                    .fvad
274                    .is_voice_segment(samples)
275                    .map_err(|_| InputVideoErrorKind::VadAnalysisFailed)?;
276
277                self.vad_buffer.push(is_voice);
278
279                Ok(())
280            }
281
282            fn finish(self) -> Result<Vec<bool>, InputVideoError> {
283                Ok(self.vad_buffer)
284            }
285        }
286
287        let vad_processor = WebRtcFvad {
288            fvad: Vad::new_with_rate(SampleRate::Rate8kHz),
289            vad_buffer: Vec::new(),
290        };
291
292        let chunk_processor = video_decoder::ChunkedAudioReceiver::new(80, vad_processor);
293
294        let vad_buffer = video_decoder::VideoDecoder::decode(file_path, chunk_processor, video_decode_progress)
295            .with_context(|_| InputVideoErrorKind::FailedToDecode {
296                path: PathBuf::from(file_path),
297            })?;
298
299        let mut voice_segments: Vec<(i64, i64)> = Vec::new();
300        let mut voice_segment_start: i64 = 0;
301
302        let combine_with_distance_lower_than = 0 / 10;
303
304        let mut last_segment_end: i64 = 0;
305        let mut already_saved_span = true;
306
307        for (i, is_voice_segment) in vad_buffer.into_iter().chain(std::iter::once(false)).enumerate() {
308            let i = i as i64;
309
310            if is_voice_segment {
311                last_segment_end = i;
312                if already_saved_span {
313                    voice_segment_start = i;
314                    already_saved_span = false;
315                }
316            } else {
317                // not a voice segment
318                if i - last_segment_end >= combine_with_distance_lower_than && !already_saved_span {
319                    voice_segments.push((voice_segment_start, last_segment_end));
320                    already_saved_span = true;
321                }
322            }
323        }
324
325        let subparse_timespans: Vec<subparse::timetypes::TimeSpan> = voice_segments
326            .into_iter()
327            .map(|(start, end)| {
328                subparse::timetypes::TimeSpan::new(
329                    subparse::timetypes::TimePoint::from_msecs(start * 10),
330                    subparse::timetypes::TimePoint::from_msecs(end * 10),
331                )
332            })
333            .collect();
334
335        Ok(VideoFileHandler {
336            //video_file_format: VideoFileFormat::NotImplemented,
337            subparse_timespans,
338        })
339    }
340
341    pub fn filter_with_min_span_length_ms(&mut self, min_vad_span_length_ms: i64) {
342        self.subparse_timespans = self
343            .subparse_timespans
344            .iter()
345            .filter(|ts| ts.len() >= TimeDelta::from_msecs(min_vad_span_length_ms))
346            .cloned()
347            .collect();
348    }
349
350    pub fn timespans(&self) -> &[subparse::timetypes::TimeSpan] {
351        self.subparse_timespans.as_slice()
352    }
353}
354
355impl InputFileHandler {
356    pub fn open(
357        file_path: &Path,
358        sub_encoding: &'static Encoding,
359        sub_fps: f64,
360        video_decode_progress: impl video_decoder::ProgressHandler,
361    ) -> Result<InputFileHandler, InputFileError> {
362        let known_subitle_endings: [&str; 6] = ["srt", "vob", "idx", "ass", "ssa", "sub"];
363
364        let extension: Option<&OsStr> = file_path.extension();
365
366        for subtitle_ending in known_subitle_endings.into_iter() {
367            if extension == Some(OsStr::new(subtitle_ending)) {
368                return Ok(SubtitleFileHandler::open_sub_file(file_path, sub_encoding, sub_fps)
369                    .map(|v| InputFileHandler::Subtitle(v))
370                    .with_context(|_| InputFileErrorKind::SubtitleFile(file_path.to_path_buf()))?);
371            }
372        }
373
374        return Ok(VideoFileHandler::open_video_file(file_path, video_decode_progress)
375            .map(|v| InputFileHandler::Video(v))
376            .with_context(|_| InputFileErrorKind::VideoFile(file_path.to_path_buf()))?);
377    }
378
379    pub fn into_subtitle_file(self) -> Option<SubtitleFile> {
380        match self {
381            InputFileHandler::Video(_) => None,
382            InputFileHandler::Subtitle(sub_handler) => Some(sub_handler.subtitle_file),
383        }
384    }
385
386    pub fn timespans(&self) -> &[subparse::timetypes::TimeSpan] {
387        match self {
388            InputFileHandler::Video(video_handler) => video_handler.timespans(),
389            InputFileHandler::Subtitle(sub_handler) => sub_handler.timespans(),
390        }
391    }
392
393    pub fn filter_video_with_min_span_length_ms(&mut self, min_vad_span_length_ms: i64) {
394        if let InputFileHandler::Video(video_handler) = self {
395            video_handler.filter_with_min_span_length_ms(min_vad_span_length_ms);
396        }
397    }
398}
399
400pub fn guess_fps_ratio(
401    ref_spans: &[alass_core::TimeSpan],
402    in_spans: &[alass_core::TimeSpan],
403    ratios: &[f64],
404    mut progress_handler: impl alass_core::ProgressHandler,
405) -> (Option<usize>, alass_core::TimeDelta) {
406    progress_handler.init(ratios.len() as i64);
407    let (delta, score) = alass_core::align_nosplit(
408        ref_spans,
409        in_spans,
410        alass_core::overlap_scoring,
411        alass_core::NoProgressHandler,
412    );
413    progress_handler.inc();
414
415    //let desc = ["25/24", "25/23.976", "24/25", "24/23.976", "23.976/25", "23.976/24"];
416    //println!("score 1: {}", score);
417
418    let (mut opt_idx, mut opt_delta, mut opt_score) = (None, delta, score);
419
420    for (scale_factor_idx, scaling_factor) in ratios.iter().cloned().enumerate() {
421        let stretched_in_spans: Vec<alass_core::TimeSpan> =
422            in_spans.iter().map(|ts| ts.scaled(scaling_factor)).collect();
423
424        let (delta, score) = alass_core::align_nosplit(
425            ref_spans,
426            &stretched_in_spans,
427            alass_core::overlap_scoring,
428            alass_core::NoProgressHandler,
429        );
430        progress_handler.inc();
431
432        //println!("score {}: {}", desc[scale_factor_idx], score);
433
434        if score > opt_score {
435            opt_score = score;
436            opt_idx = Some(scale_factor_idx);
437            opt_delta = delta;
438        }
439    }
440
441    progress_handler.finish();
442
443    (opt_idx, opt_delta)
444}
445
446pub fn print_error_chain(error: failure::Error) {
447    let show_bt_opt = std::env::vars()
448        .find(|(key, _)| key == "RUST_BACKTRACE")
449        .map(|(_, value)| value);
450    let show_bt = show_bt_opt != None && show_bt_opt != Some("0".to_string());
451
452    println!("error: {}", error);
453    if show_bt {
454        println!("stack trace: {}", error.backtrace());
455    }
456
457    for cause in error.as_fail().iter_causes() {
458        println!("caused by: {}", cause);
459        if show_bt {
460            if let Some(backtrace) = cause.backtrace() {
461                println!("stack trace: {}", backtrace);
462            }
463        }
464    }
465
466    if !show_bt {
467        println!("");
468        println!("not: run with environment variable 'RUST_BACKTRACE=1' for detailed stack traces");
469    }
470}