Files

209 lines
7.6 KiB
C++
Raw Permalink Normal View History

2026-03-11 20:43:25 +08:00
// Uses TagLib to Parse!
// this is just a simple wrapper for JNI Bridge
// git->https://github.com/taglib/taglib
#include <unistd.h>
#include <sys/syscall.h>
#include <cstdint>
#include <fcntl.h>
#include <jni.h>
#include <ctime>
#include <sys/time.h>
#include <android/log.h>
#include <sys/stat.h>
#include <cstring>
#include <cstdlib>
#include <csignal>
#include <cstdio>
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <taglib/tbytevector.h>
#include <taglib/mpegfile.h>
#include <taglib/id3v2tag.h>
#include <taglib/id3v2frame.h>
#include <taglib/attachedpictureframe.h>
#include <taglib/unsynchronizedlyricsframe.h>
#include <taglib/flacfile.h>
#include <taglib/xiphcomment.h>
#define LOG_TAG "System"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
extern "C" JNIEXPORT jobjectArray JNICALL
Java_love_qz_music_controller_storage_AudioTag_readMediaTags(JNIEnv *env, jobject thiz, jint fd) {
if (fd < 0) {
LOGE("Invalid file descriptor passed: %d", fd);
return nullptr;
}
char fd_path[64];
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", fd);
TagLib::FileRef f(fd_path);
if (f.isNull() || !f.tag() || !f.file()) {
return nullptr;
}
auto* mp3File = dynamic_cast<TagLib::MPEG::File*>(f.file());
auto* flacFile = dynamic_cast<TagLib::FLAC::File*>(f.file());
if (!mp3File && !flacFile) {
return nullptr;
}
TagLib::Tag *tag = f.tag();
std::string name = tag->title().to8Bit(true);
std::string artist = tag->artist().to8Bit(true);
std::string album = tag->album().to8Bit(true);
std::string lyric;
TagLib::ByteVector picData;
if (mp3File && mp3File->ID3v2Tag()) {
TagLib::ID3v2::Tag* id3v2 = mp3File->ID3v2Tag();
TagLib::ID3v2::FrameList picFrames = id3v2->frameList("APIC");
if (!picFrames.isEmpty()) {
auto* picFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(picFrames.front());
if (picFrame) picData = picFrame->picture();
}
TagLib::ID3v2::FrameList lyricFrames = id3v2->frameList("USLT");
if (!lyricFrames.isEmpty()) {
auto* lyricFrame = dynamic_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame*>(lyricFrames.front());
if (lyricFrame) lyric = lyricFrame->text().to8Bit(true);
}
}
else if (flacFile) {
const TagLib::List<TagLib::FLAC::Picture*>& picList = flacFile->pictureList();
if (!picList.isEmpty()) {
picData = picList.front()->data();
}
if (flacFile->xiphComment()) {
TagLib::Ogg::FieldListMap fields = flacFile->xiphComment()->fieldListMap();
if (fields.contains("LYRICS")) {
lyric = fields["LYRICS"].front().to8Bit(true);
}
}
}
jclass objClass = env->FindClass("java/lang/Object");
jobjectArray resultArray = env->NewObjectArray(5, objClass, nullptr);
env->SetObjectArrayElement(resultArray, 0, env->NewStringUTF(name.c_str()));
env->SetObjectArrayElement(resultArray, 1, env->NewStringUTF(artist.c_str()));
env->SetObjectArrayElement(resultArray, 2, env->NewStringUTF(album.c_str()));
env->SetObjectArrayElement(resultArray, 3, env->NewStringUTF(lyric.c_str()));
if (!picData.isEmpty()) {
jbyteArray jPicData = env->NewByteArray(picData.size());
env->SetByteArrayRegion(jPicData, 0, picData.size(), reinterpret_cast<const jbyte*>(picData.data()));
env->SetObjectArrayElement(resultArray, 4, jPicData);
env->DeleteLocalRef(jPicData);
}
return resultArray;
}
extern "C" JNIEXPORT jboolean JNICALL
Java_love_qz_music_controller_storage_AudioTag_writeMediaTags(JNIEnv *env, jobject thiz,
jint fd,
jstring j_title,
jstring j_artist,
jstring j_album,
jstring j_lyric,
jbyteArray j_pic) {
char fd_path[64];
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", fd);
const char *title = j_title ? env->GetStringUTFChars(j_title, nullptr) : nullptr;
const char *artist = j_artist ? env->GetStringUTFChars(j_artist, nullptr) : nullptr;
const char *album = j_album ? env->GetStringUTFChars(j_album, nullptr) : nullptr;
const char *lyric = j_lyric ? env->GetStringUTFChars(j_lyric, nullptr) : nullptr;
// 解析ByteArray(PIC)
TagLib::ByteVector picData;
if (j_pic) {
jsize picLen = env->GetArrayLength(j_pic);
jbyte *picBytes = env->GetByteArrayElements(j_pic, nullptr);
picData.setData(reinterpret_cast<const char*>(picBytes), picLen);
env->ReleaseByteArrayElements(j_pic, picBytes, JNI_ABORT);
}
bool is_saved = false;
TagLib::FileRef f(fd_path);
if (!f.isNull() && f.file()) {
TagLib::Tag *tag = f.tag();
if (title) tag->setTitle(TagLib::String(title, TagLib::String::UTF8));
if (artist) tag->setArtist(TagLib::String(artist, TagLib::String::UTF8));
if (album) tag->setAlbum(TagLib::String(album, TagLib::String::UTF8));
auto* mp3File = dynamic_cast<TagLib::MPEG::File*>(f.file());
auto* flacFile = dynamic_cast<TagLib::FLAC::File*>(f.file());
// WARNNING+DOTO("加入额外格式判断?")
TagLib::String mimeType = "image/jpeg";
if (picData.size() > 2 && (unsigned char)picData[0] == 0x89 && (unsigned char)picData[1] == 0x50) {
mimeType = "image/png";
}
if (mp3File && mp3File->ID3v2Tag()) {
TagLib::ID3v2::Tag* id3v2 = mp3File->ID3v2Tag();
if (lyric) {
id3v2->removeFrames("USLT");
if (strlen(lyric) > 0) {
auto* frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(TagLib::String::UTF8);
frame->setLanguage("eng"); // 默认语言标记
frame->setText(TagLib::String(lyric, TagLib::String::UTF8));
id3v2->addFrame(frame);
}
}
if (j_pic) {
id3v2->removeFrames("APIC");
if (!picData.isEmpty()) {
auto* frame = new TagLib::ID3v2::AttachedPictureFrame();
frame->setMimeType(mimeType);
frame->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
frame->setPicture(picData);
id3v2->addFrame(frame);
}
}
}
else if (flacFile) {
if (lyric && flacFile->xiphComment()) {
if (strlen(lyric) > 0) {
flacFile->xiphComment()->addField("LYRICS", TagLib::String(lyric, TagLib::String::UTF8), true);
} else {
flacFile->xiphComment()->removeFields("LYRICS");
}
}
if (j_pic) {
flacFile->removePictures();
if (!picData.isEmpty()) {
auto* pic = new TagLib::FLAC::Picture();
pic->setMimeType(mimeType);
pic->setType(TagLib::FLAC::Picture::FrontCover);
pic->setData(picData);
flacFile->addPicture(pic);
}
}
}
is_saved = f.save();
}
if (title) env->ReleaseStringUTFChars(j_title, title);
if (artist) env->ReleaseStringUTFChars(j_artist, artist);
if (album) env->ReleaseStringUTFChars(j_album, album);
if (lyric) env->ReleaseStringUTFChars(j_lyric, lyric);
return is_saved ? JNI_TRUE : JNI_FALSE;
}