export conversion from mp4 to gifs to another file
This commit is contained in:
parent
1158a31a5f
commit
00421ef00d
3 changed files with 161 additions and 151 deletions
149
src/convert.rs
Normal file
149
src/convert.rs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
use std::{
|
||||||
|
fs::{self, File},
|
||||||
|
process::{self, Stdio},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use gif::Encoder;
|
||||||
|
use png::Decoder;
|
||||||
|
|
||||||
|
pub async fn convert() {
|
||||||
|
println!("encode: encoding video...");
|
||||||
|
if fs::create_dir("vid").is_ok() {
|
||||||
|
// We're using ffmpeg commands because ffmpeg's api is a hunk of junk
|
||||||
|
let mut command = process::Command::new("ffmpeg")
|
||||||
|
.args([
|
||||||
|
"-i",
|
||||||
|
"vid.mp4",
|
||||||
|
"-vf",
|
||||||
|
"fps=fps=25",
|
||||||
|
"-deadline",
|
||||||
|
"realtime",
|
||||||
|
"vid_25fps.mp4",
|
||||||
|
])
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.expect("encode: unable to find or run ffmpeg");
|
||||||
|
command.wait().expect("encode: ffmpeg failed: mp4->mp4");
|
||||||
|
let mut command = process::Command::new("ffmpeg")
|
||||||
|
.args([
|
||||||
|
"-i",
|
||||||
|
"vid_25fps.mp4",
|
||||||
|
"-vf",
|
||||||
|
"scale=240:180,setsar=1:1",
|
||||||
|
"-deadline",
|
||||||
|
"realtime",
|
||||||
|
"vid/%0d.png",
|
||||||
|
])
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.expect("encode: unable to find or run ffmpeg");
|
||||||
|
command.wait().expect("encode: ffmpeg failed: mp4->png");
|
||||||
|
fs::remove_file("vid_25fps.mp4").expect("encode: rm vid_25fps.mp4 failed");
|
||||||
|
let mut command = process::Command::new("ffmpeg")
|
||||||
|
.args(["-i", "vid.mp4", "-deadline", "realtime", "aud.opus"])
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.expect("encode: unable to find or run ffmpeg");
|
||||||
|
command.wait().expect("encode: ffmpeg failed: mp4->opus");
|
||||||
|
fs::rename("aud.opus", "aud_encoded")
|
||||||
|
.expect("encode: unable to move aud.opus to aud_encoded");
|
||||||
|
}
|
||||||
|
// ffmpeg is now done converting vid.mp4 into vid/*.png
|
||||||
|
|
||||||
|
// Create vid_encoded and encode gifs into it
|
||||||
|
let _ = fs::create_dir("vid_encoded");
|
||||||
|
let dir = fs::read_dir("vid")
|
||||||
|
.expect("encode: unable to read files")
|
||||||
|
.count();
|
||||||
|
let running = Arc::new(Mutex::new(0));
|
||||||
|
println!("encode: encoding gifs...");
|
||||||
|
for n in 0..((dir as f32 / (25.0 * 5.0)).ceil() as usize) {
|
||||||
|
*running.lock().unwrap() += 1;
|
||||||
|
{
|
||||||
|
let running = running.clone();
|
||||||
|
// This thread will not interfere with tokio because it doesn't use anything async
|
||||||
|
// and will exit before anything important is done in tokio.
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut image = File::create(format!("vid_encoded/{n}"))
|
||||||
|
.expect("encode: unable to create gif file");
|
||||||
|
let mut encoder = Some(
|
||||||
|
Encoder::new(&mut image, 240, 180, &[]).expect("encode: unable to create gif"),
|
||||||
|
);
|
||||||
|
// Write the gif control bytes
|
||||||
|
encoder
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.write_extension(gif::ExtensionData::new_control_ext(
|
||||||
|
4,
|
||||||
|
gif::DisposalMethod::Any,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
.expect("encode: unable to write extension data");
|
||||||
|
encoder
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.set_repeat(gif::Repeat::Finite(0))
|
||||||
|
.expect("encode: unable to set repeat");
|
||||||
|
// Encode frames into gif
|
||||||
|
println!("encode: encoding {n}...");
|
||||||
|
for i in (n * (25 * 5))..dir {
|
||||||
|
// n number of previously encoded gifs * 25 frames per second * 5 seconds
|
||||||
|
{
|
||||||
|
let i = i + 1; // because ffmpeg starts counting at 1 :p
|
||||||
|
|
||||||
|
// Decode frame
|
||||||
|
let decoder = Decoder::new(
|
||||||
|
File::open(format!("vid/{i}.png"))
|
||||||
|
.expect(format!("encode: unable to read vid/{i}.png").as_str()),
|
||||||
|
);
|
||||||
|
let mut reader = decoder.read_info().expect(
|
||||||
|
format!("encode: invalid ffmpeg output in vid/{i}.png").as_str(),
|
||||||
|
);
|
||||||
|
let mut buf: Vec<u8> = vec![0; reader.output_buffer_size()];
|
||||||
|
let info = reader.next_frame(&mut buf).expect(
|
||||||
|
format!("encode: invalid ffmpeg output in vid/{i}.png").as_str(),
|
||||||
|
);
|
||||||
|
let bytes = &mut buf[..info.buffer_size()];
|
||||||
|
// Encode frame
|
||||||
|
let mut frame = gif::Frame::from_rgb(240, 180, bytes);
|
||||||
|
// The gif crate is a little weird with extension data, it writes a
|
||||||
|
// block for each frame, so we have to remind it of what we want again
|
||||||
|
// for each frame
|
||||||
|
frame.delay = 4;
|
||||||
|
// Add to gif
|
||||||
|
encoder
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.write_frame(&frame)
|
||||||
|
.expect("encode: unable to encode frame to gif");
|
||||||
|
}
|
||||||
|
// We don't want to encode something that is supposed to go into the next frame
|
||||||
|
if i / (25 * 5) != n {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*running.lock().unwrap() -= 1;
|
||||||
|
println!("encode: encoded {n}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Always have 6 running, but no more
|
||||||
|
while *running.lock().unwrap() >= 6 {
|
||||||
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
|
}
|
||||||
|
while *running.lock().unwrap() != 0 {
|
||||||
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
println!("encode: done");
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ impl Frame {
|
||||||
"attachments": [
|
"attachments": [
|
||||||
{
|
{
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"filename": "projbot3.gif"
|
"filename": "ProjBotV3.gif"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -55,7 +55,7 @@ impl Frame {
|
||||||
form.write_file(
|
form.write_file(
|
||||||
"files[0]",
|
"files[0]",
|
||||||
Cursor::new(self.bytes.as_slice()),
|
Cursor::new(self.bytes.as_slice()),
|
||||||
Some(OsStr::new("projbot3.gif")),
|
Some(OsStr::new("ProjBotV3.gif")),
|
||||||
"image/gif",
|
"image/gif",
|
||||||
)
|
)
|
||||||
.expect("form: attachment failed");
|
.expect("form: attachment failed");
|
||||||
|
@ -73,7 +73,7 @@ impl Frame {
|
||||||
.expect("api: write failed");
|
.expect("api: write failed");
|
||||||
stream
|
stream
|
||||||
.write_all(
|
.write_all(
|
||||||
"Host: discord.com\nUser-Agent: projbot3 image uploader (tudbut@tudbut.de)\n"
|
"Host: discord.com\nUser-Agent: ProjBotV3 image uploader (tudbut@tudbut.de)\n"
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)
|
)
|
||||||
.expect("api: write failed");
|
.expect("api: write failed");
|
||||||
|
|
157
src/main.rs
157
src/main.rs
|
@ -1,11 +1,10 @@
|
||||||
#![allow(clippy::expect_fun_call)] // I don't see a reason for this warn
|
#![allow(clippy::expect_fun_call)] // I don't see a reason for this warn
|
||||||
|
|
||||||
|
mod convert;
|
||||||
mod frame;
|
mod frame;
|
||||||
|
|
||||||
use crate::frame::Frame;
|
use crate::convert::*;
|
||||||
use gif::Encoder;
|
use crate::frame::*;
|
||||||
use png::Decoder;
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
async_trait,
|
async_trait,
|
||||||
framework::StandardFramework,
|
framework::StandardFramework,
|
||||||
|
@ -17,18 +16,15 @@ use serenity::{
|
||||||
use songbird::SerenityInit;
|
use songbird::SerenityInit;
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
fs::{self, File, OpenOptions},
|
fs::{self, OpenOptions},
|
||||||
io::Read,
|
io::Read,
|
||||||
path::Path,
|
path::Path,
|
||||||
process::{self, Stdio},
|
sync::Arc,
|
||||||
sync::{Arc, Mutex},
|
|
||||||
thread,
|
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
async fn send_video(message: Message, ctx: Context) {
|
async fn send_video(message: Message, ctx: Context) {
|
||||||
use tokio::sync::Mutex;
|
|
||||||
|
|
||||||
// Read all frames from vid_encoded
|
// Read all frames from vid_encoded
|
||||||
let mut v: Vec<Frame> = Vec::new();
|
let mut v: Vec<Frame> = Vec::new();
|
||||||
let dir = fs::read_dir("vid_encoded")
|
let dir = fs::read_dir("vid_encoded")
|
||||||
|
@ -103,7 +99,7 @@ async fn send_video(message: Message, ctx: Context) {
|
||||||
* str::parse::<i64>(
|
* str::parse::<i64>(
|
||||||
env::var("PROJBOTV3_API_TIME_FACTOR")
|
env::var("PROJBOTV3_API_TIME_FACTOR")
|
||||||
.unwrap_or_else(|_| "3".into())
|
.unwrap_or_else(|_| "3".into())
|
||||||
.as_str()
|
.as_str(),
|
||||||
)
|
)
|
||||||
.unwrap()) as u64,
|
.unwrap()) as u64,
|
||||||
))
|
))
|
||||||
|
@ -235,6 +231,7 @@ async fn send_video(message: Message, ctx: Context) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unit struct used as event handler
|
||||||
struct Handler;
|
struct Handler;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -254,143 +251,7 @@ impl EventHandler for Handler {
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// If vid_encoded doesn't exist, convert vid.mp4 into vid_encoded
|
// If vid_encoded doesn't exist, convert vid.mp4 into vid_encoded
|
||||||
if !Path::new("vid_encoded/").is_dir() {
|
if !Path::new("vid_encoded/").is_dir() {
|
||||||
println!("encode: encoding video...");
|
convert().await;
|
||||||
if fs::create_dir("vid").is_ok() {
|
|
||||||
// We're using ffmpeg commands because ffmpeg's api is a hunk of junk
|
|
||||||
let mut command = process::Command::new("ffmpeg")
|
|
||||||
.args([
|
|
||||||
"-i",
|
|
||||||
"vid.mp4",
|
|
||||||
"-vf",
|
|
||||||
"fps=fps=25",
|
|
||||||
"-deadline",
|
|
||||||
"realtime",
|
|
||||||
"vid_25fps.mp4",
|
|
||||||
])
|
|
||||||
.stdin(Stdio::inherit())
|
|
||||||
.stdout(Stdio::inherit())
|
|
||||||
.stderr(Stdio::inherit())
|
|
||||||
.spawn()
|
|
||||||
.expect("encode: unable to find or run ffmpeg");
|
|
||||||
command.wait().expect("encode: ffmpeg failed: mp4->mp4");
|
|
||||||
let mut command = process::Command::new("ffmpeg")
|
|
||||||
.args([
|
|
||||||
"-i",
|
|
||||||
"vid_25fps.mp4",
|
|
||||||
"-vf",
|
|
||||||
"scale=240:180,setsar=1:1",
|
|
||||||
"-deadline",
|
|
||||||
"realtime",
|
|
||||||
"vid/%0d.png",
|
|
||||||
])
|
|
||||||
.stdin(Stdio::inherit())
|
|
||||||
.stdout(Stdio::inherit())
|
|
||||||
.stderr(Stdio::inherit())
|
|
||||||
.spawn()
|
|
||||||
.expect("encode: unable to find or run ffmpeg");
|
|
||||||
command.wait().expect("encode: ffmpeg failed: mp4->png");
|
|
||||||
fs::remove_file("vid_25fps.mp4").expect("encode: rm vid_25fps.mp4 failed");
|
|
||||||
let mut command = process::Command::new("ffmpeg")
|
|
||||||
.args(["-i", "vid.mp4", "-deadline", "realtime", "aud.opus"])
|
|
||||||
.stdin(Stdio::inherit())
|
|
||||||
.stdout(Stdio::inherit())
|
|
||||||
.stderr(Stdio::inherit())
|
|
||||||
.spawn()
|
|
||||||
.expect("encode: unable to find or run ffmpeg");
|
|
||||||
command.wait().expect("encode: ffmpeg failed: mp4->opus");
|
|
||||||
fs::rename("aud.opus", "aud_encoded")
|
|
||||||
.expect("encode: unable to move aud.opus to aud_encoded");
|
|
||||||
}
|
|
||||||
// ffmpeg is now done converting vid.mp4 into vid/*.png
|
|
||||||
|
|
||||||
// Create vid_encoded and encode gifs into it
|
|
||||||
let _ = fs::create_dir("vid_encoded");
|
|
||||||
let dir = fs::read_dir("vid")
|
|
||||||
.expect("encode: unable to read files")
|
|
||||||
.count();
|
|
||||||
let running = Arc::new(Mutex::new(0));
|
|
||||||
println!("encode: encoding gifs...");
|
|
||||||
for n in 0..((dir as f32 / (25.0 * 5.0)).ceil() as usize) {
|
|
||||||
*running.lock().unwrap() += 1;
|
|
||||||
{
|
|
||||||
let running = running.clone();
|
|
||||||
// This thread will not interfere with tokio because it doesn't use anything async
|
|
||||||
// and will exit before anything important is done in tokio.
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut image = File::create(format!("vid_encoded/{n}"))
|
|
||||||
.expect("encode: unable to create gif file");
|
|
||||||
let mut encoder = Some(
|
|
||||||
Encoder::new(&mut image, 240, 180, &[])
|
|
||||||
.expect("encode: unable to create gif"),
|
|
||||||
);
|
|
||||||
// Write the gif control bytes
|
|
||||||
encoder
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.write_extension(gif::ExtensionData::new_control_ext(
|
|
||||||
4,
|
|
||||||
gif::DisposalMethod::Any,
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
.expect("encode: unable to write extension data");
|
|
||||||
encoder
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.set_repeat(gif::Repeat::Finite(0))
|
|
||||||
.expect("encode: unable to set repeat");
|
|
||||||
// Encode frames into gif
|
|
||||||
println!("encode: encoding {n}...");
|
|
||||||
for i in (n * (25 * 5))..dir {
|
|
||||||
// n number of previously encoded gifs * 25 frames per second * 5 seconds
|
|
||||||
{
|
|
||||||
let i = i + 1; // because ffmpeg starts counting at 1 :p
|
|
||||||
|
|
||||||
// Decode frame
|
|
||||||
let decoder = Decoder::new(
|
|
||||||
File::open(format!("vid/{i}.png"))
|
|
||||||
.expect(format!("encode: unable to read vid/{i}.png").as_str()),
|
|
||||||
);
|
|
||||||
let mut reader = decoder.read_info().expect(
|
|
||||||
format!("encode: invalid ffmpeg output in vid/{i}.png").as_str(),
|
|
||||||
);
|
|
||||||
let mut buf: Vec<u8> = vec![0; reader.output_buffer_size()];
|
|
||||||
let info = reader.next_frame(&mut buf).expect(
|
|
||||||
format!("encode: invalid ffmpeg output in vid/{i}.png").as_str(),
|
|
||||||
);
|
|
||||||
let bytes = &mut buf[..info.buffer_size()];
|
|
||||||
// Encode frame
|
|
||||||
let mut frame = gif::Frame::from_rgb(240, 180, bytes);
|
|
||||||
// The gif crate is a little weird with extension data, it writes a
|
|
||||||
// block for each frame, so we have to remind it of what we want again
|
|
||||||
// for each frame
|
|
||||||
frame.delay = 4;
|
|
||||||
// Add to gif
|
|
||||||
encoder
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.write_frame(&frame)
|
|
||||||
.expect("encode: unable to encode frame to gif");
|
|
||||||
}
|
|
||||||
// We don't want to encode something that is supposed to go into the next frame
|
|
||||||
if i / (25 * 5) != n {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*running.lock().unwrap() -= 1;
|
|
||||||
println!("encode: encoded {n}");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Always have 6 running, but no more
|
|
||||||
while *running.lock().unwrap() >= 6 {
|
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
||||||
}
|
|
||||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
||||||
}
|
|
||||||
while *running.lock().unwrap() != 0 {
|
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
||||||
}
|
|
||||||
println!("encode: done");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the discord bot
|
// Start the discord bot
|
||||||
|
|
Loading…
Add table
Reference in a new issue