Skip to content

Commit

Permalink
Data models and start populating MediaInfo in rust
Browse files Browse the repository at this point in the history
  • Loading branch information
HeavenVolkoff committed Apr 18, 2024
1 parent 0ce0079 commit b51e62a
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 19 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ model MediaInfo {
id Int @id @default(autoincrement())
// Internal FFmpeg properties
format String?
formats String?
duration BigInt?
start_time BigInt?
bitrate BigInt?
Expand Down
6 changes: 3 additions & 3 deletions crates/ffmpeg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ repository = { workspace = true }
edition = { workspace = true }

[dependencies]
chrono = { workspace = true }
ffmpeg-sys-next = "6.0.1"
libc = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["fs", "rt"] }
tracing = { workspace = true }
webp = { workspace = true }
libc = { workspace = true }
ffmpeg-sys-next = "6.0.1"


[dev-dependencies]
tempfile = { workspace = true }
Expand Down
21 changes: 10 additions & 11 deletions crates/ffmpeg/src/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,18 @@ impl<'a> Iterator for FFmpegDictIter<'a> {
type Item = (String, String);

fn next(&mut self) -> Option<(String, String)> {
while unsafe {
unsafe {
self.prev = av_dict_iterate(self.dict, self.prev);
self.prev
} != ptr::null()
{
let key = unsafe { CStr::from_ptr((*self.prev).key) };
let value = unsafe { CStr::from_ptr((*self.prev).value) };
return Some((
key.to_string_lossy().into_owned(),
value.to_string_lossy().into_owned(),
));
}
if self.prev.is_null() {
return None;
}

None
let key = unsafe { CStr::from_ptr((*self.prev).key) };
let value = unsafe { CStr::from_ptr((*self.prev).value) };
return Some((
key.to_string_lossy().into_owned(),
value.to_string_lossy().into_owned(),
));
}
}
98 changes: 96 additions & 2 deletions crates/ffmpeg/src/format_ctx.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,111 @@
use crate::{dict::FFmpegDict, error::Error, utils::check_error};
use crate::{dict::FFmpegDict, error::Error, model::MediaMetadata, utils::check_error};

use ffmpeg_sys_next::{
avformat_close_input, avformat_find_stream_info, avformat_open_input, AVFormatContext,
};

use std::{ffi::CString, ptr};
use std::{
ffi::{CStr, CString},
ptr,
};

#[derive(Debug)]
pub(crate) struct FFmpegFormatContext {
data: *mut AVFormatContext,
}

impl FFmpegFormatContext {
pub(crate) fn formats(&self) -> Option<Vec<String>> {
if self.data.is_null() {
return None;
}

let format: *const ffmpeg_sys_next::AVInputFormat = unsafe { (*self.data).iformat };
if format.is_null() {
return None;
}

let name = unsafe { (*format).name };
if name.is_null() {
return None;
}

let c_str = unsafe { CStr::from_ptr(name) };
let string = c_str.to_string_lossy().into_owned();
let entries: Vec<String> = string
.split(',')
.map(|entry| entry.trim().to_string())
.filter(|entry| !entry.is_empty())
.collect();

Some(entries)
}

pub(crate) fn metadata(&self) -> Option<MediaMetadata> {
if self.data.is_null() {
return None;
}

let metadata_ptr = unsafe { (*self.data).metadata };
if metadata_ptr.is_null() {
return None;
}

let mut media_metadata = MediaMetadata::default();

let metadata = FFmpegDict::new(Some(metadata_ptr));
for (key, value) in metadata.into_iter() {
match key.as_str() {
"album" => media_metadata.album = Some(value.clone()),
"album_artist" => media_metadata.album_artist = Some(value.clone()),
"artist" => media_metadata.artist = Some(value.clone()),
"comment" => media_metadata.comment = Some(value.clone()),
"composer" => media_metadata.composer = Some(value.clone()),
"copyright" => media_metadata.copyright = Some(value.clone()),
"creation_time" => {
if let Ok(creation_time) = chrono::DateTime::parse_from_rfc3339(&value) {
media_metadata.creation_time = Some(creation_time.into());
}
}
"date" => {
if let Ok(date) = chrono::DateTime::parse_from_rfc3339(&value) {
media_metadata.date = Some(date.into());
}
}
"disc" => {
if let Ok(disc) = value.parse() {
media_metadata.disc = Some(disc);
}
}
"encoder" => media_metadata.encoder = Some(value.clone()),
"encoded_by" => media_metadata.encoded_by = Some(value.clone()),
"filename" => media_metadata.filename = Some(value.clone()),
"genre" => media_metadata.genre = Some(value.clone()),
"language" => media_metadata.language = Some(value.clone()),
"performer" => media_metadata.performer = Some(value.clone()),
"publisher" => media_metadata.publisher = Some(value.clone()),
"service_name" => media_metadata.service_name = Some(value.clone()),
"service_provider" => media_metadata.service_provider = Some(value.clone()),
"title" => media_metadata.title = Some(value.clone()),
"track" => {
if let Ok(track) = value.parse() {
media_metadata.track = Some(track);
}
}
"variant_bitrate" => {
if let Ok(variant_bitrate) = value.parse() {
media_metadata.variant_bitrate = Some(variant_bitrate);
}
}
_ => {
media_metadata.custom.insert(key.clone(), value.clone());
}
}
}

Some(media_metadata)
}

pub(crate) fn as_mut_ptr(&mut self) -> *mut AVFormatContext {
self.data
}
Expand Down
1 change: 1 addition & 0 deletions crates/ffmpeg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod dict;
mod error;
mod film_strip;
mod format_ctx;
mod model;
mod movie_decoder;
mod probe;
mod thumbnailer;
Expand Down
110 changes: 110 additions & 0 deletions crates/ffmpeg/src/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use std::collections::HashMap;

#[derive(Default)]
pub struct MediaMetadata {
pub album: Option<String>,
pub album_artist: Option<String>,
pub artist: Option<String>,
pub comment: Option<String>,
pub composer: Option<String>,
pub copyright: Option<String>,
pub creation_time: Option<chrono::DateTime<chrono::Utc>>,
pub date: Option<chrono::DateTime<chrono::Utc>>,
pub disc: Option<i32>,
pub encoder: Option<String>,
pub encoded_by: Option<String>,
pub filename: Option<String>,
pub genre: Option<String>,
pub language: Option<String>,
pub performer: Option<String>,
pub publisher: Option<String>,
pub service_name: Option<String>,
pub service_provider: Option<String>,
pub title: Option<String>,
pub track: Option<i32>,
pub variant_bitrate: Option<i32>,
pub custom: HashMap<String, String>,
}

pub struct MediaChapter {
pub id: i32,
pub start: Option<i64>,
pub end: Option<i64>,
pub metadata: MediaMetadata,
}

pub struct MediaVideoProps {
pub pixel_format: Option<String>,
pub color_range: Option<String>,
pub bits_per_channel: Option<i32>,
pub color_space: Option<String>,
pub color_primaries: Option<String>,
pub color_transfer: Option<String>,
pub field_order: Option<String>,
pub chroma_location: Option<String>,
pub coded_width: Option<i32>,
pub coded_height: Option<i32>,
pub aspect_ratio_num: Option<i32>,
pub aspect_ratio_den: Option<i32>,
pub properties: Option<String>,
}

pub struct MediaAudioProps {
pub delay: Option<i32>,
pub padding: Option<i32>,
pub sample_rate: Option<i32>,
pub sample_format: Option<String>,
pub bit_per_sample: Option<i32>,
pub channel_layout: Option<String>,
}

pub struct MediaSubtitleProps {
pub width: Option<i32>,
pub height: Option<i32>,
}

enum Props {
MediaVideoProps,
MediaAudioProps,
MediaSubtitleProps,
}

pub struct MediaCodec {
pub kind: Option<String>,
pub tag: Option<String>,
pub name: Option<String>,
pub profile: Option<String>,
pub bit_rate: Option<i64>,
pub props: Option<Props>,
}

pub struct MediaStream {
pub id: i32,
pub name: Option<String>,
pub codec: Option<MediaCodec>,
pub aspect_ratio_num: Option<i32>,
pub aspect_ratio_den: Option<i32>,
pub frames_per_second_num: Option<i32>,
pub frames_per_second_den: Option<i32>,
pub time_base_real_den: Option<i32>,
pub time_base_real_num: Option<i32>,
pub dispositions: Option<String>,
pub metadata: MediaMetadata,
}

pub struct MediaProgram {
pub id: i32,
pub name: Option<String>,
pub streams: Vec<MediaStream>,
pub metadata: MediaMetadata,
}

pub struct MediaInfo {
pub formats: Option<Vec<String>>,
pub duration: Option<i64>,
pub start_time: Option<i64>,
pub bitrate: Option<i64>,
pub chapters: Vec<MediaChapter>,
pub programs: Vec<MediaProgram>,
pub metadata: Option<MediaMetadata>,
}
17 changes: 15 additions & 2 deletions crates/ffmpeg/src/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ use crate::{
dict::FFmpegDict,
error::Error,
format_ctx::FFmpegFormatContext,
model::MediaInfo,
utils::{from_path, CSTRING_ERROR_MSG},
};

use ffmpeg_sys_next::{av_log_set_level, AV_LOG_FATAL};

use std::{ffi::CString, path::Path};

pub fn probe(filename: impl AsRef<Path>) -> Result<(), Error> {
Expand All @@ -15,7 +18,7 @@ pub fn probe(filename: impl AsRef<Path>) -> Result<(), Error> {

// Dictionary to store format options
let mut format_opts = FFmpegDict::new(None);
// Some MPEGTS specific option (copied and pasted from ffprobe)
// Some MPEGTS specific option (copied from ffprobe)
let scan_all_pmts = CString::new("scan_all_pmts").expect(CSTRING_ERROR_MSG);
format_opts.set(
scan_all_pmts.to_owned(),
Expand All @@ -28,8 +31,18 @@ pub fn probe(filename: impl AsRef<Path>) -> Result<(), Error> {
// Reset MPEGTS specific option
format_opts.reset(scan_all_pmts)?;

// Read packets of a media file to get stream information.
// Read packets of media file to get stream information.
fmt_ctx.find_stream_info()?;

let media_info = MediaInfo {
formats: fmt_ctx.formats(),
duration: "" , // Option<i64>
start_time: "" , // Option<i64>
bitrate: "" , // Option<i64>
chapters: "" , // Vec<MediaChapter>
programs: "" , // Vec<MediaProgram>
metadata: fmt_ctx.metadata(), // MediaMetadata
};

Ok(())
}

0 comments on commit b51e62a

Please sign in to comment.