/* -*- linux-c -*- Copyright (C) 2007 Tom Szilagyi This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. $Id: metadata_ogg.c 987 2008-01-26 20:09:54Z tszilagyi $ */ #include #include #include #include #include #include #include #include "common.h" #include "i18n.h" #include "metadata_ogg.h" static const u_int32_t crc_table[256] = { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 }; /* CRC for computing Ogg page checksums. * initial value and final XOR = 0, generator polynomial=0x04c11db7. */ u_int32_t meta_ogg_crc(unsigned char * data, int length) { u_int32_t sum = 0; int i; for (i = 0; i < length; ++i) { sum = (sum << 8) ^ crc_table[((sum >> 24) & 0xff) ^ data[i]]; } return sum; } meta_ogg_page_t * meta_ogg_page_new(void) { meta_ogg_page_t * page = NULL; if ((page = calloc(1, sizeof(meta_ogg_page_t))) == NULL) { fprintf(stderr, "metadata_ogg.c: meta_ogg_page_new() failed: calloc error\n"); return NULL; } return page; } void meta_ogg_page_free(meta_ogg_page_t * page) { if (page->data != NULL) { free(page->data); } free(page); } /* void meta_ogg_page_dump(meta_ogg_page_t * page) { int i; printf("\nOgg page\n"); printf(" version=0x%02x flags=0x%02x\n", page->version, page->flags); printf(" granulepos=0x%016llx\n", page->granulepos); printf(" serialno =0x%08x\n", page->serialno); printf(" seqno =0x%08x\n", page->seqno); printf(" checksum =0x%08x\n", page->checksum); printf(" n_segments=%d\n", page->n_segments); for (i = 0; i < page->n_segments; i++) printf(" %02x", page->segment_table[i]); printf("\n"); } */ unsigned char * meta_ogg_render_page(meta_ogg_page_t * page, unsigned int * length) { int i; unsigned int total_length = 27; unsigned int data_length = 0; u_int32_t crc; unsigned char * data; total_length += page->n_segments; for (i = 0; i < page->n_segments; i++) { total_length += page->segment_table[i]; data_length += page->segment_table[i]; } data = (unsigned char *)calloc(1, total_length); if (data == NULL) { fprintf(stderr, "meta_ogg_render_page: calloc error\n"); return NULL; } /* Render Ogg header */ data[0] = 'O'; data[1] = 'g'; data[2] = 'g'; data[3] = 'S'; data[4] = page->version; data[5] = page->flags; meta_write_int64(page->granulepos, data + 6); meta_write_int32(page->serialno, data + 14); meta_write_int32(page->seqno, data + 18); meta_write_int32(0x00000000, data + 22); data[26] = page->n_segments; /* Render segment table */ for (i = 0; i < page->n_segments; i++) { data[27 + i] = page->segment_table[i]; } /* Render packet data */ memcpy(data + 27 + page->n_segments, page->data, data_length); /* Update CRC */ crc = meta_ogg_crc(data, total_length); meta_write_int32(crc, data + 22); if (length != NULL) { *length = total_length; } return data; } /* read the next page from an Ogg file */ meta_ogg_page_t * meta_ogg_read_page(FILE * file) { unsigned char buf[27]; meta_ogg_page_t * page = NULL; unsigned int data_size = 0; int i; if (fread(buf, 1, 27, file) != 27) { if (feof(file)) { return NULL; } fprintf(stderr, "meta_ogg_read_page: error reading file (Ogg header)\n"); } if ((buf[0] != 'O') || (buf[1] != 'g') || (buf[2] != 'g') || (buf[3] != 'S')) { fprintf(stderr, "meta_ogg_read_page: page sync error\n"); return NULL; } page = meta_ogg_page_new(); if (page == NULL) { return NULL; } page->version = buf[4]; page->flags = buf[5]; page->granulepos = meta_read_int64(buf + 6); page->serialno = meta_read_int32(buf + 14); page->seqno = meta_read_int32(buf + 18); page->checksum = meta_read_int32(buf + 22); page->n_segments = buf[26]; for (i = 0; i < page->n_segments; i++) { unsigned char val; if (fread(&val, 1, 1, file) != 1) { if (feof(file)) { fprintf(stderr, "meta_ogg_read_page: premature end of file\n"); } else { fprintf(stderr, "meta_ogg_read_page: error reading file (Segment table)\n"); } meta_ogg_page_free(page); return NULL; } page->segment_table[i] = val; data_size += val; } page->data = (unsigned char *)calloc(1, data_size); if (page->data == NULL) { fprintf(stderr, "meta_ogg_read_page: calloc error\n"); meta_ogg_page_free(page); return NULL; } if (fread(page->data, 1, data_size, file) != data_size) { if (feof(file)) { fprintf(stderr, "meta_ogg_read_page: premature end of file\n"); } else { fprintf(stderr, "meta_ogg_read_page: error reading file (Packet data)\n"); } meta_ogg_page_free(page); return NULL; } return page; } /* parse an Ogg stream into a list of pages */ GSList * meta_ogg_parse(char * filename) { FILE * file; GSList * slist = NULL; meta_ogg_page_t * page; if ((file = fopen(filename, "rb")) == NULL) { fprintf(stderr, "meta_ogg_parse: fopen() failed\n"); return NULL; } while (1) { page = meta_ogg_read_page(file); if (page != NULL) { slist = g_slist_prepend(slist, (gpointer)page); } else { break; } } fclose(file); return g_slist_reverse(slist); } /* render list of pages to an Ogg stream */ int meta_ogg_render(GSList * slist, char * filename, int n_pages) { FILE * file; meta_ogg_page_t * page; unsigned char * data; unsigned int length; u_int64_t total_length = 0L; int page_count = 0; if ((file = fopen(filename, (n_pages == -1) ? "wb" : "r+b")) == NULL) { fprintf(stderr, "meta_ogg_render: fopen() failed\n"); return -1; } while ((slist != NULL) && ((n_pages == -1) || (page_count < n_pages))) { page = (meta_ogg_page_t *)slist->data; data = meta_ogg_render_page(page, &length); if (data != NULL) { if (fwrite(data, 1, length, file) != length) { fprintf(stderr, "meta_ogg_render: fwrite() failed\n"); return -1; } free(data); } else { fprintf(stderr, "meta_ogg_render: rendering page failed\n"); return -1; } slist = g_slist_next(slist); total_length += length; ++page_count; } fclose(file); return 0; } void meta_ogg_free(GSList * slist) { GSList * s = slist; while (slist != NULL) { meta_ogg_page_t * page = (meta_ogg_page_t *)slist->data; meta_ogg_page_free(page); slist = g_slist_next(slist); } g_slist_free(s); } /* returns packet data size without header and segment table overhead */ unsigned int meta_ogg_get_page_size(GSList * slist, int nth) { meta_ogg_page_t * page = (meta_ogg_page_t *)g_slist_nth_data(slist, nth); int i; unsigned int size = 0; for (i = 0; i < page->n_segments; i++) { size += page->segment_table[i]; } return size; } unsigned int meta_ogg_vc_last_page_growable(meta_ogg_page_t * page) { int i; unsigned char last_lace = 255; unsigned int growable = 0; for (i = 0; page->segment_table[i] == 255; i++); last_lace = page->segment_table[i]; growable += 255 * (255 - page->n_segments); /* add new segments */ growable += (254 - last_lace); /* increase last lace of OXC to 254 */ return growable; } /* Total number of bytes the Vorbis comment packet is * growable without the need for inserting new pages. */ unsigned int meta_ogg_vc_get_total_growable(GSList * slist) { unsigned char * vc_packet; unsigned int vc_length; unsigned int n_pages; unsigned int total_growable = 0; meta_ogg_page_t * page; int i; vc_packet = meta_ogg_get_vc_packet(slist, &vc_length, &n_pages); free(vc_packet); for (i = 0; i < n_pages-1; i++) { /* OXC-only pages to max size */ total_growable += 255*255 - meta_ogg_get_page_size(slist, i+1); } /* current last page of OXC is special */ page = (meta_ogg_page_t *)g_slist_nth_data(slist, n_pages); total_growable += meta_ogg_vc_last_page_growable(page); return total_growable; } /* reads packet starting on the first page of slist */ /* n_pages: number of pages the packet spans */ unsigned char * meta_ogg_read_packet(GSList * slist, unsigned int * length, unsigned int * n_pages) { meta_ogg_page_t * page; int n = 1; int packet_length = 0; int fragment_length; /* length of packet fragment on this page */ int stored_length = 0; unsigned char * data = NULL; int i; while (1) { page = (meta_ogg_page_t *)slist->data; fragment_length = 0; for (i = 0; i < page->n_segments; i++) { packet_length += page->segment_table[i]; fragment_length += page->segment_table[i]; if (page->segment_table[i] < 255) { break; } } data = realloc(data, packet_length); if (data == NULL) { fprintf(stderr, "meta_ogg_read_packet: realloc() error\n"); return NULL; } memcpy(data + stored_length, page->data, fragment_length); stored_length += fragment_length; if (i == page->n_segments) { /* continue with next page */ slist = g_slist_next(slist); ++n; } else { break; } } if (length != NULL) { *length = packet_length; } if (n_pages != NULL) { *n_pages = n; } return data; } unsigned char * meta_ogg_get_vc_packet(GSList * slist, unsigned int * length, unsigned int * n_pages) { /* second Ogg page always begins with Vorbis comment packet */ return meta_ogg_read_packet(g_slist_next(slist), length, n_pages); } int page_data_size(meta_ogg_page_t * page) { int size = 0; int j; for (j = 0; j < page->n_segments; j++) { size += page->segment_table[j]; if (page->segment_table[j] < 255) break; } return size; } GSList * meta_ogg_vc_in_place_ovwr(GSList * slist, int n_pages, unsigned char * payload, int * n_pages_to_write) { int i; int data_pos = 0; for (i = 0; i < n_pages; i++) { meta_ogg_page_t * page = (meta_ogg_page_t *)g_slist_nth_data(slist, i+1); int size = page_data_size(page); memcpy(page->data, payload + data_pos, size); data_pos += size; } *n_pages_to_write = n_pages+1; return slist; } GSList * meta_ogg_vc_expander_encaps(GSList * slist, int n_pages, unsigned int new_length, unsigned int old_length, unsigned char * payload, int * n_pages_to_write) { int i; int data_pos = 0; int expansion = new_length - old_length; for (i = 0; i < n_pages; i++) { meta_ogg_page_t * page = (meta_ogg_page_t *)g_slist_nth_data(slist, i+1); int is_last_page = (i == n_pages-1) ? 1 : 0; int total_size = meta_ogg_get_page_size(slist, i+1); int size = page_data_size(page); int growable = is_last_page ? meta_ogg_vc_last_page_growable(page) : (255*255 - total_size); int new_size; int j; if (growable == 0) { /* this page is already full */ continue; } if (!is_last_page) { growable -= growable % 255; } if (growable > expansion) { growable = expansion; } new_size = size + growable; expansion -= growable; /* move data */ page->data = realloc(page->data, total_size + growable); memmove(page->data + new_size, page->data + size, total_size - size); memcpy(page->data, payload + data_pos, new_size); data_pos += new_size; /* update segment_table accordingly */ memmove(page->segment_table + new_size/255+1, page->segment_table + size/255+1, page->n_segments - (size/255+1)); for (j = 0; j < new_size/255; j++) page->segment_table[j] = 255; page->segment_table[j] = new_size % 255; page->n_segments += (new_size/255 - size/255); } *n_pages_to_write = -1; return slist; } GSList * meta_ogg_vc_paginator_encaps(GSList * slist, int n_pages, unsigned char * payload, unsigned int length) { meta_ogg_page_t * page; meta_ogg_page_t * succ_page; int total_size; int size; int i; int data_pos = 0; GSList * tail; succ_page = (meta_ogg_page_t *)g_slist_nth_data(slist, n_pages); total_size = meta_ogg_get_page_size(slist, n_pages); size = page_data_size(succ_page); if (size == total_size) { slist = g_slist_remove(slist, succ_page); meta_ogg_page_free(succ_page); succ_page = (meta_ogg_page_t *)g_slist_nth_data(slist, n_pages+1); } else { succ_page->flags &= ~META_OGG_FRAGM_PACKET; memmove(succ_page->data, succ_page->data + size, total_size - size); succ_page->data = realloc(succ_page->data, total_size - size); /* update segment_table accordingly */ memmove(succ_page->segment_table, succ_page->segment_table + size/255+1, succ_page->n_segments - (size/255+1)); succ_page->n_segments -= size/255+1; } /* remove pages 1..n_pages-1 */ for (i = n_pages-1; i >= 1; i--) { page = (meta_ogg_page_t *)g_slist_nth_data(slist, i); slist = g_slist_remove(slist, page); meta_ogg_page_free(page); } /* insert new pages for OXC packet */ i = 0; while (length > 0) { int j; int ins_len = 255*255; int n_segments; if (length < ins_len) { ins_len = length; } /* new page with ins_len amount of bytes starting from data_pos */ page = meta_ogg_page_new(); if (i > 0) { page->flags |= META_OGG_FRAGM_PACKET; } page->version = succ_page->version; page->granulepos = 0L; page->serialno = succ_page->serialno; page->seqno = i+1; /* There is 1 preceding Ogg page (stream init) */ page->data = calloc(ins_len, 1); if (page->data == NULL) { fprintf(stderr, "meta_ogg_vc_paginator_encaps(): malloc error\n"); return slist; } memcpy(page->data, payload + data_pos, ins_len); n_segments = ins_len/255+1; for (j = 0; j < n_segments-1; j++) { page->segment_table[j] = 255; } page->segment_table[j] = ins_len % 255; if (length > ins_len) { page->n_segments = n_segments - 1; } else { page->n_segments = n_segments; } slist = g_slist_insert(slist, page, i+1); data_pos += ins_len; length -= ins_len; ++i; } /* Renumber remaining Ogg pages */ tail = g_slist_nth(slist, i); while (tail != NULL) { page = (meta_ogg_page_t *)tail->data; page->seqno = i++; tail = g_slist_next(tail); } return slist; } GSList * meta_ogg_vc_encapsulate_payload(GSList * slist, unsigned char ** payload, unsigned int length, int * n_pages_to_write) { unsigned char * vc_packet; unsigned int vc_length; unsigned int n_pages; unsigned int total_growable; vc_packet = meta_ogg_get_vc_packet(slist, &vc_length, &n_pages); free(vc_packet); if (length <= vc_length) { /* In-place overwrite, padding with zeroes */ if (length < vc_length) { *payload = realloc(*payload, vc_length); memset(*payload + length, 0x00, vc_length - length); } return meta_ogg_vc_in_place_ovwr(slist, n_pages, *payload, n_pages_to_write); } total_growable = meta_ogg_vc_get_total_growable(slist); if (length <= vc_length + total_growable) { /* Expand existing pages */ return meta_ogg_vc_expander_encaps(slist, n_pages, length, vc_length, *payload, n_pages_to_write); } *n_pages_to_write = -1; /* re-render the whole file */ return meta_ogg_vc_paginator_encaps(slist, n_pages, *payload, length); } unsigned char * meta_ogg_vc_render(metadata_t * meta, unsigned int * length) { unsigned char * payload; unsigned int len = 16; unsigned int n_comments = 0; int n_comments_pos; meta_frame_t * frame; payload = (unsigned char *)calloc(len, 1); if (payload == NULL) { fprintf(stderr, "meta_ogg_vc_render(): calloc error\n"); return NULL; } payload[0] = 0x03; payload[1] = 'v'; payload[2] = 'o'; payload[3] = 'r'; payload[4] = 'b'; payload[5] = 'i'; payload[6] = 's'; if ((frame = metadata_get_frame_by_tag(meta, META_TAG_OXC, NULL)) == NULL) { /* no Ogg Xiph comment in this metablock */ /* we cannot really remove it, only delete all of its contents. */ len = 16; payload[len-1] = 0x01; /* last framing bit */ *length = len; return payload; } else { /* vendor string */ frame = metadata_get_frame_by_type(meta, META_FIELD_VENDOR, NULL); unsigned int str_len; if (frame == NULL) { fprintf(stderr, "meta_ogg_vc_render(): programmer error: " "no Vendor string in metablock\n"); free(payload); return NULL; } str_len = strlen(frame->field_val); meta_write_int32(str_len, payload + 7); payload = realloc(payload, len + str_len); memcpy(payload + 11, frame->field_val, str_len); len += str_len; payload = realloc(payload, len + 4); n_comments_pos = 11 + str_len; meta_write_int32(0, payload + n_comments_pos); /* n_comments */ len = n_comments_pos + 4; payload[len] = 0x01; } /* all else */ frame = metadata_get_frame_by_tag(meta, META_TAG_OXC, NULL); while (frame != NULL) { char * vc_entry; int vc_len; int field_len; char * field_val; char fval[MAXLEN]; char * str; char * renderfmt = meta_get_field_renderfmt(frame->type); int i; if (frame->type == META_FIELD_VENDOR) { frame = metadata_get_frame_by_tag(meta, META_TAG_OXC, frame); continue; } if (!meta_get_fieldname_embedded(META_TAG_OXC, frame->type, &str)) { str = frame->field_name; } vc_entry = calloc(strlen(str) + 2, 1); strcpy(vc_entry, str); for (i = 0; vc_entry[i] != '\0'; i++) { vc_entry[i] = tolower(vc_entry[i]); } strcat(vc_entry, "="); vc_len = strlen(vc_entry); if (META_FIELD_TEXT(frame->type)) { field_val = frame->field_val; field_len = strlen(frame->field_val); } else if (META_FIELD_INT(frame->type)) { snprintf(fval, MAXLEN-1, renderfmt, frame->int_val); field_val = fval; field_len = strlen(field_val); } else if (META_FIELD_FLOAT(frame->type)) { snprintf(fval, MAXLEN-1, renderfmt, frame->float_val); field_val = fval; field_len = strlen(field_val); } else { fval[0] = '\0'; field_val = fval; field_len = 0; } vc_entry = realloc(vc_entry, vc_len + field_len); memcpy(vc_entry + vc_len, field_val, field_len); vc_len += field_len; payload = realloc(payload, len + 4); meta_write_int32(vc_len, payload + len); len += 4; payload = realloc(payload, len + vc_len); memcpy(payload + len, vc_entry, vc_len); len += vc_len; ++n_comments; free(vc_entry); frame = metadata_get_frame_by_tag(meta, META_TAG_OXC, frame); } meta_write_int32(n_comments, payload + n_comments_pos); if (n_comments > 0) { payload = realloc(payload, len+1); payload[len] = 0x01; ++len; } *length = len; return payload; } #ifdef HAVE_OGG_VORBIS metadata_t * metadata_from_vorbis_comment(vorbis_comment * vc) { int i; metadata_t * meta; if (!vc) { return NULL; } meta = metadata_new(); meta->valid_tags = META_TAG_OXC; for (i = 0; i < vc->comments; i++) { char key[MAXLEN]; char val[MAXLEN]; char * end; char c; int k, n = 0; for (k = 0; ((c = vc->user_comments[i][n]) != '\0') && (c != '=') && (k < MAXLEN-1); k++) { key[k] = (k == 0) ? toupper(c) : tolower(c); ++n; } key[k] = '\0'; ++n; for (k = 0; ((c = vc->user_comments[i][n]) != '\0') && (k < MAXLEN-1); k++) { val[k] = c; ++n; } val[k] = '\0'; if (!g_utf8_validate(val, -1, (const gchar**)&end)) { fprintf(stderr, "metadata_from_vorbis_comment: invalid UTF-8 sequence in field '%s', truncating.\n", key); *end = '\0'; } if (strlen(val) > 0) { metadata_add_frame_from_keyval(meta, META_TAG_OXC, key, val); } } /* Add Vendor string */ metadata_add_frame_from_keyval(meta, META_TAG_OXC, "vendor", (vc->vendor != NULL) ? vc->vendor : ""); return meta; } #endif /* HAVE_OGG_VORBIS */