substudy/export/
exporter.rs1use std::{
4 convert::AsRef,
5 default::Default,
6 ffi::OsStr,
7 fmt::Write as fmt_Write,
8 fs,
9 io::Write,
10 path::{Path, PathBuf},
11};
12
13use anyhow::{anyhow, Context as _};
14
15use crate::{
16 align::align_available_files,
17 lang::Lang,
18 srt::{Subtitle, SubtitleFile},
19 time::{Period, ToTimestamp},
20 video::{Extraction, ExtractionSpec, Id3Metadata, Video},
21 Result,
22};
23
24pub fn os_str_to_string(os_str: &OsStr) -> String {
27 os_str.to_string_lossy().into_owned()
28}
29
30pub struct LanguageResources {
32 pub subtitles: SubtitleFile,
34
35 pub language: Option<Lang>,
37}
38
39impl LanguageResources {
40 fn new(subtitles: SubtitleFile) -> LanguageResources {
42 let language = subtitles.detect_language();
43 LanguageResources {
44 subtitles: subtitles,
45 language: language,
46 }
47 }
48}
49
50pub struct Exporter {
53 video: Video,
55
56 foreign: LanguageResources,
58
59 native: Option<LanguageResources>,
61
62 file_stem: String,
64
65 dir: PathBuf,
67
68 extractions: Vec<Extraction>,
71}
72
73impl Exporter {
74 pub fn new(
78 video: Video,
79 foreign_subtitles: SubtitleFile,
80 native_subtitles: Option<SubtitleFile>,
81 label: &str,
82 ) -> Result<Exporter> {
83 let foreign = LanguageResources::new(foreign_subtitles);
84 let native = native_subtitles.map(|subs| LanguageResources::new(subs));
85
86 let file_stem = os_str_to_string(video.file_stem());
93 let dir = Path::new("./").join(format!("{}_{}", &file_stem, label));
94 if fs::metadata(&dir).is_ok() {
95 return Err(anyhow!(
96 "Directory already exists: {}",
97 &dir.to_string_lossy()
98 ));
99 }
100 fs::create_dir_all(&dir)
101 .with_context(|| format!("could not create {}", dir.display()))?;
102
103 Ok(Exporter {
104 video: video,
105 foreign: foreign,
106 native: native,
107 file_stem: file_stem,
108 dir: dir,
109 extractions: vec![],
110 })
111 }
112
113 pub fn file_stem(&self) -> &str {
116 &self.file_stem
117 }
118
119 pub fn title(&self) -> &str {
121 &self.file_stem
122 }
123
124 pub fn video(&self) -> &Video {
126 &self.video
127 }
128
129 pub fn foreign(&self) -> &LanguageResources {
131 &self.foreign
132 }
133
134 pub fn native(&self) -> Option<&LanguageResources> {
136 self.native.as_ref()
137 }
138
139 pub fn align(&self) -> Vec<(Option<Subtitle>, Option<Subtitle>)> {
141 align_available_files(
142 &self.foreign.subtitles,
143 self.native.as_ref().map(|n| &n.subtitles),
144 )
145 }
146
147 fn media_path<T: ToTimestamp>(
150 &self,
151 timestamp: T,
152 lang: Option<Lang>,
153 extension: &str,
154 ) -> PathBuf {
155 let mut file_name =
156 format!("{}_{}", &self.file_stem, timestamp.to_file_timestamp());
157 if let Some(l) = lang {
158 write!(&mut file_name, ".{}", l.as_str()).unwrap();
159 }
160 write!(&mut file_name, ".{}", extension).unwrap();
161 self.dir.join(file_name)
162 }
163
164 pub fn schedule_image_export(&mut self, time: f32) -> String {
167 let path = self.media_path(time, None, "jpg");
168 self.extractions.push(Extraction {
169 path: path.clone(),
170 spec: ExtractionSpec::Image(time),
171 });
172 os_str_to_string(path.file_name().unwrap())
173 }
174
175 pub fn schedule_audio_export(
178 &mut self,
179 lang: Option<Lang>,
180 period: Period,
181 ) -> String {
182 self.schedule_audio_export_ext(lang, period, Default::default())
183 }
184
185 pub fn schedule_audio_export_ext(
189 &mut self,
190 lang: Option<Lang>,
191 period: Period,
192 metadata: Id3Metadata,
193 ) -> String {
194 let path = self.media_path(period, lang, "mp3");
195 let stream = lang.and_then(|l| self.video.audio_for(l));
196 self.extractions.push(Extraction {
197 path: path.clone(),
198 spec: ExtractionSpec::Audio(stream, period, metadata),
199 });
200 os_str_to_string(path.file_name().unwrap())
201 }
202
203 pub fn export_data_file<P>(&self, rel_path: P, data: &[u8]) -> Result<()>
205 where
206 P: AsRef<Path>,
207 {
208 let path = self.dir.join(rel_path.as_ref());
209 let mut f = fs::File::create(&path)
210 .with_context(|| format!("could not open {}", path.display()))?;
211 f.write_all(data)
212 .with_context(|| format!("could not write to {}", path.display()))?;
213 Ok(())
214 }
215
216 pub fn finish_exports(&mut self) -> Result<()> {
218 self.video.extract(&self.extractions)?;
219 Ok(())
220 }
221}