#define MAX_TRACKS 1024
struct mp4 {
- /* stream to read from */
- struct mp4_callback *stream;
+ const struct mp4_callback *cb;
int64_t current_position;
uint64_t moov_offset;
uint64_t moov_size;
uint8_t last_atom;
uint64_t file_size;
+
+ uint32_t read_error;
uint32_t error;
/* incremental track index while reading the file */
{
int32_t result = 1;
- result = f->stream->read(f->stream->user_data, data, size);
+ result = f->cb->read(f->cb->user_data, data, size);
if (result < size)
- f->stream->read_error++;
+ f->read_error++;
f->current_position += size;
{
return a1 == a2 && b1 == b2 && c1 == c2 && d1 == d2;
}
-
enum atoms {
/* atoms with subatoms */
ATOM_MOOV = 1,
static int32_t set_position(struct mp4 *f, int64_t position)
{
- f->stream->seek(f->stream->user_data, position);
+ f->cb->seek(f->cb->user_data, position);
f->current_position = position;
return 0;
if (t->stsz_sample_size != 0)
return 0;
t->stsz_table = para_malloc(t->stsz_sample_count * sizeof(int32_t));
- for (i = 0; i < t->stsz_sample_count && !f->stream->read_error; i++)
+ for (i = 0; i < t->stsz_sample_count && !f->read_error; i++)
t->stsz_table[i] = read_int32(f);
return 0;
}
t->stts_sample_delta = para_malloc(t->stts_entry_count
* sizeof (int32_t));
/* CVE-2017-9254 */
- for (i = 0; i < t->stts_entry_count && !f->stream->read_error; i++) {
+ for (i = 0; i < t->stts_entry_count && !f->read_error; i++) {
t->stts_sample_count[i] = read_int32(f);
t->stts_sample_delta[i] = read_int32(f);
}
sizeof (int32_t));
/* CVE-2017-9255 */
- for (i = 0; i < t->stsc_entry_count && !f->stream->read_error; i++) {
+ for (i = 0; i < t->stsc_entry_count && !f->read_error; i++) {
t->stsc_first_chunk[i] = read_int32(f);
t->stsc_samples_per_chunk[i] = read_int32(f);
t->stsc_sample_desc_index[i] = read_int32(f);
t->stco_chunk_offset = para_malloc(t->stco_entry_count
* sizeof(int32_t));
/* CVE-2017-9256 */
- for (i = 0; i < t->stco_entry_count && !f->stream->read_error; i++)
+ for (i = 0; i < t->stco_entry_count && !f->read_error; i++)
t->stco_chunk_offset[i] = read_int32(f);
return 0;
}
entry_count = read_int32(f);
/* CVE-2017-9253 */
- for (i = 0; i < entry_count && !f->stream->read_error; i++) {
+ for (i = 0; i < entry_count && !f->read_error; i++) {
uint64_t skip = get_position(f);
uint64_t size;
uint8_t atom_type = 0;
static int32_t tag_add_field(struct mp4_metadata *tags, const char *item,
const char *value, int32_t len)
{
- if (!item || (item && !*item) || !value)
- return 0;
tags->tags = para_realloc(tags->tags,
(tags->count + 1) * sizeof(struct mp4_tag));
tags->tags[tags->count].item = para_strdup(item);
"Anime", "JPop", "SynthPop",
};
-static const char *meta_index_to_genre(uint32_t idx)
-{
- if (idx > 0 && idx <= sizeof (ID3v1GenreList) / sizeof (ID3v1GenreList[0])) {
- return ID3v1GenreList[idx - 1];
- } else {
- return 0;
- }
-}
-
static char *read_string(struct mp4 *f, uint32_t length)
{
char *str = para_malloc(length + 1);
return str;
}
-static int32_t set_metadata_name(uint8_t atom_type, char **name)
+static const char *get_metadata_name(uint8_t atom_type)
{
- static char *tag_names[] = {
- "unknown", "title", "artist", "writer", "album",
- "date", "tool", "comment", "genre", "track",
- "disc", "compilation", "genre", "tempo", "cover",
- "album_artist", "contentgroup", "lyrics", "description",
- "network", "show", "episodename",
- "sorttitle", "sortalbum", "sortartist", "sortalbumartist",
- "sortwriter", "sortshow",
- "season", "episode", "podcast"
- };
- uint8_t tag_idx = 0;
-
switch (atom_type) {
- case ATOM_TITLE:
- tag_idx = 1;
- break;
- case ATOM_ARTIST:
- tag_idx = 2;
- break;
- case ATOM_WRITER:
- tag_idx = 3;
- break;
- case ATOM_ALBUM:
- tag_idx = 4;
- break;
- case ATOM_DATE:
- tag_idx = 5;
- break;
- case ATOM_TOOL:
- tag_idx = 6;
- break;
- case ATOM_COMMENT:
- tag_idx = 7;
- break;
- case ATOM_GENRE1:
- tag_idx = 8;
- break;
- case ATOM_TRACK:
- tag_idx = 9;
- break;
- case ATOM_DISC:
- tag_idx = 10;
- break;
- case ATOM_COMPILATION:
- tag_idx = 11;
- break;
- case ATOM_GENRE2:
- tag_idx = 12;
- break;
- case ATOM_TEMPO:
- tag_idx = 13;
- break;
- case ATOM_COVER:
- tag_idx = 14;
- break;
- case ATOM_ALBUM_ARTIST:
- tag_idx = 15;
- break;
- case ATOM_CONTENTGROUP:
- tag_idx = 16;
- break;
- case ATOM_LYRICS:
- tag_idx = 17;
- break;
- case ATOM_DESCRIPTION:
- tag_idx = 18;
- break;
- case ATOM_NETWORK:
- tag_idx = 19;
- break;
- case ATOM_SHOW:
- tag_idx = 20;
- break;
- case ATOM_EPISODENAME:
- tag_idx = 21;
- break;
- case ATOM_SORTTITLE:
- tag_idx = 22;
- break;
- case ATOM_SORTALBUM:
- tag_idx = 23;
- break;
- case ATOM_SORTARTIST:
- tag_idx = 24;
- break;
- case ATOM_SORTALBUMARTIST:
- tag_idx = 25;
- break;
- case ATOM_SORTWRITER:
- tag_idx = 26;
- break;
- case ATOM_SORTSHOW:
- tag_idx = 27;
- break;
- case ATOM_SEASON:
- tag_idx = 28;
- break;
- case ATOM_EPISODE:
- tag_idx = 29;
- break;
- case ATOM_PODCAST:
- tag_idx = 30;
- break;
- default:
- tag_idx = 0;
- break;
- }
-
- *name = para_strdup(tag_names[tag_idx]);
- return 0;
-}
-
-static uint32_t min_body_size(uint8_t atom_type)
-{
- switch(atom_type) {
- case ATOM_GENRE2:
- case ATOM_TEMPO:
- return 10;
- case ATOM_TRACK:
- return sizeof (char) /* version */
- + sizeof(uint8_t) * 3 /* flags */
- + sizeof(uint32_t) /* reserved */
- + sizeof(uint16_t) /* leading uint16_t */
- + sizeof(uint16_t) /* track */
- + sizeof(uint16_t); /* totaltracks */
- case ATOM_DISC:
- return sizeof (char) /* version */
- + sizeof(uint8_t) * 3 /* flags */
- + sizeof(uint32_t) /* reserved */
- + sizeof(uint16_t) /* disc */
- + sizeof(uint16_t); /* totaldiscs */
- default: assert(false);
+ case ATOM_TITLE: return "title";
+ case ATOM_ARTIST: return "artist";
+ case ATOM_ALBUM: return "album";
+ case ATOM_DATE: return "date";
+ case ATOM_COMMENT: return "comment";
+ default: return "unknown";
}
}
-static int32_t parse_tag(struct mp4 *f, uint8_t parent, int32_t size)
+static void parse_tag(struct mp4 *f, uint8_t parent, int32_t size)
{
- uint8_t atom_type;
- uint8_t header_size = 0;
uint64_t subsize, sumsize;
- char *name = NULL;
char *data = NULL;
- uint32_t done = 0;
uint32_t len = 0;
uint64_t destpos;
for (
sumsize = 0;
- sumsize < size && !f->stream->read_error; /* CVE-2017-9222 */
+ sumsize < size && !f->read_error; /* CVE-2017-9222 */
set_position(f, destpos), sumsize += subsize
) {
+ uint8_t atom_type;
+ uint8_t header_size = 0;
subsize = atom_read_header(f, &atom_type, &header_size);
destpos = get_position(f) + subsize - header_size;
- if (done)
- continue;
- if (atom_type == ATOM_NAME) {
- read_char(f); /* version */
- read_int24(f); /* flags */
- free(name);
- name = read_string(f, subsize - (header_size + 4));
- continue;
- }
if (atom_type != ATOM_DATA)
continue;
read_char(f); /* version */
read_int24(f); /* flags */
read_int32(f); /* reserved */
-
- /* some need special attention */
- if (parent == ATOM_GENRE2 || parent == ATOM_TEMPO) {
- uint16_t val;
- if (subsize - header_size < min_body_size(parent))
- continue;
- val = read_int16(f);
- if (parent == ATOM_TEMPO) {
- char temp[16];
- sprintf(temp, "%.5u BPM", val);
- tag_add_field(&(f-> tags), "tempo",
- temp, -1);
- } else {
- const char *tmp = meta_index_to_genre(val);
- if (tmp)
- tag_add_field (&(f->tags),
- "genre", tmp, -1);
- }
- done = 1;
- } else if (parent == ATOM_TRACK || parent == ATOM_DISC) {
- uint16_t index, total;
- char temp[32];
- if (subsize - header_size < min_body_size(parent))
- continue;
- read_int16(f);
- index = read_int16(f);
- total = read_int16(f);
- if (parent == ATOM_TRACK)
- read_int16(f);
- sprintf(temp, "%d", index);
- tag_add_field(&(f->tags), parent == ATOM_TRACK?
- "track" : "disc", temp, -1);
- if (total > 0) {
- sprintf(temp, "%d", total);
- tag_add_field(& (f-> tags),
- parent == ATOM_TRACK?
- "totaltracks" : "totaldiscs", temp, -1);
- }
- done = 1;
- } else {
- free(data);
- data = read_string(f, subsize - (header_size + 8));
- len = subsize - (header_size + 8);
- }
- }
- if (data) {
- if (!done) {
- if (name == NULL)
- set_metadata_name(parent , &name);
- if (name)
- tag_add_field(&(f->tags), name, data, len);
- }
-
free(data);
+ data = read_string(f, subsize - (header_size + 8));
+ len = subsize - (header_size + 8);
}
- free(name);
- return 1;
+ if (!data)
+ return;
+ tag_add_field(&f->tags, get_metadata_name(parent), data, len);
+ free(data);
}
static int32_t read_mdhd(struct mp4 *f)
static int32_t parse_metadata(struct mp4 *f, int32_t size)
{
- uint64_t subsize, sumsize = 0;
- uint8_t atom_type;
- uint8_t header_size = 0;
+ uint64_t sumsize = 0;
while (sumsize < size) {
+ uint8_t atom_type;
+ uint64_t subsize, destpos;
+ uint8_t header_size = 0;
subsize = atom_read_header(f, &atom_type, &header_size);
if (subsize == 0)
break;
- parse_tag(f, atom_type, (uint32_t)(subsize - header_size));
+ destpos = get_position(f) + subsize - header_size;
+ switch (atom_type) {
+ case ATOM_ARTIST:
+ case ATOM_TITLE:
+ case ATOM_ALBUM:
+ case ATOM_COMMENT:
+ case ATOM_DATE:
+ parse_tag(f, atom_type, subsize - header_size);
+ }
+ set_position(f, destpos);
sumsize += subsize;
}
uint8_t header_size = 0;
f->file_size = 0;
- f->stream->read_error = 0;
+ f->read_error = 0;
while ((size =
atom_read_header(f, &atom_type, &header_size)) != 0) {
return 0;
}
-struct mp4 *mp4_open_read(struct mp4_callback *f)
+struct mp4 *mp4_open_read(const struct mp4_callback *cb)
{
- struct mp4 *ff = para_calloc(sizeof(struct mp4));
-
- ff->stream = f;
-
- parse_atoms(ff, 0);
+ struct mp4 *f = para_calloc(sizeof(struct mp4));
- if (ff->error) {
- free(ff);
- ff = NULL;
+ f->cb = cb;
+ parse_atoms(f, 0);
+ if (f->error) {
+ free(f);
+ f = NULL;
}
-
- return ff;
+ return f;
}
static int32_t tag_delete(struct mp4_metadata *tags)
return 0;
}
-void mp4_close(struct mp4 *ff)
+void mp4_close(struct mp4 *f)
{
int32_t i;
- for (i = 0; i < ff->total_tracks; i++) {
- if (ff->track[i]) {
- free(ff->track[i]->stsz_table);
- free(ff->track[i]->stts_sample_count);
- free(ff->track[i]->stts_sample_delta);
- free(ff->track[i]->stsc_first_chunk);
- free(ff->track[i]->stsc_samples_per_chunk);
- free(ff->track[i]->stsc_sample_desc_index);
- free(ff->track[i]->stco_chunk_offset);
- free(ff->track[i]);
+ for (i = 0; i < f->total_tracks; i++) {
+ if (f->track[i]) {
+ free(f->track[i]->stsz_table);
+ free(f->track[i]->stts_sample_count);
+ free(f->track[i]->stts_sample_delta);
+ free(f->track[i]->stsc_first_chunk);
+ free(f->track[i]->stsc_samples_per_chunk);
+ free(f->track[i]->stsc_sample_desc_index);
+ free(f->track[i]->stco_chunk_offset);
+ free(f->track[i]);
}
}
- tag_delete(&(ff->tags));
- free(ff);
+ tag_delete(&(f->tags));
+ free(f);
}
static int32_t chunk_of_sample(const struct mp4 *f, int32_t track,
return total;
}
-struct mp4 *mp4_open_meta(struct mp4_callback *f)
+struct mp4 *mp4_open_meta(const struct mp4_callback *cb)
{
- struct mp4 *ff = para_calloc(sizeof(struct mp4));
-
- ff->stream = f;
+ struct mp4 *f = para_calloc(sizeof(struct mp4));
- parse_atoms(ff, 1);
-
- if (ff->error) {
- free(ff);
- ff = NULL;
+ f->cb = cb;
+ parse_atoms(f, 1);
+ if (f->error) {
+ free(f);
+ f = NULL;
}
-
- return ff;
+ return f;
}
int32_t mp4_meta_get_num_items(const struct mp4 *f)
{
int32_t result = 1;
- result = f->stream->write(f->stream->user_data, data, size);
+ result = f->cb->write(f->cb->user_data, data, size);
f->current_position += size;
return write_data(f, temp, sizeof(temp));
}
-int32_t mp4_meta_update(struct mp4_callback *f, const struct mp4_metadata *data)
+int32_t mp4_meta_update(struct mp4 *f, struct mp4_metadata *meta)
{
void *new_moov_data;
uint32_t new_moov_size;
- struct mp4 *ff = para_calloc(sizeof(struct mp4));
- ff->stream = f;
- set_position(ff, 0);
-
- parse_atoms(ff, 1);
-
- if (!modify_moov(ff, data, &new_moov_data, &new_moov_size)) {
- mp4_close(ff);
+ tag_delete(&f->tags);
+ f->tags = *meta;
+ set_position(f, 0);
+ if (!modify_moov(f, meta, &new_moov_data, &new_moov_size)) {
+ mp4_close(f);
return 0;
}
/* copy moov atom to end of the file */
- if (ff->last_atom != ATOM_MOOV) {
+ if (f->last_atom != ATOM_MOOV) {
char *free_data = "free";
/* rename old moov to free */
- set_position(ff, ff->moov_offset + 4);
- write_data(ff, free_data, 4);
+ set_position(f, f->moov_offset + 4);
+ write_data(f, free_data, 4);
- set_position(ff, ff->file_size);
- write_int32(ff, new_moov_size + 8);
- write_data(ff, "moov", 4);
- write_data(ff, new_moov_data, new_moov_size);
+ set_position(f, f->file_size);
+ write_int32(f, new_moov_size + 8);
+ write_data(f, "moov", 4);
+ write_data(f, new_moov_data, new_moov_size);
} else {
- set_position(ff, ff->moov_offset);
- write_int32(ff, new_moov_size + 8);
- write_data(ff, "moov", 4);
- write_data(ff, new_moov_data, new_moov_size);
+ set_position(f, f->moov_offset);
+ write_int32(f, new_moov_size + 8);
+ write_data(f, "moov", 4);
+ write_data(f, new_moov_data, new_moov_size);
}
- f->truncate(f->user_data);
- mp4_close(ff);
+ free(new_moov_data);
+ f->cb->truncate(f->cb->user_data);
return 1;
}
-/* find a metadata item by name */
-/* returns 0 if item found, 1 if no such item */
-static int32_t meta_find_by_name(const struct mp4 *f, const char *item,
- char **value)
+static char *meta_find_by_name(const struct mp4 *f, const char *item)
{
uint32_t i;
- for (i = 0; i < f->tags.count; i++) {
- if (!strcasecmp(f->tags.tags[i].item, item)) {
- *value = para_strdup(f->tags.tags[i].value);
- return 1;
- }
- }
-
- *value = NULL;
-
- /* not found */
- return 0;
+ for (i = 0; i < f->tags.count; i++)
+ if (!strcasecmp(f->tags.tags[i].item, item))
+ return para_strdup(f->tags.tags[i].value);
+ return NULL;
}
-int32_t mp4_meta_get_artist(const struct mp4 *f, char **value)
+/**
+ * Return the value of the artist meta tag of an mp4 file.
+ *
+ * \param f Must not be NULL.
+ *
+ * \return If the file does not contain this metadata tag, the function returns
+ * NULL. Otherwise, a copy of the tag value is returned. The caller should free
+ * this memory when it is no longer needed.
+ */
+char *mp4_meta_get_artist(const struct mp4 *f)
{
- return meta_find_by_name(f, "artist", value);
+ return meta_find_by_name(f, "artist");
}
-int32_t mp4_meta_get_title(const struct mp4 *f, char **value)
+/**
+ * Return the value of the title meta tag of an mp4 file.
+ *
+ * \param f See \ref mp4_meta_get_artist().
+ * \return See \ref mp4_meta_get_artist().
+ */
+char *mp4_meta_get_title(const struct mp4 *f)
{
- return meta_find_by_name(f, "title", value);
+ return meta_find_by_name(f, "title");
}
-int32_t mp4_meta_get_date(const struct mp4 *f, char **value)
+/**
+ * Return the value of the date meta tag of an mp4 file.
+ *
+ * \param f See \ref mp4_meta_get_artist().
+ * \return See \ref mp4_meta_get_artist().
+ */
+char *mp4_meta_get_date(const struct mp4 *f)
{
- return meta_find_by_name(f, "date", value);
+ return meta_find_by_name(f, "date");
}
-int32_t mp4_meta_get_album(const struct mp4 *f, char **value)
+/**
+ * Return the value of the album meta tag of an mp4 file.
+ *
+ * \param f See \ref mp4_meta_get_artist().
+ * \return See \ref mp4_meta_get_artist().
+ */
+char *mp4_meta_get_album(const struct mp4 *f)
{
- return meta_find_by_name(f, "album", value);
+ return meta_find_by_name(f, "album");
}
-int32_t mp4_meta_get_comment(const struct mp4 *f, char **value)
+/**
+ * Return the value of the comment meta tag of an mp4 file.
+ *
+ * \param f See \ref mp4_meta_get_artist().
+ * \return See \ref mp4_meta_get_artist().
+ */
+char *mp4_meta_get_comment(const struct mp4 *f)
{
- return meta_find_by_name(f, "comment", value);
+ return meta_find_by_name(f, "comment");
}