/*
- * Copyright (C) 2009-2013 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2009 Andre Noll <maan@tuebingen.mpg.de>
*
* Licensed under the GPL v2. For licencing details see COPYING.
*/
/** \file wma_afh.c The audio format handler for WMA files. */
-#include <stdlib.h>
-#include <inttypes.h>
#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
#include <regex.h>
+#include <iconv.h>
#include "para.h"
#include "error.h"
#include "portable_io.h"
#include "string.h"
#include "wma.h"
+#include "fd.h"
-#define FOR_EACH_FRAME(_f, _buf, _size, _ba) for (_f = (_buf); \
- _f + (_ba) + WMA_FRAME_SKIP < (_buf) + (_size); \
- _f += (_ba) + WMA_FRAME_SKIP)
+#define FOR_EACH_FRAME(_f, _buf, _size, _ps) for (_f = (_buf); \
+ _f + (_ps) < (_buf) + (_size); \
+ _f += (_ps))
/*
* Must be called on a frame boundary, e.g. start + header_len.
* \return Frame count, superframe count via *num_superframes.
*/
-static int count_frames(const char *buf, int buf_size, int block_align,
+static int count_frames(const char *buf, int buf_size, uint32_t packet_size,
int *num_superframes)
{
int fc = 0, sfc = 0; /* frame count, superframe count */
const uint8_t *p;
- FOR_EACH_FRAME(p, (uint8_t *)buf, buf_size, block_align) {
+ FOR_EACH_FRAME(p, (uint8_t *)buf, buf_size, packet_size) {
fc += p[WMA_FRAME_SKIP] & 0x0f;
sfc++;
}
return out;
}
-static const char comment_header[] = {
+static const char content_description_header[] = {
0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11,
0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c
};
static void read_asf_tags(const char *buf, int buf_size, struct taginfo *ti)
{
const char *p, *end = buf + buf_size, *q;
- uint16_t len1, len2, len3, len4, len5;
+ uint16_t len1, len2, len3, len4;
- p = search_pattern(comment_header, sizeof(comment_header),
- buf, buf_size);
+ p = search_pattern(content_description_header,
+ sizeof(content_description_header), buf, buf_size);
if (!p || p + 34 >= end) {
- PARA_NOTICE_LOG("comment header not found\n");
+ PARA_NOTICE_LOG("content description header not found\n");
goto next;
}
p += 24;
p += 2;
len4 = read_u16(p);
p += 2;
- len5 = read_u16(p);
+ /* ignore length of the rating information */
p += 2;
if (p + len1 >= end)
goto next;
if (p + len2 >= end)
goto next;
ti->artist = get_str16(p, len2);
- p += len2 + len3 + len4;
- if (p + len5 >= end)
+ p += len2 + len3;
+ if (p + len4 >= end)
goto next;
- ti->comment = get_str16(p, len5);
+ ti->comment = get_str16(p, len4);
next:
p = search_pattern(extended_content_header, sizeof(extended_content_header),
buf, buf_size);
}
/* Must be called on a frame boundary. */
-static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align,
+static int wma_make_chunk_table(char *buf, size_t buf_size, uint32_t packet_size,
struct afh_info *afhi)
{
const uint8_t *f, *start = (uint8_t *)buf;
afhi->chunk_table[0] = 0;
afhi->chunk_table[1] = afhi->header_len;
- num_frames = count_frames(buf, buf_size, block_align,
+ num_frames = count_frames(buf, buf_size, packet_size,
&num_superframes);
ret = -E_NO_WMA;
if (num_frames == 0 || num_superframes == 0)
frames_per_chunk = num_frames / num_superframes / 2;
PARA_INFO_LOG("%d frames per chunk\n", frames_per_chunk);
j = 1;
- FOR_EACH_FRAME(f, start, buf_size, block_align) {
+ FOR_EACH_FRAME(f, start, buf_size, packet_size) {
count += f[WMA_FRAME_SKIP] & 0x0f;
while (count > j * frames_per_chunk) {
j++;
afhi->chunk_table,
ct_size * sizeof(uint32_t));
}
- afhi->chunk_table[j] = f - start + afhi->header_len + block_align + WMA_FRAME_SKIP;
+ afhi->chunk_table[j] = f - start + afhi->header_len
+ + packet_size;
}
}
afhi->chunks_total = j;
afhi->frequency = ahi.sample_rate;
afhi->channels = ahi.channels;
afhi->header_len = ahi.header_len;
+
+ afhi->techinfo = make_message("%s%s%s%s%s",
+ ahi.use_exp_vlc? "exp vlc" : "",
+ (ahi.use_bit_reservoir && ahi.use_exp_vlc)? ", " : "",
+ ahi.use_bit_reservoir? "bit reservoir" : "",
+ (ahi.use_variable_block_len &&
+ (ahi.use_exp_vlc || ahi.use_bit_reservoir)? ", " : ""),
+ ahi.use_variable_block_len? "vbl" : ""
+ );
wma_make_chunk_table(map + ahi.header_len, numbytes - ahi.header_len,
- ahi.block_align, afhi);
+ ahi.packet_size, afhi);
read_asf_tags(map, ahi.header_len, &afhi->tags);
return 0;
}
-static const char* wma_suffixes[] = {"wma", NULL};
+struct asf_object {
+ char *ptr;
+ uint64_t size;
+};
+
+struct tag_object_nums {
+ int content_descr_obj_num;
+ int extended_content_descr_obj_num;
+};
+
+struct afs_top_level_header_object {
+ uint64_t size;
+ uint32_t num_objects;
+ uint8_t reserved1, reserved2;
+ struct asf_object *objects;
+};
+
+#define CHECK_HEADER(_p, _h) (memcmp((_p), (_h), sizeof((_h))) == 0)
+
+static int read_asf_objects(const char *src, size_t size, uint32_t num_objects,
+ struct asf_object *objs, struct tag_object_nums *ton)
+{
+ int i;
+ const char *p;
+
+ for (i = 0, p = src; i < num_objects; p += objs[i++].size) {
+ if (p + 24 > src + size)
+ return -E_NO_WMA;
+ objs[i].ptr = (char *)p;
+ objs[i].size = read_u64(p + 16);
+ if (p + objs[i].size > src + size)
+ return -E_NO_WMA;
+
+ if (CHECK_HEADER(p, content_description_header))
+ ton->content_descr_obj_num = i;
+ else if (CHECK_HEADER(p, extended_content_header))
+ ton->extended_content_descr_obj_num = i;
+ }
+ return 1;
+}
+
+static const char top_level_header_object_guid[] = {
+ 0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11,
+ 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c
+};
+
+static int convert_utf8_to_utf16(char *src, char **dst)
+{
+ /*
+ * Without specifying LE (little endian), iconv includes a byte order
+ * mark (e.g. 0xFFFE) at the beginning.
+ */
+ iconv_t cd = iconv_open("UTF-16LE", "UTF-8");
+ size_t sz, inbytes, outbytes, inbytesleft, outbytesleft;
+ char *inbuf, *outbuf;
+ int ret;
+
+ if (!src || !*src) {
+ *dst = para_calloc(2);
+ ret = 0;
+ goto out;
+ }
+ if (cd == (iconv_t) -1)
+ return -ERRNO_TO_PARA_ERROR(errno);
+ inbuf = src;
+ /* even though src is in UTF-8, strlen() should DTRT */
+ inbytes = inbytesleft = strlen(src);
+ outbytes = outbytesleft = 4 * inbytes + 2; /* hope that's enough */
+ *dst = outbuf = para_malloc(outbytes);
+ sz = iconv(cd, ICONV_CAST &inbuf, &inbytesleft, &outbuf, &outbytesleft);
+ if (sz == (size_t)-1) {
+ ret = -ERRNO_TO_PARA_ERROR(errno);
+ goto out;
+ }
+ assert(outbytes >= outbytesleft);
+ assert(outbytes - outbytesleft < INT_MAX - 2);
+ ret = outbytes - outbytesleft;
+ outbuf = para_realloc(*dst, ret + 2);
+ outbuf[ret] = outbuf[ret + 1] = '\0';
+ ret += 2;
+ *dst = outbuf;
+ PARA_INFO_LOG("converted %s to %d UTF-16 bytes\n", src, ret);
+out:
+ if (ret < 0)
+ free(*dst);
+ if (iconv_close(cd) < 0)
+ PARA_WARNING_LOG("iconv_close: %s\n", strerror(errno));
+ return ret;
+}
+
+/* The content description object contains artist, title, comment. */
+static int make_cdo(struct taginfo *tags, const struct asf_object *cdo,
+ struct asf_object *result)
+{
+ const char *cr, *rating; /* orig data */
+ uint16_t orig_title_bytes, orig_artist_bytes, orig_cr_bytes,
+ orig_comment_bytes, orig_rating_bytes;
+ /* pointers to new UTF-16 tags */
+ char *artist = NULL, *title = NULL, *comment = NULL;
+ /* number of bytes in UTF-16 for the new tags */
+ int artist_bytes, title_bytes, comment_bytes, ret;
+ char *p, null[2] = "\0\0";
+
+ result->ptr = NULL;
+ result->size = 0;
+ ret = convert_utf8_to_utf16(tags->artist, &artist);
+ if (ret < 0)
+ return ret;
+ artist_bytes = ret;
+ ret = convert_utf8_to_utf16(tags->title, &title);
+ if (ret < 0)
+ goto out;
+ title_bytes = ret;
+ ret = convert_utf8_to_utf16(tags->comment, &comment);
+ if (ret < 0)
+ goto out;
+ comment_bytes = ret;
+
+ if (cdo) {
+ /*
+ * Sizes of the five fields (stored as 16-bit numbers) are
+ * located after the header (16 bytes) and the cdo size (8
+ * bytes).
+ */
+ orig_title_bytes = read_u16(cdo->ptr + 24);
+ orig_artist_bytes = read_u16(cdo->ptr + 26);
+ orig_cr_bytes = read_u16(cdo->ptr + 28);
+ orig_comment_bytes = read_u16(cdo->ptr + 30);
+ orig_rating_bytes = read_u16(cdo->ptr + 32);
+ cr = cdo->ptr + 34 + orig_title_bytes + orig_artist_bytes;
+ rating = cr + orig_cr_bytes + orig_comment_bytes;
+ } else {
+ orig_title_bytes = 2;
+ orig_artist_bytes = 2;
+ orig_cr_bytes = 2;
+ orig_comment_bytes = 2;
+ orig_rating_bytes = 2;
+ cr = null;
+ rating = null;
+ }
+
+ /* compute size of result cdo */
+ result->size = 16 + 8 + 5 * 2 + title_bytes + artist_bytes
+ + orig_cr_bytes + comment_bytes + orig_rating_bytes;
+ PARA_DEBUG_LOG("cdo is %zu bytes\n", (size_t)result->size);
+ p = result->ptr = para_malloc(result->size);
+ memcpy(p, content_description_header, 16);
+ p += 16;
+ write_u64(p, result->size);
+ p += 8;
+ write_u16(p, title_bytes);
+ p += 2;
+ write_u16(p, artist_bytes);
+ p += 2;
+ write_u16(p, orig_cr_bytes);
+ p += 2;
+ write_u16(p, comment_bytes);
+ p += 2;
+ write_u16(p, orig_rating_bytes);
+ p += 2;
+ memcpy(p, title, title_bytes);
+ p += title_bytes;
+ memcpy(p, artist, artist_bytes);
+ p += artist_bytes;
+ memcpy(p, cr, orig_cr_bytes);
+ p += orig_cr_bytes;
+ memcpy(p, comment, comment_bytes);
+ p += comment_bytes;
+ memcpy(p, rating, orig_rating_bytes);
+ p += orig_rating_bytes;
+ assert(p - result->ptr == result->size);
+ ret = 1;
+out:
+ free(artist);
+ free(title);
+ free(comment);
+ return ret;
+}
+
+/* The extended content description object contains album and year. */
+static int make_ecdo(struct taginfo *tags, struct asf_object *result)
+{
+ int ret;
+ char *p, *album = NULL, *year = NULL, null[2] = "\0\0";
+ int album_bytes, year_bytes;
+
+ result->ptr = NULL;
+ result->size = 0;
+ ret = convert_utf8_to_utf16(tags->album, &album);
+ if (ret < 0)
+ return ret;
+ album_bytes = ret;
+ ret = convert_utf8_to_utf16(tags->year, &year);
+ if (ret < 0)
+ goto out;
+ year_bytes = ret;
+ result->size = 16 + 8 + 2; /* GUID, size, count */
+ /* name_length + name + null + data type + val length + val */
+ result->size += 2 + sizeof(album_tag_header) + 2 + 2 + 2 + album_bytes;
+ result->size += 2 + sizeof(year_tag_header) + 2 + 2 + 2 + year_bytes;
+
+ p = result->ptr = para_malloc(result->size);
+ memcpy(p, extended_content_header, 16);
+ p += 16;
+ write_u64(p, result->size);
+ p += 8;
+ write_u16(p, 2); /* count */
+ p += 2;
+
+ /* album */
+ write_u16(p, sizeof(album_tag_header) + 2);
+ p += 2;
+ memcpy(p, album_tag_header, sizeof(album_tag_header));
+ p += sizeof(album_tag_header);
+ memcpy(p, null, 2);
+ p += 2;
+ write_u16(p, 0); /* data type (UTF-16) */
+ p += 2;
+ write_u16(p, album_bytes);
+ p += 2;
+ memcpy(p, album, album_bytes);
+ p += album_bytes;
+
+ /* year */
+ write_u16(p, sizeof(year_tag_header));
+ p += 2;
+ memcpy(p, year_tag_header, sizeof(year_tag_header));
+ p += sizeof(year_tag_header);
+ memcpy(p, null, 2);
+ p += 2;
+ write_u16(p, 0); /* data type (UTF-16) */
+ p += 2;
+ write_u16(p, year_bytes);
+ p += 2;
+ memcpy(p, year, year_bytes);
+ p += year_bytes;
+ assert(p - result->ptr == result->size);
+ ret = 1;
+out:
+ free(album);
+ free(year);
+ return ret;
+}
+
+static int write_output_file(int fd, const char *map, size_t mapsize,
+ struct afs_top_level_header_object *top, struct tag_object_nums *ton,
+ struct asf_object *cdo, struct asf_object *ecdo)
+{
+ int i, ret;
+ uint64_t sz; /* of the new header object */
+ uint32_t num_objects;
+ char tmp[8];
+
+ sz = 16 + 8 + 4 + 1 + 1; /* top-level header object */
+ for (i = 0; i < top->num_objects; i++) {
+ if (i == ton->content_descr_obj_num)
+ continue;
+ if (i == ton->extended_content_descr_obj_num)
+ continue;
+ sz += top->objects[i].size;
+ }
+ sz += cdo->size;
+ sz += ecdo->size;
+ num_objects = top->num_objects;
+ if (ton->content_descr_obj_num < 0)
+ num_objects++;
+ if (ton->extended_content_descr_obj_num < 0)
+ num_objects++;
+ ret = xwrite(fd, top_level_header_object_guid, 16);
+ if (ret < 0)
+ goto out;
+ write_u64(tmp, sz);
+ ret = xwrite(fd, tmp, 8);
+ if (ret < 0)
+ goto out;
+ write_u32(tmp, num_objects);
+ ret = xwrite(fd, tmp, 4);
+ if (ret < 0)
+ goto out;
+ write_u8(tmp, top->reserved1);
+ ret = xwrite(fd, tmp, 1);
+ if (ret < 0)
+ goto out;
+ write_u8(tmp, top->reserved2);
+ ret = xwrite(fd, tmp, 1);
+ if (ret < 0)
+ goto out;
+ /*
+ * Write cto and ecto as objects 0 and 1 if they did not exist in the
+ * original file.
+ */
+ if (ton->content_descr_obj_num < 0) {
+ ret = xwrite(fd, cdo->ptr, cdo->size);
+ if (ret < 0)
+ goto out;
+ }
+ if (ton->extended_content_descr_obj_num < 0) {
+ ret = xwrite(fd, ecdo->ptr, ecdo->size);
+ if (ret < 0)
+ goto out;
+ }
+
+ for (i = 0; i < top->num_objects; i++) {
+ char *buf = top->objects[i].ptr;
+ sz = top->objects[i].size;
+ if (i == ton->content_descr_obj_num) {
+ buf = cdo->ptr;
+ sz = cdo->size;
+ } else if (i == ton->extended_content_descr_obj_num) {
+ buf = ecdo->ptr;
+ sz = ecdo->size;
+ }
+ ret = xwrite(fd, buf, sz);
+ if (ret < 0)
+ goto out;
+ }
+ ret = xwrite(fd, map + top->size, mapsize - top->size);
+out:
+ return ret;
+}
+
+static int wma_rewrite_tags(const char *map, size_t mapsize,
+ struct taginfo *tags, int fd,
+ __a_unused const char *filename)
+{
+ struct afs_top_level_header_object top;
+ struct tag_object_nums ton = {-1, -1};
+ const char *p = map;
+ /* (extended) content description object */
+ struct asf_object cdo = {.ptr = NULL}, ecdo = {.ptr = NULL};
+ int ret;
+
+ /* guid + size + num_objects + 2 * reserved */
+ if (mapsize < 16 + 8 + 4 + 1 + 1)
+ return -E_NO_WMA;
+ if (memcmp(map, top_level_header_object_guid, 16))
+ return -E_NO_WMA;
+ p += 16;
+ top.size = read_u64(p);
+ PARA_INFO_LOG("header_size: %lu\n", (long unsigned)top.size);
+ if (top.size >= mapsize)
+ return -E_NO_WMA;
+ p += 8;
+ top.num_objects = read_u32(p);
+ PARA_NOTICE_LOG("%u header objects\n", top.num_objects);
+ if (top.num_objects > top.size / 24)
+ return -E_NO_WMA;
+ p += 4;
+ top.reserved1 = read_u8(p);
+ p++;
+ top.reserved2 = read_u8(p);
+ if (top.reserved2 != 2)
+ return -E_NO_WMA;
+ p++; /* objects start at p */
+ top.objects = para_malloc(top.num_objects * sizeof(struct asf_object));
+ ret = read_asf_objects(p, top.size - (p - map), top.num_objects,
+ top.objects, &ton);
+ if (ret < 0)
+ goto out;
+ ret = make_cdo(tags, ton.content_descr_obj_num >= 0?
+ top.objects + ton.content_descr_obj_num : NULL, &cdo);
+ if (ret < 0)
+ goto out;
+ ret = make_ecdo(tags, &ecdo);
+ if (ret < 0)
+ goto out;
+ ret = write_output_file(fd, map, mapsize, &top, &ton, &cdo,
+ &ecdo);
+out:
+ free(cdo.ptr);
+ free(ecdo.ptr);
+ free(top.objects);
+ return ret;
+}
+
+static const char * const wma_suffixes[] = {"wma", NULL};
/**
* The init function of the wma audio format handler.
{
afh->get_file_info = wma_get_file_info;
afh->suffixes = wma_suffixes;
+ afh->rewrite_tags = wma_rewrite_tags;
}