diff --git a/.gitignore b/.gitignore
index ea8c4bf..5db0552 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
/target
+/vid*
+/aud*
diff --git a/Cargo.lock b/Cargo.lock
index 8dcce77..96dad40 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -193,6 +193,12 @@ dependencies = [
"unicode-width",
]
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
[[package]]
name = "command_attr"
version = "0.4.1"
@@ -542,6 +548,16 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "gif"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
[[package]]
name = "h2"
version = "0.3.14"
@@ -1051,6 +1067,18 @@ dependencies = [
"pnet_base",
]
+[[package]]
+name = "png"
+version = "0.17.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "flate2",
+ "miniz_oxide",
+]
+
[[package]]
name = "poly1305"
version = "0.7.2"
@@ -1082,7 +1110,9 @@ name = "projbotv3"
version = "0.1.0"
dependencies = [
"form-data-builder",
+ "gif",
"openssl",
+ "png",
"serenity",
"songbird",
"tokio",
@@ -2003,6 +2033,12 @@ dependencies = [
"webpki",
]
+[[package]]
+name = "weezl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
+
[[package]]
name = "winapi"
version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index 8c7691f..cc0a0a2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,3 +11,5 @@ serenity = {version="0.11.5", features=["builder", "cache", "client", "framework
songbird = {version="0.3.0", features=["driver", "serenity-rustls"]}
openssl = "0.10.42"
form-data-builder = "1.0.1"
+png = "0.17.6"
+gif = "0.11.4"
diff --git a/src/main.rs b/src/main.rs
index eb58e83..258a00d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,15 +1,19 @@
use std::{
env,
ffi::OsStr,
- fs::{self, OpenOptions},
+ fs::{self, File, OpenOptions},
io::{Cursor, Read, Write},
net::{Shutdown, TcpStream},
- sync::Arc,
- time::{Duration, SystemTime},
+ path::Path,
+ process::{self, Stdio},
+ sync::{Arc, Mutex},
+ time::{Duration, SystemTime}, thread,
};
use form_data_builder::FormData;
+use gif::Encoder;
use openssl::ssl::{Ssl, SslContext, SslMethod, SslStream};
+use png::Decoder;
use serenity::{
async_trait,
framework::StandardFramework,
@@ -134,6 +138,8 @@ impl Frame {
}
async fn send_frames(message: Message, ctx: Context) {
+ use tokio::sync::Mutex;
+
let mut v: Vec = Vec::new();
let dir = fs::read_dir("vid_encoded").expect("unable to read dir");
let dir: Vec<_> = dir.collect();
@@ -161,7 +167,6 @@ async fn send_frames(message: Message, ctx: Context) {
.expect("voice: unable to initialize songbird");
let c0: Arc>> = Arc::new(Mutex::new(None));
let c1 = c0.clone();
- //thread::spawn(move || {tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async move {
tokio::spawn(async move {
let message = message;
let ctx = ctx;
@@ -176,8 +181,6 @@ async fn send_frames(message: Message, ctx: Context) {
)
.await
.expect("discord: unable to send");
- println!("starting to send in {}@{}", n.id.0, message.channel_id.0);
- //thread::spawn(move || {tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async move {
tokio::spawn(async move {
let sa = unix_millis();
println!("voice: init");
@@ -185,6 +188,7 @@ async fn send_frames(message: Message, ctx: Context) {
.create_channel(http, |c| c.name("ProjBotV3-Sound").kind(ChannelType::Voice))
.await
.expect("voice: unable to create channel");
+ let api_time = unix_millis() - sa;
*c0.lock().await = Some(channel.id);
println!("voice: joining");
let (handler, err) = songbird.join(guild_id, channel.id).await;
@@ -192,7 +196,7 @@ async fn send_frames(message: Message, ctx: Context) {
panic!("voice: error {e}");
}
println!("voice: loading");
- let handle = handler.lock().await.play_source(
+ let handle = handler.lock().await.play_only_source(
songbird::ffmpeg("aud_encoded")
.await
.expect("voice: unable to load"),
@@ -200,12 +204,11 @@ async fn send_frames(message: Message, ctx: Context) {
handle.make_playable().unwrap();
handle.pause().expect("voice: unable to pause");
handle.set_volume(1.0).unwrap();
- println!("voice: waiting for video");
- tokio::time::sleep(Duration::from_millis(5000 - (unix_millis() - sa))).await;
+ println!("voice: waiting for video [api_time={api_time}]");
+ tokio::time::sleep(Duration::from_millis(5000 - (unix_millis() - sa) + (api_time * 2))).await;
println!("voice: playing");
handle.play().expect("voice: unable to play");
- println!("{:?}", handle.get_info().await);
- }); //});
+ });
let mut sa = unix_millis();
let mut to_compensate_for = 0;
while let Some(mut frame) = v.next() {
@@ -293,6 +296,7 @@ async fn send_frames(message: Message, ctx: Context) {
println!("vid: completing");
frame.complete_send();
}
+ tokio::time::sleep(Duration::from_millis(5000)).await;
n.delete(&ctx.http)
.await
.expect("discord: unable to delete message");
@@ -301,7 +305,7 @@ async fn send_frames(message: Message, ctx: Context) {
.await
.expect("discord: unable to delete voice channel");
}
- }); //});
+ });
}
struct Handler;
@@ -310,12 +314,10 @@ struct Handler;
impl EventHandler for Handler {
async fn message(&self, ctx: Context, message: Message) {
if message.guild_id == None {
- println!("DM");
return;
}
if message.content == "!play" {
- println!("hi");
send_frames(message, ctx).await;
}
}
@@ -323,6 +325,120 @@ impl EventHandler for Handler {
#[tokio::main]
async fn main() {
+ if !Path::new("vid_encoded/").is_dir() {
+ println!("encode: encoding video...");
+ fs::create_dir("vid").expect("encode: unable to modify files");
+ 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");
+
+ fs::create_dir("vid_encoded").expect("encode: unable to modify files");
+ let dir: Vec<_> = fs::read_dir("vid")
+ .expect("encode: unable to read files")
+ .collect();
+ let dir = dir.len();
+ 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) {
+ let running = running.clone();
+ thread::spawn(move || {
+ *running.lock().unwrap() += 1;
+ 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"),
+ );
+ 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");
+ println!("encode: encoding {n}...");
+ for i in (n * (25 * 5) + 1)..=dir {
+ let decoder = Decoder::new(
+ File::open(format!("vid/{}.png", i))
+ .expect(format!("encode: unable to read vid/{}.png", i).as_str()),
+ );
+ let mut reader = decoder
+ .read_info()
+ .expect(format!("encode: invalid ffmpeg output in vid/{}.png", i).as_str());
+ let mut buf: Vec = vec![0; reader.output_buffer_size()];
+ let info = reader
+ .next_frame(&mut buf)
+ .expect(format!("encode: invalid ffmpeg output in vid/{}.png", i).as_str());
+ let bytes = &mut buf[..info.buffer_size()];
+ let mut frame = gif::Frame::from_rgb(240, 180, &mut *bytes);
+ frame.delay = 4;
+ encoder
+ .as_mut()
+ .unwrap()
+ .write_frame(&frame)
+ .expect("encode: unable to encode frame to gif");
+ if i / (25 * 5) != n {
+ break;
+ }
+ }
+ *running.lock().unwrap() -= 1;
+ println!("encode: encoded {n}");
+ });
+ thread::sleep(Duration::from_millis(5000));
+ }
+ while *running.lock().unwrap() != 0 {
+ tokio::time::sleep(Duration::from_millis(100)).await;
+ }
+ println!("encode: done");
+ }
+
let framework = StandardFramework::new().configure(|c| c.prefix("!"));
let mut client = Client::builder(
env::args()