diff --git a/musikr/src/main/jni/build.rs b/musikr/src/main/jni/build.rs index 49edd5c79..bbdc45a01 100644 --- a/musikr/src/main/jni/build.rs +++ b/musikr/src/main/jni/build.rs @@ -121,6 +121,7 @@ fn main() { .file("shim/tk_shim.cpp") .file("shim/picture_shim.cpp") .file("shim/xiph_shim.cpp") + .file("shim/id3_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/id3_shim.cpp b/musikr/src/main/jni/shim/id3_shim.cpp new file mode 100644 index 000000000..eb75555a2 --- /dev/null +++ b/musikr/src/main/jni/shim/id3_shim.cpp @@ -0,0 +1,39 @@ +#include "id3_shim.hpp" + +namespace taglib_shim { + const TagLib::ID3v2::TextIdentificationFrame* Frame_asTextIdentification(const TagLib::ID3v2::Frame* frame) { + return dynamic_cast(frame); + } + + const TagLib::ID3v2::UserTextIdentificationFrame* Frame_asUserTextIdentification(const TagLib::ID3v2::Frame* frame) { + return dynamic_cast(frame); + } + + const TagLib::ID3v2::AttachedPictureFrame* Frame_asAttachedPicture(const TagLib::ID3v2::Frame* frame) { + return dynamic_cast(frame); + } + + std::unique_ptr AttachedPictureFrame_picture(const TagLib::ID3v2::AttachedPictureFrame& frame) { + return std::make_unique(frame.picture()); + } + + std::unique_ptr TextIdentificationFrame_fieldList(const TagLib::ID3v2::TextIdentificationFrame& frame) { + return std::make_unique(frame.fieldList()); + } + + std::unique_ptr UserTextIdentificationFrame_fieldList(const TagLib::ID3v2::UserTextIdentificationFrame& frame) { + return std::make_unique(frame.fieldList()); + } + + TagLib::ID3v2::Tag* File_ID3v2Tag(TagLib::MPEG::File* file, bool create) { + return file->ID3v2Tag(create); + } + + std::unique_ptr> Tag_frameList(const TagLib::ID3v2::Tag& tag) { + auto frames = std::make_unique>(); + for (const auto& frame : tag.frameList()) { + frames->push_back(WrappedFrame{frame}); + } + return frames; + } +} \ No newline at end of file diff --git a/musikr/src/main/jni/shim/id3_shim.hpp b/musikr/src/main/jni/shim/id3_shim.hpp new file mode 100644 index 000000000..7058ccf6f --- /dev/null +++ b/musikr/src/main/jni/shim/id3_shim.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "rust/cxx.h" +#include "taglib/id3v2tag.h" +#include "taglib/id3v2frame.h" +#include "taglib/textidentificationframe.h" +#include "taglib/unsynchronizedlyricsframe.h" +#include "taglib/attachedpictureframe.h" +#include "taglib/tbytevector.h" +#include "taglib/mpegfile.h" + +namespace taglib_shim { + struct WrappedFrame { + const TagLib::ID3v2::Frame* inner; + const TagLib::ID3v2::Frame* get() const { return inner; } + }; + + // Frame type checking and casting + const TagLib::ID3v2::TextIdentificationFrame* Frame_asTextIdentification(const TagLib::ID3v2::Frame* frame); + const TagLib::ID3v2::UserTextIdentificationFrame* Frame_asUserTextIdentification(const TagLib::ID3v2::Frame* frame); + const TagLib::ID3v2::AttachedPictureFrame* Frame_asAttachedPicture(const TagLib::ID3v2::Frame* frame); + + // Frame data access + std::unique_ptr AttachedPictureFrame_picture(const TagLib::ID3v2::AttachedPictureFrame& frame); + std::unique_ptr TextIdentificationFrame_fieldList(const TagLib::ID3v2::TextIdentificationFrame& frame); + std::unique_ptr UserTextIdentificationFrame_fieldList(const TagLib::ID3v2::UserTextIdentificationFrame& frame); + + // ID3v2 tag access + TagLib::ID3v2::Tag* File_ID3v2Tag(TagLib::MPEG::File* file, bool create); + std::unique_ptr> Tag_frameList(const TagLib::ID3v2::Tag& tag); +} \ No newline at end of file diff --git a/musikr/src/main/jni/src/lib.rs b/musikr/src/main/jni/src/lib.rs index 45b0417e0..efd0b9ad3 100644 --- a/musikr/src/main/jni/src/lib.rs +++ b/musikr/src/main/jni/src/lib.rs @@ -8,7 +8,6 @@ mod taglib; mod jstream; use taglib::file_ref::FileRef; -use taglib::file::File; use jstream::JInputStream; type SharedEnv<'local> = Rc>>; diff --git a/musikr/src/main/jni/src/taglib/bridge.rs b/musikr/src/main/jni/src/taglib/bridge.rs index d2f90f14f..acdea39d4 100644 --- a/musikr/src/main/jni/src/taglib/bridge.rs +++ b/musikr/src/main/jni/src/taglib/bridge.rs @@ -32,6 +32,8 @@ mod bridge_impl { include!("shim/tk_shim.hpp"); include!("shim/picture_shim.hpp"); include!("shim/xiph_shim.hpp"); + include!("shim/id3_shim.hpp"); + include!("taglib/mpegfile.h"); #[namespace = "TagLib"] #[cxx_name = "IOStream"] @@ -55,6 +57,18 @@ mod bridge_impl { type CPPFile; #[cxx_name = "audioProperties"] fn audioProperties(self: Pin<&CPPFile>) -> *mut CppAudioProperties; + #[namespace = "taglib_shim"] + unsafe fn File_asVorbis(file: *mut CPPFile) -> *mut CPPVorbisFile; + #[namespace = "taglib_shim"] + unsafe fn File_asOpus(file: *mut CPPFile) -> *mut CPPOpusFile; + #[namespace = "taglib_shim"] + unsafe fn File_asMPEG(file: *mut CPPFile) -> *mut CPPMPEGFile; + #[namespace = "taglib_shim"] + unsafe fn File_asFLAC(file: *mut CPPFile) -> *mut CPPFLACFile; + #[namespace = "taglib_shim"] + unsafe fn File_asMP4(file: *mut CPPFile) -> *mut CPPMP4File; + #[namespace = "taglib_shim"] + unsafe fn File_asWAV(file: *mut CPPFile) -> *mut CPPWAVFile; #[namespace = "TagLib"] #[cxx_name = "AudioProperties"] @@ -68,34 +82,6 @@ mod bridge_impl { #[cxx_name = "channels"] fn channels(self: Pin<&CppAudioProperties>) -> i32; - #[namespace = "TagLib::FLAC"] - #[cxx_name = "Picture"] - type CPPFLACPicture; - #[namespace = "taglib_shim"] - fn Picture_data(picture: Pin<&CPPFLACPicture>) -> UniquePtr; - - #[namespace = "TagLib::Ogg"] - #[cxx_name = "XiphComment"] - type CPPXiphComment; - #[cxx_name = "fieldListMap"] - fn fieldListMap(self: Pin<& CPPXiphComment>) -> &CPPFieldListMap; - - #[namespace = "TagLib"] - #[cxx_name = "SimplePropertyMap"] - type CPPFieldListMap; - #[namespace = "taglib_shim"] - fn FieldListMap_to_entries( - field_list_map: Pin<&CPPFieldListMap>, - ) -> UniquePtr>; - - #[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::Ogg::Vorbis"] #[cxx_name = "File"] type CPPVorbisFile; @@ -132,6 +118,8 @@ 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"] #[cxx_name = "File"] @@ -141,18 +129,71 @@ mod bridge_impl { #[cxx_name = "File"] type CPPWAVFile; + #[namespace = "TagLib::FLAC"] + #[cxx_name = "Picture"] + type CPPFLACPicture; #[namespace = "taglib_shim"] - unsafe fn File_asVorbis(file: *mut CPPFile) -> *mut CPPVorbisFile; + fn Picture_data(picture: Pin<&CPPFLACPicture>) -> UniquePtr; + + #[namespace = "TagLib::Ogg"] + #[cxx_name = "XiphComment"] + type CPPXiphComment; + #[cxx_name = "fieldListMap"] + fn fieldListMap(self: Pin<& CPPXiphComment>) -> &CPPFieldListMap; + + #[namespace = "TagLib"] + #[cxx_name = "SimplePropertyMap"] + type CPPFieldListMap; #[namespace = "taglib_shim"] - unsafe fn File_asOpus(file: *mut CPPFile) -> *mut CPPOpusFile; + fn FieldListMap_to_entries( + field_list_map: Pin<&CPPFieldListMap>, + ) -> UniquePtr>; + #[namespace = "taglib_shim"] - unsafe fn File_asMPEG(file: *mut CPPFile) -> *mut CPPMPEGFile; + #[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"] + #[cxx_name = "Tag"] + type CPPID3v2Tag; #[namespace = "taglib_shim"] - unsafe fn File_asFLAC(file: *mut CPPFile) -> *mut CPPFLACFile; + fn Tag_frameList(tag: Pin<&CPPID3v2Tag>) -> UniquePtr>; + + #[namespace = "TagLib::ID3v2"] + #[cxx_name = "Frame"] + type CPPID3v2Frame; #[namespace = "taglib_shim"] - unsafe fn File_asMP4(file: *mut CPPFile) -> *mut CPPMP4File; + unsafe fn Frame_asTextIdentification(frame: *const CPPID3v2Frame) -> *const CPPID3v2TextIdentificationFrame; #[namespace = "taglib_shim"] - unsafe fn File_asWAV(file: *mut CPPFile) -> *mut CPPWAVFile; + unsafe fn Frame_asUserTextIdentification(frame: *const CPPID3v2Frame) -> *const CPPID3v2UserTextIdentificationFrame; + #[namespace = "taglib_shim"] + unsafe fn Frame_asAttachedPicture(frame: *const CPPID3v2Frame) -> *const CPPID3v2AttachedPictureFrame; + + #[namespace = "taglib_shim"] + type WrappedFrame; + fn get(self: &WrappedFrame) -> *const CPPID3v2Frame; + + #[namespace = "TagLib::ID3v2"] + #[cxx_name = "TextIdentificationFrame"] + type CPPID3v2TextIdentificationFrame; + #[namespace = "taglib_shim"] + fn TextIdentificationFrame_fieldList(frame: Pin<&CPPID3v2TextIdentificationFrame>) -> UniquePtr; + + #[namespace = "TagLib::ID3v2"] + #[cxx_name = "UserTextIdentificationFrame"] + type CPPID3v2UserTextIdentificationFrame; + #[namespace = "taglib_shim"] + fn UserTextIdentificationFrame_fieldList(frame: Pin<&CPPID3v2UserTextIdentificationFrame>) -> UniquePtr; + + #[namespace = "TagLib::ID3v2"] + #[cxx_name = "AttachedPictureFrame"] + type CPPID3v2AttachedPictureFrame; + #[namespace = "taglib_shim"] + fn AttachedPictureFrame_picture(frame: Pin<&CPPID3v2AttachedPictureFrame>) -> UniquePtr; #[namespace = "TagLib"] #[cxx_name = "String"] diff --git a/musikr/src/main/jni/src/taglib/file.rs b/musikr/src/main/jni/src/taglib/file.rs index b6d49ccf9..b4457c2da 100644 --- a/musikr/src/main/jni/src/taglib/file.rs +++ b/musikr/src/main/jni/src/taglib/file.rs @@ -1,15 +1,16 @@ use std::pin::Pin; -use super::bridge::{self, CPPFile}; +use super::bridge::{self, CPPFile, CPPMPEGFile}; use super::audioproperties::AudioProperties; +use super::mpeg::MPEGFile; use super::ogg::OpusFile; use super::ogg::VorbisFile; use super::flac::FLACFile; +use super::id3::Tag; pub struct File<'file_ref> { this: Pin<&'file_ref mut CPPFile> } - impl<'file_ref> File<'file_ref> { pub(super) fn new(this: Pin<&'file_ref mut CPPFile>) -> Self { Self { this } @@ -92,12 +93,25 @@ impl<'file_ref> File<'file_ref> { let flac_pin = flac_ref.map(|flac| unsafe { Pin::new_unchecked(flac) }); flac_pin.map(|flac| FLACFile::new(flac)) } -} -impl<'file_ref> Drop for File<'file_ref> { - fn drop(&mut self) { - unsafe { - std::ptr::drop_in_place(&mut self.this); - } + pub fn as_mpeg(&mut self) -> Option> { + let mpeg_file = unsafe { + // SAFETY: + // This FFI function will be a simple C++ dynamic_cast, which checks if + // the file can be cased down to an MPEG file. If the cast fails, a null + // pointer is returned, which will be handled by as_ref's null checking. + bridge::File_asMPEG(self.this.as_mut().get_unchecked_mut() as *mut CPPFile) + }; + let mpeg_ref = unsafe { + // SAFETY: + // - This points to a C++ FFI type ensured to be aligned by cxx's codegen. + // - The null-safe version is being used. + // - This points to a C++FFI type ensured to be valid by cxx's codegen. + // - There are no datapaths that will yield any mutable pointers or references + // to this, ensuring that it will not be mutated as per the aliasing rules. + mpeg_file.as_mut() + }; + let mpeg_pin = mpeg_ref.map(|mpeg| unsafe { Pin::new_unchecked(mpeg) }); + mpeg_pin.map(|mpeg| MPEGFile::new(mpeg)) } -} \ No newline at end of file +} diff --git a/musikr/src/main/jni/src/taglib/id3.rs b/musikr/src/main/jni/src/taglib/id3.rs new file mode 100644 index 000000000..3b0c2bd8b --- /dev/null +++ b/musikr/src/main/jni/src/taglib/id3.rs @@ -0,0 +1,108 @@ +use super::bridge::{ + self, CPPID3v2Frame, CPPID3v2TextIdentificationFrame, + CPPID3v2UserTextIdentificationFrame, CPPID3v2AttachedPictureFrame, CPPID3v2Tag +}; +use super::tk::{ByteVector, StringList}; +use std::pin::Pin; + +pub struct Tag<'file_ref> { + this: Pin<&'file_ref CPPID3v2Tag> +} + +impl<'file_ref> Tag<'file_ref> { + pub(super) fn new(this: Pin<&'file_ref CPPID3v2Tag>) -> Self { + Self { this } + } + + pub fn frames(&self) -> Vec> { + let frames = bridge::Tag_frameList(self.this.as_ref()); + frames.iter().map(|frame| { + let frame_ptr = frame.get(); + let frame_ref = unsafe { frame_ptr.as_ref().unwrap() }; + let frame_pin = unsafe { Pin::new_unchecked(frame_ref) }; + Frame::new(frame_pin) + }).collect() + } +} + +pub struct Frame<'file_ref> { + this: Pin<&'file_ref CPPID3v2Frame> +} + +impl<'file_ref> Frame<'file_ref> { + pub(super) fn new(this: Pin<&'file_ref CPPID3v2Frame>) -> Self { + Self { this } + } + + pub fn as_text_identification(&mut self) -> Option> { + let frame = unsafe { + bridge::Frame_asTextIdentification(self.this.as_ref().get_ref()) + }; + let frame_ref = unsafe { frame.as_ref() }; + let frame_pin = frame_ref.map(|frame| unsafe { Pin::new_unchecked(frame) }); + frame_pin.map(|frame| TextIdentificationFrame::new(frame)) + } + + pub fn as_user_text_identification(&mut self) -> Option> { + let frame = unsafe { + bridge::Frame_asUserTextIdentification(self.this.as_ref().get_ref()) + }; + let frame_ref = unsafe { frame.as_ref() }; + let frame_pin = frame_ref.map(|frame| unsafe { Pin::new_unchecked(frame) }); + frame_pin.map(|frame| UserTextIdentificationFrame::new(frame)) + } + + pub fn as_attached_picture(&mut self) -> Option> { + let frame = unsafe { + bridge::Frame_asAttachedPicture(self.this.as_ref().get_ref()) + }; + let frame_ref = unsafe { frame.as_ref() }; + let frame_pin = frame_ref.map(|frame| unsafe { Pin::new_unchecked(frame) }); + frame_pin.map(|frame| AttachedPictureFrame::new(frame)) + } +} + +pub struct TextIdentificationFrame<'file_ref> { + this: Pin<&'file_ref CPPID3v2TextIdentificationFrame> +} + +impl<'file_ref> TextIdentificationFrame<'file_ref> { + pub(super) fn new(this: Pin<&'file_ref CPPID3v2TextIdentificationFrame>) -> Self { + Self { this } + } + + pub fn field_list<'slf>(&'slf self) -> StringList<'file_ref> { + let field_list = bridge::TextIdentificationFrame_fieldList(self.this); + StringList::owned(field_list) + } +} + +pub struct UserTextIdentificationFrame<'file_ref> { + this: Pin<&'file_ref CPPID3v2UserTextIdentificationFrame> +} + +impl<'file_ref> UserTextIdentificationFrame<'file_ref> { + pub(super) fn new(this: Pin<&'file_ref CPPID3v2UserTextIdentificationFrame>) -> Self { + Self { this } + } + + pub fn values<'slf>(&'slf self) -> StringList<'file_ref> { + let values = bridge::UserTextIdentificationFrame_fieldList(self.this); + StringList::owned(values) + } +} + +pub struct AttachedPictureFrame<'file_ref> { + this: Pin<&'file_ref CPPID3v2AttachedPictureFrame> +} + +impl<'file_ref> AttachedPictureFrame<'file_ref> { + pub(super) fn new(this: Pin<&'file_ref CPPID3v2AttachedPictureFrame>) -> Self { + Self { this } + } + + pub fn picture(&self) -> ByteVector<'file_ref> { + let picture = bridge::AttachedPictureFrame_picture(self.this.as_ref()); + ByteVector::new(picture) + } +} \ No newline at end of file diff --git a/musikr/src/main/jni/src/taglib/mod.rs b/musikr/src/main/jni/src/taglib/mod.rs index 3e00ce9f7..dc2dd7fe2 100644 --- a/musikr/src/main/jni/src/taglib/mod.rs +++ b/musikr/src/main/jni/src/taglib/mod.rs @@ -7,4 +7,6 @@ pub mod audioproperties; pub mod ogg; pub mod flac; pub mod xiph; -pub mod tk; \ No newline at end of file +pub mod mpeg; +pub mod tk; +pub mod id3; \ No newline at end of file diff --git a/musikr/src/main/jni/src/taglib/mpeg.rs b/musikr/src/main/jni/src/taglib/mpeg.rs new file mode 100644 index 000000000..0d7de8738 --- /dev/null +++ b/musikr/src/main/jni/src/taglib/mpeg.rs @@ -0,0 +1,20 @@ +use std::pin::Pin; +use super::bridge::{self, CPPMPEGFile}; +use super::id3::Tag; + +pub struct MPEGFile<'file_ref> { + this: Pin<&'file_ref mut CPPMPEGFile> +} + +impl<'file_ref> MPEGFile<'file_ref> { + pub(super) fn new(this: Pin<&'file_ref mut CPPMPEGFile>) -> Self { + Self { this } + } + + pub fn id3v2_tag(&mut self) -> Option> { + let tag = self.this.as_mut().ID3v2Tag(false); + let tag_ref = unsafe { tag.as_ref() }; + let tag_pin = tag_ref.map(|tag| unsafe { Pin::new_unchecked(tag) }); + tag_pin.map(|tag| Tag::new(tag)) + } +} diff --git a/musikr/src/main/jni/src/taglib/tk.rs b/musikr/src/main/jni/src/taglib/tk.rs index bfa426e08..a24a7116e 100644 --- a/musikr/src/main/jni/src/taglib/tk.rs +++ b/musikr/src/main/jni/src/taglib/tk.rs @@ -1,9 +1,19 @@ use super::bridge::{self, CPPByteVector, CPPString, CPPStringList}; -use cxx::UniquePtr; +use cxx::{UniquePtr, memory::UniquePtrTarget}; use std::marker::PhantomData; use std::pin::Pin; use std::{ffi::CStr, string::ToString}; +enum This<'file_ref, T : UniquePtrTarget> { + Owned { + data: PhantomData<&'file_ref T>, + this: UniquePtr, + }, + Ref { + this: Pin<&'file_ref T>, + } +} + pub struct String<'file_ref> { this: Pin<&'file_ref CPPString>, } @@ -14,6 +24,7 @@ impl<'file_ref> String<'file_ref> { } } + impl<'file_ref> ToString for String<'file_ref> { fn to_string(&self) -> std::string::String { let c_str = self.this.toCString(true); @@ -35,16 +46,24 @@ impl<'file_ref> ToString for String<'file_ref> { } pub struct StringList<'file_ref> { - this: Pin<&'file_ref CPPStringList>, + this: This<'file_ref, CPPStringList>, } impl<'file_ref> StringList<'file_ref> { - pub(super) fn new(this: Pin<&'file_ref CPPStringList>) -> Self { - Self { this } + pub(super) fn owned(this: UniquePtr) -> Self { + Self { this: This::Owned { data: PhantomData, this } } + } + + pub(super) fn reference(this: Pin<&'file_ref CPPStringList>) -> Self { + Self { this: This::Ref { this } } } pub fn to_vec(&self) -> Vec { - let cxx_values = bridge::StringList_to_vector(self.this); + let pin = match &self.this { + This::Owned { this, .. } => unsafe { Pin::new_unchecked(this.as_ref().unwrap()) }, + This::Ref { this } => *this, + }; + let cxx_values = bridge::StringList_to_vector(pin); cxx_values .iter() .map(|value| { diff --git a/musikr/src/main/jni/src/taglib/xiph.rs b/musikr/src/main/jni/src/taglib/xiph.rs index 3d2030359..54f45dc36 100644 --- a/musikr/src/main/jni/src/taglib/xiph.rs +++ b/musikr/src/main/jni/src/taglib/xiph.rs @@ -63,7 +63,7 @@ impl<'file_ref> FieldListMap<'file_ref> { let key = tk::String::new(key_pin).to_string(); let value_ref = property_pin.value(); let value_pin = unsafe { Pin::new_unchecked(value_ref) }; - let value = tk::StringList::new(value_pin).to_vec(); + let value = tk::StringList::reference(value_pin).to_vec(); (key, value) }) .collect()