ilass_cli/
lib.rs

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
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 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
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: 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    //video_file_format: VideoFileFormat,
241    subparse_timespans: Vec<subparse::timetypes::TimeSpan>,
242    //aligner_timespans: Vec<ilass::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        audio_index: Option<usize>,
255        video_decode_progress: impl video_decoder::ProgressHandler,
256    ) -> Result<VideoFileHandler, InputVideoError> {
257        //video_decoder::VideoDecoder::decode(file_path, );
258        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                // the chunked audio receiver should only provide 10ms of 8000kHz -> 80 samples
271                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                // not a voice segment
319                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            //video_file_format: VideoFileFormat::NotImplemented,
338            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 desc = ["25/24", "25/23.976", "24/25", "24/23.976", "23.976/25", "23.976/24"];
418    //println!("score 1: {}", score);
419
420    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        //println!("score {}: {}", desc[scale_factor_idx], score);
435
436        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}