summaryrefslogtreecommitdiffstats
path: root/tdefile-plugins/ogg/vcedit.c
diff options
context:
space:
mode:
Diffstat (limited to 'tdefile-plugins/ogg/vcedit.c')
-rw-r--r--tdefile-plugins/ogg/vcedit.c331
1 files changed, 331 insertions, 0 deletions
diff --git a/tdefile-plugins/ogg/vcedit.c b/tdefile-plugins/ogg/vcedit.c
new file mode 100644
index 00000000..76e31f6c
--- /dev/null
+++ b/tdefile-plugins/ogg/vcedit.c
@@ -0,0 +1,331 @@
+/* This program is licensed under the GNU Library General Public License, version 2
+ *
+ * (c) 2000-2001 Michael Smith <msmith@labyrinth.net.au>
+ *
+ * Modified by Warren Spits <spits@cyberdude.com>
+ * - Handles vorbis files that are truncated or missing an eos flag.
+ *
+ * Comment editing backend, suitable for use by nice frontend interfaces.
+ *
+ * last modified: $Id$
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ogg/ogg.h>
+#include <vorbis/codec.h>
+
+#include "vcedit.h"
+
+#define CHUNKSIZE 4096
+
+vcedit_state *vcedit_new_state(void)
+{
+ vcedit_state *state = malloc(sizeof(vcedit_state));
+ memset(state, 0, sizeof(vcedit_state));
+
+ return state;
+}
+
+char *vcedit_error(vcedit_state *state)
+{
+ return state->lasterror;
+}
+
+vorbis_comment *vcedit_comments(vcedit_state *state)
+{
+ return state->vc;
+}
+
+static void vcedit_clear_internals(vcedit_state *state)
+{
+ if(state->vc)
+ {
+ vorbis_comment_clear(state->vc);
+ free(state->vc);
+ state->vc=NULL;
+ }
+ if(state->os)
+ {
+ ogg_stream_clear(state->os);
+ free(state->os);
+ state->os=NULL;
+ }
+ if(state->oy)
+ {
+ ogg_sync_clear(state->oy);
+ free(state->oy);
+ state->oy=NULL;
+ }
+}
+
+void vcedit_clear(vcedit_state *state)
+{
+ if(state)
+ {
+ vcedit_clear_internals(state);
+ free(state);
+ }
+}
+
+int vcedit_open(vcedit_state *state, FILE *in)
+{
+ return vcedit_open_callbacks(state, (void *)in,
+ (vcedit_read_func)fread, (vcedit_write_func)fwrite);
+}
+
+int vcedit_open_callbacks(vcedit_state *state, void *in,
+ vcedit_read_func read_func, vcedit_write_func write_func)
+{
+
+ char *buffer;
+ int bytes,i;
+ ogg_packet *header;
+ ogg_packet header_main;
+ ogg_packet header_comments;
+ ogg_packet header_codebooks;
+ ogg_page og;
+ vorbis_info vi;
+
+
+ state->in = in;
+ state->read = read_func;
+ state->write = write_func;
+ state->lasterror = 0;
+
+ state->oy = malloc(sizeof(ogg_sync_state));
+ ogg_sync_init(state->oy);
+
+ 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)
+ {
+ if(bytes<CHUNKSIZE)
+ state->lasterror = "Input truncated or empty.";
+ else
+ state->lasterror = "Input is not an Ogg bitstream.";
+ goto err;
+ }
+
+ state->serial = ogg_page_serialno(&og);
+
+ state->os = malloc(sizeof(ogg_stream_state));
+ ogg_stream_init(state->os, state->serial);
+
+ vorbis_info_init(&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(&vi, state->vc, &header_main) < 0)
+ {
+ state->lasterror = "Ogg bitstream does not contain vorbis data.";
+ goto err;
+ }
+
+ state->mainlen = header_main.bytes;
+ state->mainbuf = malloc(state->mainlen);
+ memcpy(state->mainbuf, header_main.packet, header_main.bytes);
+
+ i = 0;
+ header = &header_comments;
+ while(i<2) {
+ while(i<2) {
+ int result = ogg_sync_pageout(state->oy, &og);
+ if(result == 0) break; /* Too little data so far */
+ 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(&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);
+ }
+
+ /* Headers are done! */
+ vorbis_info_clear(&vi);
+ return 0;
+
+err:
+ vcedit_clear_internals(state);
+ return -1;
+}
+
+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;
+ int result, outresult;
+ char *buffer;
+ int bytes, eosin=0, eosout=0;
+
+ state->lasterror = 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);
+
+ vorbis_commentheader_out(state->vc, &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->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;
+ }
+
+ /* We copy the first logical stream
+ * through, rewriting the stream. */
+ while (1)
+ {
+ outresult = eosin ? ogg_stream_flush(&streamout, &ogout) :
+ ogg_stream_pageout(&streamout, &ogout);
+ if (outresult > 0)
+ {
+ 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 (ogg_page_eos(&ogout)) eosout = 1;
+ }
+ if (outresult != 0) continue;
+ if (eosout || (eosin && (result == 0))) break;
+
+ while (1)
+ {
+ result = ogg_stream_packetout(state->os, &op);
+ if (result < 0) continue;
+ if (result > 0) ogg_stream_packetin(&streamout, &op);
+ if (eosin || (result > 0)) break;
+
+ while (1)
+ {
+ result = ogg_sync_pageout(state->oy, &ogin);
+
+ if (result < 0) continue;
+ if (result > 0)
+ {
+ ogg_stream_pagein(state->os, &ogin);
+ if (ogg_page_eos(&ogin)) eosin = 1;
+ }
+ if (eosin || (result > 0)) break;
+
+ buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
+ bytes = state->read(buffer,1, CHUNKSIZE, state->in);
+ ogg_sync_wrote(state->oy, bytes);
+ if (bytes < CHUNKSIZE) eosin = 1;
+ }
+ }
+ }
+
+ eosin=0; /* clear it, because not all paths to here do */
+ eosout=1; /* handle input files that are truncated or without an eos flag */
+
+ /* 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)
+ {
+ 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 (result != 0) continue;
+ if (eosin) break;
+
+ buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
+ bytes = state->read(buffer,1, CHUNKSIZE, state->in);
+ ogg_sync_wrote(state->oy, bytes);
+ eosin = (bytes < CHUNKSIZE);
+ }
+
+cleanup:
+ ogg_stream_clear(&streamout);
+ ogg_packet_clear(&header_comments);
+
+ free(state->mainbuf);
+ free(state->bookbuf);
+
+ vcedit_clear_internals(state);
+ if(!(eosin && eosout))
+ {
+ state->lasterror =
+ "Error writing stream to output. "
+ "Output stream may be corrupted or truncated.";
+ return -1;
+ }
+
+ return 0;
+}