Skip to content

Commit

Permalink
Impl FFmpegCodecContext, retrieve codec information
Browse files Browse the repository at this point in the history
 - Improve some unsafe pointer uses
 - Impl from FFmpegFormatContext to MediaInfo conversion
  • Loading branch information
HeavenVolkoff committed Apr 19, 2024
1 parent af3b73a commit c2a852c
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 51 deletions.
7 changes: 6 additions & 1 deletion apps/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ use axum::{
routing::get,
TypedHeader,
};

#[cfg(not(feature = "assets"))]
use axum::middleware;

use sd_core::{custom_uri, Node};
use secstr::SecStr;
use tracing::{info, warn};
Expand Down Expand Up @@ -143,7 +147,8 @@ async fn main() {
}
}

let _state = AppState { auth };
#[cfg(not(feature = "assets"))]
let state = AppState { auth };

let (node, router) = match Node::new(
data_dir,
Expand Down
5 changes: 3 additions & 2 deletions core/src/api/search/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ impl SearchFilterArgs {
file_path: &mut Vec<prisma::file_path::WhereParam>,
object: &mut Vec<prisma::object::WhereParam>,
) -> Result<(), rspc::Error> {
Ok(match self {
match self {
Self::FilePath(v) => file_path.extend(v.into_params(db).await?),
Self::Object(v) => object.extend(v.into_params()),
})
};
Ok(())
}
}

Expand Down
145 changes: 145 additions & 0 deletions crates/ffmpeg/src/codec_ctx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use crate::{error::Error, model::MediaCodec, utils::check_error};

use std::{
ffi::{CStr, CString},
io::Error as IOError,
ptr,
};

use ffmpeg_sys_next::{
av_fourcc_make_string, av_get_bits_per_sample, av_get_media_type_string,
avcodec_alloc_context3, avcodec_free_context, avcodec_get_name, avcodec_parameters_to_context,
avcodec_profile_name, AVCodecContext, AVCodecParameters, AVMediaType,
AV_FOURCC_MAX_STRING_SIZE,
};
use libc::ENOMEM;

#[derive(Debug)]
pub(crate) struct FFmpegCodecContext(*mut AVCodecContext);

impl FFmpegCodecContext {
pub(crate) fn parameters_to_context(av_dict: *mut AVCodecParameters) -> Result<Self, Error> {
let ctx = unsafe { avcodec_alloc_context3(ptr::null_mut()) };
if ctx.is_null() {
Err(IOError::from_raw_os_error(ENOMEM))?;
}

check_error(
unsafe { avcodec_parameters_to_context(ctx, av_dict) },
"Fail to fill the codec context with codec parameters",
)?;

Ok(Self(ctx))
}

unsafe fn as_ref<'a>(&self) -> Option<&'a AVCodecContext> {
self.0.as_ref()
}

fn kind(&self) -> Option<(String, Option<String>)> {
unsafe { self.as_ref() }.map(|ctx| {
let media_type = unsafe { av_get_media_type_string(ctx.codec_type) };
let codec_kind = unsafe { CStr::from_ptr(media_type) };

(
codec_kind.to_string_lossy().into_owned(),
unsafe { ctx.codec.as_ref() }.and_then(|codec| {
let subkind = unsafe { CStr::from_ptr(codec.name) };
if codec_kind == subkind {
None
} else {
Some(subkind.to_string_lossy().into_owned())
}
}),
)
})
}

fn name(&self) -> Option<String> {
unsafe { self.as_ref() }.map(|ctx| {
let codec_name = unsafe { avcodec_get_name(ctx.codec_id) };
let cstr = unsafe { CStr::from_ptr(codec_name) };
String::from_utf8_lossy(cstr.to_bytes()).to_string()
})
}

fn profile(&self) -> Option<String> {
unsafe { self.as_ref() }.and_then(|ctx| {
if ctx.profile != 0 {
let profile = unsafe { avcodec_profile_name(ctx.codec_id, ctx.profile) };
let cstr = unsafe { CStr::from_ptr(profile) };
Some(String::from_utf8_lossy(cstr.to_bytes()).to_string())
} else {
None
}
})
}

fn tag(&self) -> Option<String> {
unsafe { self.as_ref() }.and_then(|ctx| {
if ctx.codec_tag != 0 {
CString::new(vec![0; AV_FOURCC_MAX_STRING_SIZE as usize])
.ok()
.map(|buffer| {
let tag = unsafe {
CString::from_raw(av_fourcc_make_string(
buffer.into_raw(),
ctx.codec_tag,
))
};
String::from_utf8_lossy(tag.as_bytes()).to_string()
})
} else {
None
}
})
}

fn bit_rate(&self) -> Option<i64> {
unsafe { self.as_ref() }.map(|ctx| match ctx.codec_type {
AVMediaType::AVMEDIA_TYPE_VIDEO
| AVMediaType::AVMEDIA_TYPE_DATA
| AVMediaType::AVMEDIA_TYPE_SUBTITLE
| AVMediaType::AVMEDIA_TYPE_ATTACHMENT => ctx.bit_rate,
AVMediaType::AVMEDIA_TYPE_AUDIO => {
let bits_per_sample = unsafe { av_get_bits_per_sample(ctx.codec_id) };
if bits_per_sample != 0 {
let bit_rate = ctx.sample_rate as i64 * ctx.ch_layout.nb_channels as i64;
if bit_rate <= std::i64::MAX / bits_per_sample as i64 {
return bit_rate * (bits_per_sample as i64);
}
}
ctx.bit_rate
}
_ => 0,
})
}
}

