00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 #include <stdio.h>
00013 #include <stdlib.h>
00014 #include <string.h>
00015 #include <errno.h>
00016 #include <ogg/ogg.h>
00017 #include <vorbis/codec.h>
00018
00019 #include "vcedit.h"
00020
00021
00022 #ifdef ENABLE_NLS
00023 #include <libintl.h>
00024 #define _(X) gettext(X)
00025 #else
00026 #define _(X) (X)
00027 #define textdomain(X)
00028 #define bindtextdomain(X, Y)
00029 #endif
00030 #ifdef gettext_noop
00031 #define N_(X) gettext_noop(X)
00032 #else
00033 #define N_(X) (X)
00034 #endif
00035
00036
00037 #define CHUNKSIZE 4096
00038
00039 vcedit_state *vcedit_new_state(void)
00040 {
00041 vcedit_state *state = malloc(sizeof(vcedit_state));
00042 memset(state, 0, sizeof(vcedit_state));
00043
00044 return state;
00045 }
00046
00047 char *vcedit_error(vcedit_state *state)
00048 {
00049 return state->lasterror;
00050 }
00051
00052 vorbis_comment *vcedit_comments(vcedit_state *state)
00053 {
00054 return state->vc;
00055 }
00056
00057 static void vcedit_clear_internals(vcedit_state *state)
00058 {
00059 char *tmp;
00060 if(state->vc)
00061 {
00062 vorbis_comment_clear(state->vc);
00063 free(state->vc);
00064 }
00065 if(state->os)
00066 {
00067 ogg_stream_clear(state->os);
00068 free(state->os);
00069 }
00070 if(state->oy)
00071 {
00072 ogg_sync_clear(state->oy);
00073 free(state->oy);
00074 }
00075 if(state->vendor)
00076 free(state->vendor);
00077 if(state->mainbuf)
00078 free(state->mainbuf);
00079 if(state->bookbuf)
00080 free(state->bookbuf);
00081 if(state->vi) {
00082 vorbis_info_clear(state->vi);
00083 free(state->vi);
00084 }
00085
00086 tmp = state->lasterror;
00087 memset(state, 0, sizeof(*state));
00088 state->lasterror = tmp;
00089 }
00090
00091 void vcedit_clear(vcedit_state *state)
00092 {
00093 if(state)
00094 {
00095 vcedit_clear_internals(state);
00096 free(state);
00097 }
00098 }
00099
00100
00101
00102
00103 static void _v_writestring(oggpack_buffer *o,char *s, int len)
00104 {
00105 while(len--)
00106 {
00107 oggpack_write(o,*s++,8);
00108 }
00109 }
00110
00111 static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op)
00112 {
00113 oggpack_buffer opb;
00114
00115 oggpack_writeinit(&opb);
00116
00117
00118 oggpack_write(&opb,0x03,8);
00119 _v_writestring(&opb,"vorbis", 6);
00120
00121
00122 oggpack_write(&opb,strlen(vendor),32);
00123 _v_writestring(&opb,vendor, strlen(vendor));
00124
00125
00126 oggpack_write(&opb,vc->comments,32);
00127 if(vc->comments){
00128 int i;
00129 for(i=0;i<vc->comments;i++){
00130 if(vc->user_comments[i]){
00131 oggpack_write(&opb,vc->comment_lengths[i],32);
00132 _v_writestring(&opb,vc->user_comments[i],
00133 vc->comment_lengths[i]);
00134 }else{
00135 oggpack_write(&opb,0,32);
00136 }
00137 }
00138 }
00139 oggpack_write(&opb,1,1);
00140
00141 op->packet = _ogg_malloc(oggpack_bytes(&opb));
00142 memcpy(op->packet, opb.buffer, oggpack_bytes(&opb));
00143
00144 op->bytes=oggpack_bytes(&opb);
00145 op->b_o_s=0;
00146 op->e_o_s=0;
00147 op->granulepos=0;
00148
00149 oggpack_writeclear(&opb);
00150 return 0;
00151 }
00152
00153 static int _blocksize(vcedit_state *s, ogg_packet *p)
00154 {
00155 int this = vorbis_packet_blocksize(s->vi, p);
00156 int ret = (this + s->prevW)/4;
00157
00158 if(!s->prevW)
00159 {
00160 s->prevW = this;
00161 return 0;
00162 }
00163
00164 s->prevW = this;
00165 return ret;
00166 }
00167
00168 static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page)
00169 {
00170 int result;
00171 char *buffer;
00172 int bytes;
00173
00174 result = ogg_stream_packetout(s->os, p);
00175
00176 if(result > 0)
00177 return 1;
00178 else
00179 {
00180 if(s->eosin)
00181 return 0;
00182 while(ogg_sync_pageout(s->oy, page) <= 0)
00183 {
00184 buffer = ogg_sync_buffer(s->oy, CHUNKSIZE);
00185 bytes = s->read(buffer,1, CHUNKSIZE, s->in);
00186 ogg_sync_wrote(s->oy, bytes);
00187 if(bytes == 0)
00188 return 0;
00189 }
00190 if(ogg_page_eos(page))
00191 s->eosin = 1;
00192 else if(ogg_page_serialno(page) != s->serial)
00193 {
00194 s->eosin = 1;
00195 s->extrapage = 1;
00196 return 0;
00197 }
00198
00199 ogg_stream_pagein(s->os, page);
00200 return _fetch_next_packet(s, p, page);
00201 }
00202 }
00203
00204 int vcedit_open(vcedit_state *state, FILE *in)
00205 {
00206 return vcedit_open_callbacks(state, (void *)in,
00207 (vcedit_read_func)fread, (vcedit_write_func)fwrite);
00208 }
00209
00210 int vcedit_open_callbacks(vcedit_state *state, void *in,
00211 vcedit_read_func read_func, vcedit_write_func write_func)
00212 {
00213
00214 char *buffer;
00215 int bytes,i;
00216 int chunks = 0;
00217 ogg_packet *header;
00218 ogg_packet header_main;
00219 ogg_packet header_comments;
00220 ogg_packet header_codebooks;
00221 ogg_page og;
00222
00223 state->in = in;
00224 state->read = read_func;
00225 state->write = write_func;
00226
00227 state->oy = malloc(sizeof(ogg_sync_state));
00228 ogg_sync_init(state->oy);
00229
00230 while(1)
00231 {
00232 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
00233 bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
00234
00235 ogg_sync_wrote(state->oy, bytes);
00236
00237 if(ogg_sync_pageout(state->oy, &og) == 1)
00238 break;
00239
00240 if(chunks++ >= 10)
00241 {
00242 if(bytes<CHUNKSIZE)
00243 state->lasterror = _("Input truncated or empty.");
00244 else
00245 state->lasterror = _("Input is not an Ogg bitstream.");
00246 goto err;
00247 }
00248 }
00249
00250 state->serial = ogg_page_serialno(&og);
00251
00252 state->os = malloc(sizeof(ogg_stream_state));
00253 ogg_stream_init(state->os, state->serial);
00254
00255 state->vi = malloc(sizeof(vorbis_info));
00256 vorbis_info_init(state->vi);
00257
00258 state->vc = malloc(sizeof(vorbis_comment));
00259 vorbis_comment_init(state->vc);
00260
00261 if(ogg_stream_pagein(state->os, &og) < 0)
00262 {
00263 state->lasterror = _("Error reading first page of Ogg bitstream.");
00264 goto err;
00265 }
00266
00267 if(ogg_stream_packetout(state->os, &header_main) != 1)
00268 {
00269 state->lasterror = _("Error reading initial header packet.");
00270 goto err;
00271 }
00272
00273 if(vorbis_synthesis_headerin(state->vi, state->vc, &header_main) < 0)
00274 {
00275 state->lasterror = _("Ogg bitstream does not contain vorbis data.");
00276 goto err;
00277 }
00278
00279 state->mainlen = header_main.bytes;
00280 state->mainbuf = malloc(state->mainlen);
00281 memcpy(state->mainbuf, header_main.packet, header_main.bytes);
00282
00283 i = 0;
00284 header = &header_comments;
00285 while(i<2) {
00286 while(i<2) {
00287 int result = ogg_sync_pageout(state->oy, &og);
00288 if(result == 0) break;
00289 else if(result == 1)
00290 {
00291 ogg_stream_pagein(state->os, &og);
00292 while(i<2)
00293 {
00294 result = ogg_stream_packetout(state->os, header);
00295 if(result == 0) break;
00296 if(result == -1)
00297 {
00298 state->lasterror = _("Corrupt secondary header.");
00299 goto err;
00300 }
00301 vorbis_synthesis_headerin(state->vi, state->vc, header);
00302 if(i==1)
00303 {
00304 state->booklen = header->bytes;
00305 state->bookbuf = malloc(state->booklen);
00306 memcpy(state->bookbuf, header->packet,
00307 header->bytes);
00308 }
00309 i++;
00310 header = &header_codebooks;
00311 }
00312 }
00313 }
00314
00315 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
00316 bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
00317 if(bytes == 0 && i < 2)
00318 {
00319 state->lasterror = _("EOF before end of vorbis headers.");
00320 goto err;
00321 }
00322 ogg_sync_wrote(state->oy, bytes);
00323 }
00324
00325
00326 state->vendor = malloc(strlen(state->vc->vendor) +1);
00327 strcpy(state->vendor, state->vc->vendor);
00328
00329
00330 return 0;
00331
00332 err:
00333 vcedit_clear_internals(state);
00334 return -1;
00335 }
00336
00337 int vcedit_write(vcedit_state *state, void *out)
00338 {
00339 ogg_stream_state streamout;
00340 ogg_packet header_main;
00341 ogg_packet header_comments;
00342 ogg_packet header_codebooks;
00343
00344 ogg_page ogout, ogin;
00345 ogg_packet op;
00346 ogg_int64_t granpos = 0;
00347 int result;
00348 char *buffer;
00349 int bytes;
00350 int needflush=0, needout=0;
00351
00352 state->eosin = 0;
00353 state->extrapage = 0;
00354
00355 header_main.bytes = state->mainlen;
00356 header_main.packet = state->mainbuf;
00357 header_main.b_o_s = 1;
00358 header_main.e_o_s = 0;
00359 header_main.granulepos = 0;
00360
00361 header_codebooks.bytes = state->booklen;
00362 header_codebooks.packet = state->bookbuf;
00363 header_codebooks.b_o_s = 0;
00364 header_codebooks.e_o_s = 0;
00365 header_codebooks.granulepos = 0;
00366
00367 ogg_stream_init(&streamout, state->serial);
00368
00369 _commentheader_out(state->vc, state->vendor, &header_comments);
00370
00371 ogg_stream_packetin(&streamout, &header_main);
00372 ogg_stream_packetin(&streamout, &header_comments);
00373 ogg_stream_packetin(&streamout, &header_codebooks);
00374
00375 while((result = ogg_stream_flush(&streamout, &ogout)))
00376 {
00377 if(state->write(ogout.header,1,ogout.header_len, out) !=
00378 (size_t) ogout.header_len)
00379 goto cleanup;
00380 if(state->write(ogout.body,1,ogout.body_len, out) !=
00381 (size_t) ogout.body_len)
00382 goto cleanup;
00383 }
00384
00385 while(_fetch_next_packet(state, &op, &ogin))
00386 {
00387 int size;
00388 size = _blocksize(state, &op);
00389 granpos += size;
00390
00391 if(needflush)
00392 {
00393 if(ogg_stream_flush(&streamout, &ogout))
00394 {
00395 if(state->write(ogout.header,1,ogout.header_len,
00396 out) != (size_t) ogout.header_len)
00397 goto cleanup;
00398 if(state->write(ogout.body,1,ogout.body_len,
00399 out) != (size_t) ogout.body_len)
00400 goto cleanup;
00401 }
00402 }
00403 else if(needout)
00404 {
00405 if(ogg_stream_pageout(&streamout, &ogout))
00406 {
00407 if(state->write(ogout.header,1,ogout.header_len,
00408 out) != (size_t) ogout.header_len)
00409 goto cleanup;
00410 if(state->write(ogout.body,1,ogout.body_len,
00411 out) != (size_t) ogout.body_len)
00412 goto cleanup;
00413 }
00414 }
00415
00416 needflush=needout=0;
00417
00418 if(op.granulepos == -1)
00419 {
00420 op.granulepos = granpos;
00421 ogg_stream_packetin(&streamout, &op);
00422 }
00423 else
00424
00425 {
00426 if(granpos > op.granulepos)
00427 {
00428 granpos = op.granulepos;
00429 ogg_stream_packetin(&streamout, &op);
00430 needflush=1;
00431 }
00432 else
00433 {
00434 ogg_stream_packetin(&streamout, &op);
00435 needout=1;
00436 }
00437 }
00438 }
00439
00440 streamout.e_o_s = 1;
00441 while(ogg_stream_flush(&streamout, &ogout))
00442 {
00443 if(state->write(ogout.header,1,ogout.header_len,
00444 out) != (size_t) ogout.header_len)
00445 goto cleanup;
00446 if(state->write(ogout.body,1,ogout.body_len,
00447 out) != (size_t) ogout.body_len)
00448 goto cleanup;
00449 }
00450
00451 if (state->extrapage)
00452 {
00453 if(state->write(ogin.header,1,ogin.header_len,
00454 out) != (size_t) ogin.header_len)
00455 goto cleanup;
00456 if (state->write(ogin.body,1,ogin.body_len, out) !=
00457 (size_t) ogin.body_len)
00458 goto cleanup;
00459 }
00460
00461 state->eosin=0;
00462 while(!state->eosin)
00463 {
00464
00465
00466 while(1)
00467 {
00468 result = ogg_sync_pageout(state->oy, &ogout);
00469 if(result==0)
00470 break;
00471 if(result<0)
00472 state->lasterror = _("Corrupt or missing data, continuing...");
00473 else
00474 {
00475
00476
00477 if(state->write(ogout.header,1,ogout.header_len,
00478 out) != (size_t) ogout.header_len) {
00479 goto cleanup;
00480 }
00481 if(state->write(ogout.body,1,ogout.body_len, out) !=
00482 (size_t) ogout.body_len) {
00483 goto cleanup;
00484 }
00485 }
00486 }
00487 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
00488 bytes = state->read(buffer,1, CHUNKSIZE, state->in);
00489 ogg_sync_wrote(state->oy, bytes);
00490 if(bytes == 0)
00491 {
00492 state->eosin = 1;
00493 break;
00494 }
00495 }
00496
00497
00498 cleanup:
00499 ogg_stream_clear(&streamout);
00500 ogg_packet_clear(&header_comments);
00501
00502 free(state->mainbuf);
00503 free(state->bookbuf);
00504 state->mainbuf = state->bookbuf = NULL;
00505
00506 if(!state->eosin)
00507 {
00508 state->lasterror =
00509 _("Error writing stream to output. "
00510 "Output stream may be corrupted or truncated.");
00511 return -1;
00512 }
00513
00514 return 0;
00515 }
00516