summaryrefslogtreecommitdiff
path: root/plugins/vorbis/vcedit.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/vorbis/vcedit.c')
-rw-r--r--plugins/vorbis/vcedit.c1065
1 files changed, 235 insertions, 830 deletions
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;
}