Skip to content

Instantly share code, notes, and snippets.

@jbis9051
Created June 10, 2025 22:22
Show Gist options
  • Save jbis9051/aae946a238be134821439dc181105c06 to your computer and use it in GitHub Desktop.
Save jbis9051/aae946a238be134821439dc181105c06 to your computer and use it in GitHub Desktop.
pub trait Format {
type Error;
const FORMAT_TYPE: FormatType;
const EXTENSIONS: &'static [&'static str];
const METADATA_VERSION: i32; // bump this if the metadata format changes
fn is_supported(path: &Path) -> bool {
let ext = path.extension().unwrap_or_default().to_str().unwrap_or_default().to_lowercase();
Self::EXTENSIONS.contains(&ext.as_str())
}
fn get_metadata(path: &Path, app_config: &AppConfig) -> Result<MediaMetadata, Self::Error>;
}
pub trait Thumbnailable: Format {
const THUMBNAIL_VERSION: i32; // bump this if the thumbnail format changes
fn generate_thumbnail(path: &Path, width: u32, height: u32, app_config: &AppConfig) -> Result<RgbImage, Self::Error>;
fn generate_full(path: &Path, app_config: &AppConfig) -> Result<RgbImage, Self::Error> {
let metadata = Self::get_metadata(path, app_config)?;
let width = metadata.width;
let height = metadata.height;
Self::generate_thumbnail(path, width, height, app_config)
}
}
pub trait Audioable: Format {
fn convert_to_mp3(from: &Path, to: &Path, app_config: &AppConfig) -> Result<Output, Self::Error>
where <Self as Format>::Error :From<std::io::Error>
{
Ok(Command::new(&app_config.ffmpeg_path)
.args(&["-i", from.to_string_lossy().to_string().as_str(), to.to_string_lossy().to_string().as_str()])
.output()?)
}
fn convert_to_wav(from: &Path, to: &Path, app_config: &AppConfig) -> Result<Output, Self::Error>
where <Self as Format>::Error :From<std::io::Error>
{
Ok(Command::new(&app_config.ffmpeg_path)
.args(&["-i", from.to_string_lossy().to_string().as_str(), to.to_string_lossy().to_string().as_str()])
.output()?)
}
}
macro_rules! all_formats {
({
map: {
$( $name:ident => $format_a:ty ),*
},
all: [$( $all:ty ),*],
thumbnailable: [$( $thumbnailable:ty ),*],
audioable: [$( $audioable:ty ),*]
}) => {
#[derive(Debug, Copy, Clone, Serialize, sqlx::Type, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[sqlx(type_name = "format_type", rename_all = "kebab-case")]
pub enum FormatType {
$( $name, )*
Unknown
}
impl FormatType {
pub const fn all() -> &'static [FormatType] {
&[
$( <$format_a as Format>::FORMAT_TYPE, )*
]
}
pub const fn thumbnailable() -> &'static [FormatType] {
&[
$( <$thumbnailable as Format>::FORMAT_TYPE, )*
]
}
pub const fn audioable() -> &'static [FormatType] {
&[
$( <$audioable as Format>::FORMAT_TYPE, )*
]
}
}
impl AnyFormat {
pub fn try_new(path: PathBuf) -> Option<Self> {
let format = {
if false {
unreachable!()
}
$(
else if <$format_a as Format>::is_supported(&path) {
FormatType::$name
}
)*
else {
return None;
}
};
Some(Self {
format,
path
})
}
}
pub(crate) mod match_format {
#[macro_export]
macro_rules! _match_format {
($format: expr, |$format_type: ident| $code: block) => {{
use $crate::media_processors::format::*;
match $format {
$( &<$all as Format>::FORMAT_TYPE => {
type $format_type = $all;
$code
}, )*
_ => panic!("invalid format type: {:?}", $format),
}
}};
(thumbnailable: $format: expr, |$format_type: ident| $code: block) => {
match_format!(thumbnailable: $format, |$format_type| $code, { panic!("invalid format type, not thumbnailable: {:?}", $format) })
};
(thumbnailable: $format: expr, |$format_type: ident| $code: block, $code_not_thumbnailable: block) => {{
use $crate::media_processors::format::*;
match $format {
$( &<$thumbnailable as Format>::FORMAT_TYPE => {
type $format_type = $thumbnailable;
$code
}, )*
_ => $code_not_thumbnailable,
}
}};
(audioable: $format: expr, |$format_type: ident| $code: block) => {
match_format!(audioable: $format, |$format_type| $code, { panic!("invalid format type, not audioable: {:?}", $format) })
};
(audioable: $format: expr, |$format_type: ident| $code: block, $code_not_audioable: block) => {{
use $crate::media_processors::format::*;
match $format {
$( &<$audioable as Format>::FORMAT_TYPE => {
type $format_type = $audioable;
$code
}, )*
_ => $code_not_audioable,
}
}};
}
pub use _match_format as match_format;
}
};
}
impl FormatType{
pub fn is_thumbnailable(&self) -> bool {
for format in Self::thumbnailable() {
if self == format {
return true;
}
}
return false;
}
}
pub use match_format::match_format as match_format;
use crate::scan_config::AppConfig;
all_formats!({
map: {
Standard => standard::Standard,
Heif => heif::Heif,
Video => video::Video,
Raw => raw::Raw,
Pdf => pdf::Pdf,
Audio => audio::Audio
},
all: [standard::Standard, heif::Heif, video::Video, raw::Raw, pdf::Pdf, audio::Audio],
thumbnailable: [standard::Standard, heif::Heif, video::Video, raw::Raw, pdf::Pdf],
audioable: [video::Video, audio::Audio]
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment