Created
June 10, 2025 22:22
-
-
Save jbis9051/aae946a238be134821439dc181105c06 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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