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