use anyhow::{bail, Result};
use clap::{Args, ValueEnum};
use std::{
fs,
fs::File,
io::{BufReader, Read, Write},
process::Command,
};
#[derive(Args, Clone, Debug)]
pub struct Merge {
#[arg(required = true)]
files: Vec<String>,
#[arg(short, long, required = true)]
output: String,
#[arg(short, long, value_enum, default_value_t = MergeType::Binary)]
_type: MergeType,
}
#[derive(Debug, Clone, ValueEnum)]
enum MergeType {
Binary,
Ffmpeg,
}
impl Merge {
pub fn execute(self) -> Result<()> {
let mut files = vec![];
for pattern in &self.files {
for file in glob::glob(pattern)? {
files.push(file?);
}
}
if 1 >= files.len() {
bail!("At least 2 files are required to merge together.")
}
match self._type {
MergeType::Binary => {
let mut output = File::create(self.output)?;
let buffer_size = 1024 * 1024 * 2;
for file in files {
let file_size = fs::metadata(&file)?.len();
if buffer_size >= file_size {
let buf = fs::read(file)?;
output.write_all(&buf)?;
} else {
let file = File::open(file)?;
let mut reader = BufReader::new(file);
loop {
let mut buf = vec![];
reader.by_ref().take(buffer_size).read_to_end(&mut buf)?;
if buf.is_empty() {
break;
}
output.write_all(&buf)?;
}
}
}
}
MergeType::Ffmpeg => {
let concat_file = "vsd-ffmpeg-concat.txt";
let mut concat = File::create(concat_file)?;
for file in files {
let file = file.to_string_lossy();
if file != self.output {
concat.write_fmt(format_args!("file '{file}'\n"))?;
}
}
let status = Command::new("ffmpeg")
.args([
"-hide_banner",
"-y",
"-f",
"concat",
"-i",
concat_file,
"-c",
"copy",
&self.output,
])
.spawn()?
.wait()?;
if !status.success() {
bail!("ffmpeg exited with code {}.", status.code().unwrap_or(1))
}
fs::remove_file(concat_file)?;
}
}
Ok(())
}
}