123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491 |
- /* 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 <[email protected]>
- *
- *
- * Comment editing backend, suitable for use by nice frontend interfaces.
- *
- * last modified: $Id: vcedit.c,v 1.3 2013/10/22 14:17:11 dromagod Exp $
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- #include <ogg/ogg.h>
- #include <vorbis/codec.h>
- #include "vcedit.h"
- //#include "i18n.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);
- }
- if(state->os)
- {
- ogg_stream_clear(state->os);
- free(state->os);
- }
- if(state->oy)
- {
- ogg_sync_clear(state->oy);
- free(state->oy);
- }
- 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);
- }
- memset(state, 0, sizeof(*state));
- }
- void vcedit_clear(vcedit_state *state)
- {
- if(state)
- {
- vcedit_clear_internals(state);
- free(state);
- }
- }
- /* 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)
- {
- while(len--)
- {
- oggpack_write(o,*s++,8);
- }
- }
- 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,(unsigned long)strlen(vendor),32);
- _v_writestring(&opb,vendor, (int)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 = _ogg_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;
- }
- static int _blocksize(vcedit_state *s, ogg_packet *p)
- {
- 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;
- }
- static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page)
- {
- int result = ogg_stream_packetout(s->os, p);
- if(result > 0)
- return 1;
- else
- {
- if(s->eosin)
- return 0;
- while(ogg_sync_pageout(s->oy, page) <= 0)
- {
- char *buffer = ogg_sync_buffer(s->oy, CHUNKSIZE);
- int bytes = (int)s->read(buffer,1, CHUNKSIZE, s->in);
- ogg_sync_wrote(s->oy, bytes);
- if(bytes == 0)
- return 0;
- }
- if(ogg_page_eos(page))
- s->eosin = 1;
- else if(ogg_page_serialno(page) != s->serial)
- {
- s->eosin = 1;
- s->extrapage = 1;
- return 0;
- }
- ogg_stream_pagein(s->os, page);
- return _fetch_next_packet(s, p, page);
- }
- }
- 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;
- state->in = in;
- state->read = read_func;
- state->write = write_func;
- state->oy = malloc(sizeof(ogg_sync_state));
- ogg_sync_init(state->oy);
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = (int)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);
- 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;
- }
- 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(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 = (int)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 */
- bytes = (int)strlen(state->vc->vendor);
- state->vendor = malloc(bytes +1);
- strncpy(state->vendor, state->vc->vendor, bytes);
- /* Headers are done! */
- 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;
- 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->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(_fetch_next_packet(state, &op, &ogin))
- {
- int size;
- size = _blocksize(state, &op);
- granpos += size;
- if(needflush)
- {
- if(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;
- }
- }
- else if(needout)
- {
- if(ogg_stream_pageout(&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;
- }
- }
- 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->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)
- {
- 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) {
- // fprintf(stderr, "Bumming out\n");
- goto cleanup;
- }
- if(state->write(ogout.body,1,ogout.body_len, out) !=
- (size_t) ogout.body_len) {
- // fprintf(stderr, "Bumming out 2\n");
- goto cleanup;
- }
- }
- }
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = (int)state->read(buffer,1, CHUNKSIZE, state->in);
- ogg_sync_wrote(state->oy, bytes);
- if(bytes == 0)
- {
- state->eosin = 1;
- break;
- }
- }
-
- cleanup:
- ogg_stream_clear(&streamout);
- ogg_packet_clear(&header_comments);
- free(state->mainbuf);
- free(state->bookbuf);
- state->mainbuf = state->bookbuf = NULL;
- if(!state->eosin)
- {
- state->lasterror =
- "Error writing stream to output. "
- "Output stream may be corrupted or truncated.";
- return -1;
- }
- return 0;
- }
|