vcedit.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /* This program is licensed under the GNU Library General Public License, version 2,
  2. * a copy of which is included with this program (LICENCE.LGPL).
  3. *
  4. * (c) 2000-2001 Michael Smith <[email protected]>
  5. *
  6. *
  7. * Comment editing backend, suitable for use by nice frontend interfaces.
  8. *
  9. * last modified: $Id: vcedit.c,v 1.3 2013/10/22 14:17:11 dromagod Exp $
  10. */
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <errno.h>
  15. #include <ogg/ogg.h>
  16. #include <vorbis/codec.h>
  17. #include "vcedit.h"
  18. //#include "i18n.h"
  19. #define CHUNKSIZE 4096
  20. vcedit_state *vcedit_new_state(void)
  21. {
  22. vcedit_state *state = malloc(sizeof(vcedit_state));
  23. memset(state, 0, sizeof(vcedit_state));
  24. return state;
  25. }
  26. char *vcedit_error(vcedit_state *state)
  27. {
  28. return state->lasterror;
  29. }
  30. vorbis_comment *vcedit_comments(vcedit_state *state)
  31. {
  32. return state->vc;
  33. }
  34. static void vcedit_clear_internals(vcedit_state *state)
  35. {
  36. if(state->vc)
  37. {
  38. vorbis_comment_clear(state->vc);
  39. free(state->vc);
  40. }
  41. if(state->os)
  42. {
  43. ogg_stream_clear(state->os);
  44. free(state->os);
  45. }
  46. if(state->oy)
  47. {
  48. ogg_sync_clear(state->oy);
  49. free(state->oy);
  50. }
  51. if(state->vendor)
  52. free(state->vendor);
  53. if(state->mainbuf)
  54. free(state->mainbuf);
  55. if(state->bookbuf)
  56. free(state->bookbuf);
  57. if(state->vi) {
  58. vorbis_info_clear(state->vi);
  59. free(state->vi);
  60. }
  61. memset(state, 0, sizeof(*state));
  62. }
  63. void vcedit_clear(vcedit_state *state)
  64. {
  65. if(state)
  66. {
  67. vcedit_clear_internals(state);
  68. free(state);
  69. }
  70. }
  71. /* Next two functions pulled straight from libvorbis, apart from one change
  72. * - we don't want to overwrite the vendor string.
  73. */
  74. static void _v_writestring(oggpack_buffer *o,char *s, int len)
  75. {
  76. while(len--)
  77. {
  78. oggpack_write(o,*s++,8);
  79. }
  80. }
  81. static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op)
  82. {
  83. oggpack_buffer opb;
  84. oggpack_writeinit(&opb);
  85. /* preamble */
  86. oggpack_write(&opb,0x03,8);
  87. _v_writestring(&opb,"vorbis", 6);
  88. /* vendor */
  89. oggpack_write(&opb,(unsigned long)strlen(vendor),32);
  90. _v_writestring(&opb,vendor, (int)strlen(vendor));
  91. /* comments */
  92. oggpack_write(&opb,vc->comments,32);
  93. if(vc->comments){
  94. int i;
  95. for(i=0;i<vc->comments;i++){
  96. if(vc->user_comments[i]){
  97. oggpack_write(&opb,vc->comment_lengths[i],32);
  98. _v_writestring(&opb,vc->user_comments[i],
  99. vc->comment_lengths[i]);
  100. }else{
  101. oggpack_write(&opb,0,32);
  102. }
  103. }
  104. }
  105. oggpack_write(&opb,1,1);
  106. op->packet = _ogg_malloc(oggpack_bytes(&opb));
  107. memcpy(op->packet, opb.buffer, oggpack_bytes(&opb));
  108. op->bytes=oggpack_bytes(&opb);
  109. op->b_o_s=0;
  110. op->e_o_s=0;
  111. op->granulepos=0;
  112. oggpack_writeclear(&opb);
  113. return 0;
  114. }
  115. static int _blocksize(vcedit_state *s, ogg_packet *p)
  116. {
  117. int this = vorbis_packet_blocksize(s->vi, p);
  118. int ret = (this + s->prevW)/4;
  119. if(!s->prevW)
  120. {
  121. s->prevW = this;
  122. return 0;
  123. }
  124. s->prevW = this;
  125. return ret;
  126. }
  127. static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page)
  128. {
  129. int result = ogg_stream_packetout(s->os, p);
  130. if(result > 0)
  131. return 1;
  132. else
  133. {
  134. if(s->eosin)
  135. return 0;
  136. while(ogg_sync_pageout(s->oy, page) <= 0)
  137. {
  138. char *buffer = ogg_sync_buffer(s->oy, CHUNKSIZE);
  139. int bytes = (int)s->read(buffer,1, CHUNKSIZE, s->in);
  140. ogg_sync_wrote(s->oy, bytes);
  141. if(bytes == 0)
  142. return 0;
  143. }
  144. if(ogg_page_eos(page))
  145. s->eosin = 1;
  146. else if(ogg_page_serialno(page) != s->serial)
  147. {
  148. s->eosin = 1;
  149. s->extrapage = 1;
  150. return 0;
  151. }
  152. ogg_stream_pagein(s->os, page);
  153. return _fetch_next_packet(s, p, page);
  154. }
  155. }
  156. int vcedit_open(vcedit_state *state, FILE *in)
  157. {
  158. return vcedit_open_callbacks(state, (void *)in,
  159. (vcedit_read_func)fread, (vcedit_write_func)fwrite);
  160. }
  161. int vcedit_open_callbacks(vcedit_state *state, void *in,
  162. vcedit_read_func read_func, vcedit_write_func write_func)
  163. {
  164. char *buffer;
  165. int bytes,i;
  166. ogg_packet *header;
  167. ogg_packet header_main;
  168. ogg_packet header_comments;
  169. ogg_packet header_codebooks;
  170. ogg_page og;
  171. state->in = in;
  172. state->read = read_func;
  173. state->write = write_func;
  174. state->oy = malloc(sizeof(ogg_sync_state));
  175. ogg_sync_init(state->oy);
  176. buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
  177. bytes = (int)state->read(buffer, 1, CHUNKSIZE, state->in);
  178. ogg_sync_wrote(state->oy, bytes);
  179. if(ogg_sync_pageout(state->oy, &og) != 1)
  180. {
  181. if(bytes<CHUNKSIZE)
  182. state->lasterror = "Input truncated or empty.";
  183. else
  184. state->lasterror = "Input is not an Ogg bitstream.";
  185. goto err;
  186. }
  187. state->serial = ogg_page_serialno(&og);
  188. state->os = malloc(sizeof(ogg_stream_state));
  189. ogg_stream_init(state->os, state->serial);
  190. state->vi = malloc(sizeof(vorbis_info));
  191. vorbis_info_init(state->vi);
  192. state->vc = malloc(sizeof(vorbis_comment));
  193. vorbis_comment_init(state->vc);
  194. if(ogg_stream_pagein(state->os, &og) < 0)
  195. {
  196. state->lasterror = "Error reading first page of Ogg bitstream.";
  197. goto err;
  198. }
  199. if(ogg_stream_packetout(state->os, &header_main) != 1)
  200. {
  201. state->lasterror = "Error reading initial header packet.";
  202. goto err;
  203. }
  204. if(vorbis_synthesis_headerin(state->vi, state->vc, &header_main) < 0)
  205. {
  206. state->lasterror = "Ogg bitstream does not contain vorbis data.";
  207. goto err;
  208. }
  209. state->mainlen = header_main.bytes;
  210. state->mainbuf = malloc(state->mainlen);
  211. memcpy(state->mainbuf, header_main.packet, header_main.bytes);
  212. i = 0;
  213. header = &header_comments;
  214. while(i<2) {
  215. while(i<2) {
  216. int result = ogg_sync_pageout(state->oy, &og);
  217. if(result == 0) break; /* Too little data so far */
  218. else if(result == 1)
  219. {
  220. ogg_stream_pagein(state->os, &og);
  221. while(i<2)
  222. {
  223. result = ogg_stream_packetout(state->os, header);
  224. if(result == 0) break;
  225. if(result == -1)
  226. {
  227. state->lasterror = "Corrupt secondary header.";
  228. goto err;
  229. }
  230. vorbis_synthesis_headerin(state->vi, state->vc, header);
  231. if(i==1)
  232. {
  233. state->booklen = header->bytes;
  234. state->bookbuf = malloc(state->booklen);
  235. memcpy(state->bookbuf, header->packet,
  236. header->bytes);
  237. }
  238. i++;
  239. header = &header_codebooks;
  240. }
  241. }
  242. }
  243. buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
  244. bytes = (int)state->read(buffer, 1, CHUNKSIZE, state->in);
  245. if(bytes == 0 && i < 2)
  246. {
  247. state->lasterror = "EOF before end of vorbis headers.";
  248. goto err;
  249. }
  250. ogg_sync_wrote(state->oy, bytes);
  251. }
  252. /* Copy the vendor tag */
  253. bytes = (int)strlen(state->vc->vendor);
  254. state->vendor = malloc(bytes +1);
  255. strncpy(state->vendor, state->vc->vendor, bytes);
  256. /* Headers are done! */
  257. return 0;
  258. err:
  259. vcedit_clear_internals(state);
  260. return -1;
  261. }
  262. int vcedit_write(vcedit_state *state, void *out)
  263. {
  264. ogg_stream_state streamout;
  265. ogg_packet header_main;
  266. ogg_packet header_comments;
  267. ogg_packet header_codebooks;
  268. ogg_page ogout, ogin;
  269. ogg_packet op;
  270. ogg_int64_t granpos = 0;
  271. int result;
  272. char *buffer;
  273. int bytes;
  274. int needflush=0, needout=0;
  275. state->eosin = 0;
  276. state->extrapage = 0;
  277. header_main.bytes = state->mainlen;
  278. header_main.packet = state->mainbuf;
  279. header_main.b_o_s = 1;
  280. header_main.e_o_s = 0;
  281. header_main.granulepos = 0;
  282. header_codebooks.bytes = state->booklen;
  283. header_codebooks.packet = state->bookbuf;
  284. header_codebooks.b_o_s = 0;
  285. header_codebooks.e_o_s = 0;
  286. header_codebooks.granulepos = 0;
  287. ogg_stream_init(&streamout, state->serial);
  288. _commentheader_out(state->vc, state->vendor, &header_comments);
  289. ogg_stream_packetin(&streamout, &header_main);
  290. ogg_stream_packetin(&streamout, &header_comments);
  291. ogg_stream_packetin(&streamout, &header_codebooks);
  292. while((result = ogg_stream_flush(&streamout, &ogout)))
  293. {
  294. if(state->write(ogout.header,1,ogout.header_len, out) !=
  295. (size_t) ogout.header_len)
  296. goto cleanup;
  297. if(state->write(ogout.body,1,ogout.body_len, out) !=
  298. (size_t) ogout.body_len)
  299. goto cleanup;
  300. }
  301. while(_fetch_next_packet(state, &op, &ogin))
  302. {
  303. int size;
  304. size = _blocksize(state, &op);
  305. granpos += size;
  306. if(needflush)
  307. {
  308. if(ogg_stream_flush(&streamout, &ogout))
  309. {
  310. if(state->write(ogout.header,1,ogout.header_len,
  311. out) != (size_t) ogout.header_len)
  312. goto cleanup;
  313. if(state->write(ogout.body,1,ogout.body_len,
  314. out) != (size_t) ogout.body_len)
  315. goto cleanup;
  316. }
  317. }
  318. else if(needout)
  319. {
  320. if(ogg_stream_pageout(&streamout, &ogout))
  321. {
  322. if(state->write(ogout.header,1,ogout.header_len,
  323. out) != (size_t) ogout.header_len)
  324. goto cleanup;
  325. if(state->write(ogout.body,1,ogout.body_len,
  326. out) != (size_t) ogout.body_len)
  327. goto cleanup;
  328. }
  329. }
  330. needflush=needout=0;
  331. if(op.granulepos == -1)
  332. {
  333. op.granulepos = granpos;
  334. ogg_stream_packetin(&streamout, &op);
  335. }
  336. else /* granulepos is set, validly. Use it, and force a flush to
  337. account for shortened blocks (vcut) when appropriate */
  338. {
  339. if(granpos > op.granulepos)
  340. {
  341. granpos = op.granulepos;
  342. ogg_stream_packetin(&streamout, &op);
  343. needflush=1;
  344. }
  345. else
  346. {
  347. ogg_stream_packetin(&streamout, &op);
  348. needout=1;
  349. }
  350. }
  351. }
  352. streamout.e_o_s = 1;
  353. while(ogg_stream_flush(&streamout, &ogout))
  354. {
  355. if(state->write(ogout.header,1,ogout.header_len,
  356. out) != (size_t) ogout.header_len)
  357. goto cleanup;
  358. if(state->write(ogout.body,1,ogout.body_len,
  359. out) != (size_t) ogout.body_len)
  360. goto cleanup;
  361. }
  362. if (state->extrapage)
  363. {
  364. if(state->write(ogin.header,1,ogin.header_len,
  365. out) != (size_t) ogin.header_len)
  366. goto cleanup;
  367. if (state->write(ogin.body,1,ogin.body_len, out) !=
  368. (size_t) ogin.body_len)
  369. goto cleanup;
  370. }
  371. state->eosin=0; /* clear it, because not all paths to here do */
  372. while(!state->eosin) /* We reached eos, not eof */
  373. {
  374. /* We copy the rest of the stream (other logical streams)
  375. * through, a page at a time. */
  376. while(1)
  377. {
  378. result = ogg_sync_pageout(state->oy, &ogout);
  379. if(result==0)
  380. break;
  381. if(result<0)
  382. state->lasterror = "Corrupt or missing data, continuing...";
  383. else
  384. {
  385. /* Don't bother going through the rest, we can just
  386. * write the page out now */
  387. if(state->write(ogout.header,1,ogout.header_len,
  388. out) != (size_t) ogout.header_len) {
  389. // fprintf(stderr, "Bumming out\n");
  390. goto cleanup;
  391. }
  392. if(state->write(ogout.body,1,ogout.body_len, out) !=
  393. (size_t) ogout.body_len) {
  394. // fprintf(stderr, "Bumming out 2\n");
  395. goto cleanup;
  396. }
  397. }
  398. }
  399. buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
  400. bytes = (int)state->read(buffer,1, CHUNKSIZE, state->in);
  401. ogg_sync_wrote(state->oy, bytes);
  402. if(bytes == 0)
  403. {
  404. state->eosin = 1;
  405. break;
  406. }
  407. }
  408. cleanup:
  409. ogg_stream_clear(&streamout);
  410. ogg_packet_clear(&header_comments);
  411. free(state->mainbuf);
  412. free(state->bookbuf);
  413. state->mainbuf = state->bookbuf = NULL;
  414. if(!state->eosin)
  415. {
  416. state->lasterror =
  417. "Error writing stream to output. "
  418. "Output stream may be corrupted or truncated.";
  419. return -1;
  420. }
  421. return 0;
  422. }