shepherd/
local.rs

1//! Functions for operations on the local host.
2
3use std::{
4    fs,
5    path::Path,
6    process::Command,
7    sync::atomic::{AtomicBool, Ordering},
8    sync::Arc,
9    time::Duration,
10};
11
12use super::Result;
13
14/// Uses `ffmpeg` to locally extract and encode the audio.
15pub fn extract_audio(
16    input: &Path,
17    output: &Path,
18    running: &Arc<AtomicBool>,
19) -> Result<()> {
20    // Convert input and output to &str
21    let input = input.to_str().ok_or("Input invalid Unicode")?;
22    let output = output.to_str().ok_or("Output invalid Unicode")?;
23    // Do the extraction
24    let output = Command::new("ffmpeg")
25        .args(&[
26            "-y", "-i", input, "-vn", "-c:a", "aac", "-b:a", "192k", output,
27        ])
28        .output()?;
29    if !output.status.success() && running.load(Ordering::SeqCst) {
30        return Err("Failed extracting audio".into());
31    }
32
33    Ok(())
34}
35
36/// Uses `ffmpeg` to locally split the video into chunks.
37pub fn split_video(
38    input: &Path,
39    output_dir: &Path,
40    segment_length: Duration,
41    running: &Arc<AtomicBool>,
42) -> Result<()> {
43    // Isolate file extension, since we want the chunks to have the same
44    let extension = input
45        .extension()
46        .ok_or("Unable to find extension")?
47        .to_str()
48        .ok_or("Unable to convert OsString extension")?;
49    // Convert input and output to &str
50    let input = input.to_str().ok_or("Input invalid Unicode")?;
51    let mut output_dir = output_dir.to_path_buf();
52    output_dir.push(format!("chunk_%03d.{}", extension));
53    let output = output_dir.to_str().ok_or("Output invalid Unicode")?;
54    // Do the chunking
55    let output = Command::new("ffmpeg")
56        .args(&[
57            "-y",
58            "-i",
59            input,
60            "-an",
61            "-c",
62            "copy",
63            "-f",
64            "segment",
65            "-segment_time",
66            &segment_length.as_secs().to_string(),
67            output,
68        ])
69        .output()?;
70    if !output.status.success() && running.load(Ordering::SeqCst) {
71        return Err("Failed splitting video".into());
72    }
73
74    Ok(())
75}
76
77/// Uses `ffmpeg` to locally combine the encoded chunks and audio.
78pub fn combine(
79    encoded_dir: &Path,
80    audio: &Path,
81    output: &Path,
82    running: &Arc<AtomicBool>,
83) -> Result<()> {
84    // Create list of encoded chunks
85    let mut chunks = fs::read_dir(&encoded_dir)?
86        .map(|res| res.and_then(|readdir| Ok(readdir.path())))
87        .map(|res| res.map_err(|e| e.into()))
88        .map(|res| res.and_then(|path| Ok(path.into_os_string())))
89        .map(|res| {
90            res.and_then(|os_string| {
91                os_string
92                    .into_string()
93                    .map_err(|_| "Failed OsString conversion".into())
94            })
95        })
96        .map(|res| res.and_then(|file| Ok(format!("file '{}'\n", file))))
97        .collect::<Result<Vec<String>>>()?;
98    // Sort them so we have the right order
99    chunks.sort();
100    // And finally join them
101    let chunks = chunks.join("");
102    // Now write that to a file
103    let mut file_list = encoded_dir.to_path_buf();
104    file_list.push("files.txt");
105    fs::write(&file_list, chunks)?;
106
107    // Convert paths to &str
108    let audio = audio.to_str().ok_or("Audio invalid Unicode")?;
109    let file_list = file_list.to_str().ok_or("File list invalid Unicode")?;
110    let output = output.to_str().ok_or("Output invalid Unicode")?;
111    // Combine everything
112    let output = Command::new("ffmpeg")
113        .args(&[
114            "-y",
115            "-f",
116            "concat",
117            "-safe",
118            "0",
119            "-i",
120            file_list,
121            "-i",
122            audio,
123            "-c",
124            "copy",
125            "-movflags",
126            "+faststart",
127            output,
128        ])
129        .output()?;
130    if !output.status.success() && running.load(Ordering::SeqCst) {
131        return Err("Failed combining video".into());
132    }
133
134    Ok(())
135}