impl Drop for FFmpegCodecContext {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { avcodec_free_context(&mut self.0) };
self.0 = std::ptr::null_mut();
}
}
}

impl From<FFmpegCodecContext> for MediaCodec {
fn from(val: FFmpegCodecContext) -> Self {
let (kind, subkind) = match val.kind() {
Some((kind, subkind)) => (Some(kind), subkind),
None => (None, None), // Handle the case when self.kind() returns None
};

MediaCodec {
kind,
subkind,
name: val.name(),
profile: val.profile(),
tag: val.tag(),
bit_rate: val.bit_rate(),
props: None,
}
}
}
32 changes: 12 additions & 20 deletions crates/ffmpeg/src/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,10 @@ impl FFmpegDict {
}

pub(crate) fn get(&self, key: CString) -> Option<String> {
let entry = unsafe { av_dict_get(self.dict, key.as_ptr(), ptr::null(), 0) };
if entry.is_null() {
return None;
}

let cstr = unsafe { CStr::from_ptr((*entry).value) };
Some(String::from_utf8_lossy(cstr.to_bytes()).to_string())
unsafe { av_dict_get(self.dict, key.as_ptr(), ptr::null(), 0).as_ref() }.map(|entry| {
let cstr = unsafe { CStr::from_ptr(entry.value) };
String::from_utf8_lossy(cstr.to_bytes()).to_string()
})
}

pub(crate) fn set(&mut self, key: CString, value: CString) -> Result<(), Error> {
Expand Down Expand Up @@ -96,19 +93,14 @@ impl<'a> Iterator for FFmpegDictIter<'a> {
type Item = (String, String);

fn next(&mut self) -> Option<(String, String)> {
unsafe {
self.prev = av_dict_iterate(self.dict, self.prev);
}
if self.prev.is_null() {
return None;
}

let key = unsafe { CStr::from_ptr((*self.prev).key) };
let value = unsafe { CStr::from_ptr((*self.prev).value) };
return Some((
String::from_utf8_lossy(key.to_bytes()).to_string(),
String::from_utf8_lossy(value.to_bytes()).to_string(),
));
unsafe { av_dict_iterate(self.dict, self.prev).as_ref() }.map(|prev| {
let key = unsafe { CStr::from_ptr(prev.key) };
let value = unsafe { CStr::from_ptr(prev.value) };
(
String::from_utf8_lossy(key.to_bytes()).to_string(),
String::from_utf8_lossy(value.to_bytes()).to_string(),
)
})
}
}

Expand Down
43 changes: 28 additions & 15 deletions crates/ffmpeg/src/format_ctx.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
codec_ctx::FFmpegCodecContext,
dict::FFmpegDict,
error::Error,
model::{MediaChapter, MediaMetadata, MediaProgram, MediaStream},
model::{MediaChapter, MediaInfo, MediaMetadata, MediaProgram, MediaStream},
utils::check_error,
};

