Add audio transcode functions

This commit is contained in:
Zoe Roux 2023-05-01 00:53:58 +09:00
parent 0b2d8a7e2e
commit a5fc5b3753
No known key found for this signature in database

View File

@ -3,6 +3,8 @@ use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use serde::Serialize; use serde::Serialize;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hasher, Hash};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Stdio; use std::process::Stdio;
use std::slice::Iter; use std::slice::Iter;
@ -10,10 +12,12 @@ use std::str::FromStr;
use std::sync::RwLock; use std::sync::RwLock;
use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::{Child, Command}; use tokio::process::{Child, Command};
use tokio::sync::watch::{self, Receiver}; use tokio::sync::watch;
use crate::utils::Signalable; use crate::utils::Signalable;
const SEGMENT_TIME: u32 = 10;
#[derive(PartialEq, Eq, Serialize, Display)] #[derive(PartialEq, Eq, Serialize, Display)]
pub enum Quality { pub enum Quality {
#[display(fmt = "240p")] #[display(fmt = "240p")]
@ -119,6 +123,18 @@ impl FromStr for Quality {
} }
} }
fn get_transcode_audio_args(audio_idx: u32) -> Vec<String> {
// TODO: Support multy audio qualities.
return vec![
"-map".to_string(),
format!("0:a:{}", audio_idx),
"-c:a".to_string(),
"aac".to_string(),
"-b:a".to_string(),
"128k".to_string(),
];
}
fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec<String> { fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec<String> {
if *quality == Quality::Original { if *quality == Quality::Original {
return vec!["-map", "0:v:0", "-c:v", "copy"] return vec!["-map", "0:v:0", "-c:v", "copy"]
@ -156,8 +172,23 @@ fn get_transcode_video_quality_args(quality: &Quality, segment_time: u32) -> Vec
.collect() .collect()
} }
// TODO: Add audios streams (and transcode them only when necesarry) async fn transcode_audio(path: String, audio: u32) -> TranscodeInfo {
async fn start_transcode(path: String, quality: Quality, start_time: u32) -> TranscodeInfo { let mut hasher = DefaultHasher::new();
path.hash(&mut hasher);
audio.hash(&mut hasher);
let hash = hasher.finish();
let child = start_transcode(
&path,
&format!("/cache/{hash}"),
get_transcode_audio_args(audio),
0,
)
.await;
todo!()
}
async fn transcode_video(path: String, quality: Quality, start_time: u32) -> TranscodeInfo {
// TODO: Use the out path below once cached segments can be reused. // TODO: Use the out path below once cached segments can be reused.
// let out_dir = format!("/cache/{show_hash}/{quality}"); // let out_dir = format!("/cache/{show_hash}/{quality}");
let uuid: String = thread_rng() let uuid: String = thread_rng()
@ -168,7 +199,26 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra
let out_dir = format!("/cache/{uuid}"); let out_dir = format!("/cache/{uuid}");
std::fs::create_dir(&out_dir).expect("Could not create cache directory"); std::fs::create_dir(&out_dir).expect("Could not create cache directory");
let segment_time: u32 = 10; let child = start_transcode(
&path,
&out_dir,
get_transcode_video_quality_args(&quality, SEGMENT_TIME),
start_time,
)
.await;
TranscodeInfo {
show: (path, quality),
job: child,
uuid,
}
}
async fn start_transcode(
path: &String,
out_dir: &String,
encode_args: Vec<String>,
start_time: u32,
) -> Child {
let mut cmd = Command::new("ffmpeg"); let mut cmd = Command::new("ffmpeg");
cmd.args(&["-progress", "pipe:1"]) cmd.args(&["-progress", "pipe:1"])
.arg("-nostats") .arg("-nostats")
@ -181,8 +231,8 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra
// .args(&["-hls_allow_cache", "1"]) // .args(&["-hls_allow_cache", "1"])
// Keep all segments in the list (else only last X are presents, useful for livestreams) // Keep all segments in the list (else only last X are presents, useful for livestreams)
.args(&["-hls_list_size", "0"]) .args(&["-hls_list_size", "0"])
.args(&["-hls_time", segment_time.to_string().as_str()]) .args(&["-hls_time", SEGMENT_TIME.to_string().as_str()])
.args(get_transcode_video_quality_args(&quality, segment_time)) .args(&encode_args)
.args(&[ .args(&[
"-hls_segment_filename".to_string(), "-hls_segment_filename".to_string(),
format!("{out_dir}/segments-%02d.ts"), format!("{out_dir}/segments-%02d.ts"),
@ -213,18 +263,10 @@ async fn start_transcode(path: String, quality: Quality, start_time: u32) -> Tra
loop { loop {
rx.changed().await.unwrap(); rx.changed().await.unwrap();
let ready_time = *rx.borrow(); let ready_time = *rx.borrow();
if ready_time >= (1.5 * segment_time as f32) as u32 + start_time { if ready_time >= (1.5 * SEGMENT_TIME as f32) as u32 + start_time {
break; return child;
} }
} }
TranscodeInfo {
show: (path, quality),
job: child,
uuid,
start_time,
ready_time: rx,
}
} }
fn get_cache_path(info: &TranscodeInfo) -> PathBuf { fn get_cache_path(info: &TranscodeInfo) -> PathBuf {
@ -235,15 +277,10 @@ fn get_cache_path_from_uuid(uuid: &String) -> PathBuf {
return PathBuf::from(format!("/cache/{uuid}/", uuid = &uuid)); return PathBuf::from(format!("/cache/{uuid}/", uuid = &uuid));
} }
struct TranscodeInfo { pub struct TranscodeInfo {
show: (String, Quality), show: (String, Quality),
// TODO: Store if the process as ended (probably Option<Child> for the job)
job: Child, job: Child,
uuid: String, uuid: String,
#[allow(dead_code)]
start_time: u32,
#[allow(dead_code)]
ready_time: Receiver<u32>,
} }
pub struct Transcoder { pub struct Transcoder {
@ -311,7 +348,7 @@ impl Transcoder {
} }
} }
let info = start_transcode(path, quality, start_time).await; let info = transcode_video(path, quality, start_time).await;
let mut path = get_cache_path(&info); let mut path = get_cache_path(&info);
path.push("stream.m3u8"); path.push("stream.m3u8");
self.running.write().unwrap().insert(client_id, info); self.running.write().unwrap().insert(client_id, info);