From 65d8959bcfa389c62ae85baf1e476ca42766a6f3 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Mon, 17 Feb 2025 09:25:17 -0700 Subject: [PATCH] musikr: add basic mp4 tag --- musikr/src/main/jni/build.rs | 1 + musikr/src/main/jni/shim/mp4_shim.cpp | 27 ++++++++++++ musikr/src/main/jni/shim/mp4_shim.hpp | 22 ++++++++++ musikr/src/main/jni/src/taglib/bridge.rs | 35 ++++++++++------ musikr/src/main/jni/src/taglib/mod.rs | 1 + musikr/src/main/jni/src/taglib/mp4.rs | 53 ++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 13 deletions(-) create mode 100644 musikr/src/main/jni/shim/mp4_shim.cpp create mode 100644 musikr/src/main/jni/shim/mp4_shim.hpp create mode 100644 musikr/src/main/jni/src/taglib/mp4.rs diff --git a/musikr/src/main/jni/build.rs b/musikr/src/main/jni/build.rs index 58b4dc62a..00b7b7d45 100644 --- a/musikr/src/main/jni/build.rs +++ b/musikr/src/main/jni/build.rs @@ -123,6 +123,7 @@ fn main() { .file("shim/xiph_shim.cpp") .file("shim/id3v1_shim.cpp") .file("shim/id3v2_shim.cpp") + .file("shim/mp4_shim.cpp") .include(format!("taglib/pkg/{}/include", target)) .include(".") // Add the current directory to include path .flag_if_supported("-std=c++14"); diff --git a/musikr/src/main/jni/shim/mp4_shim.cpp b/musikr/src/main/jni/shim/mp4_shim.cpp new file mode 100644 index 000000000..a01add652 --- /dev/null +++ b/musikr/src/main/jni/shim/mp4_shim.cpp @@ -0,0 +1,27 @@ +#include "mp4_shim.hpp" +#include +#include +#include + +namespace taglib_shim { + ItemMapEntry::ItemMapEntry(TagLib::String key, TagLib::MP4::Item value) + : key_(std::move(key)), value_(std::move(value)) {} + + const TagLib::String& ItemMapEntry::key() const { + return key_; + } + + const TagLib::MP4::Item& ItemMapEntry::value() const { + return value_; + } + + std::unique_ptr> ItemMap_to_entries(const TagLib::MP4::ItemMap& map) { + auto entries = std::make_unique>(); + + for (auto it = map.begin(); it != map.end(); ++it) { + entries->emplace_back(it->first, it->second); + } + + return entries; + } +} \ No newline at end of file diff --git a/musikr/src/main/jni/shim/mp4_shim.hpp b/musikr/src/main/jni/shim/mp4_shim.hpp new file mode 100644 index 000000000..e4c00565c --- /dev/null +++ b/musikr/src/main/jni/shim/mp4_shim.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include +#include "rust/cxx.h" + +namespace taglib_shim { + class ItemMapEntry { + public: + ItemMapEntry(TagLib::String key, TagLib::MP4::Item value); + const TagLib::String& key() const; + const TagLib::MP4::Item& value() const; + + private: + TagLib::String key_; + TagLib::MP4::Item value_; + }; + + std::unique_ptr> ItemMap_to_entries(const TagLib::MP4::ItemMap& map); +} \ No newline at end of file diff --git a/musikr/src/main/jni/src/taglib/bridge.rs b/musikr/src/main/jni/src/taglib/bridge.rs index 39257f176..5d26f28a8 100644 --- a/musikr/src/main/jni/src/taglib/bridge.rs +++ b/musikr/src/main/jni/src/taglib/bridge.rs @@ -34,6 +34,7 @@ mod bridge_impl { include!("shim/xiph_shim.hpp"); include!("shim/id3v2_shim.hpp"); include!("shim/id3v1_shim.hpp"); + include!("shim/mp4_shim.hpp"); include!("taglib/mpegfile.h"); #[namespace = "TagLib"] @@ -45,15 +46,12 @@ mod bridge_impl { #[cxx_name = "FileRef"] type CPPFileRef; unsafe fn new_FileRef(stream: *mut CPPIOStream) -> UniquePtr; - #[cxx_name = "isNull"] fn isNull(self: Pin<&CPPFileRef>) -> bool; - #[cxx_name = "file"] fn file(self: Pin<&CPPFileRef>) -> *mut CPPFile; #[namespace = "TagLib"] #[cxx_name = "File"] type CPPFile; - #[cxx_name = "audioProperties"] fn audioProperties(self: Pin<&CPPFile>) -> *mut CppAudioProperties; #[namespace = "taglib_shim"] unsafe fn File_asVorbis(file: *mut CPPFile) -> *mut CPPVorbisFile; @@ -71,13 +69,9 @@ mod bridge_impl { #[namespace = "TagLib"] #[cxx_name = "AudioProperties"] type CppAudioProperties; - #[cxx_name = "lengthInMilliseconds"] fn lengthInMilliseconds(self: Pin<&CppAudioProperties>) -> i32; - #[cxx_name = "bitrate"] fn bitrate(self: Pin<&CppAudioProperties>) -> i32; - #[cxx_name = "sampleRate"] fn sampleRate(self: Pin<&CppAudioProperties>) -> i32; - #[cxx_name = "channels"] fn channels(self: Pin<&CppAudioProperties>) -> i32; #[namespace = "TagLib::Ogg::Vorbis"] @@ -97,7 +91,6 @@ mod bridge_impl { #[namespace = "TagLib::FLAC"] #[cxx_name = "File"] type CPPFLACFile; - #[cxx_name = "xiphComment"] fn xiphComment(self: Pin<&mut CPPFLACFile>, create: bool) -> *mut CPPXiphComment; #[namespace = "taglib_shim"] fn FLACFile_pictureList(file: Pin<&mut CPPFLACFile>) -> UniquePtr; @@ -118,7 +111,6 @@ mod bridge_impl { #[namespace = "TagLib::MPEG"] #[cxx_name = "File"] type CPPMPEGFile; - #[cxx_name = "ID3v2Tag"] fn ID3v2Tag(self: Pin<&mut CPPMPEGFile>, create: bool) -> *mut CPPID3v2Tag; #[namespace = "TagLib::MP4"] @@ -140,7 +132,6 @@ mod bridge_impl { type CPPXiphComment; // Explicit lifecycle definition to state while the Pin is temporary, the CPPFieldListMap // ref returned actually has the same lifetime as the CPPXiphComment. - #[cxx_name = "fieldListMap"] fn fieldListMap<'slf, 'file_ref>(self: Pin<&'slf CPPXiphComment>) -> &'file_ref CPPFieldListMap; #[namespace = "TagLib"] @@ -154,9 +145,7 @@ mod bridge_impl { #[namespace = "taglib_shim"] #[cxx_name = "FieldListEntry"] type CPPFieldListEntry; - #[cxx_name = "key"] fn key(self: Pin<&CPPFieldListEntry>) -> &CPPString; - #[cxx_name = "value"] fn value(self: Pin<&CPPFieldListEntry>) -> &CPPStringList; #[namespace = "TagLib::ID3v2"] @@ -221,7 +210,6 @@ mod bridge_impl { #[namespace = "TagLib"] #[cxx_name = "String"] type CPPString; - #[cxx_name = "toCString"] fn toCString(self: Pin<&CPPString>, unicode: bool) -> *const c_char; #[namespace = "TagLib"] @@ -249,6 +237,27 @@ mod bridge_impl { fn ID3v1Tag_genreIndex(tag: Pin<&CPPID3v1Tag>) -> u32; fn ID3v1Tag_year(tag: Pin<&CPPID3v1Tag>) -> u32; fn ID3v1Tag_track(tag: Pin<&CPPID3v1Tag>) -> u32; + + #[namespace = "TagLib::MP4"] + #[cxx_name = "Tag"] + type CPPMP4Tag; + + #[namespace = "TagLib::MP4"] + #[cxx_name = "ItemMap"] + type CPPItemMap; + fn itemMap<'slf, 'file_ref>(self: Pin<&'slf CPPMP4Tag>) -> &'file_ref CPPItemMap; + fn ItemMap_to_entries(map: Pin<&CPPItemMap>) -> UniquePtr>; + + #[namespace = "taglib_shim"] + #[cxx_name = "ItemMapEntry"] + type CPPItemMapEntry; + fn key(self: Pin<&CPPItemMapEntry>) -> &CPPString; + fn value(self: Pin<&CPPItemMapEntry>) -> &CPPMP4Item; + + #[namespace = "TagLib::MP4"] + #[cxx_name = "Item"] + type CPPMP4Item; + } } diff --git a/musikr/src/main/jni/src/taglib/mod.rs b/musikr/src/main/jni/src/taglib/mod.rs index 976ec4bbd..1f7e5a361 100644 --- a/musikr/src/main/jni/src/taglib/mod.rs +++ b/musikr/src/main/jni/src/taglib/mod.rs @@ -11,3 +11,4 @@ pub mod xiph; pub mod tk; pub mod id3v2; pub mod id3v1; +pub mod mp4; diff --git a/musikr/src/main/jni/src/taglib/mp4.rs b/musikr/src/main/jni/src/taglib/mp4.rs new file mode 100644 index 000000000..1180bdb09 --- /dev/null +++ b/musikr/src/main/jni/src/taglib/mp4.rs @@ -0,0 +1,53 @@ +pub use super::bridge::CPPMP4Tag; +use super::bridge::{CPPItemMap, ItemMap_to_entries, CPPItemMapEntry}; +use super::tk; +use super::this::{OwnedThis, RefThis, RefThisMut, ThisMut, This}; +use std::collections::HashMap; +use std::pin::Pin; + +pub struct MP4Tag<'file_ref> { + this: RefThisMut<'file_ref, CPPMP4Tag>, +} + +impl<'file_ref> MP4Tag<'file_ref> { + pub fn new(this: RefThisMut<'file_ref, CPPMP4Tag>) -> Self { + Self { this } + } + + pub fn item_map<'slf>(&'slf self) -> ItemMap<'file_ref> { + let map: &'file_ref CPPItemMap = self.this.pin().itemMap(); + let map_this = unsafe { RefThis::new(map) }; + ItemMap::new(map_this) + } +} + +pub struct ItemMap<'file_ref> { + this: RefThis<'file_ref, CPPItemMap>, +} + +impl<'file_ref> ItemMap<'file_ref> { + pub fn new(this: RefThis<'file_ref, CPPItemMap>) -> Self { + Self { this } + } + + pub fn to_hashmap(&self) -> HashMap { + let cxx_vec = ItemMap_to_entries(self.this.pin()); + cxx_vec + .iter() + .map(|property| { + // SAFETY: + // - This pin is only used in this unsafe scope. + // - The pin is used as a C++ this pointer in the ffi call, which does + // not change address by C++ semantics. + // - The values returned are copied and thus not dependent on the address + // of self. + let property_pin = unsafe { Pin::new_unchecked(property) }; + let key_ref = property_pin.key(); + let key_this = unsafe { RefThis::new(key_ref) }; + let key = tk::String::new(key_this).to_string(); + // For now, we're just returning () as a placeholder for MP4::Item + (key, ()) + }) + .collect() + } +} \ No newline at end of file