vcedit.c 10.9 KB
Newer Older
1 2
/* This program is licensed under the GNU Library General Public License, version 2,
 * a copy of which is included with this program (LICENCE.LGPL).
Michael Smith's avatar
 
Michael Smith committed
3 4 5 6 7 8
 *
 * (c) 2000-2001 Michael Smith <msmith@labyrinth.net.au>
 *
 *
 * Comment editing backend, suitable for use by nice frontend interfaces.
 *
9
 * last modified: $Id: vcedit.c,v 1.22 2002/09/12 12:48:27 msmith Exp $
Michael Smith's avatar
 
Michael Smith committed
10 11 12 13
 */

#include <stdio.h>
#include <stdlib.h>
14
#include <string.h>
15
#include <errno.h>
Michael Smith's avatar
 
Michael Smith committed
16 17 18 19
#include <ogg/ogg.h>
#include <vorbis/codec.h>

#include "vcedit.h"
20 21
#include "i18n.h"

Michael Smith's avatar
 
Michael Smith committed
22 23 24 25 26 27 28 29 30 31 32

#define CHUNKSIZE 4096

vcedit_state *vcedit_new_state(void)
{
	vcedit_state *state = malloc(sizeof(vcedit_state));
	memset(state, 0, sizeof(vcedit_state));

	return state;
}

33
char *vcedit_error(vcedit_state *state)
Michael Smith's avatar
 
Michael Smith committed
34
{
35
	return state->lasterror;
Michael Smith's avatar
 
Michael Smith committed
36 37 38 39 40 41 42
}

vorbis_comment *vcedit_comments(vcedit_state *state)
{
	return state->vc;
}

Michael Smith's avatar
 
Michael Smith committed
43 44
static void vcedit_clear_internals(vcedit_state *state)
{
45
    char *tmp;
Michael Smith's avatar
 
Michael Smith committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
	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);
	}
61 62
	if(state->vendor)
		free(state->vendor);
63 64 65 66 67 68 69 70 71
    if(state->mainbuf)
        free(state->mainbuf);
    if(state->bookbuf)
        free(state->bookbuf);
    if(state->vi) {
       	vorbis_info_clear(state->vi);
        free(state->vi);
    }

72
    tmp = state->lasterror;
73
    memset(state, 0, sizeof(*state));
74
    state->lasterror = tmp;
Michael Smith's avatar
 
Michael Smith committed
75 76
}

77 78 79 80 81 82 83 84
void vcedit_clear(vcedit_state *state)
{
	if(state)
	{
		vcedit_clear_internals(state);
		free(state);
	}
}
Michael Smith's avatar
 
Michael Smith committed
85

86 87 88
/* Next two functions pulled straight from libvorbis, apart from one change
 * - we don't want to overwrite the vendor string.
 */
