/* 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 * * * Comment editing backend, suitable for use by nice frontend interfaces. * * last modified: $Id: vcedit.c,v 1.23 2003/09/03 07:58:05 calc Exp $ */ #include #include #include #include #include #include #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) { 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->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); } tmp = state->lasterror; memset(state, 0, sizeof(*state)); state->lasterror = tmp; } 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,strlen(vendor),32); _v_writestring(&opb,vendor, strlen(vendor)); /* comments */ oggpack_write(&opb,vc->comments,32); if(vc->comments){ int i; for(i=0;icomments;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; char *buffer; int bytes; 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) { 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; } 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; int chunks = 0; 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(byteslasterror = _("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 = 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; } 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) { 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; } } 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; }