+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)