89
static void _v_writestring(oggpack_buffer *o,char *s, int len)
90
{
91
	while(len--)
92 93 94 95 96 97 98 99 100 101 102 103 104
	{
		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);
105
	_v_writestring(&opb,"vorbis", 6);
106 107 108

	/* vendor */
	oggpack_write(&opb,strlen(vendor),32);
109
	_v_writestring(&opb,vendor, strlen(vendor));
110 111 112 113 114 115 116 117

	/* 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);
118 119
				_v_writestring(&opb,vc->user_comments[i], 
                        vc->comment_lengths[i]);
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
			}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;

Michael Smith's avatar
Michael Smith committed
135
	oggpack_writeclear(&opb);
136 137 138
	return 0;
}

139 140
static int _blocksize(vcedit_state *s, ogg_packet *p)
{
141
	int this = vorbis_packet_blocksize(s->vi, p);
142 143 144 145 146 147 148 149 150 151 152 153
	int ret = (this + s->prevW)/4;

	if(!s->prevW)
	{
		s->prevW = this;
		return 0;
	}

	s->prevW = this;
	return ret;
}

154
static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page)
155 156 157 158 159
{
	int result;
	char *buffer;
	int bytes;

160
	result = ogg_stream_packetout(s->os, p);
161 162 163 164 165

	if(result > 0)
		return 1;
	else
	{
166 167 168
		if(s->eosin)
			return 0;
		while(ogg_sync_pageout(s->oy, page) <= 0)
169 170 171 172 173 174 175
		{
			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;
		}
176 177 178 179 180 181 182 183
		if(ogg_page_eos(page))
			s->eosin = 1;
		else if(ogg_page_serialno(page) != s->serial)
		{
			s->eosin = 1;
			s->extrapage = 1;
			return 0;
		}
184

185 186
		ogg_stream_pagein(s->os, page);
		return _fetch_next_packet(s, p, page);
187 188 189
	}
}

Michael Smith's avatar
 
Michael Smith committed
190
int vcedit_open(vcedit_state *state, FILE *in)
191 192 193 194 195 196 197
{
	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)
Michael Smith's avatar
 
Michael Smith committed
198 199 200 201
{

	char *buffer;
	int bytes,i;
202
    int chunks = 0;
Michael Smith's avatar
 
Michael Smith committed
203 204 205 206
	ogg_packet *header;
	ogg_packet	header_main;
	ogg_packet  header_comments;
	ogg_packet	header_codebooks;
Michael Smith's avatar
 
Michael Smith committed
207 208
	ogg_page    og;

Michael Smith's avatar
 
Michael Smith committed
209
	state->in = in;
210 211
	state->read = read_func;
	state->write = write_func;
Michael Smith's avatar
 
Michael Smith committed
212

Michael Smith's avatar
 
Michael Smith committed
213
	state->oy = malloc(sizeof(ogg_sync_state));
Michael Smith's avatar
 
Michael Smith committed
214 215
	ogg_sync_init(state->oy);

216 217 218 219
    while(1)
    {
    	buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
	    bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
Michael Smith's avatar
 
Michael Smith committed
220

221
    	ogg_sync_wrote(state->oy, bytes);
Michael Smith's avatar
 
Michael Smith committed
222

223 224 225 226 227 228 229 230 231 232 233 234
        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(bytes<CHUNKSIZE)
			    state->lasterror = _("Input truncated or empty.");
    		else
	    		state->lasterror = _("Input is not an Ogg bitstream.");
		    goto err;
    	}
    }
Michael Smith's avatar
 
Michael Smith committed
235

Michael Smith's avatar
 
Michael Smith committed
236
	state->serial = ogg_page_serialno(&og);
Michael Smith's avatar
 
Michael Smith committed
237

Michael Smith's avatar
 
Michael Smith committed
238
	state->os = malloc(sizeof(ogg_stream_state));
Michael Smith's avatar
 
Michael Smith committed
239 240
	ogg_stream_init(state->os, state->serial);

241 242
    state->vi = malloc(sizeof(vorbis_info));
	vorbis_info_init(state->vi);
Michael Smith's avatar
 
Michael Smith committed
243 244

	state->vc = malloc(sizeof(vorbis_comment));
Michael Smith's avatar
 
Michael Smith committed
245 246
	vorbis_comment_init(state->vc);

Michael Smith's avatar
 
Michael Smith committed
247
	if(ogg_stream_pagein(state->os, &og) < 0)
Michael Smith's avatar
 
Michael Smith committed
248
	{
249
		state->lasterror = _("Error reading first page of Ogg bitstream.");
Michael Smith's avatar
 
Michael Smith committed
250 251 252 253 254
		goto err;
	}

	if(ogg_stream_packetout(state->os, &header_main) != 1)
	{
255
		state->lasterror = _("Error reading initial header packet.");
Michael Smith's avatar
 
Michael Smith committed
256 257 258
		goto err;
	}

259
	if(vorbis_synthesis_headerin(state->vi, state->vc, &header_main) < 0)
Michael Smith's avatar
 
Michael Smith committed
260
	{
261
		state->lasterror = _("Ogg bitstream does not contain vorbis data.");
Michael Smith's avatar
 
Michael Smith committed
262 263 264 265 266 267 268 269 270 271 272
		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) {
Michael Smith's avatar
 
Michael Smith committed
273
			int result = ogg_sync_pageout(state->oy, &og);
Michael Smith's avatar
 
Michael Smith committed
274 275 276
			if(result == 0) break; /* Too little data so far */
			else if(result == 1)
			{
Michael Smith's avatar
 
Michael Smith committed
277
				ogg_stream_pagein(state->os, &og);
Michael Smith's avatar
 
Michael Smith committed
278 279 280 281 282 283
				while(i<2)
				{
					result = ogg_stream_packetout(state->os, header);
					if(result == 0) break;
					if(result == -1)
					{
284
						state->lasterror = _("Corrupt secondary header.");
Michael Smith's avatar
 
Michael Smith committed
285 286
						goto err;
					}
287
					vorbis_synthesis_headerin(state->vi, state->vc, header);
Michael Smith's avatar
 
Michael Smith committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301
					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);
302
		bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
Michael Smith's avatar
 
Michael Smith committed
303 304
		if(bytes == 0 && i < 2)
		{
305
			state->lasterror = _("EOF before end of vorbis headers.");
Michael Smith's avatar
 
Michael Smith committed
306 307 308 309 310
			goto err;
		}
		ogg_sync_wrote(state->oy, bytes);
	}

311 312 313 314
	/* Copy the vendor tag */
	state->vendor = malloc(strlen(state->vc->vendor) +1);
	strcpy(state->vendor, state->vc->vendor);

Michael Smith's avatar
 
Michael Smith committed
315 316 317 318
	/* Headers are done! */
	return 0;

err:
Michael Smith's avatar
 
Michael Smith committed
319
	vcedit_clear_internals(state);
Michael Smith's avatar
 
Michael Smith committed
320 321 322
	return -1;
}

323
int vcedit_write(vcedit_state *state, void *out)
Michael Smith's avatar
 
