summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorayumi <ayumi@noreply.codeberg.org>2025-01-30 10:08:37 +0100
committerayumi <ayumi@noreply.codeberg.org>2025-03-13 00:58:30 +0100
commita3639860761dcdb5ef9c31bb34497f32cadd9ff3 (patch)
treec3e546006241f9602750512ae8122663a36cb851
parent52217b637d09fc8fe19dcaa16378e39b6034a54b (diff)
downloadtangara-fw-a3639860761dcdb5ef9c31bb34497f32cadd9ff3.tar.gz
Add support for APEv2 tags and detecting WavPack files
-rw-r--r--lib/libtags/CMakeLists.txt2
-rw-r--r--lib/libtags/ape.c233
-rw-r--r--lib/libtags/tags.c2
-rw-r--r--lib/libtags/tags.h1
4 files changed, 237 insertions, 1 deletions
diff --git a/lib/libtags/CMakeLists.txt b/lib/libtags/CMakeLists.txt
index d8dce988..db5cd0c2 100644
--- a/lib/libtags/CMakeLists.txt
+++ b/lib/libtags/CMakeLists.txt
@@ -1,5 +1,5 @@
idf_component_register(
- SRCS 437.c 8859.c flac.c id3genres.c id3v1.c id3v2.c it.c m4a.c mod.c opus.c
+ SRCS 437.c 8859.c ape.c flac.c id3genres.c id3v1.c id3v2.c it.c m4a.c mod.c opus.c
s3m.c tags.c utf16.c vorbis.c wav.c xm.c
INCLUDE_DIRS .
)
diff --git a/lib/libtags/ape.c b/lib/libtags/ape.c
new file mode 100644
index 00000000..7ba30649
--- /dev/null
+++ b/lib/libtags/ape.c
@@ -0,0 +1,233 @@
+#include <math.h>
+#include "tagspriv.h"
+
+#define leu16int(d) (u16int)(((uchar*)(d))[1]<<8 | ((uchar*)(d))[0]<<0)
+
+enum
+{
+ HeaderSize = 32,
+ FooterSize = HeaderSize,
+
+ MagicOffset = 0,
+ VersionOffset = 8,
+ SizeOffset = 12,
+ CountOffset = 16,
+ FlagsOffset = 20,
+
+ WvHeaderSize = 32,
+ WvMagicOffset = 0,
+ WvVersionOffset = 8,
+ WvSamplesHighOffset = 11,
+ WvSamplesLowOffset = 12,
+ WvFlagsOffset = 24,
+ WvSampleRateMask = 0xf << 23,
+ WvSampleRateShift = 23,
+ WvCustomSampleRate = 16,
+ WvMonoMask = 4,
+};
+
+typedef enum
+{
+ TagUTF8,
+ TagBinary,
+ TagExternal,
+ TagReserved,
+
+ TagInvalid,
+} TagType;
+
+static int
+isWavpack(Tagctx *ctx)
+{
+ uchar header[WvHeaderSize];
+ int size;
+ u16int version;
+ u32int flags, samplerate;
+ uvlong samples;
+
+ if(ctx->seek(ctx, 0, 0) < 0)
+ return 0;
+ if(ctx->read(ctx, header, WvHeaderSize) != WvHeaderSize)
+ return 0;
+ if(memcmp(header+WvMagicOffset, "wvpk", 4))
+ return 0;
+ version = leu16int(header+WvVersionOffset);
+ if(version<0x402 || version>0x410)
+ return 0;
+ samples = (uvlong)(*(uchar*)(header+WvSamplesHighOffset))<<32 | leuint(header+WvSamplesLowOffset);
+ flags = leuint(header+WvFlagsOffset);
+ if((flags&WvSampleRateMask)>>WvSampleRateShift != WvCustomSampleRate){
+ const u32int samplerates[] = {6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000};
+ samplerate = samplerates[(flags&WvSampleRateMask)>>WvSampleRateShift];
+ ctx->samplerate = samplerate;
+ uvlong duration = round((double)samples/samplerate*1000);
+ ctx->duration = duration;
+ }
+ ctx->channels = flags&WvMonoMask ? 1 : 2;
+ if(ctx->seek(ctx, 0, 0) < 0)
+ return 0;
+ if(size = ctx->seek(ctx, 0, 2), size < 0)
+ return 0;
+ ctx->bitrate = (double)size*8.0/(samples/samplerate)/1000;
+ return 1;
+}
+
+static int
+detectFormat(Tagctx *ctx)
+{
+ if(isWavpack(ctx))
+ return Fwavpack;
+ return Funknown;
+}
+
+static int
+tagHasHeader(u32int tags)
+{
+ return (tags&(1<<31)) >> 31;
+}
+
+static int
+tagIsHeader(u32int tags)
+{
+ return (tags&(1<<29)) >> 29;
+}
+
+static TagType
+tagGetType(u32int tags)
+{
+ switch((tags&(1<<1))>>1 | (tags&(1<<2))>>1 | (tags&(1<<3))>>1 | (tags&(1<<4))>>1){
+ case 0:
+ return TagUTF8;
+ case 1:
+ return TagBinary;
+ case 2:
+ return TagExternal;
+ case 3:
+ return TagReserved;
+ default:
+ return TagInvalid;
+ }
+}
+
+static int
+tagTagType(char *name)
+{
+ if(!strcmp(name, "Album"))
+ return Talbum;
+ else if(!strcmp(name, "Album Artist"))
+ return Talbumartist;
+ else if(!strcmp(name, "Artist"))
+ return Tartist;
+ else if(!strcmp(name, "Comment"))
+ return Tcomment;
+ else if(!strcmp(name, "Composer"))
+ return Tcomposer;
+ else if(!strncmp(name, "Cover Art (", 11)){
+ if(name[strlen(name)-1] == ')')
+ return Timage;
+ }else if(!strcmp(name, "Genre"))
+ return Tgenre;
+ else if(!strcmp(name, "Replaygain_Album_Gain"))
+ return Talbumgain;
+ else if(!strcmp(name, "Replaygain_Album_Peak"))
+ return Talbumpeak;
+ else if(!strcmp(name, "Replaygain_Track_Gain"))
+ return Ttrackgain;
+ else if(!strcmp(name, "Replaygain_Track_Peak"))
+ return Ttrackpeak;
+ else if(!strcmp(name, "Title"))
+ return Ttitle;
+ else if(!strcmp(name, "Track"))
+ return Ttrack;
+ else if(!strcmp(name, "Year"))
+ return Tdate;
+ return Tunknown;
+}
+
+int
+tagape(Tagctx *ctx)
+{
+ uchar footer[FooterSize];
+ u32int i, count;
+
+ ctx->format = detectFormat(ctx);
+
+ if(ctx->seek(ctx, -FooterSize, 2) < 0)
+ return -1;
+ if(ctx->read(ctx, footer, FooterSize) != FooterSize)
+ return -1;
+ if(memcmp(footer+MagicOffset, "APETAGEX", 8))
+ return -1;
+ if(leuint(footer+VersionOffset) != 2000)
+ return -1;
+ if(tagIsHeader(leuint(footer+FlagsOffset)))
+ return -1;
+
+ if(ctx->seek(ctx, -FooterSize-leuint(footer+SizeOffset), 2) < 0)
+ return -1;
+ if(tagHasHeader(leuint(footer+FlagsOffset))){
+ uchar header[HeaderSize];
+ if(ctx->read(ctx, header, HeaderSize) != HeaderSize)
+ return -1;
+ if(memcmp(header, footer, 23))
+ return -1;
+ if(!tagHasHeader(leuint(header+FlagsOffset)))
+ return -1;
+ if(!tagIsHeader(leuint(header+FlagsOffset)))
+ return -1;
+ }else if(ctx->seek(ctx, HeaderSize, 1) < 0)
+ return -1;
+
+ for(i = 0, count = leuint(footer+CountOffset); i < count; i++){
+ int valueOffset = 0;
+ char c;
+ u32int d, length, flags;
+
+ if(ctx->read(ctx, &d, 4) != 4)
+ return -1;
+ length = leuint(&d);
+ if(ctx->read(ctx, &d, 4) != 4)
+ return -1;
+ flags = leuint(&d);
+
+ do{
+ if(valueOffset == ctx->bufsz)
+ return -1;
+ if(ctx->read(ctx, &c, 1) != 1)
+ return -1;
+ if(c<' ' || c>'~')
+ if(c != '\0')
+ return -1;
+ ctx->buf[valueOffset++] = c;
+ }while(c != '\0');
+ if(valueOffset+1+(int)length>ctx->bufsz && tagTagType(ctx->buf)!=Timage){
+ if(ctx->seek(ctx, length, 1) < 0)
+ return -1;
+ continue;
+ }
+
+ switch(tagGetType(flags)){
+ u32int keyOffset;
+ case TagUTF8:
+ if(ctx->read(ctx, ctx->buf+valueOffset, length) != (int)length)
+ return -1;
+ (ctx->buf+valueOffset)[length] = '\0';
+ for(keyOffset = 0; keyOffset != length;){
+ txtcb(ctx, tagTagType(ctx->buf), ctx->buf, ctx->buf+valueOffset+keyOffset);
+ if(keyOffset += strlen(ctx->buf+valueOffset+keyOffset), keyOffset != length)
+ keyOffset++;
+ }
+ break;
+ case TagBinary:
+ if(tagTagType(ctx->buf) == Timage)
+ tagscallcb(ctx, Timage, ctx->buf, ctx->buf, ctx->seek(ctx, 0, 1), length, NULL);
+ if(ctx->seek(ctx, length, 1) < 0)
+ return -1;
+ break;
+ default:
+ if(ctx->seek(ctx, length, 1) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
diff --git a/lib/libtags/tags.c b/lib/libtags/tags.c
index b1d6ac33..d3c577dd 100644
--- a/lib/libtags/tags.c
+++ b/lib/libtags/tags.c
@@ -8,6 +8,7 @@ struct Getter
int format;
};
+extern int tagape(Tagctx *ctx);
extern int tagflac(Tagctx *ctx);
extern int tagid3v1(Tagctx *ctx);
extern int tagid3v2(Tagctx *ctx);
@@ -22,6 +23,7 @@ extern int tagmod(Tagctx *ctx);
static const Getter g[] =
{
+ {tagape, Funknown},
{tagid3v2, Fmp3},
{tagid3v1, Fmp3},
{tagvorbis, Fogg},
diff --git a/lib/libtags/tags.h b/lib/libtags/tags.h
index b2aa2dfb..0b54936a 100644
--- a/lib/libtags/tags.h
+++ b/lib/libtags/tags.h
@@ -37,6 +37,7 @@ enum
Fm4a,
Fopus,
Fwav,
+ Fwavpack,
Fit,
Fxm,
Fs3m,