use handlebars::Handlebars;
use rustc_serialize::json::{ToJson, Json};
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::str::from_utf8;
use align::align_available_files;
use err::{err_str, Result};
use srt::SubtitleFile;
use time::Period;
use video::{Extraction, ExtractionSpec, Video};
pub struct ExportRequest {
pub video: Video,
pub foreign_subtitles: SubtitleFile,
pub native_subtitles: Option<SubtitleFile>,
}
#[derive(RustcEncodable)]
struct SubtitleInfo {
index: usize,
image_path: String,
audio_path: String,
foreign_text: Option<String>,
native_text: Option<String>,
}
impl ToJson for SubtitleInfo {
fn to_json(&self) -> Json {
let mut m: BTreeMap<String, Json> = BTreeMap::new();
m.insert("index".to_string(), self.index.to_json());
m.insert("image_path".to_string(), self.image_path.to_json());
m.insert("audio_path".to_string(), self.audio_path.to_json());
m.insert("foreign_text".to_string(), self.foreign_text.to_json());
if let &Some(ref s) = &self.native_text {
m.insert("native_text".to_string(), s.to_json());
}
m.to_json()
}
}
#[derive(RustcEncodable)]
struct ExportInfo {
filename: String,
subtitles: Vec<SubtitleInfo>,
}
impl ToJson for ExportInfo {
fn to_json(&self) -> Json {
let mut m: BTreeMap<String, Json> = BTreeMap::new();
m.insert("filename".to_string(), self.filename.to_json());
m.insert("subtitles".to_string(), self.subtitles.to_json());
m.to_json()
}
}
fn write_all(path: &Path, data: &[u8]) -> Result<()> {
let mut f = try!(fs::File::create(path));
try!(f.write_all(data));
Ok(())
}
pub fn export(request: &ExportRequest) -> Result<()> {
let path_str = |os_str: &OsStr| { os_str.to_string_lossy().into_owned() };
let stem = path_str(request.video.file_stem());
let dir = Path::new("./").join(format!("{}_review", &stem));
if fs::metadata(&dir).is_ok() {
return Err(err_str(format!("Directory already exists: {}",
&dir.to_string_lossy())));
}
try!(fs::create_dir_all(&dir));
let media_path = |index: usize, ext: &str| -> PathBuf {
dir.join(format!("{}_{:03}.{}", stem, index, ext))
};
let mut extractions: Vec<Extraction> = vec!();
let mut bindings = ExportInfo {
filename: path_str(request.video.file_name()),
subtitles: vec!(),
};
let aligned =
align_available_files(&request.foreign_subtitles,
request.native_subtitles.as_ref());
for sub in aligned.iter().enumerate() {
let (i, &(ref foreign, ref native)) = sub;
let index = i + 1;
let period = Period::from_union_opt(
foreign.as_ref().map(|s| s.period),
native.as_ref().map(|s| s.period),
).expect("subtitle pair must not be empty").grow(0.5, 0.5);
let image_path = media_path(index, "jpg");
extractions.push(Extraction {
path: image_path.clone(),
spec: ExtractionSpec::Image(period.midpoint()),
});
let audio_path = media_path(index, "mp3");
extractions.push(Extraction {
path: audio_path.clone(),
spec: ExtractionSpec::Audio(period),
});
bindings.subtitles.push(SubtitleInfo {
index: index,
image_path: path_str(image_path.file_name().unwrap()),
audio_path: path_str(audio_path.file_name().unwrap()),
foreign_text: foreign.as_ref().map(|s| s.plain_text()),
native_text: native.as_ref().map(|s| s.plain_text()),
});
}
try!(write_all(&dir.join("style.css"), &include_bytes!("style.css")[..]));
try!(write_all(&dir.join("play.svg"), &include_bytes!("play.svg")[..]));
let template = try!(from_utf8(include_bytes!("review.html.hbs")));
let mut handlebars = Handlebars::new();
try!(handlebars.register_template_string("review", template.to_owned()));
let html = try!(handlebars.render("review", &bindings));
try!(write_all(&dir.join("index.html"), html.as_bytes()));
try!(request.video.extract(&extractions));
Ok(())
}