use std::fmt;
use std::fs::File;
use std::path::Path;
use std::io;
use std::io::prelude::*;
use std::io::{BufReader, BufWriter, Cursor};
use std::io::ErrorKind::NotFound;
use std::error::Error;
use std::iter::once;
use std::convert::{TryFrom, TryInto};
use std::cmp::{min, max};
use alass_core::TimeSpan;
use subparse::SubtitleFile;
use subparse::timetypes::{TimeSpan as SubTimeSpan, TimePoint as SubTimePoint};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crate::{SyncError, VoiceActivity};
use crate::util::to_alass_timepoint;
use TimeSpansLoadError::*;
use TimeSpansSaveError::*;
pub struct TimeSpans(pub Vec<SubTimeSpan>);
impl TimeSpans {
pub fn push(self: &mut Self, span: SubTimeSpan) {
self.0.push(span)
}
pub fn save(self: &Self, filename: &str) -> Result<(), TimeSpansSaveError> {
let file = File::create(Path::new(filename))
.map_err(|cause| WriteError { cause })?;
let bytes: Vec<u8> = self.try_into()
.map_err(|cause| SerializeError { cause })?;
let _ = BufWriter::new(file)
.write(bytes.as_slice())
.map_err(|cause| WriteError { cause })?;
Ok(())
}
pub fn load(filename: &str) -> Result<Self, TimeSpansLoadError> {
let file = File::open(Path::new(filename))
.map_err(|cause| match cause.kind() {
NotFound => FileNotFound { path: filename.to_string() },
_ => ReadError { cause, path: filename.to_string() }
})?;
let bytes = &mut vec![];
BufReader::new(file)
.read_to_end(bytes)
.map_err(|cause| ReadError { cause, path: filename.to_string() })?;
let spans: TimeSpans = Self::try_from(bytes.as_slice())
.map_err(|cause| DeserializeError { cause })?;
Ok(spans)
}
pub fn from_sub_file(sub_file: &SubtitleFile) -> Result<Self, SyncError> {
let entries = sub_file.get_subtitle_entries().expect("Unable to read subtitle entries");
let spans = TimeSpans(entries.into_iter()
.map(|subentry| subentry.timespan)
.map(|span: SubTimeSpan| SubTimeSpan::new(min(span.start, span.end), max(span.start, span.end)))
.collect());
Ok(spans)
}
pub fn to_alass_timespans(self: &Self, interval: i64) -> Vec<TimeSpan> {
self.0.iter().cloned()
.map(|timespan| {
TimeSpan::new_safe(
to_alass_timepoint(timespan.start, interval),
to_alass_timepoint(timespan.end, interval),
)
}).collect()
}
}
impl IntoIterator for TimeSpans {
type Item = SubTimeSpan;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> TryInto<Vec<u8>> for &'a TimeSpans {
type Error = io::Error;
fn try_into(self: &'a TimeSpans) -> Result<Vec<u8>, Self::Error> {
let mut bytes = vec![];
bytes.write_u32::<LittleEndian>(self.0.len() as u32)?;
for s in self.0.iter() {
bytes.write_i64::<LittleEndian>(s.start.msecs())?;
bytes.write_i64::<LittleEndian>(s.end.msecs())?;
}
Ok(bytes)
}
}
impl TryFrom<&[u8]> for TimeSpans {
type Error = io::Error;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let mut rdr = Cursor::new(bytes);
let count = rdr.read_u32::<LittleEndian>()?;
let spans: Result<Vec<SubTimeSpan>, io::Error> = (0..count).map(|_| {
let start = rdr.read_i64::<LittleEndian>()?;
let end = rdr.read_i64::<LittleEndian>()?;
Ok(SubTimeSpan {
start: SubTimePoint::from_msecs(start),
end: SubTimePoint::from_msecs(end)
})
}).collect();
Ok(TimeSpans(spans?))
}
}
impl From<&VoiceActivity> for TimeSpans {
fn from(activity: &VoiceActivity) -> TimeSpans {
let timespans: Vec<SubTimeSpan> =
once(&false).chain(activity.data.iter().chain(once(&false)))
.collect::<Vec<&bool>>()
.windows(2)
.enumerate()
.filter_map(|(i, v)| if *v[0] != *v[1] { Some(i as i64) } else { None })
.map(|t| SubTimePoint::from_msecs(t * activity.chunk_millis as i64))
.collect::<Vec<SubTimePoint>>()
.chunks(2)
.map(|s| SubTimeSpan::new(s[0], s[1]))
.collect();
TimeSpans(timespans)
}
}
#[derive(Debug)]
pub enum TimeSpansLoadError {
FileNotFound { path: String },
ReadError { cause: io::Error, path: String },
DeserializeError { cause: io::Error },
}
impl Error for TimeSpansLoadError {}
impl fmt::Display for TimeSpansLoadError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
FileNotFound { path } => write!(f, "Span file does not exist (path='{}')", path),
ReadError { cause, path } => write!(f, "Error reading timespans from disk (msg='{}', path='{}')", cause, path),
DeserializeError { cause } => write!(f, "Error parsing timespans from bytes (msg='{}')", cause)
}
}
}
#[derive(Debug)]
pub enum TimeSpansSaveError {
SerializeError { cause: io::Error },
WriteError { cause: io::Error }
}
impl Error for TimeSpansSaveError {}
impl fmt::Display for TimeSpansSaveError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SerializeError { cause } => write!(f, "Error serializing timespans to bytes! (msg='{}')", cause),
WriteError { cause } => write!(f, "Error saving timespan data to disk! (msg='{}')", cause)
}
}
}