text/plain
•
4.86 KB
•
165 lines
#include "../audio_metadata.h"
#include "helper.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// ============================================================================
// M4A/MP4 Parser
// ============================================================================
// Read atom header, return atom size (0 on error)
static uint64_t read_atom_header(FILE *f, char *type) {
uint8_t header[8];
if (fread(header, 1, 8, f) != 8) return 0;
uint32_t size = read_be32(header);
memcpy(type, &header[4], 4);
type[4] = '\0';
if (size == 1) {
// Extended size (64-bit)
uint8_t ext[8];
if (fread(ext, 1, 8, f) != 8) return 0;
return ((uint64_t)read_be32(ext) << 32) | read_be32(&ext[4]);
}
return size;
}
// Parse mvhd atom for duration
static bool parse_mvhd(FILE *f, uint32_t size, audio_metadata_t *meta) {
if (size < 20) return false;
uint8_t buf[32];
if (fread(buf, 1, 32, f) != 32) return false;
uint8_t version = buf[0];
uint32_t timescale, duration;
if (version == 0) {
// 32-bit values
timescale = read_be32(&buf[12]);
duration = read_be32(&buf[16]);
} else {
// 64-bit values - need to read more
fseek(f, -32, SEEK_CUR);
uint8_t buf64[40];
if (fread(buf64, 1, 40, f) != 40) return false;
timescale = read_be32(&buf64[20]);
duration = read_be32(&buf64[28]); // Only use lower 32 bits
}
if (timescale > 0) {
meta->duration_ms = (uint32_t)((uint64_t)duration * 1000 / timescale);
}
return true;
}
// Parse iTunes metadata atom
static void parse_ilst_data(FILE *f, uint32_t size, char *dest, size_t dest_size) {
// Skip to 'data' atom
long end = ftell(f) + size - 8;
while (ftell(f) < end) {
char type[5];
uint64_t atom_size = read_atom_header(f, type);
if (atom_size == 0) break;
if (strcmp(type, "data") == 0) {
// Skip version and flags (4 bytes) and null (4 bytes)
fseek(f, 8, SEEK_CUR);
size_t text_len = atom_size - 16; // 8 header + 8 skipped
if (text_len > 0 && text_len < 1024) {
if (text_len >= dest_size) text_len = dest_size - 1;
if (fread(dest, 1, text_len, f) == text_len) {
dest[text_len] = '\0';
}
}
return;
}
// Skip this atom
fseek(f, atom_size - 8, SEEK_CUR);
}
}
// Parse ilst (iTunes metadata list)
static void parse_ilst(FILE *f, uint32_t size, audio_metadata_t *meta) {
long end = ftell(f) + size - 8;
while (ftell(f) < end) {
char type[5];
uint64_t atom_size = read_atom_header(f, type);
if (atom_size == 0) break;
// iTunes uses special atoms: \251nam (title), \251ART (artist), \251alb (album)
if (strcmp(type, "\251nam") == 0) {
parse_ilst_data(f, atom_size, meta->title, sizeof(meta->title));
} else if (strcmp(type, "\251ART") == 0) {
parse_ilst_data(f, atom_size, meta->artist, sizeof(meta->artist));
} else if (strcmp(type, "\251alb") == 0) {
parse_ilst_data(f, atom_size, meta->album, sizeof(meta->album));
} else {
fseek(f, atom_size - 8, SEEK_CUR);
}
}
}
// Recursively search for atoms in moov
static void parse_moov_recursive(FILE *f, long end, audio_metadata_t *meta) {
while (ftell(f) < end) {
long atom_start = ftell(f);
char type[5];
uint64_t atom_size = read_atom_header(f, type);
if (atom_size == 0 || atom_size > (uint64_t)(end - atom_start)) break;
if (strcmp(type, "mvhd") == 0) {
parse_mvhd(f, atom_size, meta);
} else if (strcmp(type, "udta") == 0 || strcmp(type, "meta") == 0) {
// meta has 4 extra bytes (version/flags)
if (strcmp(type, "meta") == 0) {
fseek(f, 4, SEEK_CUR);
}
parse_moov_recursive(f, atom_start + atom_size, meta);
} else if (strcmp(type, "ilst") == 0) {
parse_ilst(f, atom_size, meta);
}
fseek(f, atom_start + atom_size, SEEK_SET);
}
}
bool parse_m4a(const char *path, audio_metadata_t *meta) {
FILE *f = fopen(path, "rb");
if (!f) return false;
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);
// Look for moov atom
while (ftell(f) < file_size) {
long atom_start = ftell(f);
char type[5];
uint64_t atom_size = read_atom_header(f, type);
if (atom_size == 0) break;
if (strcmp(type, "moov") == 0) {
parse_moov_recursive(f, atom_start + atom_size, meta);
meta->valid = (meta->duration_ms > 0);
break;
}
fseek(f, atom_start + atom_size, SEEK_SET);
}
fclose(f);
return meta->valid;
}