code quality changes, better error handling and logging
This commit is contained in:
parent
0d92a39309
commit
ed61d12070
18
src/main.rs
18
src/main.rs
@ -18,18 +18,12 @@ async fn main() {
|
|||||||
.init();
|
.init();
|
||||||
|
|
||||||
let addr = env::var("LISTEN").unwrap_or_else(|_| "0.0.0.0:8090".to_string());
|
let addr = env::var("LISTEN").unwrap_or_else(|_| "0.0.0.0:8090".to_string());
|
||||||
let pool = ThreadPool::new(match env::var("NUM_WORKERS") {
|
let pool = ThreadPool::new(
|
||||||
Ok(val) => match val.parse::<usize>() {
|
env::var("NUM_WORKERS")
|
||||||
Ok(val) => {
|
.ok()
|
||||||
if val > 0 {
|
.and_then(|val| val.parse::<usize>().ok())
|
||||||
Some(val);
|
.filter(|&val| val > 0)
|
||||||
}
|
);
|
||||||
None
|
|
||||||
}
|
|
||||||
Err(_) => None,
|
|
||||||
},
|
|
||||||
Err(_) => None,
|
|
||||||
});
|
|
||||||
let temp_dir = env::var("TEMP_DIR").unwrap_or_else(|_| {
|
let temp_dir = env::var("TEMP_DIR").unwrap_or_else(|_| {
|
||||||
env::temp_dir()
|
env::temp_dir()
|
||||||
.to_str()
|
.to_str()
|
||||||
|
@ -11,7 +11,7 @@ use tower_http::trace::TraceLayer;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::dto::{ConvertRequest, ConvertResponse};
|
use crate::dto::{ConvertRequest, ConvertResponse};
|
||||||
use crate::task::Task;
|
use crate::task::{Task, TaskParams};
|
||||||
use crate::thread_pool::ThreadPool;
|
use crate::thread_pool::ThreadPool;
|
||||||
|
|
||||||
const CONTENT_LENGTH_LIMIT: usize = 30 * 1024 * 1024;
|
const CONTENT_LENGTH_LIMIT: usize = 30 * 1024 * 1024;
|
||||||
@ -66,19 +66,19 @@ async fn enqueue_file(
|
|||||||
None => return error_response("Invalid output path"),
|
None => return error_response("Invalid output path"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let task = Task::new(
|
let params = TaskParams {
|
||||||
task_id,
|
format: req.format,
|
||||||
req.format,
|
codec: req.codec,
|
||||||
req.codec,
|
codec_opts: req.codec_opts,
|
||||||
req.codec_opts,
|
bit_rate: req.bit_rate,
|
||||||
req.bit_rate,
|
max_bit_rate: req.max_bit_rate,
|
||||||
req.max_bit_rate,
|
sample_rate: req.sample_rate,
|
||||||
req.sample_rate,
|
channel_layout: req.channel_layout,
|
||||||
req.channel_layout,
|
upload_url: req.upload_url,
|
||||||
req.upload_url,
|
input_path: input_path.to_string(),
|
||||||
input_path.to_string(),
|
output_path: output_path.to_string(),
|
||||||
output_path.to_string(),
|
};
|
||||||
);
|
let task = Task::new(task_id, params);
|
||||||
|
|
||||||
// Enqueue the task to the thread pool
|
// Enqueue the task to the thread pool
|
||||||
server.thread_pool.enqueue(task);
|
server.thread_pool.enqueue(task);
|
||||||
|
95
src/task.rs
95
src/task.rs
@ -5,45 +5,12 @@ use tracing::{debug, error};
|
|||||||
|
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
id: uuid::Uuid,
|
id: uuid::Uuid,
|
||||||
codec: String,
|
params: TaskParams,
|
||||||
format: String,
|
|
||||||
codec_opts: Option<String>,
|
|
||||||
bit_rate: usize,
|
|
||||||
max_bit_rate: usize,
|
|
||||||
sample_rate: i32,
|
|
||||||
channel_layout: String,
|
|
||||||
input_path: String,
|
|
||||||
output_path: String,
|
|
||||||
upload_url: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
pub fn new(
|
pub fn new(id: uuid::Uuid, params: TaskParams) -> Self {
|
||||||
id: uuid::Uuid,
|
Task { id, params }
|
||||||
format: String,
|
|
||||||
codec: String,
|
|
||||||
codec_opts: Option<String>,
|
|
||||||
bit_rate: usize,
|
|
||||||
max_bit_rate: usize,
|
|
||||||
sample_rate: i32,
|
|
||||||
channel_layout: String,
|
|
||||||
upload_url: String,
|
|
||||||
input_path: String,
|
|
||||||
output_path: String,
|
|
||||||
) -> Self {
|
|
||||||
Task {
|
|
||||||
id,
|
|
||||||
format,
|
|
||||||
codec,
|
|
||||||
codec_opts,
|
|
||||||
bit_rate,
|
|
||||||
max_bit_rate,
|
|
||||||
sample_rate,
|
|
||||||
channel_layout,
|
|
||||||
input_path,
|
|
||||||
output_path,
|
|
||||||
upload_url,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(self) {
|
pub fn execute(self) {
|
||||||
@ -51,7 +18,7 @@ impl Task {
|
|||||||
"performing transcoding for task with id: {}",
|
"performing transcoding for task with id: {}",
|
||||||
self.id.to_string()
|
self.id.to_string()
|
||||||
);
|
);
|
||||||
let mut ictx = match format::input(&self.input_path) {
|
let mut ictx = match format::input(&self.params.input_path) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("couldn't initialize input context: {:?}", err);
|
error!("couldn't initialize input context: {:?}", err);
|
||||||
@ -59,14 +26,14 @@ impl Task {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let octx = if let Some(codec_opts) = self.codec_opts {
|
let octx = if let Some(codec_opts) = self.params.codec_opts {
|
||||||
format::output_as_with(
|
format::output_as_with(
|
||||||
&self.output_path,
|
&self.params.output_path,
|
||||||
&self.format,
|
&self.params.format,
|
||||||
params_to_avdictionary(&codec_opts),
|
params_to_avdictionary(&codec_opts),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format::output_as(&self.output_path, &self.format)
|
format::output_as(&self.params.output_path, &self.params.format)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut octx = match octx {
|
let mut octx = match octx {
|
||||||
@ -81,11 +48,11 @@ impl Task {
|
|||||||
&mut ictx,
|
&mut ictx,
|
||||||
&mut octx,
|
&mut octx,
|
||||||
TranscoderParams {
|
TranscoderParams {
|
||||||
codec: self.codec,
|
codec: self.params.codec,
|
||||||
bit_rate: self.bit_rate,
|
bit_rate: self.params.bit_rate,
|
||||||
max_bit_rate: self.max_bit_rate,
|
max_bit_rate: self.params.max_bit_rate,
|
||||||
sample_rate: self.sample_rate,
|
sample_rate: self.params.sample_rate,
|
||||||
channel_layout: match self.channel_layout.as_str() {
|
channel_layout: match self.params.channel_layout.as_str() {
|
||||||
"stereo" => ChannelLayout::STEREO,
|
"stereo" => ChannelLayout::STEREO,
|
||||||
"mono" => ChannelLayout::MONO,
|
"mono" => ChannelLayout::MONO,
|
||||||
"stereo_downmix" => ChannelLayout::STEREO_DOWNMIX,
|
"stereo_downmix" => ChannelLayout::STEREO_DOWNMIX,
|
||||||
@ -118,8 +85,11 @@ impl Task {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
transcoder.receive_and_process_decoded_frames(&mut octx)
|
transcoder
|
||||||
.unwrap_or_else(|err| error!("failure during processing decoded frames: {:?}", err));
|
.receive_and_process_decoded_frames(&mut octx)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
error!("failure during processing decoded frames: {:?}", err)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +108,8 @@ impl Task {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
transcoder.get_and_process_filtered_frames(&mut octx)
|
transcoder
|
||||||
|
.get_and_process_filtered_frames(&mut octx)
|
||||||
.unwrap_or_else(|err| error!("failure during processing filtered frames: {:?}", err));
|
.unwrap_or_else(|err| error!("failure during processing filtered frames: {:?}", err));
|
||||||
|
|
||||||
if let Err(err) = transcoder.send_eof_to_encoder() {
|
if let Err(err) = transcoder.send_eof_to_encoder() {
|
||||||
@ -146,19 +117,39 @@ impl Task {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
transcoder.receive_and_process_encoded_packets(&mut octx)
|
transcoder
|
||||||
|
.receive_and_process_encoded_packets(&mut octx)
|
||||||
.unwrap_or_else(|err| error!("failure during transcoding: {:?}", err));
|
.unwrap_or_else(|err| error!("failure during transcoding: {:?}", err));
|
||||||
|
|
||||||
if let Err(err) = octx.write_trailer() {
|
if let Err(err) = octx.write_trailer() {
|
||||||
error!("couldn't finish transcoding: {:?}", err);
|
error!("couldn't finish transcoding: {:?}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"finished transcoding for task with id: {}",
|
||||||
|
self.id.to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TaskParams {
|
||||||
|
pub format: String,
|
||||||
|
pub codec: String,
|
||||||
|
pub codec_opts: Option<String>,
|
||||||
|
pub bit_rate: usize,
|
||||||
|
pub max_bit_rate: usize,
|
||||||
|
pub sample_rate: i32,
|
||||||
|
pub channel_layout: String,
|
||||||
|
pub input_path: String,
|
||||||
|
pub output_path: String,
|
||||||
|
pub upload_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn params_to_avdictionary(input: &str) -> Dictionary {
|
fn params_to_avdictionary(input: &str) -> Dictionary {
|
||||||
let mut dict: Dictionary = Dictionary::new();
|
let mut dict: Dictionary = Dictionary::new();
|
||||||
for pair in input.split(";") {
|
for pair in input.split(';') {
|
||||||
let mut parts = pair.split(":");
|
let mut parts = pair.split(':');
|
||||||
|
|
||||||
if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
|
if let (Some(key), Some(value)) = (parts.next(), parts.next()) {
|
||||||
dict.set(key, value);
|
dict.set(key, value);
|
||||||
|
@ -2,6 +2,7 @@ use std::sync::mpsc::{self, Receiver, Sender};
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use ffmpeg_next::log::Level;
|
||||||
|
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ impl Worker {
|
|||||||
let thread = thread::spawn(move || {
|
let thread = thread::spawn(move || {
|
||||||
ffmpeg_next::init()
|
ffmpeg_next::init()
|
||||||
.unwrap_or_else(|err| tracing::error!("couldn't init FFmpeg: {:?}", err));
|
.unwrap_or_else(|err| tracing::error!("couldn't init FFmpeg: {:?}", err));
|
||||||
|
ffmpeg_next::util::log::set_level(Level::Quiet);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let task = {
|
let task = {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
extern crate ffmpeg_next as ffmpeg;
|
extern crate ffmpeg_next as ffmpeg;
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use ffmpeg::{codec, filter, format, frame, media};
|
use ffmpeg::{codec, filter, format, frame, media};
|
||||||
use ffmpeg_next::error::EAGAIN;
|
use ffmpeg_next::error::EAGAIN;
|
||||||
@ -35,15 +34,19 @@ impl Transcoder {
|
|||||||
.ok_or("could not find best audio stream")?;
|
.ok_or("could not find best audio stream")?;
|
||||||
|
|
||||||
let context = codec::context::Context::from_parameters(input.parameters())?;
|
let context = codec::context::Context::from_parameters(input.parameters())?;
|
||||||
let mut decoder = context.decoder().audio().map_err(|err| {
|
let mut decoder = context
|
||||||
format!("couldn't find decoder for input file: {}", err)
|
.decoder()
|
||||||
})?;
|
.audio()
|
||||||
|
.map_err(|err| format!("couldn't find decoder for input file: {}", err))?;
|
||||||
|
|
||||||
let codec = ffmpeg::encoder::find_by_name(&*params.codec)
|
let codec = ffmpeg::encoder::find_by_name(¶ms.codec)
|
||||||
.ok_or_else(|| format!("couldn't find codec with name: {}", params.codec))?
|
.ok_or_else(|| format!("couldn't find codec with name: {}", params.codec))?
|
||||||
.audio()?;
|
.audio()?;
|
||||||
|
|
||||||
let global = octx.format().flags().contains(format::flag::Flags::GLOBAL_HEADER);
|
let global = octx
|
||||||
|
.format()
|
||||||
|
.flags()
|
||||||
|
.contains(format::flag::Flags::GLOBAL_HEADER);
|
||||||
|
|
||||||
decoder.set_parameters(input.parameters())?;
|
decoder.set_parameters(input.parameters())?;
|
||||||
|
|
||||||
@ -70,13 +73,26 @@ impl Transcoder {
|
|||||||
encoder.set_format(
|
encoder.set_format(
|
||||||
codec
|
codec
|
||||||
.formats()
|
.formats()
|
||||||
.ok_or_else(|| format!("failed to get supported formats for codec: {}", codec.name()))?
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"failed to get supported formats for codec: {}",
|
||||||
|
codec.name()
|
||||||
|
)
|
||||||
|
})?
|
||||||
.next()
|
.next()
|
||||||
.ok_or("no supported formats found for codec")?,
|
.ok_or("no supported formats found for codec")?,
|
||||||
);
|
);
|
||||||
|
|
||||||
encoder.set_bit_rate(if params.bit_rate > 0 { params.bit_rate } else { decoder.bit_rate() });
|
encoder.set_bit_rate(if params.bit_rate > 0 {
|
||||||
encoder.set_max_bit_rate(if params.max_bit_rate > 0 { params.max_bit_rate } else { decoder.max_bit_rate() });
|
params.bit_rate
|
||||||
|
} else {
|
||||||
|
decoder.bit_rate()
|
||||||
|
});
|
||||||
|
encoder.set_max_bit_rate(if params.max_bit_rate > 0 {
|
||||||
|
params.max_bit_rate
|
||||||
|
} else {
|
||||||
|
decoder.max_bit_rate()
|
||||||
|
});
|
||||||
encoder.set_time_base((1, sample_rate));
|
encoder.set_time_base((1, sample_rate));
|
||||||
output.set_time_base((1, sample_rate));
|
output.set_time_base((1, sample_rate));
|
||||||
|
|
||||||
@ -121,11 +137,26 @@ impl Transcoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_frame_to_filter(&mut self, frame: &ffmpeg::Frame) -> Result<(), ffmpeg::Error> {
|
fn add_frame_to_filter(&mut self, frame: &ffmpeg::Frame) -> Result<(), ffmpeg::Error> {
|
||||||
self.filter.get("in").unwrap().source().add(frame)
|
if let Some(mut ctx) = self.filter.get("in") {
|
||||||
|
let mut source = ctx.source();
|
||||||
|
source.add(frame)
|
||||||
|
} else {
|
||||||
|
Err(ffmpeg::Error::Other {
|
||||||
|
errno: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub(crate) fn flush_filter(&mut self) -> Result<(), ffmpeg::Error> {
|
pub(crate) fn flush_filter(&mut self) -> Result<(), ffmpeg::Error> {
|
||||||
self.filter.get("in").unwrap().source().flush()
|
if let Some(mut ctx) = self.filter.get("in") {
|
||||||
|
let mut source = ctx.source();
|
||||||
|
source.flush()
|
||||||
|
} else {
|
||||||
|
Err(ffmpeg::Error::Other {
|
||||||
|
errno: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_and_process_filtered_frames(
|
pub(crate) fn get_and_process_filtered_frames(
|
||||||
@ -134,7 +165,10 @@ impl Transcoder {
|
|||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let mut filtered = frame::Audio::empty();
|
let mut filtered = frame::Audio::empty();
|
||||||
loop {
|
loop {
|
||||||
let mut ctx = self.filter.get("out").ok_or("cannot get context from filter")?;
|
let mut ctx = self
|
||||||
|
.filter
|
||||||
|
.get("out")
|
||||||
|
.ok_or("cannot get context from filter")?;
|
||||||
|
|
||||||
if let Err(err) = ctx.sink().frame(&mut filtered) {
|
if let Err(err) = ctx.sink().frame(&mut filtered) {
|
||||||
if err != ffmpeg::Error::Eof {
|
if err != ffmpeg::Error::Eof {
|
||||||
@ -171,8 +205,12 @@ impl Transcoder {
|
|||||||
|
|
||||||
if let Err(mut err) = self.get_and_process_filtered_frames(octx) {
|
if let Err(mut err) = self.get_and_process_filtered_frames(octx) {
|
||||||
let expected = ffmpeg::Error::Other { errno: EAGAIN };
|
let expected = ffmpeg::Error::Other { errno: EAGAIN };
|
||||||
if err.downcast_mut::<ffmpeg::error::Error>().ok_or(ffmpeg::Error::Bug) == Err(expected) {
|
if err
|
||||||
continue
|
.downcast_mut::<ffmpeg::error::Error>()
|
||||||
|
.ok_or(ffmpeg::Error::Bug)
|
||||||
|
== Err(expected)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,11 +244,16 @@ fn filter_graph(
|
|||||||
filter.output("in", 0)?.input("out", 0)?.parse(spec)?;
|
filter.output("in", 0)?.input("out", 0)?.parse(spec)?;
|
||||||
filter.validate()?;
|
filter.validate()?;
|
||||||
|
|
||||||
println!("{}", filter.dump());
|
|
||||||
|
|
||||||
if let Some(codec) = encoder.codec() {
|
if let Some(codec) = encoder.codec() {
|
||||||
if !codec.capabilities().contains(codec::capabilities::Capabilities::VARIABLE_FRAME_SIZE) {
|
if !codec
|
||||||
filter.get("out").unwrap().sink().set_frame_size(encoder.frame_size());
|
.capabilities()
|
||||||
|
.contains(codec::capabilities::Capabilities::VARIABLE_FRAME_SIZE)
|
||||||
|
{
|
||||||
|
filter
|
||||||
|
.get("out")
|
||||||
|
.unwrap()
|
||||||
|
.sink()
|
||||||
|
.set_frame_size(encoder.frame_size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user