text/plain
•
4.85 KB
•
189 lines
#include "../audio_metadata.h"
#include "helper.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Parse a Vorbis comment block for title/artist/album tags
static void parse_vorbis_comment(FILE* f,
uint32_t block_size,
audio_metadata_t* meta) {
if (block_size < 8 || block_size > 1024 * 1024) {
return;
}
uint8_t* buf = malloc(block_size);
if (!buf) {
return;
}
if (fread(buf, 1, block_size, f) != block_size) {
free(buf);
return;
}
// Vorbis comment format:
// 4 bytes: vendor string length (LE)
// N bytes: vendor string
// 4 bytes: number of comments (LE)
// For each comment:
// 4 bytes: comment length (LE)
// N bytes: "KEY=value" UTF-8
uint32_t pos = 0;
if (pos + 4 > block_size) {
free(buf);
return;
}
uint32_t vendor_len = (uint32_t)buf[pos] | ((uint32_t)buf[pos + 1] << 8) |
((uint32_t)buf[pos + 2] << 16) |
((uint32_t)buf[pos + 3] << 24);
pos += 4 + vendor_len;
if (pos + 4 > block_size) {
free(buf);
return;
}
uint32_t num_comments = (uint32_t)buf[pos] | ((uint32_t)buf[pos + 1] << 8) |
((uint32_t)buf[pos + 2] << 16) |
((uint32_t)buf[pos + 3] << 24);
pos += 4;
for (uint32_t i = 0; i < num_comments && pos + 4 <= block_size; i++) {
uint32_t comment_len =
(uint32_t)buf[pos] | ((uint32_t)buf[pos + 1] << 8) |
((uint32_t)buf[pos + 2] << 16) | ((uint32_t)buf[pos + 3] << 24);
pos += 4;
if (pos + comment_len > block_size) {
break;
}
// Find '=' separator
const char* comment = (const char*)&buf[pos];
const char* eq = memchr(comment, '=', comment_len);
if (eq) {
size_t key_len = (size_t)(eq - comment);
const char* value = eq + 1;
size_t value_len = comment_len - key_len - 1;
// Case-insensitive key match
if (key_len == 5 && strncasecmp(comment, "TITLE", 5) == 0) {
if (value_len >= sizeof(meta->title)) {
value_len = sizeof(meta->title) - 1;
}
memcpy(meta->title, value, value_len);
meta->title[value_len] = '\0';
} else if (key_len == 6 && strncasecmp(comment, "ARTIST", 6) == 0) {
if (value_len >= sizeof(meta->artist)) {
value_len = sizeof(meta->artist) - 1;
}
memcpy(meta->artist, value, value_len);
meta->artist[value_len] = '\0';
} else if (key_len == 5 && strncasecmp(comment, "ALBUM", 5) == 0) {
if (value_len >= sizeof(meta->album)) {
value_len = sizeof(meta->album) - 1;
}
memcpy(meta->album, value, value_len);
meta->album[value_len] = '\0';
}
}
pos += comment_len;
}
free(buf);
}
bool parse_flac(const char* path, audio_metadata_t* meta) {
FILE* f = fopen(path, "rb");
if (!f) {
return false;
}
// Verify "fLaC" magic
uint8_t magic[4];
if (fread(magic, 1, 4, f) != 4 || memcmp(magic, "fLaC", 4) != 0) {
fclose(f);
return false;
}
// Read metadata blocks
bool last_block = false;
bool got_streaminfo = false;
while (!last_block) {
// Block header: 1 byte (last flag + type), 3 bytes (size)
uint8_t block_header[4];
if (fread(block_header, 1, 4, f) != 4) {
break;
}
last_block = (block_header[0] & 0x80) != 0;
uint8_t block_type = block_header[0] & 0x7F;
uint32_t block_size = ((uint32_t)block_header[1] << 16) |
((uint32_t)block_header[2] << 8) |
block_header[3];
if (block_type == 0) {
// STREAMINFO block (always 34 bytes)
if (block_size < 34) {
break;
}
uint8_t si[34];
if (fread(si, 1, 34, f) != 34) {
break;
}
// Byte layout of STREAMINFO:
// 0-1: min block size
// 2-3: max block size
// 4-6: min frame size
// 7-9: max frame size
// 10-13: sample rate (20 bits), channels-1 (3 bits), bps-1 (5
// bits), total samples high (4 bits) 14-17: total samples low (32
// bits) 18-33: MD5
uint32_t sample_rate = ((uint32_t)si[10] << 12) |
((uint32_t)si[11] << 4) |
((uint32_t)si[12] >> 4);
meta->channels = ((si[12] >> 1) & 0x07) + 1;
// total_samples is 36 bits: 4 bits from byte 13, 32 bits from bytes
// 14-17
uint64_t total_samples =
((uint64_t)(si[13] & 0x0F) << 32) | ((uint64_t)si[14] << 24) |
((uint64_t)si[15] << 16) | ((uint64_t)si[16] << 8) | si[17];
meta->sample_rate = sample_rate;
if (sample_rate > 0) {
meta->duration_ms =
(uint32_t)(total_samples * 1000 / sample_rate);
}
// Skip remaining if block is larger than 34
if (block_size > 34) {
fseek(f, block_size - 34, SEEK_CUR);
}
got_streaminfo = true;
} else if (block_type == 4) {
// VORBIS_COMMENT block
parse_vorbis_comment(f, block_size, meta);
} else {
// Skip unknown block
fseek(f, block_size, SEEK_CUR);
}
}
fclose(f);
if (!got_streaminfo) {
return false;
}
meta->valid = true;
return true;
}