Expand Down Expand Up @@ -50,11 +51,7 @@ impl FFmpegFormatContext {
Ok(ctx)
}

pub(crate) unsafe fn as_mut<'a>(&mut self) -> Option<&'a mut AVFormatContext> {
self.data.as_mut()
}

pub(crate) unsafe fn as_ref<'a>(&self) -> Option<&'a AVFormatContext> {
unsafe fn as_ref<'a>(&self) -> Option<&'a AVFormatContext> {
self.data.as_ref()
}

Expand All @@ -67,7 +64,7 @@ impl FFmpegFormatContext {
Ok(())
}

pub fn formats(&self) -> Vec<String> {
fn formats(&self) -> Vec<String> {
unsafe { self.as_ref() }
.and_then(|ctx| {
unsafe { ctx.iformat.as_ref() }.and_then(|format| {
Expand All @@ -88,7 +85,7 @@ impl FFmpegFormatContext {
.unwrap_or(vec![])
}

pub fn duration(&self) -> Option<TimeDelta> {
fn duration(&self) -> Option<TimeDelta> {
unsafe { self.as_ref() }.and_then(|ctx| {
let duration = ctx.duration;
if duration == AV_NOPTS_VALUE {
Expand All @@ -100,7 +97,7 @@ impl FFmpegFormatContext {
})
}

pub fn start_time(&self) -> Option<TimeDelta> {
fn start_time(&self) -> Option<TimeDelta> {
unsafe { self.as_ref() }.and_then(|ctx| {
let start_time = ctx.start_time;
if start_time == AV_NOPTS_VALUE {
Expand All @@ -114,11 +111,11 @@ impl FFmpegFormatContext {
})
}

pub fn bit_rate(&self) -> Option<i64> {
fn bit_rate(&self) -> Option<i64> {
unsafe { self.as_ref() }.map(|ctx| ctx.bit_rate)
}

pub fn chapters(&self) -> Vec<MediaChapter> {
fn chapters(&self) -> Vec<MediaChapter> {
unsafe { self.as_ref() }
.and_then(
|ctx| match (ctx.nb_chapters, unsafe { ctx.chapters.as_ref() }) {
Expand All @@ -144,7 +141,7 @@ impl FFmpegFormatContext {
.unwrap_or(vec![])
}

pub fn stream(&self, id: u32) -> Option<MediaStream> {
fn stream(&self, id: u32) -> Option<MediaStream> {
unsafe { self.as_ref() }
.and_then(|ctx| unsafe { ctx.streams.as_ref() })
.and_then(|streams| unsafe { streams.offset(id as isize).as_ref() })
Expand Down Expand Up @@ -219,7 +216,9 @@ impl FFmpegFormatContext {
MediaStream {
id: stream.id as u32,
name,
codec: None,
codec: FFmpegCodecContext::parameters_to_context(stream.codecpar)
.ok()
.map(|codec| codec.into()),
aspect_ratio_num: aspect_ratio.num,
aspect_ratio_den: aspect_ratio.den,
frames_per_second_num: stream.avg_frame_rate.num,
Expand All @@ -232,7 +231,7 @@ impl FFmpegFormatContext {
})
}

pub fn programs(&self) -> Vec<MediaProgram> {
fn programs(&self) -> Vec<MediaProgram> {
unsafe { self.as_ref() }
.map(|ctx| {
let mut visited_streams: HashSet<u32> = HashSet::new();
Expand Down Expand Up @@ -295,7 +294,7 @@ impl FFmpegFormatContext {
.unwrap_or(vec![])
}

pub fn metadata(&self) -> Option<MediaMetadata> {
fn metadata(&self) -> Option<MediaMetadata> {
if self.data.is_null() {
return None;
}
Expand All @@ -317,3 +316,17 @@ impl Drop for FFmpegFormatContext {
}
}
}

impl From<FFmpegFormatContext> for MediaInfo {
fn from(val: FFmpegFormatContext) -> Self {
MediaInfo {
formats: val.formats(),
duration: val.duration(),
start_time: val.start_time(),
bitrate: val.bit_rate(),
chapters: val.chapters(),
programs: val.programs(),
metadata: val.metadata(),
}
}
}
1 change: 1 addition & 0 deletions crates/ffmpeg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{

use std::path::Path;

mod codec_ctx;
mod dict;
mod error;
mod film_strip;
Expand Down
5 changes: 3 additions & 2 deletions crates/ffmpeg/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,18 @@ pub struct MediaSubtitleProps {
pub height: Option<i32>,
}

enum Props {
pub enum Props {
MediaVideoProps,

Check warning on line 68 in crates/ffmpeg/src/model.rs

View workflow job for this annotation

GitHub Actions / Clippy (ubuntu-20.04)

variants `MediaVideoProps`, `MediaAudioProps`, and `MediaSubtitleProps` are never constructed

warning: variants `MediaVideoProps`, `MediaAudioProps`, and `MediaSubtitleProps` are never constructed --> crates/ffmpeg/src/model.rs:68:2 | 67 | pub enum Props { | ----- variants in this enum 68 | MediaVideoProps, | ^^^^^^^^^^^^^^^ 69 | MediaAudioProps, | ^^^^^^^^^^^^^^^ 70 | MediaSubtitleProps, | ^^^^^^^^^^^^^^^^^^

Check warning on line 68 in crates/ffmpeg/src/model.rs

View workflow job for this annotation

GitHub Actions / Clippy (ubuntu-20.04)

variant name ends with the enum's name

warning: variant name ends with the enum's name --> crates/ffmpeg/src/model.rs:68:2 | 68 | MediaVideoProps, | ^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names = note: `#[warn(clippy::enum_variant_names)]` on by default
MediaAudioProps,

Check warning on line 69 in crates/ffmpeg/src/model.rs

View workflow job for this annotation

GitHub Actions / Clippy (ubuntu-20.04)

variant name ends with the enum's name

warning: variant name ends with the enum's name --> crates/ffmpeg/src/model.rs:69:2 | 69 | MediaAudioProps, | ^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names
MediaSubtitleProps,

Check warning on line 70 in crates/ffmpeg/src/model.rs

View workflow job for this annotation

GitHub Actions / Clippy (ubuntu-20.04)

variant name ends with the enum's name

warning: variant name ends with the enum's name --> crates/ffmpeg/src/model.rs:70:2 | 70 | MediaSubtitleProps, | ^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names
}

Check warning on line 71 in crates/ffmpeg/src/model.rs

View workflow job for this annotation

GitHub Actions / Clippy (ubuntu-20.04)

all variants have the same prefix: `Media`

warning: all variants have the same prefix: `Media` --> crates/ffmpeg/src/model.rs:67:1 | 67 | / pub enum Props { 68 | | MediaVideoProps, 69 | | MediaAudioProps, 70 | | MediaSubtitleProps, 71 | | } | |_^ | = help: remove the prefixes and use full paths to the variants instead of glob imports = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names

pub struct MediaCodec {
pub kind: Option<String>,
pub tag: Option<String>,
pub subkind: Option<String>,
pub name: Option<String>,
pub profile: Option<String>,
pub tag: Option<String>,
pub bit_rate: Option<i64>,
pub props: Option<Props>,
}
Expand Down
4 changes: 4 additions & 0 deletions crates/ffmpeg/src/movie_decoder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
error::{Error, FFmpegError},
probe::probe,
utils::{check_error, from_path, CSTRING_ERROR_MSG},
video_frame::{FFmpegFrame, FrameSource, VideoFrame},
};
Expand Down Expand Up @@ -53,6 +54,9 @@ impl MovieDecoder {
) -> Result<Self, Error> {
let filename = filename.as_ref();

// TODO: Remove this, just here to test and so clippy stops complaining about it being unused
let _ = probe(filename);

let input_file = if filename == Path::new("-") {
Path::new("pipe:")
} else {
Expand Down

0 comments on commit c2a852c

Please sign in to comment.