Merge branch 't/misc'
[paraslash.git] / flac_afh.c
index 4ac68cd..21f86f4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 Andre Noll <maan@systemlinux.org>
+ * Copyright (C) 2011-2014 Andre Noll <maan@systemlinux.org>
  *
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 /** \file flac_afh.c Audio format handler for flac files. */
 
 #include <regex.h>
+#include <FLAC/stream_decoder.h>
+#include <FLAC/metadata.h>
 
 #include "para.h"
 #include "error.h"
 #include "afh.h"
+#include "string.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 {
+       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 - 1;
+}
+
+static int meta_close_cb(FLAC__IOHandle __a_unused handle)
 {
        return 0;
 }
 
+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;
+       }
+}
+
+static int flac_read_meta(struct private_flac_afh_data *pfad)
+{
+       int ret;
+       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
+       };
+       FLAC__Metadata_Chain *chain;
+       FLAC__Metadata_Iterator *iter;
+       FLAC__StreamMetadata_StreamInfo *info = NULL;
+       FLAC__bool ok;
+
+       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);
+       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);
+free_chain:
+       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 insits 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 const char* flac_suffixes[] = {"flac", NULL};
 
 /**