musikr: more fine-grained file handling

This commit is contained in:
Alexander Capehart 2025-02-08 17:45:20 -07:00
parent acee4ddedd
commit 013f25f46f
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 176 additions and 126 deletions

View file

@ -2,37 +2,37 @@
namespace taglib_shim { namespace taglib_shim {
// File type checking functions // File conversion functions
bool File_isMPEG(TagLib::File* file) { TagLib::Ogg::Vorbis::File* File_asVorbis(TagLib::File* file) {
return dynamic_cast<TagLib::MPEG::File*>(file) != nullptr; return dynamic_cast<TagLib::Ogg::Vorbis::File*>(file);
} }
bool File_isFLAC(TagLib::File* file) { TagLib::Ogg::Opus::File* File_asOpus(TagLib::File* file) {
return dynamic_cast<TagLib::FLAC::File*>(file) != nullptr; return dynamic_cast<TagLib::Ogg::Opus::File*>(file);
} }
bool File_isMP4(TagLib::File* file) { TagLib::MPEG::File* File_asMPEG(TagLib::File* file) {
return dynamic_cast<TagLib::MP4::File*>(file) != nullptr; return dynamic_cast<TagLib::MPEG::File*>(file);
} }
bool File_isOgg(TagLib::File* file) { TagLib::FLAC::File* File_asFLAC(TagLib::File* file) {
return dynamic_cast<TagLib::Ogg::File*>(file) != nullptr; return dynamic_cast<TagLib::FLAC::File*>(file);
} }
bool File_isOpus(TagLib::File* file) { TagLib::MP4::File* File_asMP4(TagLib::File* file) {
return dynamic_cast<TagLib::Ogg::Opus::File*>(file) != nullptr; return dynamic_cast<TagLib::MP4::File*>(file);
} }
bool File_isWAV(TagLib::File* file) { TagLib::RIFF::WAV::File* File_asWAV(TagLib::File* file) {
return dynamic_cast<TagLib::RIFF::WAV::File*>(file) != nullptr; return dynamic_cast<TagLib::RIFF::WAV::File*>(file);
} }
bool File_isWavPack(TagLib::File* file) { TagLib::WavPack::File* File_asWavPack(TagLib::File* file) {
return dynamic_cast<TagLib::WavPack::File*>(file) != nullptr; return dynamic_cast<TagLib::WavPack::File*>(file);
} }
bool File_isAPE(TagLib::File* file) { TagLib::APE::File* File_asAPE(TagLib::File* file) {
return dynamic_cast<TagLib::APE::File*>(file) != nullptr; return dynamic_cast<TagLib::APE::File*>(file);
} }
} // namespace taglib_shim } // namespace taglib_shim

View file

@ -12,18 +12,18 @@
#include <taglib/wavfile.h> #include <taglib/wavfile.h>
#include <taglib/wavpackfile.h> #include <taglib/wavpackfile.h>
#include <taglib/apefile.h> #include <taglib/apefile.h>
#include <taglib/vorbisfile.h>
namespace taglib_shim { namespace taglib_shim {
// File type checking functions // File conversion functions
bool File_isMPEG(TagLib::File* file); TagLib::Ogg::Vorbis::File* File_asVorbis(TagLib::File* file);
bool File_isFLAC(TagLib::File* file); TagLib::Ogg::Opus::File* File_asOpus(TagLib::File* file);
bool File_isMP4(TagLib::File* file); TagLib::MPEG::File* File_asMPEG(TagLib::File* file);
bool File_isOgg(TagLib::File* file); TagLib::FLAC::File* File_asFLAC(TagLib::File* file);
bool File_isOpus(TagLib::File* file); TagLib::MP4::File* File_asMP4(TagLib::File* file);
bool File_isWAV(TagLib::File* file); TagLib::RIFF::WAV::File* File_asWAV(TagLib::File* file);
bool File_isWavPack(TagLib::File* file); TagLib::WavPack::File* File_asWavPack(TagLib::File* file);
bool File_isAPE(TagLib::File* file); TagLib::APE::File* File_asAPE(TagLib::File* file);
} // namespace taglib_shim } // namespace taglib_shim

View file

@ -28,7 +28,7 @@ pub extern "C" fn Java_org_oxycblt_musikr_metadata_MetadataJNI_openFile<'local>(
let file_ref = match FileRef::from_stream(stream) { let file_ref = match FileRef::from_stream(stream) {
Some(file_ref) => file_ref, Some(file_ref) => file_ref,
None => { None => {
let error = "Failed to create FileRef"; let error = "Failed to create File";
let error_str = env.new_string(error).expect("Couldn't create error string!"); let error_str = env.new_string(error).expect("Couldn't create error string!");
return error_str.into_raw(); return error_str.into_raw();
} }

View file

@ -3,61 +3,100 @@ pub(crate) mod bindings {
unsafe extern "C++" { unsafe extern "C++" {
include!("taglib/taglib.h"); include!("taglib/taglib.h");
include!("taglib/tstring.h"); include!("taglib/tstring.h");
include!("taglib/vorbisfile.h");
include!("taglib/xiphcomment.h");
include!("shim/iostream_shim.hpp"); include!("shim/iostream_shim.hpp");
include!("shim/file_shim.hpp"); include!("shim/file_shim.hpp");
#[namespace = "TagLib"] #[namespace = "TagLib"]
type FileRef; type FileRef;
#[namespace = "TagLib"]
type File;
#[namespace = "TagLib"]
#[cxx_name = "String"]
type TagString;
#[namespace = "TagLib"]
type AudioProperties;
#[namespace = "taglib_shim"]
type RustIOStream;
#[namespace = "taglib_shim"]
type RustStream;
// Create a FileRef from an iostream
#[namespace = "taglib_shim"]
unsafe fn new_rust_iostream(stream: *mut RustStream) -> UniquePtr<RustIOStream>;
#[namespace = "taglib_shim"]
fn new_FileRef_from_stream(stream: UniquePtr<RustIOStream>) -> UniquePtr<FileRef>;
// FileRef helper functions
fn isNull(self: Pin<&FileRef>) -> bool; fn isNull(self: Pin<&FileRef>) -> bool;
fn file(self: Pin<&FileRef>) -> *mut File; fn file(self: Pin<&FileRef>) -> *mut File;
#[namespace = "taglib_shim"]
type RustIOStream;
// Create a FileRef from an iostream
#[namespace = "taglib_shim"]
unsafe fn new_rust_iostream(stream: *mut RustStream) -> UniquePtr<RustIOStream>;
#[namespace = "taglib_shim"]
type RustStream;
#[namespace = "taglib_shim"]
fn new_FileRef_from_stream(stream: UniquePtr<RustIOStream>) -> UniquePtr<FileRef>;
#[namespace = "TagLib"]
type File;
fn audioProperties(self: Pin<&File>) -> *mut AudioProperties; fn audioProperties(self: Pin<&File>) -> *mut AudioProperties;
// File type checking functions #[namespace = "TagLib"]
#[namespace = "taglib_shim"] type AudioProperties;
unsafe fn File_isMPEG(file: *mut File) -> bool;
#[namespace = "taglib_shim"]
unsafe fn File_isFLAC(file: *mut File) -> bool;
#[namespace = "taglib_shim"]
unsafe fn File_isMP4(file: *mut File) -> bool;
#[namespace = "taglib_shim"]
unsafe fn File_isOgg(file: *mut File) -> bool;
#[namespace = "taglib_shim"]
unsafe fn File_isOpus(file: *mut File) -> bool;
#[namespace = "taglib_shim"]
unsafe fn File_isWAV(file: *mut File) -> bool;
#[namespace = "taglib_shim"]
unsafe fn File_isWavPack(file: *mut File) -> bool;
#[namespace = "taglib_shim"]
unsafe fn File_isAPE(file: *mut File) -> bool;
// AudioProperties methods
fn lengthInMilliseconds(self: Pin<&AudioProperties>) -> i32; fn lengthInMilliseconds(self: Pin<&AudioProperties>) -> i32;
fn bitrate(self: Pin<&AudioProperties>) -> i32; fn bitrate(self: Pin<&AudioProperties>) -> i32;
fn sampleRate(self: Pin<&AudioProperties>) -> i32; fn sampleRate(self: Pin<&AudioProperties>) -> i32;
fn channels(self: Pin<&AudioProperties>) -> i32; fn channels(self: Pin<&AudioProperties>) -> i32;
// String conversion utilities #[namespace = "TagLib::Ogg::Vorbis"]
#[cxx_name = "File"]
type VorbisFile;
unsafe fn tag(self: Pin<&VorbisFile>) -> *mut XiphComment;
#[namespace = "TagLib::FLAC"]
#[cxx_name = "File"]
type FLACFile;
#[namespace = "TagLib::Ogg::Opus"]
#[cxx_name = "File"]
type OpusFile;
#[namespace = "TagLib::Ogg"]
type XiphComment;
unsafe fn fieldListMap(self: Pin<&XiphComment>) -> &FieldListMap;
#[namespace = "TagLib::Ogg"]
type FieldListMap;
#[namespace = "TagLib::MPEG"]
#[cxx_name = "File"]
type MPEGFile;
#[namespace = "TagLib::MP4"]
#[cxx_name = "File"]
type MP4File;
#[namespace = "TagLib::RIFF::WAV"]
#[cxx_name = "File"]
type WAVFile;
#[namespace = "TagLib::WavPack"]
#[cxx_name = "File"]
type WavPackFile;
#[namespace = "TagLib::APE"]
#[cxx_name = "File"]
type APEFile;
// File conversion functions
#[namespace = "taglib_shim"]
unsafe fn File_asVorbis(file: *mut File) -> *mut VorbisFile;
#[namespace = "taglib_shim"]
unsafe fn File_asOpus(file: *mut File) -> *mut OpusFile;
#[namespace = "taglib_shim"]
unsafe fn File_asMPEG(file: *mut File) -> *mut MPEGFile;
#[namespace = "taglib_shim"]
unsafe fn File_asFLAC(file: *mut File) -> *mut FLACFile;
#[namespace = "taglib_shim"]
unsafe fn File_asMP4(file: *mut File) -> *mut MP4File;
#[namespace = "taglib_shim"]
unsafe fn File_asWAV(file: *mut File) -> *mut WAVFile;
#[namespace = "taglib_shim"]
unsafe fn File_asWavPack(file: *mut File) -> *mut WavPackFile;
#[namespace = "taglib_shim"]
unsafe fn File_asAPE(file: *mut File) -> *mut APEFile;
#[namespace = "TagLib"]
#[cxx_name = "String"]
type TagString;
#[namespace = "taglib_shim"] #[namespace = "taglib_shim"]
unsafe fn toCString(self: Pin<&TagString>, unicode: bool) -> *const c_char; unsafe fn toCString(self: Pin<&TagString>, unicode: bool) -> *const c_char;
#[namespace = "taglib_shim"] #[namespace = "taglib_shim"]

View file

@ -1,20 +1,9 @@
mod ffi; mod ffi;
mod stream; mod stream;
use std::pin::{pin, Pin};
use cxx::UniquePtr;
pub use stream::{RustStream, TagLibStream};
use ffi::bindings; use ffi::bindings;
use std::pin::Pin;
/// Audio properties of a media file pub use stream::{RustStream, TagLibStream};
#[derive(Default)]
pub struct AudioProperties {
pub length_in_milliseconds: i32,
pub bitrate_in_kilobits_per_second: i32,
pub sample_rate_in_hz: i32,
pub number_of_channels: i32,
}
pub enum File { pub enum File {
Unknown { Unknown {
@ -46,29 +35,13 @@ pub enum File {
}, },
} }
impl Default for File { /// Audio properties of a media file
fn default() -> Self { #[derive(Default)]
File::Unknown { pub struct AudioProperties {
audio_properties: None, pub length_in_milliseconds: i32,
} pub bitrate_in_kilobits_per_second: i32,
} pub sample_rate_in_hz: i32,
} pub number_of_channels: i32,
impl File {
/// Get the audio properties of the file, if available
pub fn audio_properties(&self) -> Option<&AudioProperties> {
match self {
File::Unknown { audio_properties, .. } |
File::MP3 { audio_properties, .. } |
File::FLAC { audio_properties, .. } |
File::MP4 { audio_properties, .. } |
File::OGG { audio_properties, .. } |
File::Opus { audio_properties, .. } |
File::WAV { audio_properties, .. } |
File::WavPack { audio_properties, .. } |
File::APE { audio_properties, .. } => audio_properties.as_ref()
}
}
} }
// Safe wrapper for FileRef that owns extracted data // Safe wrapper for FileRef that owns extracted data
@ -81,13 +54,13 @@ impl FileRef {
pub fn from_stream<'a, T: TagLibStream + 'a>(stream: T) -> Option<Self> { pub fn from_stream<'a, T: TagLibStream + 'a>(stream: T) -> Option<Self> {
// Create the RustStream wrapper // Create the RustStream wrapper
let rust_stream = stream::RustStream::new(stream); let rust_stream = stream::RustStream::new(stream);
// Convert to raw pointer for FFI // Convert to raw pointer for FFI
let raw_stream = Box::into_raw(Box::new(rust_stream)) as *mut bindings::RustStream; let raw_stream = Box::into_raw(Box::new(rust_stream)) as *mut bindings::RustStream;
// Create the RustIOStream C++ wrapper // Create the RustIOStream C++ wrapper
let iostream = unsafe { ffi::bindings::new_rust_iostream(raw_stream) }; let iostream = unsafe { ffi::bindings::new_rust_iostream(raw_stream) };
// Create FileRef from iostream // Create FileRef from iostream
let file_ref = ffi::bindings::new_FileRef_from_stream(iostream); let file_ref = ffi::bindings::new_FileRef_from_stream(iostream);
if file_ref.is_null() { if file_ref.is_null() {
@ -117,25 +90,63 @@ impl FileRef {
// Determine file type and create appropriate variant // Determine file type and create appropriate variant
let file = unsafe { let file = unsafe {
if ffi::bindings::File_isMPEG(file_ptr) { let mpeg_file = ffi::bindings::File_asMPEG(file_ptr);
File::MP3 { audio_properties } if !mpeg_file.is_null() {
} else if ffi::bindings::File_isFLAC(file_ptr) { return Some(FileRef {
File::FLAC { audio_properties } file: File::MP3 { audio_properties }
} else if ffi::bindings::File_isMP4(file_ptr) { });
File::MP4 { audio_properties }
} else if ffi::bindings::File_isOpus(file_ptr) {
File::Opus { audio_properties }
} else if ffi::bindings::File_isOgg(file_ptr) {
File::OGG { audio_properties }
} else if ffi::bindings::File_isWAV(file_ptr) {
File::WAV { audio_properties }
} else if ffi::bindings::File_isWavPack(file_ptr) {
File::WavPack { audio_properties }
} else if ffi::bindings::File_isAPE(file_ptr) {
File::APE { audio_properties }
} else {
File::Unknown { audio_properties }
} }
let flac_file = ffi::bindings::File_asFLAC(file_ptr);
if !flac_file.is_null() {
return Some(FileRef {
file: File::FLAC { audio_properties }
});
}
let mp4_file = ffi::bindings::File_asMP4(file_ptr);
if !mp4_file.is_null() {
return Some(FileRef {
file: File::MP4 { audio_properties }
});
}
let wav_file = ffi::bindings::File_asWAV(file_ptr);
if !wav_file.is_null() {
return Some(FileRef {
file: File::WAV { audio_properties }
});
}
let wavpack_file = ffi::bindings::File_asWavPack(file_ptr);
if !wavpack_file.is_null() {
return Some(FileRef {
file: File::WavPack { audio_properties }
});
}
let ape_file = ffi::bindings::File_asAPE(file_ptr);
if !ape_file.is_null() {
return Some(FileRef {
file: File::APE { audio_properties }
});
}
let vorbis_file = ffi::bindings::File_asVorbis(file_ptr);
if !vorbis_file.is_null() {
return Some(FileRef {
file: File::OGG { audio_properties }
});
}
let opus_file = ffi::bindings::File_asOpus(file_ptr);
if !opus_file.is_null() {
return Some(FileRef {
file: File::Opus { audio_properties }
});
}
File::Unknown { audio_properties }
}; };
// Clean up C++ objects - they will be dropped when file_ref is dropped // Clean up C++ objects - they will be dropped when file_ref is dropped
@ -147,4 +158,4 @@ impl FileRef {
pub fn file(&self) -> &File { pub fn file(&self) -> &File {
&self.file &self.file
} }
} }