X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=flac_afh.c;h=2e2842b183c91bfbe06ec2de0616fb52872ba4f4;hp=4ac68cdce7063f8e4262a92fa25761d2cc9d77fa;hb=b74bb1b8dbd1fbe2bbd918ae7a77ebafba674e7c;hpb=c9a877508a65ae272980383682aa5338f87cd2dd diff --git a/flac_afh.c b/flac_afh.c index 4ac68cdc..2e2842b1 100644 --- a/flac_afh.c +++ b/flac_afh.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 Andre Noll + * Copyright (C) 2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -7,18 +7,515 @@ /** \file flac_afh.c Audio format handler for flac files. */ #include +#include +#include #include "para.h" #include "error.h" #include "afh.h" +#include "string.h" +#include "fd.h" -static int flac_get_file_info(char *map, size_t numbytes, __a_unused int fd, - struct afh_info *afhi) +struct private_flac_afh_data { + const char *map; + size_t map_bytes; + size_t fpos; + struct afh_info *afhi; + unsigned blocksize; +}; + +static size_t copy_data(struct private_flac_afh_data *pfad, void *buf, + size_t want) +{ + size_t copy, have = pfad->map_bytes - pfad->fpos; + + if (have == 0) + return 0; + copy = have < want? have : want; + memcpy(buf, pfad->map + pfad->fpos, copy); + pfad->fpos += copy; + return copy; +} + +static size_t meta_read_cb(void *ptr, size_t size, size_t nmemb, + FLAC__IOHandle handle) +{ + struct private_flac_afh_data *pfad = handle; + return copy_data(pfad, ptr, nmemb * size); +} + +static int meta_seek_cb(FLAC__IOHandle handle, FLAC__int64 offset, int whence) +{ + struct private_flac_afh_data *pfad = handle; + + if (offset < 0) + return -1; + + switch (whence) { + case SEEK_SET: + if (offset >= pfad->map_bytes) + return -1; + pfad->fpos = offset; + return 0; + case SEEK_CUR: + if (pfad->fpos + offset >= pfad->map_bytes) + return -1; + pfad->fpos += offset; + return 0; + case SEEK_END: + if (offset >= pfad->map_bytes) + return -1; + pfad->fpos = offset; + return 0; + default: + return -1; + } +} + +static FLAC__int64 meta_tell_cb(FLAC__IOHandle handle) +{ + struct private_flac_afh_data *pfad = handle; + return pfad->fpos; +} + +static int meta_eof_cb(FLAC__IOHandle handle) +{ + struct private_flac_afh_data *pfad = handle; + return pfad->fpos == pfad->map_bytes; +} + +static int meta_close_cb(FLAC__IOHandle __a_unused handle) { return 0; } -static const char* flac_suffixes[] = {"flac", NULL}; +static const FLAC__IOCallbacks meta_callbacks = { + .read = meta_read_cb, + .write = NULL, + .seek = meta_seek_cb, + .tell = meta_tell_cb, + .eof = meta_eof_cb, + .close = meta_close_cb +}; + +static void free_tags(struct taginfo *tags) +{ + freep(&tags->artist); + freep(&tags->title); + freep(&tags->album); + freep(&tags->year); + freep(&tags->comment); +} + +static bool copy_if_tag_type(const char *tag, int taglen, const char *type, + char **p) +{ + char *q = key_value_copy(tag, taglen, type); + if (!q) + return false; + free(*p); + *p = q; + return true; +} + +static void flac_read_vorbis_comments(FLAC__StreamMetadata_VorbisComment *vc, + struct taginfo *tags) +{ + int i; + FLAC__StreamMetadata_VorbisComment_Entry *comment = vc->comments; + + PARA_INFO_LOG("found %u vorbis comments\n", vc->num_comments); + for (i = 0; i < vc->num_comments; i++) { + char *e = (char *)comment[i].entry; + int len = comment[i].length; + if (copy_if_tag_type(e, len, "artist", &tags->artist)) + continue; + if (copy_if_tag_type(e, len, "title", &tags->title)) + continue; + if (copy_if_tag_type(e, len, "album", &tags->album)) + continue; + if (copy_if_tag_type(e, len, "year", &tags->year)) + continue; + if (copy_if_tag_type(e, len, "comment", &tags->comment)) + continue; + } +} + +/* + * FLAC__metadata_object_vorbiscomment_replace_comment() is buggy in some + * libFLAC versions (see commit e95399c1 in the flac git repository). Hence we + * use delete and add as a workaround. + */ +static int flac_replace_vorbis_comment(FLAC__StreamMetadata *b, + const char *tag, const char* val) +{ + FLAC__bool ok; + FLAC__StreamMetadata_VorbisComment_Entry entry; + int ret; + + PARA_INFO_LOG("replacing %s\n", tag); + ret = FLAC__metadata_object_vorbiscomment_remove_entries_matching( + b, tag); + if (ret < 0) + return -E_FLAC_REPLACE_COMMENT; + ok = FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair( + &entry, tag, val? val : ""); + if (!ok) + return -E_FLAC_REPLACE_COMMENT; + ok = FLAC__metadata_object_vorbiscomment_append_comment(b, entry, + false /* no copy */); + if (!ok) { + free(entry.entry); + return -E_FLAC_REPLACE_COMMENT; + } + return 1; +} + +static int flac_replace_vorbis_comments(FLAC__Metadata_Chain *chain, + FLAC__StreamMetadata *b, struct taginfo *tags) +{ + FLAC__bool ok; + int ret; + + ret = flac_replace_vorbis_comment(b, "artist", tags->artist); + if (ret < 0) + return ret; + ret = flac_replace_vorbis_comment(b, "title", tags->title); + if (ret < 0) + return ret; + ret = flac_replace_vorbis_comment(b, "album", tags->album); + if (ret < 0) + return ret; + ret = flac_replace_vorbis_comment(b, "year", tags->year); + if (ret < 0) + return ret; + ret = flac_replace_vorbis_comment(b, "comment", tags->comment); + if (ret < 0) + return ret; + /* + * Even if padding is disabled, libflac will try to modify the original + * file inplace if the metadata size has not changed. This won't work + * here though, because the original file is mapped read-only. Since + * there is no option to force the use of a temp file we work around + * this shortcoming by adding a dummy entry which increases the size of + * the meta data. If the entry already exists, we simply remove it. + */ + ok = FLAC__metadata_chain_check_if_tempfile_needed(chain, + false /* no padding */); + if (!ok) { + PARA_INFO_LOG("adding/removing dummy comment\n"); + ret = FLAC__metadata_object_vorbiscomment_remove_entries_matching( + b, "comment2"); + if (ret < 0) + return -E_FLAC_REPLACE_COMMENT; + if (ret == 0) { /* nothing was removed */ + ret = flac_replace_vorbis_comment(b, "comment2", + "avoid inplace write"); + if (ret < 0) + return ret; + } + assert(FLAC__metadata_chain_check_if_tempfile_needed(chain, + false /* no padding */)); + } + return 1; +} + +static int flac_init_meta(struct private_flac_afh_data *pfad, + FLAC__Metadata_Chain **chainp, FLAC__Metadata_Iterator **iterp) +{ + int ret; + FLAC__bool ok; + FLAC__Metadata_Chain *chain; + FLAC__Metadata_Iterator *iter; + + *chainp = NULL; + *iterp = NULL; + chain = FLAC__metadata_chain_new(); + if (!chain) + return -E_FLAC_CHAIN_ALLOC; + ret = -E_FLAC_CHAIN_READ; + ok = FLAC__metadata_chain_read_with_callbacks(chain, pfad, + meta_callbacks); + if (!ok) + goto free_chain; + ret = -E_FLAC_ITER_ALLOC; + iter = FLAC__metadata_iterator_new(); + if (!iter) + goto free_chain; + FLAC__metadata_iterator_init(iter, chain); + *iterp = iter; + *chainp = chain; + return 1; +free_chain: + FLAC__metadata_chain_delete(chain); + return ret; +} + +static int flac_read_meta(struct private_flac_afh_data *pfad) +{ + int ret; + FLAC__Metadata_Chain *chain; + FLAC__Metadata_Iterator *iter; + FLAC__StreamMetadata_StreamInfo *info = NULL; + FLAC__bool ok; + + ret = flac_init_meta(pfad, &chain, &iter); + if (ret < 0) + return ret; + for (;;) { + FLAC__StreamMetadata *b; + b = FLAC__metadata_iterator_get_block(iter); + if (!b) + break; + if (b->type == FLAC__METADATA_TYPE_STREAMINFO) { + info = &b->data.stream_info; + ret = -E_FLAC_VARBLOCK; + if (info->min_blocksize != info->max_blocksize) + goto free_iter; + pfad->afhi->frequency = info->sample_rate; + pfad->afhi->channels = info->channels; + pfad->blocksize = info->min_blocksize; + } + if (b->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) + flac_read_vorbis_comments(&b->data.vorbis_comment, + &pfad->afhi->tags); + ok = FLAC__metadata_iterator_next(iter); + if (!ok) + break; + } + ret = info? 0: -E_FLAC_STREAMINFO; +free_iter: + FLAC__metadata_iterator_delete(iter); + FLAC__metadata_chain_delete(chain); + if (ret < 0) + free_tags(&pfad->afhi->tags); + return ret; +} + +static FLAC__StreamDecoderReadStatus read_cb( + __a_unused const FLAC__StreamDecoder *decoder, + FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + struct private_flac_afh_data *pfad = client_data; + + assert(*bytes > 0); + *bytes = copy_data(pfad, buffer, *bytes); + if (*bytes == 0) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +static FLAC__StreamDecoderTellStatus tell_cb( + __a_unused const FLAC__StreamDecoder *decoder, + FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + struct private_flac_afh_data *pfad = client_data; + + *absolute_byte_offset = pfad->fpos; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +/* libflac insists on this callback being present. */ +static FLAC__StreamDecoderWriteStatus write_cb( + __a_unused const FLAC__StreamDecoder *decoder, + __a_unused const FLAC__Frame *frame, + __a_unused const FLAC__int32 *const buffer[], + __a_unused void *client_data) +{ + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static void error_cb( + __a_unused const FLAC__StreamDecoder *decoder, + FLAC__StreamDecoderErrorStatus status, + __a_unused void *client_data) +{ + PARA_ERROR_LOG("%s\n", FLAC__StreamDecoderErrorStatusString[status]); +} + +static int flac_afh_read_chunks(struct private_flac_afh_data *pfad) +{ + FLAC__StreamDecoder *decoder; + FLAC__StreamDecoderInitStatus init_status; + FLAC__bool ok; + FLAC__uint64 c; + unsigned chunk_table_size = 0; + int ret; + struct afh_info *afhi = pfad->afhi; + + PARA_INFO_LOG("reading chunk table\n"); + afhi->chunk_table = NULL; + decoder = FLAC__stream_decoder_new(); + if (!decoder) + return -E_FLAC_AFH_DECODER_ALLOC; + ret = -E_FLAC_AFH_DECODER_INIT; + init_status = FLAC__stream_decoder_init_stream( + decoder, + read_cb, + NULL, /* seek */ + tell_cb, + NULL, /* length */ + NULL, /* eof */ + write_cb, + NULL, + error_cb, + pfad + ); + if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) + goto free_decoder; + ret = -E_FLAC_SKIP_META; + ok = FLAC__stream_decoder_process_until_end_of_metadata(decoder); + if (!ok) + goto free_decoder; + for (c = 0;; c++) { + FLAC__uint64 pos; + FLAC__StreamDecoderState state; + + ret = -E_FLAC_DECODE_POS; + ok = FLAC__stream_decoder_get_decode_position(decoder, &pos); + if (!ok) + goto free_decoder; + if (c >= chunk_table_size) { + chunk_table_size = 2 * chunk_table_size + 100; + afhi->chunk_table = para_realloc(afhi->chunk_table, + chunk_table_size * sizeof(uint32_t)); + } + afhi->chunk_table[c] = pos; + + ok = FLAC__stream_decoder_skip_single_frame(decoder); + if (!ok) + break; + state = FLAC__stream_decoder_get_state(decoder); + if (state == FLAC__STREAM_DECODER_END_OF_STREAM) + break; + } + afhi->chunks_total = c; + ret = 1; +free_decoder: + FLAC__stream_decoder_finish(decoder); + FLAC__stream_decoder_delete(decoder); + if (ret < 0) + freep(&afhi->chunk_table); + return ret; +} + +static int flac_get_file_info(char *map, size_t map_bytes, __a_unused int fd, + struct afh_info *afhi) +{ + struct private_flac_afh_data pfad_struct = { + .map = map, + .map_bytes = map_bytes, + .afhi = afhi + }, *pfad = &pfad_struct; + int ret; + double chunk_time; + + afhi->header_len = 0; + ret = flac_read_meta(pfad); + if (ret < 0) + return ret; + pfad->fpos = 0; + ret = flac_afh_read_chunks(pfad); + if (ret < 0) { + free_tags(&afhi->tags); + return ret; + } + afhi->techinfo = make_message("blocksize: %u", pfad->blocksize); + afhi->seconds_total = DIV_ROUND_UP(afhi->chunks_total * pfad->blocksize, + afhi->frequency); + afhi->bitrate = pfad->map_bytes * 8 / afhi->seconds_total / 1024; + chunk_time = (double)pfad->blocksize / afhi->frequency; + afhi->chunk_tv.tv_sec = chunk_time; + chunk_time *= 1000 * 1000; + chunk_time -= afhi->chunk_tv.tv_sec * 1000 * 1000; + afhi->chunk_tv.tv_usec = chunk_time; + return 1; +} + +static size_t temp_write_cb(const void *ptr, size_t size, size_t nmemb, + FLAC__IOHandle handle) +{ + int ret, fd = *(int *)handle; + size_t n = size * nmemb; /* FIXME: possible overflow */ + + ret = write_all(fd, ptr, n); + + /* + * libflac expects POSIX semantics: If an error occurs, or the end of + * the file is reached, the return value is a short item count or zero. + */ + if (ret < 0) { + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + return 0; + } + return nmemb; +} + +/* only the write callback needs to be supplied for writing the temp file. */ +static const FLAC__IOCallbacks temp_callbacks = { + .write = temp_write_cb, +}; + +static int flac_write_chain(FLAC__Metadata_Chain *chain, + struct private_flac_afh_data *pfad, int fd) +{ + FLAC__bool ok; + + ok = FLAC__metadata_chain_write_with_callbacks_and_tempfile(chain, + false /* no padding*/, pfad, + meta_callbacks, &fd, temp_callbacks); + if (!ok) { + FLAC__Metadata_ChainStatus st; + st = FLAC__metadata_chain_status(chain); + PARA_ERROR_LOG("chain status: %d\n", st); + if (st == FLAC__METADATA_CHAIN_STATUS_READ_ERROR) + PARA_ERROR_LOG("read error\n"); + return -E_FLAC_WRITE_CHAIN; + } + return 1; +} + +static int flac_rewrite_tags(const char *map, size_t map_bytes, + struct taginfo *tags, int fd, __a_unused const char *filename) +{ + int ret; + FLAC__Metadata_Chain *chain; + FLAC__Metadata_Iterator *iter; + FLAC__StreamMetadata *b = NULL; + FLAC__bool ok; + struct private_flac_afh_data *pfad = para_calloc(sizeof(*pfad)); + + pfad->map = map; + pfad->map_bytes = map_bytes; + pfad->fpos = 0; + + ret = flac_init_meta(pfad, &chain, &iter); + if (ret < 0) + goto free_pfad; + for (ok = true; ok; ok = FLAC__metadata_iterator_next(iter)) { + b = FLAC__metadata_iterator_get_block(iter); + assert(b); + if (b->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) + break; + b = NULL; + } + ret = -E_FLAC_REPLACE_COMMENT; + if (!b) + goto out; + ret = flac_replace_vorbis_comments(chain, b, tags); + if (ret < 0) + goto out; + ret = flac_write_chain(chain, pfad, fd); +out: + FLAC__metadata_iterator_delete(iter); + FLAC__metadata_chain_delete(chain); +free_pfad: + free(pfad); + return ret; +} + +static const char * const flac_suffixes[] = {"flac", NULL}; /** * The init function of the flac audio format handler. @@ -29,4 +526,5 @@ void flac_afh_init(struct audio_format_handler *afh) { afh->get_file_info = flac_get_file_info, afh->suffixes = flac_suffixes; + afh->rewrite_tags = flac_rewrite_tags; }