diff options
author | Alexey Yakovenko <waker@users.sourceforge.net> | 2014-05-02 22:30:18 +0200 |
---|---|---|
committer | Alexey Yakovenko <waker@users.sourceforge.net> | 2014-05-02 22:30:18 +0200 |
commit | 4b981c37a45e9a77e887c490603bc7c182a917aa (patch) | |
tree | 9ac7fb19fe4ee9e4878f9f3809e2ad4c1a6a27ce | |
parent | d6103b0afe49748e9f3a0a1c136f679c0d2234f5 (diff) |
vorbis: new tagging code by Ian Nartowicz, updated license
-rw-r--r-- | plugins/vorbis/COPYING | 55 | ||||
-rw-r--r-- | plugins/vorbis/i18n.h | 18 | ||||
-rw-r--r-- | plugins/vorbis/vcedit.c | 1065 | ||||
-rw-r--r-- | plugins/vorbis/vcedit.h | 82 | ||||
-rw-r--r-- | plugins/vorbis/vceditaux.h | 9 | ||||
-rw-r--r-- | plugins/vorbis/vorbis.c | 378 |
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, |