summaryrefslogtreecommitdiff
path: root/plugins/vorbis
diff options
context:
space:
mode:
authorGravatar Alexey Yakovenko <waker@users.sourceforge.net>2014-05-02 22:30:18 +0200
committerGravatar Alexey Yakovenko <waker@users.sourceforge.net>2014-05-02 22:30:18 +0200
commit4b981c37a45e9a77e887c490603bc7c182a917aa (patch)
tree9ac7fb19fe4ee9e4878f9f3809e2ad4c1a6a27ce /plugins/vorbis
parentd6103b0afe49748e9f3a0a1c136f679c0d2234f5 (diff)
vorbis: new tagging code by Ian Nartowicz, updated license
Diffstat (limited to 'plugins/vorbis')
-rw-r--r--plugins/vorbis/COPYING55
-rw-r--r--plugins/vorbis/i18n.h18
-rw-r--r--plugins/vorbis/vcedit.c1065
-rw-r--r--plugins/vorbis/vcedit.h82
-rw-r--r--plugins/vorbis/vceditaux.h9
-rw-r--r--plugins/vorbis/vorbis.c378
6 files changed, 540 insertions, 1067 deletions
diff --git a/plugins/vorbis/COPYING b/plugins/vorbis/COPYING
new file mode 100644
index 00000000..f076a6a0
--- /dev/null
+++ b/plugins/vorbis/COPYING
@@ -0,0 +1,55 @@
+OggVorbis plugin for DeaDBeeF
+Copyright (C) 2009-2014 Alexey Yakovenko et al.
+
+vcedit.c - ogg tagging library
+Copyright (C) 2014 Ian Nartowicz <deadbeef@nartowicz.co.uk>
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source distribution.
+
+
+
+Uses libogg,libvorbis Copyright (c) 2002, Xiph.org Foundation
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the DeaDBeeF Player nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/plugins/vorbis/i18n.h b/plugins/vorbis/i18n.h
deleted file mode 100644
index 86248307..00000000
--- a/plugins/vorbis/i18n.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef VORBIS_TOOLS_I18N_H
-#define VORBIS_TOOLS_I18N_H
-
-#ifdef ENABLE_NLS
-#include <libintl.h>
-#define _(X) gettext(X)
-#else
-#define _(X) (X)
-#define textdomain(X)
-#define bindtextdomain(X, Y)
-#endif
-#ifdef gettext_noop
-#define N_(X) gettext_noop(X)
-#else
-#define N_(X) (X)
-#endif
-
-#endif
diff --git a/plugins/vorbis/vcedit.c b/plugins/vorbis/vcedit.c
index 2300b3da..5709f89c 100644
--- a/plugins/vorbis/vcedit.c
+++ b/plugins/vorbis/vcedit.c
@@ -1,880 +1,285 @@
-/* This program is licensed under the GNU Library General Public License, version 2,
- * a copy of which is included with this program (LICENCE.LGPL).
- *
- * (c) 2000-2001 Michael Smith <msmith@xiph.org>
- *
- *
- * Comment editing backend, suitable for use by nice frontend interfaces.
- *
- * last modified: $Id: vcedit.c 16826 2010-01-27 04:16:24Z xiphmont $
- */
-
-/* Handle muxed streams and the Vorbis renormalization without having
- * to understand remuxing:
- * Linked list of buffers (buffer_chain). Start a link and whenever
- * you encounter an unknown page from the current stream (ie we found
- * its bos in the bos section) push it onto the current buffer. Whenever
- * you encounter the stream being renormalized create a new link in the
- * chain.
- * On writing, write the contents of the first link before every Vorbis
- * page written, and move to the next link. Assuming the Vorbis pages
- * in match vorbis pages out, the order of pages from different logical
- * streams will be unchanged.
- * Special case: header. After writing the vorbis headers, and before
- * starting renormalization, flush accumulated links (takes care of
- * situations where number of secondary vorbis header pages changes due
- * to remuxing. Similarly flush links at the end of renormalization
- * and before the start of the next chain is written.
- *
- */
+/*
+ This file is part of Deadbeef Player source code
+ http://deadbeef.sourceforge.net
+
+ Ogg Vorbis plugin Ogg edit functions
+
+ Copyright (C) 2014 Ian Nartowicz <deadbeef@nartowicz.co.uk>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdbool.h>
#include <ogg/ogg.h>
-#include <vorbis/codec.h>
-
+#include <deadbeef.h>
#include "vcedit.h"
-#include "vceditaux.h"
-#include "i18n.h"
-
-#define CHUNKSIZE 4096
-#define BUFFERCHUNK CHUNKSIZE
-
-/* Helper function, shouldn't need to call directly */
-static int page_buffer_push(vcedit_buffer_chain *bufferlink, ogg_page *og) {
- int result=0;
- char *tmp;
- vcedit_page_buffer *buffer;
-
- buffer = &bufferlink->buffer;
- tmp = realloc(buffer->data,
- buffer->data_len + og->header_len + og->body_len);
- if(tmp) {
- buffer->data = tmp;
- memcpy(buffer->data + buffer->data_len, og->header,
- og->header_len);
- buffer->data_len += og->header_len;
- memcpy(buffer->data + buffer->data_len, og->body,
- og->body_len);
- result = 1;
- buffer->data_len += og->body_len;
- } else {
- result = -1;
- }
-
- return result;
-}
-
-/* Write and free the first link using callbacks */
-static int buffer_chain_writelink(vcedit_state *state, void *out) {
- int result = 0;
- vcedit_buffer_chain *tmpchain;
- vcedit_page_buffer *tmpbuffer;
-
- tmpchain = state->sidebuf;
- tmpbuffer = &tmpchain->buffer;
- if(tmpbuffer->data_len)
- {
- if(state->write(tmpbuffer->data,1,tmpbuffer->data_len, out) !=
- (size_t) tmpbuffer->data_len)
- result = -1;
- else
- result = 1;
- }
-
- free(tmpbuffer->data);
- state->sidebuf = tmpchain->next;
- free(tmpchain);
- return result;
-}
-
-
-static int buffer_chain_newlink(vcedit_state *state) {
- int result = 1;
- vcedit_buffer_chain *bufferlink;
-
- if(!state->sidebuf) {
- state->sidebuf = malloc (sizeof *state->sidebuf);
- if(state->sidebuf) {
- bufferlink = state->sidebuf;
- } else {
- result = -1;
- }
- } else {
- bufferlink=state->sidebuf;
- while(bufferlink->next) {
- bufferlink = bufferlink->next;
- }
- bufferlink->next = malloc (sizeof *bufferlink->next);
- if(bufferlink->next) {
- bufferlink = bufferlink->next;
- } else {
- result = -1;
- }
- }
-
- if(result > 0 ) {
- bufferlink->next = 0;
- bufferlink->buffer.data = 0;
- bufferlink->buffer.data_len = 0;
- }
- else
- state->lasterror =
- _("Couldn't get enough memory for input buffering.");
-
- return result;
-}
-
-
-/* Push page onto the end of the buffer chain */
-static int buffer_chain_push(vcedit_state *state, ogg_page *og) {
- /* If there is no sidebuffer yet we need to create one, otherwise
- * traverse to the last buffer and push the new page onto it. */
- int result=1;
- vcedit_buffer_chain *bufferlink;
- if(!state->sidebuf) {
- result = buffer_chain_newlink(state);
- }
-
- if(result > 0) {
- bufferlink = state->sidebuf;
- while(bufferlink->next) {
- bufferlink = bufferlink->next;
- }
- result = page_buffer_push(bufferlink, og);
- }
-
- if(result < 0)
- state->lasterror =
- _("Couldn't get enough memory for input buffering.");
-
- return result;
-}
-
-
-
-static int vcedit_supported_stream(vcedit_state *state, ogg_page *og) {
- ogg_stream_state os;
- vorbis_info vi;
- vorbis_comment vc;
- ogg_packet header;
- int result = 0;
-
- ogg_stream_init(&os, ogg_page_serialno(og));
- vorbis_info_init(&vi);
- vorbis_comment_init(&vc);
-
- if( !ogg_page_bos(og) )
- result = -1;
-
- if(result >= 0 && ogg_stream_pagein(&os, og) < 0)
- {
- state->lasterror =
- _("Error reading first page of Ogg bitstream.");
- result = -1;
- }
-
- if(result >= 0 && ogg_stream_packetout(&os, &header) != 1)
- {
- state->lasterror = _("Error reading initial header packet.");
- result = -1;
- }
-
- if(result >= 0 && vorbis_synthesis_headerin(&vi, &vc, &header) >= 0)
- {
- result = 1;
- } else {
- /* Not vorbis, may eventually become a chain of checks (Speex,
- * Theora), but for the moment return 0, bos scan will push
- * the current page onto the buffer.
- */
- }
-
- ogg_stream_clear(&os);
- vorbis_info_clear(&vi);
- vorbis_comment_clear(&vc);
- return result;
-}
-
-
-static int vcedit_contains_serial (vcedit_state *state, int serialno) {
- int result = 0;
- size_t count;
- for( count=0; count < state->serials.streams_len; count++ ) {
- if ( *(state->serials.streams + count ) == serialno )
- result = 1;
- }
-
- return result;
-}
+#define VORBISNAME "Vorbis"
+#define TAGMAGIC "\3vorbis"
+#define CODEMAGIC "\5vorbis"
-static int vcedit_add_serial (vcedit_state *state, long serial) {
- int result = 0;
- long *tmp;
-
-
- if( vcedit_contains_serial(state, serial) )
- {
- result = 1;
- } else {
- tmp = realloc(state->serials.streams,
- (state->serials.streams_len + 1) * sizeof *tmp);
- if(tmp) {
- state->serials.streams = tmp;
- *(state->serials.streams +
- state->serials.streams_len) = serial;
- state->serials.streams_len += 1;
- result = 1;
- } else {
- state->lasterror =
- _("Couldn't get enough memory to register new stream serial number.");
- result = -1;
- }
- }
- return result;
-}
-
-
-/* For the benefit of the secondary header read only. Quietly creates
- * newlinks and pushes pages onto the buffer in the right way */
-static int vcedit_target_pageout (vcedit_state *state, ogg_page *og) {
- int result = 0;
- int pageout_result;
- pageout_result = ogg_sync_pageout(state->oy, og);
- if(pageout_result > 0)
- {
- if(state->serial == ogg_page_serialno(og))
- result = buffer_chain_newlink(state);
- else
- result = buffer_chain_push(state, og);
- } else if (pageout_result < 0) {
- /* Vorbis comment traditionally ignores the not-synced
- * error from pageout, so give it a different code. */
- result = -2;
- }
- return result;
-}
-
+#define CHUNKSIZE 4096
+#define MAXOGGPAGE 65536
-/* (I'm paranoid about memset(x,0,len) not giving null pointers */
-vcedit_state *vcedit_new_state(void) {
- vcedit_state *state = malloc(sizeof(vcedit_state));
- if(state) {
- memset(state, 0, sizeof(vcedit_state));
- state->sidebuf = 0;
- state->serials.streams = 0;
- state->serials.streams_len = 0;
- }
- return state;
+static const char *ogg_codec(ogg_page *og)
+{
+ typedef struct {
+ const char *magic;
+ const size_t length;
+ const char *codec;
+ } codec_t;
+ const codec_t codecs[] = {
+ {.codec = "Opus", .length = 19, .magic = "OpusHead"},
+ {.codec = "Vorbis", .length = 30, .magic = "\1vorbis"},
+ {.codec = "Flac", .length = 47, .magic = "\177FLAC"},
+ {.codec = "Speex", .length = 80, .magic = "Speex "},
+ {.codec = "Celt", .length = 80, .magic = "CELT"}, // obsolete
+ {.codec = "MIDI", .length = 13, .magic = "OggMIDI\0"},
+ {.codec = "PCM", .length = 28, .magic = "PCM "},
+ {.codec = "Theora", .length = 42, .magic = "\200theora"},
+ {.codec = "Daala", .length = 38, .magic = "\200daala"},
+ {.codec = "Dirac", .length = 5, .magic = "BBCD\0"},
+ {.codec = "Skeleton", .length = 80, .magic = "fishead\0"},
+ {.codec = "Kate", .length = 64, .magic = "\200kate\0\0\0"},
+ {.codec = "CMML", .length = 29, .magic = "CMML\0\0\0\0"},
+ {.codec = "YUV4MPEG", .length = 8, .magic = "YUV4Mpeg"},
+ {.codec = "UVS", .length = 48, .magic = "UVS "},
+ {.codec = "YUV", .length = 32, .magic = "\0YUV"},
+ {.codec = "RGB", .length = 24, .magic = "\0RGB"},
+ {.codec = "JNG", .length = 48, .magic = "\213JNG\r\n\032\n"},
+ {.codec = "MNG", .length = 48, .magic = "\212MNG\r\n\032\n"},
+ {.codec = "PNG", .length = 48, .magic = "\211PNG\r\n\032\n"},
+ {.codec = "Spots", .length = 52, .magic = "SPOTS\0\0\0"},
+ {.codec = NULL}
+ };
+
+ for (const codec_t *match = codecs; match->codec; match++)
+ if ((size_t)og->body_len >= match->length && !memcmp(og->body, match->magic, strlen(match->codec)))
+ return match->codec;
+
+ return "unknown";
}
-char *vcedit_error(vcedit_state *state) {
- return state->lasterror;
+static void _oggpack_chars(oggpack_buffer *opb, const char *s, size_t length)
+{
+ while (length--)
+ oggpack_write(opb, *s++, 8);
}
-vorbis_comment *vcedit_comments(vcedit_state *state) {
- return state->vc;
+static void _oggpack_string(oggpack_buffer *opb, const char *s)
+{
+ oggpack_write(opb, strlen(s), 32);
+ _oggpack_chars(opb, s, strlen(s));
}
-static void vcedit_clear_internals(vcedit_state *state) {
- char *tmp;
- if(state->vc) {
- vorbis_comment_clear(state->vc);
- free(state->vc);
- }
- if(state->os) {
- ogg_stream_clear(state->os);
- free(state->os);
- }
- if(state->oy) {
- ogg_sync_clear(state->oy);
- free(state->oy);
- }
- if(state->serials.streams_len) {
- free(state->serials.streams);
- state->serials.streams_len = 0;
- state->serials.streams = 0;
- }
- while(state->sidebuf) {
- vcedit_buffer_chain *tmpbuffer;
- tmpbuffer = state->sidebuf;
- state->sidebuf = tmpbuffer->next;
- free(tmpbuffer->buffer.data);
- free(tmpbuffer);
- }
- if(state->vendor)
- free(state->vendor);
- if(state->mainbuf)
- free(state->mainbuf);
- if(state->bookbuf)
- free(state->bookbuf);
- if(state->vi) {
- vorbis_info_clear(state->vi);
- free(state->vi);
+static int get_page(DB_FILE *in, ogg_sync_state *oy, ogg_page *og, char **lasterror)
+{
+ uint16_t chunks_left = MAXOGGPAGE / CHUNKSIZE;
+ while (ogg_sync_pageout(oy, og) != 1) {
+ char *buffer = ogg_sync_buffer(oy, CHUNKSIZE);
+ if (!in || !buffer || !chunks_left--) {
+ *lasterror = "can't find Ogg bitstream.";
+ return -1;
+ }
+
+ const size_t bytes = in->vfs->read(buffer, 1, CHUNKSIZE, in);
+ if (!bytes) {
+ *lasterror = "unexpected EOF.";
+ return 0;
+ }
+
+ ogg_sync_wrote(oy, bytes);
}
- tmp = state->lasterror;
- memset(state, 0, sizeof(*state));
- state->lasterror = tmp;
+ return ogg_page_serialno(og);
}
-void vcedit_clear(vcedit_state *state)
+static bool write_page(FILE *out, ogg_page *og)
{
- if(state)
- {
- vcedit_clear_internals(state);
- free(state);
- }
+ return fwrite(og->header, 1, og->header_len, out) != (size_t)og->header_len ||
+ fwrite(og->body, 1, og->body_len, out) != (size_t)og->body_len;
}
-/* Next two functions pulled straight from libvorbis, apart from one change
- * - we don't want to overwrite the vendor string.
- */
-static void _v_writestring(oggpack_buffer *o,char *s, int len)
+static int write_page_and_get_next(DB_FILE *in, FILE *out, ogg_sync_state *oy, ogg_page *og, char **lasterror)
{
- while(len--)
- {
- oggpack_write(o,*s++,8);
- }
-}
+ if (write_page(out, og))
+ return -1;
-static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op)
-{
- oggpack_buffer opb;
-
- oggpack_writeinit(&opb);
-
- /* preamble */
- oggpack_write(&opb,0x03,8);
- _v_writestring(&opb,"vorbis", 6);
-
- /* vendor */
- oggpack_write(&opb,strlen(vendor),32);
- _v_writestring(&opb,vendor, strlen(vendor));
-
- /* comments */
- oggpack_write(&opb,vc->comments,32);
- if(vc->comments){
- int i;
- for(i=0;i<vc->comments;i++){
- if(vc->user_comments[i]){
- oggpack_write(&opb,vc->comment_lengths[i],32);
- _v_writestring(&opb,vc->user_comments[i],
- vc->comment_lengths[i]);
- }else{
- oggpack_write(&opb,0,32);
- }
- }
- }
- oggpack_write(&opb,1,1);
-
- op->packet = malloc(oggpack_bytes(&opb));
- memcpy(op->packet, opb.buffer, oggpack_bytes(&opb));
-
- op->bytes=oggpack_bytes(&opb);
- op->b_o_s=0;
- op->e_o_s=0;
- op->granulepos=0;
-
- oggpack_writeclear(&opb);
- return 0;
+ return get_page(in, oy, og, lasterror);
}
-static int _blocksize(vcedit_state *s, ogg_packet *p)
+static bool write_vorbis_tags(FILE *out, const int serial, const char *vendor, const size_t num_tags, char **tags, ogg_packet *codebooks)
{
- int this = vorbis_packet_blocksize(s->vi, p);
- int ret = (this + s->prevW)/4;
-
- if(!s->prevW)
- {
- s->prevW = this;
- return 0;
- }
-
- s->prevW = this;
- return ret;
+ oggpack_buffer opb;
+ oggpack_writeinit(&opb);
+ _oggpack_chars(&opb, TAGMAGIC, strlen(TAGMAGIC));
+ _oggpack_string(&opb, vendor);
+ oggpack_write(&opb, num_tags, 32);
+ for (size_t i = 0; i < num_tags; i++)
+ _oggpack_string(&opb, tags[i]);
+ oggpack_write(&opb, 1, 1);
+ oggpack_writealign(&opb);
+
+
+ ogg_stream_state os;
+ ogg_stream_init(&os, serial);
+ os.b_o_s = 1;
+ os.pageno = 1;
+ ogg_packet op;
+ memset(&op, '\0', sizeof(op));
+ op.packet = oggpack_get_buffer(&opb);
+ op.bytes = oggpack_bytes(&opb);
+ ogg_stream_packetin(&os, &op);
+ oggpack_writeclear(&opb);
+ ogg_stream_packetin(&os, codebooks);
+
+ ogg_page og;
+ while (ogg_stream_flush(&os, &og))
+ if (write_page(out, &og))
+ return true;
+
+ return ogg_stream_check(&os) || ogg_stream_clear(&os);
}
-static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page)
+static int extract_codebooks(DB_FILE *in, ogg_sync_state *oy, ogg_page *og, int serial, ogg_packet *codebooks, char **lasterror)
{
- int result;
- char *buffer;
- int bytes;
- int serialno;
-
- result = ogg_stream_packetout(s->os, p);
-
- if(result > 0)
- return 1;
- else {
- while(1) {
- if(s->eosin)
- return 0;
-
- while(ogg_sync_pageout(s->oy, page) <= 0)
- {
- buffer = ogg_sync_buffer(s->oy, CHUNKSIZE);
- bytes = s->read(buffer,1, CHUNKSIZE, s->in);
- ogg_sync_wrote(s->oy, bytes);
- if(bytes == 0)
- return 0;
- }
-
- serialno = ogg_page_serialno(page);
- if(ogg_page_serialno(page) != s->serial)
- {
- if(vcedit_contains_serial(s, serialno)) {
- result = buffer_chain_push(s, page);
- if(result < 0)
- return result;
- }
- else
- {
- s->eosin = 1;
- s->extrapage = 1;
- return 0;
- }
- }
- else
- {
- ogg_stream_pagein(s->os, page);
- result = buffer_chain_newlink(s);
- if (result < 0)
- return result;
-
- if(ogg_page_eos(page))
- s->eosin = 1;
- }
- result = ogg_stream_packetout(s->os, p);
- if(result > 0)
- return 1;
- }
- /* Here == trouble */
- return 0;
- }
-}
+ memset(codebooks, '\0', sizeof(codebooks));
+ ogg_stream_state os;
+ if (ogg_stream_init(&os, serial)) {
+ *lasterror = "cannot init Ogg stream.";
+ return -1;
+ }
+ os.pageno = 1;
+ os.b_o_s = 1;
+
+ ogg_packet op;
+ ogg_stream_pagein(&os, og);
+ do {
+ while (ogg_stream_packetpeek(&os, NULL) == 0) {
+ if ((serial = get_page(in, oy, og, lasterror)) <= 0)
+ goto cleanup;
+ fprintf(stderr, "Serial: %d, %ld bytes (%s)\n", serial, og->header_len + og->body_len, og->body);
+ if (ogg_stream_pagein(&os, og)) {
+ *lasterror = "failed to stream page for codebooks packet.";
+ goto cleanup;
+ }
+ }
+ } while (ogg_stream_packetout(&os, &op) != 1 || op.bytes < strlen(CODEMAGIC) || memcmp(op.packet, CODEMAGIC, strlen(CODEMAGIC)));
+
+ if (!(codebooks->packet = malloc(op.bytes))) {
+ *lasterror = "cannot allocate codebooks packet.";
+ goto cleanup;
+ }
+ codebooks->bytes = op.bytes;
+ memcpy(codebooks->packet, op.packet, op.bytes);
-int vcedit_open(vcedit_state *state, FILE *in)
-{
- return vcedit_open_callbacks(state, (void *)in,
- (vcedit_read_func)fread, (vcedit_write_func)fwrite);
+cleanup:
+ ogg_stream_clear(&os);
+
+ return codebooks->packet ? serial : -1;
}
-int vcedit_open_callbacks(vcedit_state *state, void *in,
- vcedit_read_func read_func, vcedit_write_func write_func)
+off_t vcedit_write_metadata(DB_FILE *in, const char *fname, int link, const char *vendor, const int num_tags, char **tags, char **lasterror)
{
-
- char *buffer;
- int bytes,i;
- int chunks = 0;
- int read_bos, test_supported, page_pending;
- int have_vorbis;
- ogg_packet *header;
- ogg_packet header_main;
- ogg_packet header_comments;
- ogg_packet header_codebooks;
- ogg_page og;
-
- state->in = in;
- state->read = read_func;
- state->write = write_func;
-
- state->oy = malloc(sizeof(ogg_sync_state));
- ogg_sync_init(state->oy);
-
- while(1)
- {
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
-
- ogg_sync_wrote(state->oy, bytes);
-
- if(ogg_sync_pageout(state->oy, &og) == 1)
- break;
-
- if(chunks++ >= 10) /* Bail if we don't find data in the first 40 kB */
- {
- if(bytes<CHUNKSIZE)
- state->lasterror = _("Input truncated or empty.");
- else
- state->lasterror = _("Input is not an Ogg bitstream.");
- goto err;
- }
+ off_t file_size = 0;
+ ogg_page og;
+ ogg_sync_state oy;
+ ogg_sync_init(&oy);
+
+ char outname[PATH_MAX];
+ snprintf(outname, PATH_MAX, "%s.temp", fname);
+ FILE *out = fopen(outname, "w+b");
+ if (!out) {
+ *lasterror = "unable to open temporary file for writing.";
+ goto cleanup;
+ }
+ if (!in) {
+ *lasterror = "file not opened for reading.";
+ goto cleanup;
}
- /* BOS loop, starting with a loaded ogg page. */
- if(buffer_chain_newlink(state) < 0)
- goto err;
-
- for( read_bos = 1, have_vorbis = 0 ; read_bos; )
- {
- test_supported = vcedit_supported_stream(state, &og);
- if(test_supported < 0)
- {
- goto err;
- }
- else if (test_supported == 0 || have_vorbis )
- {
- if(vcedit_add_serial ( state, ogg_page_serialno(&og)) < 0)
- goto err;
- if( buffer_chain_push(state, &og) < 0)
- goto err;
- }
- else if (test_supported > 0)
- {
- if(buffer_chain_newlink(state) < 0)
- goto err;
- state->serial = ogg_page_serialno(&og);
- if(vcedit_add_serial ( state, ogg_page_serialno(&og)) < 0)
- goto err;
-
- state->os = malloc(sizeof(ogg_stream_state));
- ogg_stream_init(state->os, state->serial);
-
- state->vi = malloc(sizeof(vorbis_info));
- vorbis_info_init(state->vi);
-
- state->vc = malloc(sizeof(vorbis_comment));
- vorbis_comment_init(state->vc);
-
- if(ogg_stream_pagein(state->os, &og) < 0)
- {
- state->lasterror =
- _("Error reading first page of Ogg bitstream.");
- goto err;
- }
-
- if(ogg_stream_packetout(state->os, &header_main) != 1)
- {
- state->lasterror =
- _("Error reading initial header packet.");
- goto err;
- }
-
- if(vorbis_synthesis_headerin(state->vi, state->vc,
- &header_main) < 0)
- {
- state->lasterror =
- _("Ogg bitstream does not contain Vorbis data.");
- goto err;
- }
- have_vorbis = 1;
- }
- while(1)
- {
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
-
- if(bytes == 0)
- {
- state->lasterror =
- _("EOF before recognised stream.");
- goto err;
- }
-
- ogg_sync_wrote(state->oy, bytes);
-
- if(ogg_sync_pageout(state->oy, &og) == 1)
- break;
- }
- if(!ogg_page_bos(&og)) {
- read_bos = 0;
- page_pending = 1;
- }
+ /* Copy through pages until we reach the right info header */
+ int codec_serial = 0;
+ int serial = get_page(in, &oy, &og, lasterror);
+ while (serial > 0 && !codec_serial) {
+ while (serial > 0 && !ogg_page_bos(&og))
+ serial = write_page_and_get_next(in, out, &oy, &og, lasterror);
+
+ while (serial > 0 && ogg_page_bos(&og)) {
+ if (link < 1 && !strcmp(ogg_codec(&og), VORBISNAME)) {
+ codec_serial = serial;
+ }
+ serial = write_page_and_get_next(in, out, &oy, &og, lasterror);
+ }
+ link--;
}
- if(!state->os) {
- state->lasterror = _("Ogg bitstream does not contain a supported data-type.");
- goto err;
- }
-
- state->mainlen = header_main.bytes;
- state->mainbuf = malloc(state->mainlen);
- memcpy(state->mainbuf, header_main.packet, header_main.bytes);
-
- if(ogg_page_serialno(&og) == state->serial)
- {
- if(buffer_chain_newlink(state) < 0)
- goto err;
- }
-
- else
- {
- if(buffer_chain_push(state, &og) < 0)
- goto err;
- page_pending = 0;
- }
-
- i = 0;
- header = &header_comments;
- while(i<2) {
- while(i<2) {
- int result;
- if(!page_pending)
- result = vcedit_target_pageout(state, &og);
- else
- {
- result = 1;
- page_pending = 0;
- }
- if(result == 0 || result == -2) break; /* Too little data so far */
- else if(result == -1) goto err;
- else if(result == 1)
- {
- ogg_stream_pagein(state->os, &og);
- while(i<2)
- {
- result = ogg_stream_packetout(state->os, header);
- if(result == 0) break;
- if(result == -1)
- {
- state->lasterror = _("Corrupt secondary header.");
- goto err;
- }
- vorbis_synthesis_headerin(state->vi, state->vc, header);
- if(i==1)
- {
- state->booklen = header->bytes;
- state->bookbuf = malloc(state->booklen);
- memcpy(state->bookbuf, header->packet,
- header->bytes);
- }
- i++;
- header = &header_codebooks;
- }
- }
- }
-
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
- if(bytes == 0 && i < 2)
- {
- state->lasterror = _("EOF before end of Vorbis headers.");
- goto err;
- }
- ogg_sync_wrote(state->oy, bytes);
- }
-
- /* Copy the vendor tag */
- state->vendor = malloc(strlen(state->vc->vendor) +1);
- strcpy(state->vendor, state->vc->vendor);
-
- /* Headers are done! */
- return 0;
-
-err:
- vcedit_clear_internals(state);
- return -1;
-}
+ /* Copy additional pages up to our comment header */
+ while (serial > 0 && serial != codec_serial)
+ serial = write_page_and_get_next(in, out, &oy, &og, lasterror);
+ if (serial <= 0)
+ goto cleanup;
+
+ /* Add the codebook packet to our comment header and save it */
+ ogg_packet codebooks;
+ if ((serial = extract_codebooks(in, &oy, &og, codec_serial, &codebooks, lasterror)) <= 0)
+ goto cleanup;
+ const bool write_tags = write_vorbis_tags(out, codec_serial, vendor, num_tags, tags, &codebooks);
+ ogg_packet_clear(&codebooks);
+ if (write_tags) {
+ *lasterror = "internal error writing Vorbis comment header packet.";
+ goto cleanup;
+ }
-int vcedit_write(vcedit_state *state, void *out)
-{
- ogg_stream_state streamout;
- ogg_packet header_main;
- ogg_packet header_comments;
- ogg_packet header_codebooks;
-
- ogg_page ogout, ogin;
- ogg_packet op;
- ogg_int64_t granpos = 0;
- int result;
- char *buffer;
- int bytes;
- int needflush=0, needout=0;
-
- state->eosin = 0;
- state->extrapage = 0;
-
- header_main.bytes = state->mainlen;
- header_main.packet = state->mainbuf;
- header_main.b_o_s = 1;
- header_main.e_o_s = 0;
- header_main.granulepos = 0;
-
- header_codebooks.bytes = state->booklen;
- header_codebooks.packet = state->bookbuf;
- header_codebooks.b_o_s = 0;
- header_codebooks.e_o_s = 0;
- header_codebooks.granulepos = 0;
-
- ogg_stream_init(&streamout, state->serial);
-
- _commentheader_out(state->vc, state->vendor, &header_comments);
-
- ogg_stream_packetin(&streamout, &header_main);
- ogg_stream_packetin(&streamout, &header_comments);
- ogg_stream_packetin(&streamout, &header_codebooks);
-
- while((result = ogg_stream_flush(&streamout, &ogout)))
- {
- if(state->sidebuf && buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- if(state->write(ogout.header,1,ogout.header_len, out) !=
- (size_t) ogout.header_len)
- goto cleanup;
- if(state->write(ogout.body,1,ogout.body_len, out) !=
- (size_t) ogout.body_len)
- goto cleanup;
- }
-
- while(state->sidebuf) {
- if(buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- }
- if(buffer_chain_newlink(state) < 0)
- goto cleanup;
-
- while(_fetch_next_packet(state, &op, &ogin))
- {
- int size;
- size = _blocksize(state, &op);
- granpos += size;
-
- if(needflush)
- {
- if(ogg_stream_flush(&streamout, &ogout))
- {
- if(state->sidebuf &&
- buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- if(state->write(ogout.header,1,ogout.header_len,
- out) != (size_t) ogout.header_len)
- goto cleanup;
- if(state->write(ogout.body,1,ogout.body_len,
- out) != (size_t) ogout.body_len)
- goto cleanup;
- }
- }
- else if(needout)
- {
- if(ogg_stream_pageout(&streamout, &ogout))
- {
- if(state->sidebuf &&
- buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- if(state->write(ogout.header,1,ogout.header_len,
- out) != (size_t) ogout.header_len)
- goto cleanup;
- if(state->write(ogout.body,1,ogout.body_len,
- out) != (size_t) ogout.body_len)
- goto cleanup;
- }
- }
-
- needflush=needout=0;
-
- if(op.granulepos == -1)
- {
- op.granulepos = granpos;
- ogg_stream_packetin(&streamout, &op);
- }
- else /* granulepos is set, validly. Use it, and force a flush to
- account for shortened blocks (vcut) when appropriate */
- {
- if(granpos > op.granulepos)
- {
- granpos = op.granulepos;
- ogg_stream_packetin(&streamout, &op);
- needflush=1;
- }
- else
- {
- ogg_stream_packetin(&streamout, &op);
- needout=1;
- }
- }
- }
-
- streamout.e_o_s = 1;
- while(ogg_stream_flush(&streamout, &ogout))
- {
- if(state->sidebuf && buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- if(state->write(ogout.header,1,ogout.header_len,
- out) != (size_t) ogout.header_len)
- goto cleanup;
- if(state->write(ogout.body,1,ogout.body_len,
- out) != (size_t) ogout.body_len)
- goto cleanup;
- }
-
- if (state->extrapage)
- {
- /* This is the first page of a new chain, get rid of the
- * sidebuffer */
- while(state->sidebuf)
- if(buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- if(state->write(ogin.header,1,ogin.header_len,
- out) != (size_t) ogin.header_len)
- goto cleanup;
- if (state->write(ogin.body,1,ogin.body_len, out) !=
- (size_t) ogin.body_len)
- goto cleanup;
- }
-
- state->eosin=0; /* clear it, because not all paths to here do */
- while(!state->eosin) /* We reached eos, not eof */
- {
- /* We copy the rest of the stream (other logical streams)
- * through, a page at a time. */
- while(1)
- {
- result = ogg_sync_pageout(state->oy, &ogout);
- if(result==0)
- break;
- if(result<0)
- state->lasterror = _("Corrupt or missing data, continuing...");
- else
- {
- /* Don't bother going through the rest, we can just
- * write the page out now */
- if(state->write(ogout.header,1,ogout.header_len,
- out) != (size_t) ogout.header_len) {
- goto cleanup;
- }
- if(state->write(ogout.body,1,ogout.body_len, out) !=
- (size_t) ogout.body_len) {
- goto cleanup;
- }
- }
- }
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = state->read(buffer,1, CHUNKSIZE, state->in);
- ogg_sync_wrote(state->oy, bytes);
- if(bytes == 0)
- {
- state->eosin = 1;
- break;
- }
- }
+ /* Blindly copy through the remaining pages */
+ serial = get_page(in, &oy, &og, lasterror);
+ while (serial > 0)
+ serial = write_page_and_get_next(in, out, &oy, &og, lasterror);
+ if (serial < 0)
+ goto cleanup;
+ fseeko(out, 0, SEEK_END);
+ file_size = ftello(out);
cleanup:
- ogg_stream_clear(&streamout);
+ if (in)
+ in->vfs->close(in);
- /* We don't ogg_packet_clear() this, because the memory was allocated in
- _commentheader_out(), so we mirror that here */
- _ogg_free(header_comments.packet);
+ if (out)
+ fclose(out);
- free(state->mainbuf);
- free(state->bookbuf);
- state->mainbuf = state->bookbuf = NULL;
+ ogg_sync_clear(&oy);
- if(!state->eosin)
- {
- state->lasterror =
- _("Error writing stream to output. "
- "Output stream may be corrupted or truncated.");
- return -1;
- }
+ if (file_size <= 0) {
+ unlink(outname);
+ if (!*lasterror)
+ *lasterror = "error writing new file, changes backed out.";
+ return -1;
+ }
- return 0;
+ rename(outname, fname);
+ return file_size;
}
diff --git a/plugins/vorbis/vcedit.h b/plugins/vorbis/vcedit.h
index 173876f3..7eabe877 100644
--- a/plugins/vorbis/vcedit.h
+++ b/plugins/vorbis/vcedit.h
@@ -1,69 +1,35 @@
-/* This program is licensed under the GNU Library General Public License, version 2,
- * a copy of which is included with this program (with filename LICENSE.LGPL).
- *
- * (c) 2000-2001 Michael Smith <msmith@xiph.org>
- *
- * VCEdit header.
- *
- * last modified: $ID:$
- */
+/*
+ This file is part of Deadbeef Player source code
+ http://deadbeef.sourceforge.net
-#ifndef __VCEDIT_H
-#define __VCEDIT_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <stdio.h>
-#include <ogg/ogg.h>
-#include <vorbis/codec.h>
+ Ogg Vorbis plugin Ogg edit functions header
-typedef size_t (*vcedit_read_func)(void *, size_t, size_t, void *);
-typedef size_t (*vcedit_write_func)(const void *, size_t, size_t, void *);
+ Copyright (C) 2014 Ian Nartowicz <deadbeef@nartowicz.co.uk>
-typedef struct {
- long *streams;
- size_t streams_len;
-} vcedit_serial_nos;
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
-typedef struct {
- ogg_sync_state *oy;
- ogg_stream_state *os;
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
- vorbis_comment *vc;
- vorbis_info *vi;
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
- vcedit_read_func read;
- vcedit_write_func write;
+*/
- void *in;
- int serial;
- vcedit_serial_nos serials;
- unsigned char *mainbuf;
- unsigned char *bookbuf;
- int mainlen;
- int booklen;
- char *lasterror;
- char *vendor;
- int prevW;
- int extrapage;
- int eosin;
- struct vcedit_buffer_chain *sidebuf;
-} vcedit_state;
+#ifndef __VCEDIT_H
+#define __VCEDIT_H
-extern vcedit_state * vcedit_new_state(void);
-extern void vcedit_clear(vcedit_state *state);
-extern vorbis_comment * vcedit_comments(vcedit_state *state);
-extern int vcedit_open(vcedit_state *state, FILE *in);
-extern int vcedit_open_callbacks(vcedit_state *state, void *in,
- vcedit_read_func read_func, vcedit_write_func write_func);
-extern int vcedit_write(vcedit_state *state, void *out);
-extern char * vcedit_error(vcedit_state *state);
+#define ALBUM_ART_KEY "METADATA_BLOCK_PICTURE"
+#define ALBUM_ART_META "metadata_block_picture"
-#ifdef __cplusplus
-}
-#endif
+off_t vcedit_write_metadata(DB_FILE *in, const char *fname, int link, const char *vendor, const int num_tags, char **tags, char **lasterror);
#endif /* __VCEDIT_H */
-
diff --git a/plugins/vorbis/vceditaux.h b/plugins/vorbis/vceditaux.h
deleted file mode 100644
index bb40eaeb..00000000
--- a/plugins/vorbis/vceditaux.h
+++ /dev/null
@@ -1,9 +0,0 @@
-typedef struct vcedit_page_buffer {
- char *data;
- size_t data_len;
-} vcedit_page_buffer;
-
-typedef struct vcedit_buffer_chain {
- struct vcedit_buffer_chain *next;
- struct vcedit_page_buffer buffer;
-} vcedit_buffer_chain;
diff --git a/plugins/vorbis/vorbis.c b/plugins/vorbis/vorbis.c
index 46a4022c..1bd4a382 100644
--- a/plugins/vorbis/vorbis.c
+++ b/plugins/vorbis/vorbis.c
@@ -1,20 +1,26 @@
/*
- DeaDBeeF - ultimate music player for GNU/Linux systems with X11
- Copyright (C) 2009-2013 Alexey Yakovenko <waker@users.sourceforge.net>
+ DeaDBeeF -- the music player
+ Copyright (C) 2009-2014 Alexey Yakovenko and other contributors
- 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 software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
- 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.
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source distribution.
*/
+
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
@@ -26,6 +32,7 @@
#include <limits.h>
#include <unistd.h>
#include <math.h>
+#include <stdbool.h>
#include "../../deadbeef.h"
#include "vcedit.h"
@@ -35,6 +42,9 @@
//#define trace(...) { fprintf (stderr, __VA_ARGS__); }
#define trace(fmt,...)
+#define RG_REFERENCE_LOUDNESS -1
+#define DELIMITER "\n - \n"
+
static DB_decoder_t plugin;
static DB_functions_t *deadbeef;
@@ -74,6 +84,53 @@ cvorbis_ftell (void *datasource) {
return deadbeef->ftell (datasource);
}
+static const char
+*gain_tag_name(const int tag_enum)
+{
+ switch(tag_enum) {
+ case DDB_REPLAYGAIN_ALBUMGAIN:
+ return "REPLAYGAIN_ALBUM_GAIN";
+ case DDB_REPLAYGAIN_ALBUMPEAK:
+ return "REPLAYGAIN_ALBUM_PEAK";
+ case DDB_REPLAYGAIN_TRACKGAIN:
+ return "REPLAYGAIN_TRACK_GAIN";
+ case DDB_REPLAYGAIN_TRACKPEAK:
+ return "REPLAYGAIN_TRACK_PEAK";
+ case RG_REFERENCE_LOUDNESS:
+ return "REPLAYGAIN_REFERENCE_LOUDNESS";
+ default:
+ return NULL;
+ }
+}
+
+static const char
+*gain_meta_key(const int tag_enum)
+{
+ switch(tag_enum) {
+ case DDB_REPLAYGAIN_ALBUMGAIN:
+ return ":REPLAYGAIN_ALBUMGAIN";
+ case DDB_REPLAYGAIN_ALBUMPEAK:
+ return ":REPLAYGAIN_ALBUMPEAK";
+ case DDB_REPLAYGAIN_TRACKGAIN:
+ return ":REPLAYGAIN_TRACKGAIN";
+ case DDB_REPLAYGAIN_TRACKPEAK:
+ return ":REPLAYGAIN_TRACKPEAK";
+ case RG_REFERENCE_LOUDNESS:
+ return ":REPLAYGAIN_REFERENCE_LOUDNESS";
+ default:
+ return NULL;
+ }
+}
+
+static bool
+is_special_tag(const char *tag) {
+ return !strcasecmp(tag, gain_tag_name(DDB_REPLAYGAIN_ALBUMGAIN)) ||
+ !strcasecmp(tag, gain_tag_name(DDB_REPLAYGAIN_ALBUMPEAK)) ||
+ !strcasecmp(tag, gain_tag_name(DDB_REPLAYGAIN_TRACKGAIN)) ||
+ !strcasecmp(tag, gain_tag_name(DDB_REPLAYGAIN_TRACKPEAK)) ||
+ !strcasecmp(tag, gain_tag_name(RG_REFERENCE_LOUDNESS));
+}
+
static const char *metainfo[] = {
"ARTIST", "artist",
"TITLE", "title",
@@ -142,6 +199,9 @@ update_vorbis_comments (DB_playItem_t *it, vorbis_comment *vc, int refresh_playl
else if (!strncasecmp (s, "replaygain_track_peak=", 22)) {
deadbeef->pl_set_item_replaygain (it, DDB_REPLAYGAIN_TRACKPEAK, atof (s+22));
}
+ else if (!strncasecmp (s, "replaygain_reference_loudness=", 30)) {
+ deadbeef->pl_replace_meta(it, gain_meta_key(RG_REFERENCE_LOUDNESS), s+30);
+ }
else {
const char *p = s;
while (*p && *p != '=') {
@@ -591,7 +651,7 @@ cvorbis_read_metadata (DB_playItem_t *it) {
DB_FILE *fp = NULL;
OggVorbis_File vorbis_file;
vorbis_info *vi = NULL;
-
+
deadbeef->pl_lock ();
fp = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI"));
deadbeef->pl_unlock ();
@@ -636,153 +696,127 @@ error:
}
-int
-cvorbis_write_metadata (DB_playItem_t *it) {
- vcedit_state *state = NULL;
- vorbis_comment *vc = NULL;
- FILE *fp = NULL;
- FILE *out = NULL;
- int err = -1;
- char outname[PATH_MAX] = "";
- char fname[PATH_MAX];
- deadbeef->pl_get_meta (it, ":URI", fname, sizeof (fname));
-
- struct field {
- struct field *next;
- int size;
- uint8_t data[0];
- };
+static void
+split_tag(vorbis_comment *tags, const char *key, const char *value)
+{
+ if (key && value) {
+ const char *p;
+ while (p = strstr(value, DELIMITER)) {
+ char v[p - value + 1];
+ strncpy(v, value, p-value);
+ v[p-value] = 0;
+ vorbis_comment_add_tag(tags, key, v);
+ value = p + strlen(DELIMITER);
+ }
- struct field *preserved_fields = NULL;
+ vorbis_comment_add_tag(tags, key, value);
+ }
+}
- state = vcedit_new_state ();
- if (!state) {
- trace ("cvorbis_write_metadata: vcedit_new_state failed\n");
- return -1;
+static void
+merge_gain_tag(DB_playItem_t *it, vorbis_comment *vc, vorbis_comment *tags, const int tag_enum, const char *pattern, const int min, const int max)
+{
+ const char *key = gain_tag_name(tag_enum);
+
+ char *end;
+ const char *meta_value = deadbeef->pl_find_meta(it, key);
+ const float value = meta_value ? strtof(meta_value, &end) : 0;
+ if (meta_value && end != meta_value && value > min && value < max) {
+ char tag_value[10];
+ sprintf(tag_value, pattern, value);
+ vorbis_comment_add_tag(tags, key, tag_value);
+ }
+ else {
+ const char *tag_value = vorbis_comment_query(vc, key, 0);
+ if (tag_value)
+ vorbis_comment_add_tag(tags, key, tag_value);
}
- fp = fopen (fname, "rb");
+}
+
+static const char
+*map_tag(const char *key)
+{
+ for (int i = 0; metainfo[i]; i += 2)
+ if (!strcasecmp (metainfo[i+1], key))
+ return metainfo[i];
+ return key;
+}
+
+static vorbis_comment
+*create_tags_list(DB_playItem_t *it, const char *fname, vorbis_comment *tags)
+{
+ DB_FILE *fp = deadbeef->fopen (fname);
if (!fp) {
- trace ("cvorbis_write_metadata: failed to read metadata from %s\n", fname);
- goto error;
+ trace ("vorbis: failed to fopen %s\n", fname);
+ return NULL;
}
- if (vcedit_open (state, fp) != 0) {
- trace ("cvorbis_write_metadata: vcedit_open failed, error: %s\n", vcedit_error (state));
- goto error;
+ ov_callbacks ovcb = {
+ .read_func = cvorbis_fread,
+ .seek_func = cvorbis_fseek,
+ .close_func = cvorbis_fclose,
+ .tell_func = cvorbis_ftell
+ };
+ OggVorbis_File vorbis_file;
+ int err = ov_test_callbacks (fp, &vorbis_file, NULL, 0, ovcb);
+ if (err != 0) {
+ trace ("ov_test_callbacks returned %d\n", err);
+ deadbeef->fclose (fp);
+ return NULL;
}
- vc = vcedit_comments (state);
+ vorbis_comment *vc = ov_comment (&vorbis_file, -1);
if (!vc) {
- trace ("cvorbis_write_metadata: vcedit_comments failed, error: %s\n", vcedit_error (state));
- goto error;
+ trace ("ov_comment failed\n");
+ ov_clear (&vorbis_file);
+ return NULL;
}
-#if 0
- // copy all unknown fields to separate buffer
- for (int i = 0; i < vc->comments; i++) {
- int m;
- for (m = 0; metainfo[m]; m += 2) {
- int l = strlen (metainfo[m]);
- if (vc->comment_lengths[i] > l && !strncasecmp (vc->user_comments[i], metainfo[m], l) && vc->user_comments[i][l] == '=') {
- break;
- }
- }
- if (!metainfo[m]) {
- trace ("preserved field: %s\n", vc->user_comments[i]);
- // unknown field
- struct field *f = malloc (sizeof (struct field) + vc->comment_lengths[i]);
- memset (f, 0, sizeof (struct field));
- memcpy (f->data, vc->user_comments[i], vc->comment_lengths[i]);
- f->size = vc->comment_lengths[i];
- f->next = preserved_fields;
- preserved_fields = f;
- }
+ vorbis_comment_init(tags);
+ if (!(tags->vendor = strdup(vc->vendor))) {
+ ov_clear (&vorbis_file);
+ trace("create_tags_list: cannot allocate tags list\n");
+ return NULL;
}
-#endif
- vorbis_comment_clear(vc);
- vorbis_comment_init(vc);
-
- // add unknown/custom fields
deadbeef->pl_lock ();
- DB_metaInfo_t *m = deadbeef->pl_get_metadata_head (it);
+ merge_gain_tag(it, vc, tags, DDB_REPLAYGAIN_ALBUMGAIN, "%0.2f dB", -100, 100);
+ merge_gain_tag(it, vc, tags, DDB_REPLAYGAIN_ALBUMPEAK, "%0.8f", 0, 2);
+ merge_gain_tag(it, vc, tags, DDB_REPLAYGAIN_TRACKGAIN, "%0.2f dB", -100, 100);
+ merge_gain_tag(it, vc, tags, DDB_REPLAYGAIN_TRACKPEAK, "%0.8f", 0, 2);
+ merge_gain_tag(it, vc, tags, RG_REFERENCE_LOUDNESS, "%0.1f dB", 0, 128);
+ DB_metaInfo_t *m = deadbeef->pl_get_metadata_head(it);
while (m) {
- if (m->key[0] != ':') {
- int i;
- for (i = 0; metainfo[i]; i += 2) {
- if (!strcasecmp (metainfo[i+1], m->key)) {
- break;
- }
- }
- const char *val = m->value;
- if (val && *val) {
- while (val) {
- const char *next = strchr (val, '\n');
- int l;
- if (next) {
- l = next - val;
- next++;
- }
- else {
- l = strlen (val);
- }
- if (l > 0) {
- char s[100+l+1];
- int n = snprintf (s, sizeof (s), "%s=", metainfo[i] ? metainfo[i] : m->key);
- strncpy (s+n, val, l);
- *(s+n+l) = 0;
- vorbis_comment_add (vc, s);
- }
- val = next;
- }
- }
- }
+ if (m->key[0] != ':' && m->key[0] != '!' && !is_special_tag(m->key))
+ split_tag(tags, map_tag(m->key), (char *)m->value);
m = m->next;
}
deadbeef->pl_unlock ();
- // add preserved fields
- for (struct field *f = preserved_fields; f; f = f->next) {
- vorbis_comment_add (vc, f->data);
- }
-
- snprintf (outname, sizeof (outname), "%s.temp.ogg", fname);
+ ov_clear (&vorbis_file);
+ return tags;
+}
- out = fopen (outname, "w+b");
- if (!out) {
- trace ("cvorbis_write_metadata: failed to open %s for writing\n", outname);
- goto error;
- }
+static int
+cvorbis_write_metadata (DB_playItem_t *it) {
+ char fname[PATH_MAX];
+ deadbeef->pl_get_meta (it, ":URI", fname, sizeof (fname));
- if (vcedit_write (state, out) < 0) {
- trace ("cvorbis_write_metadata: failed to write tags to %s, error: %s\n", fname, vcedit_error (state));
- goto error;
- }
+ vorbis_comment tags;
+ if (!create_tags_list(it, fname, &tags))
+ return -1;
- err = 0;
-error:
- if (fp) {
- fclose (fp);
- }
- if (out) {
- fclose (out);
- }
- if (state) {
- vcedit_clear (state);
- }
- while (preserved_fields) {
- struct field *next = preserved_fields->next;
- free (preserved_fields);
- preserved_fields = next;
+ char *vorbis_error = NULL;
+ const off_t file_size = vcedit_write_metadata (deadbeef->fopen(fname), fname, -1, tags.vendor, tags.comments, tags.user_comments, &vorbis_error);
+ vorbis_comment_clear(&tags);
+ if (file_size <= 0) {
+ trace ("cvorbis_write_metadata: failed to write tags to %s, error: %s\n", fname, vorbis_error);
+ return -1;
}
- if (!err) {
- rename (outname, fname);
- }
- else if (out) {
- unlink (outname);
- }
+ deadbeef->pl_set_meta_int(it, ":FILE_SIZE", file_size);
+ return cvorbis_read_metadata(it);
- return err;
+ return 0;
}
@@ -798,22 +832,62 @@ static DB_decoder_t plugin = {
.plugin.id = "stdogg",
.plugin.name = "OggVorbis decoder",
.plugin.descr = "OggVorbis decoder using standard xiph.org libraries",
- .plugin.copyright =
- "Copyright (C) 2009-2013 Alexey Yakovenko <waker@users.sourceforge.net>\n"
- "\n"
- "This program is free software; you can redistribute it and/or\n"
- "modify it under the terms of the GNU General Public License\n"
- "as published by the Free Software Foundation; either version 2\n"
- "of the License, or (at your option) any later version.\n"
- "\n"
- "This program is distributed in the hope that it will be useful,\n"
- "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
- "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
- "GNU General Public License for more details.\n"
- "\n"
- "You should have received a copy of the GNU General Public License\n"
- "along with this program; if not, write to the Free Software\n"
- "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"
+ .plugin.copyright =
+ "OggVorbis plugin for DeaDBeeF\n"
+ "Copyright (C) 2009-2014 Alexey Yakovenko et al.\n"
+ "\n"
+ "vcedit.c\n"
+ "Ogg Vorbis plugin Ogg edit functions\n"
+ "\n"
+ "Copyright (C) 2014 Ian Nartowicz <deadbeef@nartowicz.co.uk>\n"
+ "This software is provided 'as-is', without any express or implied\n"
+ "warranty. In no event will the authors be held liable for any damages\n"
+ "arising from the use of this software.\n"
+ "\n"
+ "Permission is granted to anyone to use this software for any purpose,\n"
+ "including commercial applications, and to alter it and redistribute it\n"
+ "freely, subject to the following restrictions:\n"
+ "\n"
+ "1. The origin of this software must not be misrepresented; you must not\n"
+ " claim that you wrote the original software. If you use this software\n"
+ " in a product, an acknowledgment in the product documentation would be\n"
+ " appreciated but is not required.\n"
+ "\n"
+ "2. Altered source versions must be plainly marked as such, and must not be\n"
+ " misrepresented as being the original software.\n"
+ "\n"
+ "3. This notice may not be removed or altered from any source distribution.\n"
+ "\n"
+ "\n"
+ "\n"
+ "Uses libogg,libvorbis Copyright (c) 2002, Xiph.org Foundation\n"
+ "\n"
+ "Redistribution and use in source and binary forms, with or without\n"
+ "modification, are permitted provided that the following conditions\n"
+ "are met:\n"
+ "\n"
+ "- Redistributions of source code must retain the above copyright\n"
+ "notice, this list of conditions and the following disclaimer.\n"
+ "\n"
+ "- Redistributions in binary form must reproduce the above copyright\n"
+ "notice, this list of conditions and the following disclaimer in the\n"
+ "documentation and/or other materials provided with the distribution.\n"
+ "\n"
+ "- Neither the name of the DeaDBeeF Player nor the names of its\n"
+ "contributors may be used to endorse or promote products derived from\n"
+ "this software without specific prior written permission.\n"
+ "\n"
+ "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n"
+ "``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n"
+ "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n"
+ "A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR\n"
+ "CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n"
+ "EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n"
+ "PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n"
+ "PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n"
+ "LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n"
+ "NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n"
+ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
,
.plugin.website = "http://deadbeef.sf.net",
.plugin.start = vorbis_start,