#include <SD.h>
#define SDCARD_CS_PIN BUILTIN_SDCARD
SdFat sd;
SdFile dir;
File file;
#include "SPI.h"
char itemToOpen[256];
String title;
String artist;
String tracklen;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
if (!(SD.sdfs.begin(SdioConfig(FIFO_SDIO)))) {
while (1) {
Serial.println("Unable to access the SD card");
delay(500);
}
}
strcpy(itemToOpen, "/MusicBee/Music/Deadmau5/5 years of mau5/1-04 - Some Chords.mp3");
getTags();
strcpy(itemToOpen, "/MusicBee/Music/Deadmau5/At Play 2/1-01 - Outta My Life (Touch Mix).mp3");
getTags();
strcpy(itemToOpen, "/MusicBee/Music/Deadmau5/For Lack Of A Better Name/1-03 - Ghosts 'n Stuff (feat. Rob Swire).mp3");
getTags();
strcpy(itemToOpen, "/MusicBee/Music/Deadmau5/Album Title Goes Here/1-03 - The Veldt (feat. Chris James) [8 Minute Edit].mp3");
getTags();
}
void getTags(){
title = ""; //clear tag strings
artist = "";
Serial.print("Opening file ");
Serial.println(itemToOpen);
file = SD.open(itemToOpen);
if (!file) {
Serial.println("File failed to open");
}
id3Read();
file.close();
Serial.print("Title: ");
Serial.println(title);
Serial.print("Artist: ");
Serial.println(artist);
Serial.print("Track length (ms): ");
Serial.println(tracklen);
}
void loop() {
}
String UTF16UTF8(const char* buf, const uint32_t len)
{
// converts unicode in UTF-8, buff contains the string to be converted up to len
// range U+1 ... U+FFFF
//if no BOM found, BE is default
String out;
out.reserve(len);
auto *tmpbuf = (uint8_t*)malloc(len + 1);
if (!tmpbuf)
return String(); // out of memory;
auto *t = tmpbuf;
auto bitorder = false; //Default to BE
auto *p = (uint16_t*) buf;
const auto *pe = (uint16_t*) &buf[len];
auto code = *p;
if (code == 0xFEFF) {
bitorder = false;
p++;
} // LSB/MSB
else if (code == 0xFFFE) {
bitorder = true;
p++;
} // MSB/LSB
while (p < pe) {
code = *p++;
if (bitorder == true)
code = __builtin_bswap16(code);
if (code < 0X80) {
*t++ = code & 0xff;
}
else if (code < 0X800) {
*t++ = ((code >> 6) | 0XC0);
*t++ = ((code & 0X3F) | 0X80);
}
else {
*t++ = ((code >> 12) | 0XE0);
*t++ = (((code >> 6) & 0X3F) | 0X80);
*t++ = ((code & 0X3F) | 0X80);
}
}
*t = 0;
out = (char*)tmpbuf;
free(tmpbuf);
return out;
}
// https://id3.org/id3v2.4.0-structure
typedef struct
{
char id[3]; //"ID3"
uint8_t version[2]; //04 00 Version 4
uint8_t flag_unsynchronization: 1;
uint8_t flag_extendedheader : 1;
uint8_t flag_experimental : 1;
uint8_t flag_footerpresent : 1;
uint8_t flags_zero: 4;
uint32_t size;
} __attribute__((packed)) ID3TAG;
typedef struct
{
uint32_t size;
uint8_t numFlagBytes;
uint8_t flag_zero1: 1;
uint8_t flag_update: 1;
uint8_t flag_crc: 1;
uint8_t flag_restrictions: 1;
uint8_t flag_zero2: 4;
} __attribute__((packed)) ID3TAG_EXTHEADER;
typedef struct
{
char id[4]; //Tag ID
uint32_t size;
uint8_t flags[2]; //Several bits.. not needed here
} __attribute__((packed)) ID3FRAME;
//for ID3V2.4 only,
//if using ID3V4, replace calls to __builtin_bswap32() in later functions with calls this function
/*static uint32_t id3_unsyncsafe(uint32_t data)
{
data = __builtin_bswap32(data);
auto out = 0;
auto mask = 0x7f000000ul;
do {
out >>= 1;
out |= data & mask;
mask >>= 8;
} while (mask);
return out;
}
*/
/*
id3_getString
The first byte tells the encoding:
$00 ISO-8859-1 [ISO-8859-1]. Terminated with $00.
$01 UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM. All
strings in the same frame SHALL have the same byteorder.
Terminated with $00 00.
$02 UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.
Terminated with $00 00.
$03 UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.
*/
static String id3_getString(File file, unsigned int len)
{
if (len > 127) len = 127;
char buf[len + 1];
file.readBytes(buf, len);
buf[len] = 0;
const auto encoding = buf[0];
auto *s = &buf[1];
len--;
switch (encoding) {
case 0x00:
Serial.println("Encoding is ISO-8859-1");
break;
// return latin1UTF8(s);
case 0x01:
Serial.println("Encoding is UTF-16 with BOM");
return UTF16UTF8(s, len);
case 0x02:
Serial.println("Encoding is UTF-16BE without BOM");
return UTF16UTF8(s, len);
case 0x03:
Serial.println("Encoding is UTF-8");
break;
// return String(s);
}
return String(s);
}
void id3Read()
{
Serial.println("Reading ID3 header");
//Try to skip or parse ID3 Tags - fill buffer
bool ID3present = false;
auto numTagstoFind = 3;
ID3TAG id3;
auto lenRead = 0;
lenRead += file.readBytes((char*)&id3, sizeof(id3));
if (strncmp (id3.id, "ID3", 3) != 0) {
Serial.println("No ID3 header found");
return; //Identifier not found
}
//size is a "syncsafe" integer
id3.size = __builtin_bswap32(id3.size) + 10;
// log_e("ID3 size: %u", id3.size);
if (id3.version[0] < 2 || id3.version[0] == 0xff || id3.version[1] == 0xff || id3.flags_zero != 0) {
Serial.println("Abort!");
return;
}
// log_d("ID3v%u.%u found", (unsigned)id3.version[0], (unsigned)id3.version[1] );
//TODO: Handle ID3V2
if (id3.version[0] == 2 || id3.version[0] > 4 ) {
// log_w("Can't handle this ID3 version");
Serial.println("Can't handle this ID3 version");
goto end;
}
if (id3.flag_extendedheader) {
// log_v("Has extended header. Skipping.");
Serial.println("Has extended header. Skipping.");
ID3TAG_EXTHEADER extheader;
lenRead += file.readBytes((char*)&extheader, sizeof(extheader));
extheader.size = __builtin_bswap32(extheader.size);
if (extheader.size < 6) goto end; //error
lenRead += extheader.size;
}
//ID3 Frames:
file.seek(lenRead);
Serial.println("Searching for frames");
do {
ID3FRAME frameheader;
lenRead += file.readBytes((char*)&frameheader, sizeof(frameheader));
frameheader.size = __builtin_bswap32(frameheader.size);
if (frameheader.id[0] == 0 || frameheader.id[1] == 0 || frameheader.id[2] == 0 ) {
Serial.println("Blank data, break");
break;
}
if (lenRead + frameheader.size >= id3.size) {
Serial.println("End of ID3, break");
break;
}
Serial.printf("ID3 Frame: %c%c%c%c size:%u\n", frameheader.id[0], frameheader.id[1], frameheader.id[2] , frameheader.id[3], frameheader.size);
if ( strncmp("TIT2", frameheader.id, 4) == 0) {
Serial.print("Found TIT2 - ");
title = id3_getString(file, frameheader.size);
numTagstoFind--;
}
else if ( strncmp("TPE1", frameheader.id, 4) == 0) {
Serial.print("Found TPE1 - ");
artist = id3_getString(file, frameheader.size);
numTagstoFind--;
}
else if ( strncmp("TLEN", frameheader.id, 4) == 0) {
Serial.print("Found TLEN - ");
tracklen = id3_getString(file, frameheader.size);
numTagstoFind--;
}
if (numTagstoFind == 0) {
Serial.println("Found all searched tags");
break;
}
lenRead += frameheader.size;
file.seek(lenRead);
} while (1);
end:
//Skip to first mp3/aac frame:
file.seek(id3.size);
}