musikr: add id3v2 tags

This commit is contained in:
Alexander Capehart 2025-02-15 15:37:54 -07:00
parent 0f3bed413d
commit f7d61cd1dc
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
11 changed files with 325 additions and 51 deletions

View file

@ -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");

View file

@ -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<const TagLib::ID3v2::TextIdentificationFrame*>(frame);
}
const TagLib::ID3v2::UserTextIdentificationFrame* Frame_asUserTextIdentification(const TagLib::ID3v2::Frame* frame) {
return dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(frame);
}
const TagLib::ID3v2::AttachedPictureFrame* Frame_asAttachedPicture(const TagLib::ID3v2::Frame* frame) {
return dynamic_cast<const TagLib::ID3v2::AttachedPictureFrame*>(frame);
}
std::unique_ptr<TagLib::ByteVector> AttachedPictureFrame_picture(const TagLib::ID3v2::AttachedPictureFrame& frame) {
return std::make_unique<TagLib::ByteVector>(frame.picture());
}
std::unique_ptr<TagLib::StringList> TextIdentificationFrame_fieldList(const TagLib::ID3v2::TextIdentificationFrame& frame) {
return std::make_unique<TagLib::StringList>(frame.fieldList());
}
std::unique_ptr<TagLib::StringList> UserTextIdentificationFrame_fieldList(const TagLib::ID3v2::UserTextIdentificationFrame& frame) {
return std::make_unique<TagLib::StringList>(frame.fieldList());
}
TagLib::ID3v2::Tag* File_ID3v2Tag(TagLib::MPEG::File* file, bool create) {
return file->ID3v2Tag(create);
}
std::unique_ptr<std::vector<WrappedFrame>> Tag_frameList(const TagLib::ID3v2::Tag& tag) {
auto frames = std::make_unique<std::vector<WrappedFrame>>();
for (const auto& frame : tag.frameList()) {
frames->push_back(WrappedFrame{frame});
}
return frames;
}
}

View file

@ -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<TagLib::ByteVector> AttachedPictureFrame_picture(const TagLib::ID3v2::AttachedPictureFrame& frame);
std::unique_ptr<TagLib::StringList> TextIdentificationFrame_fieldList(const TagLib::ID3v2::TextIdentificationFrame& frame);
std::unique_ptr<TagLib::StringList> UserTextIdentificationFrame_fieldList(const TagLib::ID3v2::UserTextIdentificationFrame& frame);
// ID3v2 tag access
TagLib::ID3v2::Tag* File_ID3v2Tag(TagLib::MPEG::File* file, bool create);
std::unique_ptr<std::vector<WrappedFrame>> Tag_frameList(const TagLib::ID3v2::Tag& tag);
}

View file

@ -8,7 +8,6 @@ mod taglib;
mod jstream;
use taglib::file_ref::FileRef;
use taglib::file::File;
use jstream::JInputStream;
type SharedEnv<'local> = Rc<RefCell<JNIEnv<'local>>>;

View file

@ -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<CPPByteVector>;
#[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<CxxVector<CPPFieldListEntry>>;
#[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<CPPByteVector>;
#[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<CxxVector<CPPFieldListEntry>>;
#[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<CxxVector<WrappedFrame>>;
#[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<CPPStringList>;
#[namespace = "TagLib::ID3v2"]
#[cxx_name = "UserTextIdentificationFrame"]
type CPPID3v2UserTextIdentificationFrame;
#[namespace = "taglib_shim"]
fn UserTextIdentificationFrame_fieldList(frame: Pin<&CPPID3v2UserTextIdentificationFrame>) -> UniquePtr<CPPStringList>;
#[namespace = "TagLib::ID3v2"]
#[cxx_name = "AttachedPictureFrame"]
type CPPID3v2AttachedPictureFrame;
#[namespace = "taglib_shim"]
fn AttachedPictureFrame_picture(frame: Pin<&CPPID3v2AttachedPictureFrame>) -> UniquePtr<CPPByteVector>;
#[namespace = "TagLib"]
#[cxx_name = "String"]

View file

@ -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<MPEGFile<'file_ref>> {
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))
}
}
}

View file

@ -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<Frame<'file_ref>> {
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<TextIdentificationFrame<'file_ref>> {
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<UserTextIdentificationFrame<'file_ref>> {
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<AttachedPictureFrame<'file_ref>> {
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)
}
}

View file

@ -7,4 +7,6 @@ pub mod audioproperties;
pub mod ogg;
pub mod flac;
pub mod xiph;
pub mod tk;
pub mod mpeg;
pub mod tk;
pub mod id3;

View file

@ -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<Tag<'file_ref>> {
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))
}
}

View file

@ -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<T>,
},
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<CPPStringList>) -> 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<std::string::String> {
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| {

View file

@ -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()