Video Gifs #34
@@ -5,13 +5,19 @@ use log::{debug, error, info, trace, warn};
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::io::{Result, Stdout};
|
use std::io::{Result, Stdout};
|
||||||
use std::process::{Output, Stdio};
|
use std::process::{Output, Stdio};
|
||||||
|
use std::time::Instant;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
pub struct Ffmpeg;
|
pub struct Ffmpeg;
|
||||||
|
|
||||||
|
pub enum GifType {
|
||||||
|
Overview,
|
||||||
|
OverviewVideo { duration: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
impl Ffmpeg {
|
impl Ffmpeg {
|
||||||
async fn _generate_playlist(&self, input_file: &str, output_file: &str) -> Result<String> {
|
async fn _generate_playlist(&self, input_file: &str, output_file: &str) -> Result<String> {
|
||||||
let ffmpeg_result: Result<Output> = tokio::process::Command::new("ffmpeg")
|
let ffmpeg_result: Result<Output> = Command::new("ffmpeg")
|
||||||
.arg("-i")
|
.arg("-i")
|
||||||
.arg(input_file)
|
.arg(input_file)
|
||||||
.arg("-c:v")
|
.arg("-c:v")
|
||||||
@@ -41,7 +47,7 @@ impl Ffmpeg {
|
|||||||
ffmpeg_result.map(|_| output_file.to_string())
|
ffmpeg_result.map(|_| output_file.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_video_duration(&self, input_file: &str) -> Result<String> {
|
async fn get_video_duration(&self, input_file: &str) -> Result<u32> {
|
||||||
Command::new("ffprobe")
|
Command::new("ffprobe")
|
||||||
.args(["-i", input_file])
|
.args(["-i", input_file])
|
||||||
.args(["-show_entries", "format=duration"])
|
.args(["-show_entries", "format=duration"])
|
||||||
@@ -51,16 +57,24 @@ impl Ffmpeg {
|
|||||||
.await
|
.await
|
||||||
.map(|out| String::from_utf8_lossy(&out.stdout).trim().to_string())
|
.map(|out| String::from_utf8_lossy(&out.stdout).trim().to_string())
|
||||||
.inspect(|duration| debug!("Found video duration: {:?}", duration))
|
.inspect(|duration| debug!("Found video duration: {:?}", duration))
|
||||||
|
.and_then(|duration| {
|
||||||
|
duration
|
||||||
|
.parse::<f32>()
|
||||||
|
.map(|duration| duration as u32)
|
||||||
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
|
||||||
|
})
|
||||||
|
.inspect(|duration| debug!("Found video duration: {:?}", duration))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate_video_gif(
|
pub async fn generate_video_gif(
|
||||||
&self,
|
&self,
|
||||||
input_file: &str,
|
input_file: &str,
|
||||||
output_file: &str,
|
output_file: &str,
|
||||||
duration_sec: u32,
|
gif_type: GifType,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
info!("Creating gif for: '{}'", input_file);
|
info!("Creating gif for: '{}'", input_file);
|
||||||
|
|
||||||
|
match gif_type {
|
||||||
|
GifType::Overview => {
|
||||||
let temp_dir = tempfile::tempdir()?;
|
let temp_dir = tempfile::tempdir()?;
|
||||||
let temp_path = temp_dir
|
let temp_path = temp_dir
|
||||||
.path()
|
.path()
|
||||||
@@ -110,7 +124,34 @@ impl Ffmpeg {
|
|||||||
error!("Error creating gif for '{}': {:?}", input_file, e);
|
error!("Error creating gif for '{}': {:?}", input_file, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
GifType::OverviewVideo { duration } => {
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
match self
|
||||||
|
.get_video_duration(input_file)
|
||||||
|
.and_then(|input_duration| {
|
||||||
|
Command::new("ffmpeg")
|
||||||
|
.args(["-i", input_file])
|
||||||
|
.args([
|
||||||
|
"-vf",
|
||||||
|
// Grab 1 second of frames equally spaced to create a 'duration' second long video scaled to 720px on longest side
|
||||||
|
&format!(
|
||||||
|
"select='lt(mod(t,{}),1)',setpts=N/FRAME_RATE/TB,scale='if(gt(iw,ih),720,-2)':'if(gt(ih,iw),720,-2)",
|
||||||
|
input_duration / duration
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.arg("-an")
|
||||||
|
.arg(output_file)
|
||||||
|
.status()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(out) => info!("Finished clip '{}' with code {:?} in {:?}", output_file, out.code(), start.elapsed()),
|
||||||
|
Err(e) => error!("Error creating video overview: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(output_file.to_string())
|
Ok(output_file.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,10 +167,11 @@ impl Ffmpeg {
|
|||||||
"[0:v]scale=480:-1:flags=lanczos,crop='min(in_w,in_h)':'min(in_w,in_h)':(in_w-out_w)/2:(in_h-out_h)/2, paletteuse",
|
"[0:v]scale=480:-1:flags=lanczos,crop='min(in_w,in_h)':'min(in_w,in_h)':(in_w-out_w)/2:(in_h-out_h)/2, paletteuse",
|
||||||
])
|
])
|
||||||
.args(["-loop", "0"]) // loop forever
|
.args(["-loop", "0"]) // loop forever
|
||||||
|
.args(["-final_delay", "75"])
|
||||||
.arg(output_file)
|
.arg(output_file)
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.status()
|
.status()
|
||||||
.map_ok(|out| out.code().unwrap_or( -1))
|
.map_ok(|out| out.code().unwrap_or(-1))
|
||||||
.inspect_ok(|output| debug!("ffmpeg gif create exit code: {:?}", output))
|
.inspect_ok(|output| debug!("ffmpeg gif create exit code: {:?}", output))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user