Michael Smith committed
324 325 326 327 328 329
{
	ogg_stream_state streamout;
	ogg_packet header_main;
	ogg_packet header_comments;
	ogg_packet header_codebooks;

330
	ogg_page ogout, ogin;
Michael Smith's avatar
 
Michael Smith committed
331
	ogg_packet op;
332
	ogg_int64_t granpos = 0;
Michael Smith's avatar
 
Michael Smith committed
333 334
	int result;
	char *buffer;
335
	int bytes;
336
	int needflush=0, needout=0;
Michael Smith's avatar
 
Michael Smith committed
337

338 339 340
	state->eosin = 0;
	state->extrapage = 0;

Michael Smith's avatar
 
Michael Smith committed
341 342 343 344 345 346 347 348 349 350 351 352 353 354
	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);

355
	_commentheader_out(state->vc, state->vendor, &header_comments);
Michael Smith's avatar
 
Michael Smith committed
356 357 358 359 360

	ogg_stream_packetin(&streamout, &header_main);
	ogg_stream_packetin(&streamout, &header_comments);
	ogg_stream_packetin(&streamout, &header_codebooks);

Michael Smith's avatar
 
Michael Smith committed
361
	while((result = ogg_stream_flush(&streamout, &ogout)))
Michael Smith's avatar
 
Michael Smith committed
362
	{
Michael Smith's avatar
Michael Smith committed
363 364
		if(state->write(ogout.header,1,ogout.header_len, out) !=
				(size_t) ogout.header_len)
Michael Smith's avatar
 
Michael Smith committed
365
			goto cleanup;
Michael Smith's avatar
Michael Smith committed
366 367
		if(state->write(ogout.body,1,ogout.body_len, out) != 
				(size_t) ogout.body_len)
Michael Smith's avatar
 
Michael Smith committed
368
			goto cleanup;
Michael Smith's avatar
 
Michael Smith committed
369 370
	}

371
	while(_fetch_next_packet(state, &op, &ogin))
Michael Smith's avatar
 
Michael Smith committed
372
	{
373 374 375 376
		int size;
		size = _blocksize(state, &op);
		granpos += size;

377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
		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;
403 404

		if(op.granulepos == -1)
Michael Smith's avatar
 
Michael Smith committed
405
		{
406 407 408 409 410 411 412
			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)
Michael Smith's avatar
 
Michael Smith committed
413
			{
414 415
				granpos = op.granulepos;
				ogg_stream_packetin(&streamout, &op);
416
				needflush=1;
Michael Smith's avatar
 
Michael Smith committed
417
			}
418
			else 
419
			{
420
				ogg_stream_packetin(&streamout, &op);
421
				needout=1;
422
			}
423 424 425
		}		
	}

426
	streamout.e_o_s = 1;
427 428 429 430 431 432 433 434
	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;
Michael Smith's avatar
 
Michael Smith committed
435 436
	}

437 438 439 440 441 442 443 444 445 446 447 448
	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 */
Michael Smith's avatar
 
Michael Smith committed
449
	{
450 451 452
		/* We copy the rest of the stream (other logical streams)
		 * through, a page at a time. */
		while(1)
Michael Smith's avatar
 
Michael Smith committed
453
		{
454
			result = ogg_sync_pageout(state->oy, &ogout);
455 456
			if(result==0)
                break;
457
			if(result<0)
458
				state->lasterror = _("Corrupt or missing data, continuing...");
459 460 461 462 463
			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, 
464
						out) != (size_t) ogout.header_len) {
465
					goto cleanup;
466
                }
467
				if(state->write(ogout.body,1,ogout.body_len, out) !=
468
						(size_t) ogout.body_len) {
469
					goto cleanup;
470
                }
471
			}
Michael Smith's avatar
 
Michael Smith committed
472
		}
473 474 475
		buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
		bytes = state->read(buffer,1, CHUNKSIZE, state->in);
		ogg_sync_wrote(state->oy, bytes);
476 477
		if(bytes == 0) 
		{
478
			state->eosin = 1;
479 480
			break;
		}
Michael Smith's avatar
 
Michael Smith committed
481 482 483
	}
							

Michael Smith's avatar
 
Michael Smith committed
484
cleanup:
Michael Smith's avatar
 
Michael Smith committed
485
	ogg_stream_clear(&streamout);
Michael Smith's avatar
 
Michael Smith committed
486
	ogg_packet_clear(&header_comments);
Michael Smith's avatar
 
Michael Smith committed
487

Michael Smith's avatar
 
Michael Smith committed
488 489
	free(state->mainbuf);
	free(state->bookbuf);
490
    state->mainbuf = state->bookbuf = NULL;
Michael Smith's avatar
 
Michael Smith committed
491

492
	if(!state->eosin)
Michael Smith's avatar
 
Michael Smith committed
493
	{
494
		state->lasterror =
495 496
			_("Error writing stream to output. "
			"Output stream may be corrupted or truncated.");
Michael Smith's avatar
 
Michael Smith committed
497 498 499
		return -1;
	}

Michael Smith's avatar
 
Michael Smith committed
500 501
	return 0;
}
502