Updated comment padding patches
Michael Gold <[email protected]>
| Newsgroups | gmane.comp.multimedia.ogg.vorbis.devel |
|---|---|
| Message-ID | <[email protected]> |
I've attached updated patches (against vorbis-tools 1.3.0b3.5) to
support comment padding. No changes to libvorbis are necessary. The
padding is added in the same way as oggenc2 (null bytes within the
packet, at the end) and shouldn't cause problems with any reasonable
decoder or comment editor implementations.
Padding is enabled by default, except for small files (less than 30 s
for oggenc, or less than 256 KB for vorbiscomment). When enabled, the
comment packet is padded to a multiple of some blocksize so that the
packet size won't depend too much on the initial comment size. An extra
block is added if less than half a block remains free.
Oggenc now has a --comment-padding=n option, and vorbiscomment has a
similar --padding=n option. n=0 disables padding, n=1 forces it with an
automatically-chosen blocksize, and any other value sets the blocksize.
The auto-selected blocksize is
512 bytes when (nominal bitrate > 0 and < 96000)
or padding is forced on a small file
4096 bytes otherwise
(though vorbiscomment's new in-place editing feature will work with any
amount of padding, as long as the data will fit).
-- Michael
_______________________________________________
Vorbis-dev mailing list
[email protected]
http://lists.xiph.org/mailman/listinfo/vorbis-dev
oggenc-pad-2.diff
(text/x-diff, 6.1 KB)
diff --git a/oggenc/encode.c b/oggenc/encode.c
index 86f1596..0e95baf 100644
--- a/oggenc/encode.c
+++ b/oggenc/encode.c
@@ -458,6 +458,53 @@ int oe_encode(oe_enc_opt *opt)
#endif
}
+ /* Add comment padding, if necessary */
+ if (opt->comment_padding)
+ {
+ int padblock = opt->comment_padding;
+ const long bitrate = vi.bitrate_nominal;
+ const long size = header_comments.bytes;
+
+ if (padblock == 1 || padblock < 0)
+ {
+ /* no padding size given; try to pick a reasonable default */
+
+ const long seconds = opt->total_samples_per_channel / opt->rate;
+ if (padblock != 1 && seconds < 30)
+ {
+ /* unless padding was requested (padblock==1), disable it
+ * for files of short or unknown (seconds==0) length */
+ padblock = 0;
+ }
+ else if (seconds < 30 || (bitrate > 0 && bitrate < 96000))
+ padblock = 512;
+ else
+ padblock = 4096;
+ }
+
+ /* pad to a multiple of padblock bytes,
+ * leaving at least padblock/2 bytes free */
+ if (padblock)
+ {
+ char *new_packet;
+ long padding;
+ const int min_padding = padblock / 2;
+
+ padding = size + (padblock - 1) + min_padding;
+ padding -= (padding % padblock) + size;
+
+ new_packet = malloc(size + padding);
+ if (new_packet)
+ {
+ memcpy(new_packet, header_comments.packet, size);
+ memset(new_packet + size, 0, padding);
+
+ header_comments.bytes = size + padding;
+ header_comments.packet = new_packet;
+ }
+ }
+ }
+
/* write the next Vorbis headers */
ogg_stream_packetin(&os,&header_comments);
ogg_stream_packetin(&os,&header_codebooks);
diff --git a/oggenc/encode.h b/oggenc/encode.h
index e3dd33b..9d16d41 100644
--- a/oggenc/encode.h
+++ b/oggenc/encode.h
@@ -62,6 +62,7 @@ typedef struct
int advopt_count;
int copy_comments;
+ int comment_padding;
int with_skeleton;
int quiet;
int rawmode;
@@ -122,6 +123,7 @@ typedef struct
int endianness;
int resamplefreq;
int copy_comments;
+ int comment_padding;
int with_skeleton;
/* Various bitrate/quality options */
diff --git a/oggenc/oggenc.c b/oggenc/oggenc.c
index 345b92c..8f3e332 100644
--- a/oggenc/oggenc.c
+++ b/oggenc/oggenc.c
@@ -39,6 +39,7 @@ struct option long_options[] = {
{"quiet",0,0,'Q'},
{"help",0,0,'h'},
{"skeleton",no_argument,NULL, 'k'},
+ {"comment-padding",2,0,0},
{"comment",1,0,'c'},
{"artist",1,0,'a'},
{"album",1,0,'l'},
@@ -87,15 +88,25 @@ int main(int argc, char **argv)
{
/* Default values */
oe_options opt = {
+ /* title, artist, album, comments, tracknum */
NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0,
+ /* dates, genre, lyrics, lyrics_language, advopt */
NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0,
- 1, 0, 0, 0,
+ /* copy_comments, comment_padding, with_skeleton, quiet, rawmode */
+ 1, -1, 0, 0, 0,
+ /* raw_{samplesize, samplerate, channels, endianness} */
16,44100,2, 0,
+ /* namefmt{, _remove, _replace} */
NULL, DEFAULT_NAMEFMT_REMOVE, DEFAULT_NAMEFMT_REPLACE,
+ /* outfile */
NULL,
+ /* managed, min_bitrate, nominal_bitrate, max_bitrate */
0, -1,-1,-1,
+ /* quality, quality_set */
.3,-1,
+ /* resamplefreq, downmix, scale */
0,0,0.f,
+ /* serial, skeleton_serial, kate_serial, fixedserial, ignorelength */
0, 0, 0, 0, 0};
int i;
@@ -178,6 +189,7 @@ int main(int argc, char **argv)
enc_opts.error = encode_error;
enc_opts.comments = &vc;
enc_opts.copy_comments = opt.copy_comments;
+ enc_opts.comment_padding = opt.comment_padding;
enc_opts.with_skeleton = opt.with_skeleton;
enc_opts.ignorelength = opt.ignorelength;
@@ -538,6 +550,11 @@ static void usage(void)
" Default settings for the above two arguments are platform\n"
" specific.\n"));
fprintf(stdout, _(
+ " --comment-padding=n Pad the comment packet to a multiple of n bytes.\n"
+ " Specify 0 to disable padding, or 1 to force it using an\n"
+ " automatically-determined amount. By default padding is\n"
+ " automatic, but disabled for short input files.\n"));
+ fprintf(stdout, _(
" --utf8 Tells oggenc that the command line parameters date, title,\n"
" album, artist, genre, and comment are already in UTF-8.\n"
" On Windows, this switch applies to file names too.\n"
@@ -699,6 +716,19 @@ static void parse_options(int argc, char **argv, oe_options *opt)
if(!strcmp(long_options[option_index].name, "skeleton")) {
opt->with_skeleton = 1;
}
+ else if(!strcmp(long_options[option_index].name,
+ "comment-padding")) {
+ if(optarg)
+ {
+ if(sscanf(optarg, "%d", &opt->comment_padding) != 1) {
+ fprintf(stderr, _("WARNING: Couldn't parse"
+ " comment-padding argument \"%s\"\n"),
+ optarg);
+ opt->comment_padding = -1;
+ }
+ }
+ else opt->comment_padding = 1;
+ }
else if(!strcmp(long_options[option_index].name, "managed")) {
if(!opt->managed){
if(!opt->quiet)
vcomment-pad-2.diff
(text/x-diff, 45.4 KB)
diff --git a/vorbiscomment/vcedit.c b/vorbiscomment/vcedit.c
index b46dfb6..82ca5eb 100644
--- a/vorbiscomment/vcedit.c
+++ b/vorbiscomment/vcedit.c
@@ -9,25 +9,6 @@
* last modified: $Id: vcedit.c,v 1.23 2003/09/03 07:58:05 calc Exp $
*/
-/* Handle muxed streams and the Vorbis renormalization without having
- * to understand remuxing:
- * Linked list of buffers (buffer_chain). Start a link and whenever
- * you encounter an unknown page from the current stream (ie we found
- * its bos in the bos section) push it onto the current buffer. Whenever
- * you encounter the stream being renormalized create a new link in the
- * chain.
- * On writing, write the contents of the first link before every Vorbis
- * page written, and move to the next link. Assuming the Vorbis pages
- * in match vorbis pages out, the order of pages from different logical
- * streams will be unchanged.
- * Special case: header. After writing the vorbis headers, and before
- * starting renormalization, flush accumulated links (takes care of
- * situations where number of secondary vorbis header pages changes due
- * to remuxing. Similarly flush links at the end of renormalization
- * and before the start of the next chain is written.
- *
- */
-
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
@@ -47,119 +28,6 @@
#define CHUNKSIZE 4096
#define BUFFERCHUNK CHUNKSIZE
-/* Helper function, shouldn't need to call directly */
-static int page_buffer_push(vcedit_buffer_chain *bufferlink, ogg_page *og) {
- int result=0;
- char *tmp;
- vcedit_page_buffer *buffer;
-
- buffer = &bufferlink->buffer;
- tmp = realloc(buffer->data,
- buffer->data_len + og->header_len + og->body_len);
- if(tmp) {
- buffer->data = tmp;
- memcpy(buffer->data + buffer->data_len, og->header,
- og->header_len);
- buffer->data_len += og->header_len;
- memcpy(buffer->data + buffer->data_len, og->body,
- og->body_len);
- result = 1;
- buffer->data_len += og->body_len;
- } else {
- result = -1;
- }
-
- return result;
-}
-
-/* Write and free the first link using callbacks */
-static int buffer_chain_writelink(vcedit_state *state, void *out) {
- int result = 0;
- vcedit_buffer_chain *tmpchain;
- vcedit_page_buffer *tmpbuffer;
-
- tmpchain = state->sidebuf;
- tmpbuffer = &tmpchain->buffer;
- if(tmpbuffer->data_len)
- {
- if(state->write(tmpbuffer->data,1,tmpbuffer->data_len, out) !=
- (size_t) tmpbuffer->data_len)
- result = -1;
- else
- result = 1;
- }
-
- free(tmpbuffer->data);
- state->sidebuf = tmpchain->next;
- free(tmpchain);
- return result;
-}
-
-
-static int buffer_chain_newlink(vcedit_state *state) {
- int result = 1;
- vcedit_buffer_chain *bufferlink;
-
- if(!state->sidebuf) {
- state->sidebuf = malloc (sizeof *state->sidebuf);
- if(state->sidebuf) {
- bufferlink = state->sidebuf;
- } else {
- result = -1;
- }
- } else {
- bufferlink=state->sidebuf;
- while(bufferlink->next) {
- bufferlink = bufferlink->next;
- }
- bufferlink->next = malloc (sizeof *bufferlink->next);
- if(bufferlink->next) {
- bufferlink = bufferlink->next;
- } else {
- result = -1;
- }
- }
-
- if(result > 0 ) {
- bufferlink->next = 0;
- bufferlink->buffer.data = 0;
- bufferlink->buffer.data_len = 0;
- }
- else
- state->lasterror =
- _("Couldn't get enough memory for input buffering.");
-
- return result;
-}
-
-
-/* Push page onto the end of the buffer chain */
-static int buffer_chain_push(vcedit_state *state, ogg_page *og) {
- /* If there is no sidebuffer yet we need to create one, otherwise
- * traverse to the last buffer and push the new page onto it. */
- int result=1;
- vcedit_buffer_chain *bufferlink;
- if(!state->sidebuf) {
- result = buffer_chain_newlink(state);
- }
-
- if(result > 0) {
- bufferlink = state->sidebuf;
- while(bufferlink->next) {
- bufferlink = bufferlink->next;
- }
- result = page_buffer_push(bufferlink, og);
- }
-
- if(result < 0)
- state->lasterror =
- _("Couldn't get enough memory for input buffering.");
-
- return result;
-}
-
-
-
static int vcedit_supported_stream(vcedit_state *state, ogg_page *og) {
ogg_stream_state os;
vorbis_info vi;
@@ -203,75 +71,13 @@ static int vcedit_supported_stream(vcedit_state *state, ogg_page *og) {
return result;
}
-
-static int vcedit_contains_serial (vcedit_state *state, int serialno) {
- int result = 0;
- size_t count;
- for( count=0; count < state->serials.streams_len; count++ ) {
- if ( *(state->serials.streams + count ) == serialno )
- result = 1;
- }
-
- return result;
-}
-
-
-static int vcedit_add_serial (vcedit_state *state, long serial) {
- int result = 0;
- long *tmp;
-
-
- if( vcedit_contains_serial(state, serial) )
- {
- result = 1;
- } else {
- tmp = realloc(state->serials.streams,
- (state->serials.streams_len + 1) * sizeof *tmp);
- if(tmp) {
- state->serials.streams = tmp;
- *(state->serials.streams +
- state->serials.streams_len) = serial;
- state->serials.streams_len += 1;
- result = 1;
- } else {
- state->lasterror =
- _("Couldn't get enough memory to register new stream serial number.");
- result = -1;
- }
- }
- return result;
-}
-
-
-/* For the benefit of the secondary header read only. Quietly creates
- * newlinks and pushes pages onto the buffer in the right way */
-static int vcedit_target_pageout (vcedit_state *state, ogg_page *og) {
- int result = 0;
- int pageout_result;
- pageout_result = ogg_sync_pageout(state->oy, og);
- if(pageout_result > 0)
- {
- if(state->serial == ogg_page_serialno(og))
- result = buffer_chain_newlink(state);
- else
- result = buffer_chain_push(state, og);
- } else if (pageout_result < 0) {
- /* Vorbis comment traditionally ignores the not-synced
- * error from pageout, so give it a different code. */
- result = -2;
- }
- return result;
-}
-
-
/* (I'm paranoid about memset(x,0,len) not giving null pointers */
vcedit_state *vcedit_new_state(void) {
vcedit_state *state = malloc(sizeof(vcedit_state));
if(state) {
memset(state, 0, sizeof(vcedit_state));
- state->sidebuf = 0;
- state->serials.streams = 0;
- state->serials.streams_len = 0;
+ state->saved_pages = NULL;
+ state->padding_blocksize = -1;
}
return state;
}
@@ -284,6 +90,10 @@ vorbis_comment *vcedit_comments(vcedit_state *state) {
return state->vc;
}
+void vcedit_set_padding(vcedit_state *state, int padding_blocksize) {
+ state->padding_blocksize = padding_blocksize;
+}
+
static void vcedit_clear_internals(vcedit_state *state) {
char *tmp;
if(state->vc) {
@@ -298,22 +108,8 @@ static void vcedit_clear_internals(vcedit_state *state) {
ogg_sync_clear(state->oy);
free(state->oy);
}
- if(state->serials.streams_len) {
- free(state->serials.streams);
- state->serials.streams_len = 0;
- state->serials.streams = 0;
- }
- while(state->sidebuf) {
- vcedit_buffer_chain *tmpbuffer;
- tmpbuffer = state->sidebuf;
- state->sidebuf = tmpbuffer->next;
- free(tmpbuffer->buffer.data);
- free(tmpbuffer);
- }
if(state->vendor)
free(state->vendor);
- if(state->mainbuf)
- free(state->mainbuf);
if(state->bookbuf)
free(state->bookbuf);
if(state->vi) {
@@ -321,6 +117,15 @@ static void vcedit_clear_internals(vcedit_state *state) {
free(state->vi);
}
+ while(state->saved_pages != NULL)
+ {
+ saved_page *tmp = state->saved_pages;
+ state->saved_pages = tmp->next;
+ free(tmp->page.header);
+ free(tmp->page.body);
+ free(tmp);
+ }
+
tmp = state->lasterror;
memset(state, 0, sizeof(*state));
state->lasterror = tmp;
@@ -335,8 +140,9 @@ void vcedit_clear(vcedit_state *state)
}
}
-/* Next two functions pulled straight from libvorbis, apart from one change
+/* Next two functions pulled straight from libvorbis, apart from these changes:
* - we don't want to overwrite the vendor string.
+ * - padding can be added to the end of the comment.
*/
static void _v_writestring(oggpack_buffer *o,char *s, int len)
{
@@ -346,8 +152,10 @@ static void _v_writestring(oggpack_buffer *o,char *s, int len)
}
}
-static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op)
+static int _commentheader_out(vorbis_comment *vc, char *vendor,
+ ogg_packet *op, int padding_blocksize)
{
+ long size, padding = 0;
oggpack_buffer opb;
oggpack_writeinit(&opb);
@@ -376,10 +184,20 @@ static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op)
}
oggpack_write(&opb,1,1);
- op->packet = malloc(oggpack_bytes(&opb));
- memcpy(op->packet, opb.buffer, oggpack_bytes(&opb));
+ size = oggpack_bytes(&opb);
+ if(padding_blocksize)
+ {
+ const int min_padding = padding_blocksize / 2;
+
+ padding = size + (padding_blocksize - 1) + min_padding;
+ padding -= (padding % padding_blocksize) + size;
+ }
+
+ op->packet = malloc(size + padding);
+ memcpy(op->packet, opb.buffer, size);
+ memset(op->packet + size, 0, padding);
- op->bytes=oggpack_bytes(&opb);
+ op->bytes=size+padding;
op->b_o_s=0;
op->e_o_s=0;
op->granulepos=0;
@@ -388,281 +206,347 @@ static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op)
return 0;
}
-static int _blocksize(vcedit_state *s, ogg_packet *p)
+/**
+ * Fetches the next page, without reading much more than max_read bytes
+ * of input. If max_read is -1, there is no limit.
+ *
+ * Returns 1 on success, or 0 if there wasn't enough data for a page
+ * (either due to max_read, or a read error / end of file)
+ */
+static int fetch_next_page(vcedit_state *s, ogg_page *page, int max_read)
+{
+ int read = 0, bytes;
+ char *buffer;
+
+ while(ogg_sync_pageout(s->oy, page) <= 0)
+ {
+ if(read >= max_read && max_read >= 0)
+ return 0;
+
+ buffer = ogg_sync_buffer(s->oy, CHUNKSIZE);
+ bytes = s->read(buffer, 1, CHUNKSIZE, s->in);
+
+ ogg_sync_wrote(s->oy, bytes);
+ s->filepos += bytes;
+ read += bytes;
+
+ if(bytes < CHUNKSIZE)
+ {
+ /* stop reading data, but loop once more for the
+ * ogg_sync_pageout call */
+ max_read = 0;
+ s->read_failed = 1;
+ if(bytes == 0)
+ return 0;
+ }
+ }
+
+ return 1; // got a page
+}
+
+/**
+ * Copies the given page (which must be the last page returned by
+ * fetch_next_page) into a saved_page structure and appends it to
+ * s->saved_pages. last_sp provides a pointer to the last element of that
+ * list, and is updated to point to the new page.
+ *
+ * Returns 0 on success, or -1 if malloc fails.
+ */
+static int save_page(vcedit_state *s, ogg_page *page, saved_page **last_sp)
{
- int this = vorbis_packet_blocksize(s->vi, p);
- int ret = (this + s->prevW)/4;
+ saved_page *sp;
+ unsigned char *header_data, *body_data;
+ long page_endpos;
+
+ /* This assumes the page was just read from the input file. */
+ page_endpos = s->filepos - (s->oy->fill - s->oy->returned);
- if(!s->prevW)
+ sp = malloc(sizeof(saved_page));
+ header_data = malloc(page->header_len);
+ body_data = malloc(page->body_len);
+ if(!sp || !header_data || !body_data)
{
- s->prevW = this;
- return 0;
+ s->lasterror =
+ _("Couldn't allocate memory to save Ogg pages");
+ if(header_data) free(header_data);
+ if(body_data) free(body_data);
+ if(sp) free(sp);
+ return -1;
}
- s->prevW = this;
- return ret;
+ memcpy(header_data, page->header, page->header_len);
+ memcpy(body_data, page->body, page->body_len);
+
+ sp->page.header = header_data;
+ sp->page.header_len = page->header_len;
+ sp->page.body = body_data;
+ sp->page.body_len = page->body_len;
+
+ sp->page_position = page_endpos
+ - page->header_len - page->body_len;
+ sp->comment_len = 0;
+
+ /* add to linked list */
+ sp->next = NULL;
+ if(*last_sp) (*last_sp)->next = sp;
+ else s->saved_pages = sp;
+ (*last_sp) = sp;
+
+ return 0;
}
-static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page)
+static int read_headers(vcedit_state *s, ogg_page *page,
+ saved_page **last_sp)
{
- int result;
- char *buffer;
- int bytes;
- int serialno;
+ int i = 0, first_page = 1;
- result = ogg_stream_packetout(s->os, p);
+ while(i<2)
+ {
+ int result, vorbis_stream;
+ long page_endpos, seg_startpos, seg_endpos;
+ saved_page *sp;
+ ogg_packet packet;
- if(result > 0)
- return 1;
- else {
- while(1) {
- if(s->eosin)
- return 0;
+ /* read an Ogg page */
- while(ogg_sync_pageout(s->oy, page) <= 0)
+ if(first_page)
+ {
+ /* we already have a valid page the first time through */
+ first_page = 0;
+ }
+ else
+ {
+ result = fetch_next_page(s, page, -1);
+ if(result != 1)
{
- 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;
+ s->lasterror = _("EOF or input error"
+ " before end of vorbis headers.");
+ return -1;
+ }
+ }
+
+ /* save the page with some metadata; all pages are saved up to
+ * and including the last Vorbis header */
+
+ if(save_page(s, page, last_sp) < 0)
+ return -1;
+
+ sp = (*last_sp);
+ page_endpos = sp->page_position
+ + sp->page.header_len + sp->page.body_len;
+
+ /** if it's the stream we'll be editing, handle the headers **/
+
+ vorbis_stream = (ogg_page_serialno(page) == s->serial);
+ if(vorbis_stream)
+ ogg_stream_pagein(s->os, page);
+ while(vorbis_stream && i<2)
+ {
+ seg_startpos = page_endpos
+ - s->os->body_fill + s->os->body_returned;
+ if(page_endpos - seg_startpos > page->body_len)
+ {
+ /* startpos is pointing to the Ogg header;
+ * modify it to point to the first byte of the body */
+ seg_startpos = page_endpos - page->body_len;
}
- serialno = ogg_page_serialno(page);
- if(ogg_page_serialno(page) != s->serial)
+ result = ogg_stream_packetout(s->os, &packet);
+ if(result == -1)
{
- if(vcedit_contains_serial(s, serialno)) {
- result = buffer_chain_push(s, page);
- if(result < 0)
- return result;
- }
- else
- {
- s->eosin = 1;
- s->extrapage = 1;
- return 0;
- }
- }
- else
+ s->lasterror = _("Corrupt secondary header.");
+ return -1;
+ }
+
+ if(result == 0)
+ seg_endpos = page_endpos;
+ else /* the packet ends on this page */
+ seg_endpos = page_endpos
+ - s->os->body_fill + s->os->body_returned;
+
+ /* We're reading the comment packet; save information about its
+ * position (even if the packet is still incomplete). */
+ if (i == 0 && (seg_endpos != seg_startpos))
{
- ogg_stream_pagein(s->os, page);
- result = buffer_chain_newlink(s);
- if (result < 0)
- return result;
+ /* This code will only be executed once per page. */
+ sp->comment_len = seg_endpos - seg_startpos;
+ sp->comment_offset = seg_startpos
+ - (sp->page_position + sp->page.header_len);
+ }
+
+ if(result == 0) /* no complete packet yet */
+ break;
+
+ /* got a complete header packet */
- if(ogg_page_eos(page))
- s->eosin = 1;
+ if(vorbis_synthesis_headerin(s->vi, s->vc, &packet) < 0)
+ {
+ s->lasterror = _("Error decoding secondary header.");
+ return -1;
+ }
+
+ if(i==1) /* codebook packet */
+ {
+ s->bookbuf = malloc(packet.bytes);
+ if(!s->bookbuf)
+ {
+ s->lasterror =
+ _("Couldn't allocate memory for codebooks.");
+ return -1;
+ }
+ memcpy(s->bookbuf, packet.packet, packet.bytes);
+ s->booklen = packet.bytes;
}
- result = ogg_stream_packetout(s->os, p);
- if(result > 0)
- return 1;
+ i++;
}
- /* Here == trouble */
- return 0;
}
+
+ if(s->os->body_returned < s->os->body_fill)
+ {
+ /* The Vorbis I spec requires that "the third header packet finishes
+ * the page on which it ends", which is convenient since it would be
+ * annoying to deal with a partial audio packet here.
+ */
+ s->lasterror =
+ _("Non-header data contained in a Vorbis header page.");
+ return -1;
+ }
+
+ return 0;
}
+
+
+
int vcedit_open(vcedit_state *state, FILE *in)
{
- return vcedit_open_callbacks(state, (void *)in,
- (vcedit_read_func)fread, (vcedit_write_func)fwrite);
+ return vcedit_open_callbacks_seekable(state, (void *)in,
+ (vcedit_read_func)fread, (vcedit_write_func)fwrite,
+ (vcedit_seek_func)fseek, (vcedit_tell_func)ftell);
}
int vcedit_open_callbacks(vcedit_state *state, void *in,
vcedit_read_func read_func, vcedit_write_func write_func)
{
+ return vcedit_open_callbacks_seekable(state, in,
+ read_func, write_func, NULL, NULL);
+}
- char *buffer;
- int bytes,i;
- int chunks = 0;
- int read_bos, test_supported, page_pending;
- int have_vorbis;
- ogg_packet *header;
- ogg_packet header_main;
- ogg_packet header_comments;
- ogg_packet header_codebooks;
- ogg_page og;
+int vcedit_open_callbacks_seekable(vcedit_state *state, void *in,
+ vcedit_read_func read_func, vcedit_write_func write_func,
+ vcedit_seek_func seek_func, vcedit_tell_func tell_func)
+{
+
+ int have_vorbis = 0;
+ ogg_page og;
+ int result;
+ saved_page *last_sp = NULL;
state->in = in;
state->read = read_func;
state->write = write_func;
+ state->seek = seek_func;
+ state->tell = tell_func;
+
+ state->filepos = 0;
+ state->filesize = -1;
+ if (seek_func && tell_func) /* determine the file size */
+ {
+ state->filepos = tell_func(in);
+ if (state->filepos != -1 && seek_func(in, 0, 2 /*EOF*/) == 0)
+ {
+ state->filesize = tell_func(in);
+ if (seek_func(in, state->filepos, 0) == -1)
+ {
+ state->lasterror = _("Seek failed.");
+ goto err;
+ }
+ }
+ }
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(bytes<CHUNKSIZE)
- state->lasterror = _("Input truncated or empty.");
- else
- state->lasterror = _("Input is not an Ogg bitstream.");
- goto err;
+ /* Bail if we don't find data in the first 40 kB */
+ result = fetch_next_page(state, &og, 40*1024);
+ if(result < 0)
+ {
+ state->lasterror = _("Input truncated or empty.");
+ goto err;
}
- }
-
- /* BOS loop, starting with a loaded ogg page. */
- if(buffer_chain_newlink(state) < 0)
- goto err;
-
- for( read_bos = 1, have_vorbis = 0 ; read_bos; )
- {
- test_supported = vcedit_supported_stream(state, &og);
- if(test_supported < 0)
- {
- goto err;
- }
- else if (test_supported == 0 || have_vorbis )
- {
- if(vcedit_add_serial ( state, ogg_page_serialno(&og)) < 0)
- goto err;
- if( buffer_chain_push(state, &og) < 0)
- goto err;
- }
- else if (test_supported > 0)
- {
- if(buffer_chain_newlink(state) < 0)
- goto err;
- state->serial = ogg_page_serialno(&og);
- if(vcedit_add_serial ( state, ogg_page_serialno(&og)) < 0)
- goto err;
-
- 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;
- }
- have_vorbis = 1;
- }
- while(1)
- {
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
-
- if(bytes == 0)
- {
- state->lasterror =
- _("EOF before recognised stream.");
- goto err;
- }
-
- ogg_sync_wrote(state->oy, bytes);
-
- if(ogg_sync_pageout(state->oy, &og) == 1)
- break;
- }
- if(!ogg_page_bos(&og)) {
- read_bos = 0;
- page_pending = 1;
- }
- }
-
- if(!state->os) {
- state->lasterror = _("Ogg bitstream does not contain a supported data-type.");
+ else if(result == 0)
+ {
+ state->lasterror = _("Input is not an Ogg bitstream.");
goto err;
}
- state->mainlen = header_main.bytes;
- state->mainbuf = malloc(state->mainlen);
- memcpy(state->mainbuf, header_main.packet, header_main.bytes);
-
- if(ogg_page_serialno(&og) == state->serial)
+ while(ogg_page_bos(&og))
{
- if(buffer_chain_newlink(state) < 0)
+ int test_supported;
+ ogg_packet header_main;
+
+ if(save_page(state, &og, &last_sp) < 0)
goto err;
- }
- else
- {
- if(buffer_chain_push(state, &og) < 0)
+ test_supported = vcedit_supported_stream(state, &og);
+ if(test_supported < 0)
+ {
goto err;
- page_pending = 0;
- }
+ }
+ else if (test_supported > 0 && !have_vorbis)
+ {
+ state->serial = ogg_page_serialno(&og);
- i = 0;
- header = &header_comments;
- while(i<2) {
- while(i<2) {
- int result;
- if(!page_pending)
- result = vcedit_target_pageout(state, &og);
- else
+ 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)
{
- result = 1;
- page_pending = 0;
+ state->lasterror =
+ _("Error reading first page of Ogg bitstream.");
+ goto err;
}
- if(result == 0 || result == -2) break; /* Too little data so far */
- else if(result == -1) goto err;
- else if(result == 1)
+
+ if(ogg_stream_packetout(state->os, &header_main) != 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;
- }
+ 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;
+ }
+
+ have_vorbis = 1;
}
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
- if(bytes == 0 && i < 2)
+ result = fetch_next_page(state, &og, -1);
+ if(result < 0)
{
- state->lasterror = _("EOF before end of Vorbis headers.");
+ state->lasterror = _("EOF before recognised stream.");
goto err;
}
- ogg_sync_wrote(state->oy, bytes);
}
+ if(!state->os) {
+ state->lasterror = _("Ogg bitstream does not contain a supported data-type.");
+ goto err;
+ }
+
+ if (read_headers(state, &og, &last_sp) < 0)
+ goto err;
+
/* Copy the vendor tag */
state->vendor = malloc(strlen(state->vc->vendor) +1);
strcpy(state->vendor, state->vc->vendor);
@@ -675,29 +559,77 @@ err:
return -1;
}
+static void set_ogg_pageno(ogg_page *og, long pageno){
+ og->header[18] = (unsigned char)(pageno & 0xff);
+ og->header[19] = (unsigned char)((pageno>>8) & 0xff);
+ og->header[20] = (unsigned char)((pageno>>16) & 0xff);
+ og->header[21] = (unsigned char)((pageno>>24) & 0xff);
+}
+
+static int write_page(vcedit_state *s, void *out, ogg_page *page)
+{
+ if(s->write(page->header, 1, page->header_len, out)
+ != (size_t)page->header_len)
+ goto err;
+
+ if(s->write(page->body, 1, page->body_len, out)
+ != (size_t)page->body_len)
+ goto err;
+
+ return 0;
+err:
+ s->lasterror = _("Error writing stream to output. "
+ "Output stream may be corrupted or truncated.");
+ 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;
+ saved_page *sp;
+ ogg_page og;
+ int result, streams_open = 0, ret;
+ int padblock = state->padding_blocksize;
+
+ /* First, write out the BOS page (containing the main header) for the
+ * stream we're editing, and write out all saved pages from other
+ * streams (in the order they were read). */
+ for(sp = state->saved_pages; sp; sp = sp->next)
+ {
+ ogg_page *page = &sp->page;
+ if(ogg_page_bos(page)) ++streams_open;
+ if(ogg_page_eos(page)) --streams_open;
- ogg_page ogout, ogin;
- ogg_packet op;
- ogg_int64_t granpos = 0;
- int result;
- char *buffer;
- int bytes;
- int needflush=0, needout=0;
+ if(ogg_page_bos(page) || state->serial != ogg_page_serialno(page))
+ {
+ if (write_page(state, out, page) < 0)
+ {
+ ret = -1;
+ goto cleanup;
+ }
+ }
+ }
- state->eosin = 0;
- state->extrapage = 0;
+ /* We stopped saving pages after receiving the complete set of headers
+ * for our Vorbis stream, so now we're at the correct place to write
+ * those headers (the comment and codebook headers). */
- 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;
+ if (padblock == 1 || padblock == -1)
+ {
+ /* no padding size given; try to pick a reasonable default */
+
+ const long bitrate = state->vi ? state->vi->bitrate_nominal : 0;
+ if ((padblock != 1) && (state->filesize < 256*1024))
+ padblock = 0; /* filesize may be unknown (-1) */
+ else if (state->filesize < 256*1024
+ || (bitrate > 0 && bitrate < 96000))
+ padblock = 512;
+ else
+ padblock = 4096;
+ }
+ _commentheader_out(state->vc, state->vendor, &header_comments, padblock);
header_codebooks.bytes = state->booklen;
header_codebooks.packet = state->bookbuf;
@@ -706,176 +638,201 @@ int vcedit_write(vcedit_state *state, void *out)
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)))
+ streamout.b_o_s = 1; /* indicates the BOS page was already written */
+ streamout.pageno = 1; /* page number 0 was the BOS */
+ while((result = ogg_stream_flush(&streamout, &og)))
{
- if(state->sidebuf && buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- 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)
+ if (write_page(state, out, &og) < 0)
+ {
+ ret = -1;
goto cleanup;
+ }
}
- while(state->sidebuf) {
- if(buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- }
- if(buffer_chain_newlink(state) < 0)
- goto cleanup;
-
- while(_fetch_next_packet(state, &op, &ogin))
+ /* All headers are written. Now we just need to copy pages -- but since
+ * we may have added or removed pages in the stream we edited, we'll need
+ * to renumber its pages. */
+ while((result = fetch_next_page(state, &og, -1)) == 1)
{
- int size;
- size = _blocksize(state, &op);
- granpos += size;
+ if(ogg_page_bos(&og)) ++streams_open;
+ if(ogg_page_eos(&og)) --streams_open;
- if(needflush)
+ if(state->serial == ogg_page_serialno(&og))
{
- if(ogg_stream_flush(&streamout, &ogout))
+ if(ogg_page_pageno(&og) != streamout.pageno)
{
- if(state->sidebuf &&
- buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- 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;
+ set_ogg_pageno(&og, streamout.pageno);
+ ogg_page_checksum_set(&og);
}
+ streamout.pageno++;
}
- else if(needout)
- {
- if(ogg_stream_pageout(&streamout, &ogout))
- {
- if(state->sidebuf &&
- buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- 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)
+ if (write_page(state, out, &og) < 0)
{
- op.granulepos = granpos;
- ogg_stream_packetin(&streamout, &op);
+ ret = -1;
+ goto cleanup;
}
- 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;
- }
- }
}
+ ret = 0;
- streamout.e_o_s = 1;
- while(ogg_stream_flush(&streamout, &ogout))
+cleanup:
+ ogg_stream_clear(&streamout);
+
+ /* We don't ogg_packet_clear() this, because the memory was allocated in
+ _commentheader_out(), so we mirror that here */
+ _ogg_free(header_comments.packet);
+
+ free(state->bookbuf);
+ state->bookbuf = NULL;
+
+ if((ret == 0) && streams_open)
{
- if(state->sidebuf && buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- 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;
+ state->lasterror = _("Input file has unclosed streams"
+ " and may be corrupted or truncated.");
+ ret = -1;
}
- if (state->extrapage)
- {
- /* This is the first page of a new chain, get rid of the
- * sidebuffer */
- while(state->sidebuf)
- if(buffer_chain_writelink(state, out) < 0)
- goto cleanup;
- 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;
+ return ret;
+}
+
+/* Updates the comment pages in memory with new content, if there's enough
+ * space for an in-place update. Pads the comment if necessary; if max_padding
+ * is not -1, it specifies the maximum number of padding bytes that are
+ * considered acceptable.
+ *
+ * Returns 1 on success, -1 on failure, or 0 if we can't update the comment
+ * in place (due to a lack of space or the max_padding limit).
+ */
+int vcedit_update_comment_pages(vcedit_state *s, long max_padding)
+{
+ ogg_packet header_comments;
+ saved_page *sp;
+ unsigned char *data;
+ long expected_len = 16, available_len = 0, data_len;
+ int i;
+ int ret = 0; /* error */
+
+ header_comments.packet = NULL;
+
+ expected_len += strlen(s->vendor);
+ expected_len += 4 * s->vc->comments;
+ for(i = 0; i < s->vc->comments; i++){
+ if(s->vc->user_comments[i])
+ expected_len += s->vc->comment_lengths[i];
}
- state->eosin=0; /* clear it, because not all paths to here do */
- while(!state->eosin) /* We reached eos, not eof */
+ for(sp = s->saved_pages; sp; sp = sp->next)
+ available_len += sp->comment_len;
+
+ if(expected_len > available_len)
+ goto cleanup;
+
+ if(available_len - expected_len > max_padding
+ && max_padding >= 0)
+ goto cleanup;
+
+ /* We won't add any padding here, but we'll clear any unused portions
+ * of the packet later. */
+ _commentheader_out(s->vc, s->vendor, &header_comments, 0);
+
+ data = header_comments.packet;
+ data_len = header_comments.bytes;
+ if(data_len > available_len)
+ goto cleanup;
+
+ sp = s->saved_pages;
+ while(data_len)
{
- /* We copy the rest of the stream (other logical streams)
- * through, a page at a time. */
- while(1)
+ unsigned char *dst = sp->page.body + sp->comment_offset;
+ long len = sp->comment_len;
+ if(len > data_len)
+ len = data_len;
+
+ if(len)
{
- 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;
- }
- }
+ memcpy(dst, data, len);
+ memset(dst + len, 0, sp->comment_len - len);
+ ogg_page_checksum_set(&sp->page);
+
+ data += len;
+ data_len -= len;
}
- buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
- bytes = state->read(buffer,1, CHUNKSIZE, state->in);
- ogg_sync_wrote(state->oy, bytes);
- if(bytes == 0)
+ sp = sp->next;
+ }
+
+ while(sp)
+ {
+ long len = sp->comment_len;
+ if(len)
{
- state->eosin = 1;
- break;
+ memset(sp->page.body + sp->comment_offset, 0, sp->comment_len);
+ ogg_page_checksum_set(&sp->page);
}
+ sp = sp->next;
}
-
+ ret = 1; /* success */
cleanup:
- ogg_stream_clear(&streamout);
+ /* We don't ogg_packet_clear() this, because the memory was allocated in
+ _commentheader_out(), so we mirror that here */
+ _ogg_free(header_comments.packet);
+ return ret;
+}
- /* We don't ogg_packet_clear() this, because the memory was allocated in
- _commentheader_out(), so we mirror that here */
- _ogg_free(header_comments.packet);
+/* Overwrites the comment pages in the given file, which must be the same file
+ * opened by vcedit_open (or have the same position and layout for all comment
+ * pages).
+ *
+ * Returns 0 on success, or a negative value on error:
+ * -1 if the output file was partially updated before the error occurred,
+ * -2 if no changes had been made yet.
+ */
+int vcedit_overwrite(vcedit_state *s, void *out)
+{
+ saved_page *sp;
+ long pos = -1;
+ int wrote = 0;
+ ssize_t bytes;
- free(state->mainbuf);
- free(state->bookbuf);
- state->mainbuf = state->bookbuf = NULL;
+ for(sp = s->saved_pages; sp; sp = sp->next)
+ {
+ if(sp->comment_len == 0) continue;
+
+ if(pos != sp->page_position)
+ {
+ if (!s->seek) goto err;
+ if (s->seek(out, sp->page_position, SEEK_SET) == -1)
+ goto err;
+ pos = sp->page_position;
+ }
+
+ bytes = s->write(sp->page.header, 1, sp->page.header_len, out);
+ if(bytes > 0) wrote = 1;
+ pos += bytes;
+ if(bytes != (size_t)sp->page.header_len)
+ goto err;
+
+ bytes = s->write(sp->page.body, 1, sp->page.body_len, out);
+ pos += bytes;
+ if(bytes != (size_t)sp->page.body_len)
+ goto err;
+ }
+ return 0; // success
- if(!state->eosin)
+err:
+ if(wrote)
{
- state->lasterror =
- _("Error writing stream to output. "
- "Output stream may be corrupted or truncated.");
+ s->lasterror = _("Error updating stream in place. "
+ "Output stream may be corrupted.");
return -1;
}
-
- return 0;
+ else
+ {
+ s->lasterror = _("Error updating stream in place. "
+ "No changes were made.");
+ return -2;
+ }
}
diff --git a/vorbiscomment/vcedit.h b/vorbiscomment/vcedit.h
index 173876f..009b95a 100644
--- a/vorbiscomment/vcedit.h
+++ b/vorbiscomment/vcedit.h
@@ -21,11 +21,16 @@ extern "C" {
typedef size_t (*vcedit_read_func)(void *, size_t, size_t, void *);
typedef size_t (*vcedit_write_func)(const void *, size_t, size_t, void *);
+typedef int (*vcedit_seek_func)(void *, long, int);
+typedef long (*vcedit_tell_func)(void *);
-typedef struct {
- long *streams;
- size_t streams_len;
-} vcedit_serial_nos;
+typedef struct saved_page_t {
+ int page_position;
+ ogg_page page;
+ struct saved_page_t *next;
+ int comment_offset;
+ int comment_len;
+} saved_page;
typedef struct {
ogg_sync_state *oy;
@@ -36,30 +41,38 @@ typedef struct {
vcedit_read_func read;
vcedit_write_func write;
+ vcedit_seek_func seek;
+ vcedit_tell_func tell;
void *in;
int serial;
- vcedit_serial_nos serials;
- unsigned char *mainbuf;
unsigned char *bookbuf;
- int mainlen;
int booklen;
char *lasterror;
char *vendor;
- int prevW;
- int extrapage;
- int eosin;
- struct vcedit_buffer_chain *sidebuf;
+
+ int read_failed; /* could be caused by EOF */
+ ogg_int64_t filepos;
+ ogg_int64_t filesize;
+ saved_page *saved_pages;
+
+ int padding_blocksize;
} vcedit_state;
extern vcedit_state * vcedit_new_state(void);
extern void vcedit_clear(vcedit_state *state);
extern vorbis_comment * vcedit_comments(vcedit_state *state);
+extern void vcedit_set_padding(vcedit_state *state, int padding_blocksize);
extern int vcedit_open(vcedit_state *state, FILE *in);
extern int vcedit_open_callbacks(vcedit_state *state, void *in,
vcedit_read_func read_func, vcedit_write_func write_func);
+extern int vcedit_open_callbacks_seekable(vcedit_state *state, void *in,
+ vcedit_read_func read_func, vcedit_write_func write_func,
+ vcedit_seek_func seek_func, vcedit_tell_func tell_func);
extern int vcedit_write(vcedit_state *state, void *out);
extern char * vcedit_error(vcedit_state *state);
+extern int vcedit_update_comment_pages(vcedit_state *state, long max_padding);
+extern int vcedit_overwrite(vcedit_state *state, void *out);
#ifdef __cplusplus
}
diff --git a/vorbiscomment/vcomment.c b/vorbiscomment/vcomment.c
index 3e60903..35ae38c 100644
--- a/vorbiscomment/vcomment.c
+++ b/vorbiscomment/vcomment.c
@@ -40,7 +40,9 @@ struct option long_options[] = {
{"quiet",0,0,'q'}, /* unused */
{"version", 0, 0, 'V'},
{"commentfile",1,0,'c'},
+ {"padding",2,0,1},
{"raw", 0,0,'R'},
+ {"inplace",0,0,'p'},
{NULL,0,0,0}
};
@@ -49,6 +51,8 @@ typedef struct {
/* mode and flags */
int mode;
int raw;
+ int inplace;
+ int padding;
/* file names and handles */
char *infilename, *outfilename;
@@ -74,7 +78,8 @@ int add_comment(char *line, vorbis_comment *vc, int raw);
param_t *new_param(void);
void free_param(param_t *param);
void parse_options(int argc, char *argv[], param_t *param);
-void open_files(param_t *p);
+void open_input(param_t *p);
+void open_output(param_t *p);
void close_files(param_t *p, int output_written);
char *
@@ -162,10 +167,10 @@ read_line (FILE *input)
int main(int argc, char **argv)
{
- vcedit_state *state;
+ vcedit_state *state = NULL;
vorbis_comment *vc;
param_t *param;
- int i;
+ int i, ret, output_written = 0;
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
@@ -177,7 +182,7 @@ int main(int argc, char **argv)
/* take care of opening the requested files */
/* relevent file pointers are returned in the param struct */
- open_files(param);
+ open_input(param);
/* which mode are we in? */
@@ -189,22 +194,18 @@ int main(int argc, char **argv)
{
fprintf(stderr, _("Failed to open file as Vorbis: %s\n"),
vcedit_error(state));
- close_files(param, 0);
- free_param(param);
- vcedit_clear(state);
- return 1;
+ ret = 1;
+ goto cleanup;
}
/* extract and display the comments */
+ open_output(param);
vc = vcedit_comments(state);
print_comments(param->com, vc, param->raw);
/* done */
- vcedit_clear(state);
-
- close_files(param, 0);
- free_param(param);
- return 0;
+ ret = 0;
+ goto cleanup;
}
if (param->mode == MODE_WRITE || param->mode == MODE_APPEND) {
@@ -215,10 +216,8 @@ int main(int argc, char **argv)
{
fprintf(stderr, _("Failed to open file as Vorbis: %s\n"),
vcedit_error(state));
- close_files(param, 0);
- free_param(param);
- vcedit_clear(state);
- return 1;
+ ret = 1;
+ goto cleanup;
}
/* grab and clear the exisiting comments */
@@ -251,29 +250,51 @@ int main(int argc, char **argv)
}
}
- /* write out the modified stream */
- if(vcedit_write(state, param->out) < 0)
+ vcedit_set_padding(state, param->padding);
+ if(param->tempoutfile && param->inplace
+ && vcedit_update_comment_pages(state, 8192) > 0)
{
- fprintf(stderr, _("Failed to write comments to output file: %s\n"),
- vcedit_error(state));
- close_files(param, 0);
- free_param(param);
- vcedit_clear(state);
- return 1;
+ /* ensure the input file isn't deleted in close_files */
+ param->tempoutfile = 0;
+
+ int rv = vcedit_overwrite(state, param->in);
+ if(rv < 0)
+ {
+ fprintf(stderr, _("Failed to update comments in place.\n"));
+ if(rv == -1)
+ fprintf(stderr, _("The file might now be corrupt.\n"));
+ ret = 1;
+ goto cleanup;
+ }
+ }
+ else
+ {
+ /* write out the modified stream */
+ open_output(param);
+ if(vcedit_write(state, param->out) < 0)
+ {
+ fprintf(stderr, _("Failed to write comments to output file: %s\n"),
+ vcedit_error(state));
+ ret = 1;
+ goto cleanup;
+ }
}
/* done */
- vcedit_clear(state);
-
- close_files(param, 1);
- free_param(param);
- return 0;
+ output_written = 1;
+ ret = 0;
+ goto cleanup;
}
/* should never reach this point */
fprintf(stderr, _("no action specified\n"));
- free_param(param);
- return 1;
+ ret = 1;
+
+cleanup:
+ if(state) vcedit_clear(state);
+ close_files(param, output_written);
+ free_param(param);
+ return ret;
}
/**********
@@ -404,6 +425,12 @@ void usage(void)
" When listing, write comments to the specified file.\n"
" When editing, read comments from the specified file.\n"));
printf (_(" -R, --raw Read and write comments in UTF-8\n"));
+ printf (_(" -p, --inplace Overwrite the comments without rewriting the file,\n"
+ " if possible.\n"));
+ printf (_(" --padding=n Pad the comment packet to a multiple of n bytes.\n"
+ " Specify 0 to disable padding, or 1 to force it using\n"
+ " an automatically-determined amount. By default\n"
+ " padding is automatic, but disabled for small files.\n"));
printf ("\n");
printf (_(" -h, --help Display this help\n"));
@@ -451,6 +478,8 @@ param_t *new_param(void)
/* mode and flags */
param->mode = MODE_LIST;
param->raw = 0;
+ param->inplace = 0;
+ param->padding = -1;
/* filenames */
param->infilename = NULL;
@@ -485,13 +514,25 @@ void parse_options(int argc, char *argv[], param_t *param)
setlocale(LC_ALL, "");
- while ((ret = getopt_long(argc, argv, "alwhqVc:t:R",
+ while ((ret = getopt_long(argc, argv, "alwhqVc:t:Rp",
long_options, &option_index)) != -1) {
switch (ret) {
case 0:
fprintf(stderr, _("Internal error parsing command options\n"));
exit(1);
break;
+ case 1: /* --padding */
+ if(optarg)
+ {
+ if(sscanf(optarg, "%d", ¶m->padding) != 1) {
+ fprintf(stderr, _("WARNING: Couldn't parse"
+ " padding argument \"%s\"\n"),
+ optarg);
+ param->padding = -1;
+ }
+ }
+ else param->padding = 1;
+ break;
case 'l':
param->mode = MODE_LIST;
break;
@@ -523,6 +564,9 @@ void parse_options(int argc, char *argv[], param_t *param)
(param->commentcount+1)*sizeof(char *));
param->comments[param->commentcount++] = strdup(optarg);
break;
+ case 'p':
+ param->inplace = 1;
+ break;
default:
usage();
exit(1);
@@ -547,15 +591,21 @@ void parse_options(int argc, char *argv[], param_t *param)
strcpy(param->outfilename, param->infilename);
strcat(param->outfilename, ".vctemp");
}
+ else if(param->inplace)
+ {
+ fprintf(stderr, _("No output filename may be provided"
+ " when using the --inplace option.\n"));
+ exit(1);
+ }
else
param->outfilename = strdup(argv[optind+1]);
}
}
/**********
- open_files()
+ open_input(), open_output()
- This function takes care of opening the appropriate files
+ These functions takes care of opening the appropriate files
based on the mode and filenames in the param structure.
A filename of '-' is interpreted as stdin/out.
@@ -564,14 +614,14 @@ void parse_options(int argc, char *argv[], param_t *param)
***********/
-void open_files(param_t *p)
+void open_input(param_t *p)
{
/* for all modes, open the input file */
if (strncmp(p->infilename,"-",2) == 0) {
p->in = stdin;
} else {
- p->in = fopen(p->infilename, "rb");
+ p->in = fopen(p->infilename, p->inplace ? "r+b" : "rb");
}
if (p->in == NULL) {
fprintf(stderr,
@@ -582,6 +632,30 @@ void open_files(param_t *p)
if (p->mode == MODE_WRITE || p->mode == MODE_APPEND) {
+ /* commentfile is input */
+
+ if ((p->commentfilename == NULL) ||
+ (strncmp(p->commentfilename,"-",2) == 0)) {
+ p->com = stdin;
+ } else {
+ p->com = fopen(p->commentfilename, "r");
+ }
+ if (p->com == NULL) {
+ fprintf(stderr,
+ _("Error opening comment file '%s'.\n"),
+ p->commentfilename);
+ exit(1);
+ }
+
+ }
+
+ /* all done */
+}
+
+void open_output(param_t *p)
+{
+ if (p->mode == MODE_WRITE || p->mode == MODE_APPEND) {
+
/* open output for write mode */
if(!strcmp(p->infilename, p->outfilename)) {
fprintf(stderr, _("Input filename may not be the same as output filename\n"));
@@ -600,21 +674,6 @@ void open_files(param_t *p)
exit(1);
}
- /* commentfile is input */
-
- if ((p->commentfilename == NULL) ||
- (strncmp(p->commentfilename,"-",2) == 0)) {
- p->com = stdin;
- } else {
- p->com = fopen(p->commentfilename, "r");
- }
- if (p->com == NULL) {
- fprintf(stderr,
- _("Error opening comment file '%s'.\n"),
- p->commentfilename);
- exit(1);
- }
-
} else {
/* in list mode, commentfile is output */
signature.asc
(application/pgp-signature, 197 B)
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.9 (GNU/Linux) iEYEARECAAYFAklK10MACgkQ+RZl+46r4TfxRwCff136XuB62ay+kRQGJmx3Hnk1 w2gAn0zAlNKnuMlY84oKZH3J7xGwt94c =ui2l -----END PGP SIGNATURE-----