X-Git-Url: http://git.code-monkey.de/?a=blobdiff_plain;f=ext%2Fvcedit.c;h=f5f493ae9203322130eb3ced4d346e456192518e;hb=d3f963e54c68e2f50751c090e6863b192e911c5a;hp=fd89008587c452b23db169e16c5435cc1a88593f;hpb=e517c533f959a75908b7361b1969146d29cdfa02;p=ruby-vorbistagger.git diff --git a/ext/vcedit.c b/ext/vcedit.c index fd89008..f5f493a 100644 --- a/ext/vcedit.c +++ b/ext/vcedit.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -41,41 +43,70 @@ struct vcedit_state_St { vorbis_info vi; FILE *in; + mode_t file_mode; + bool opened; long serial; - unsigned char *mainbuf; - unsigned char *bookbuf; - int mainlen; - int booklen; + + ogg_packet packet_main; + ogg_packet packet_code_books; + char *vendor; int prevW; - int extrapage; - int eosin; + + bool extra_page; + bool eos; char filename[0]; }; static void -vcedit_state_free (vcedit_state *state) +ogg_packet_init (ogg_packet *p, unsigned char *buf, long len) { - free (state->mainbuf); - free (state->bookbuf); - free (state->vendor); + p->packet = buf; + p->bytes = len; - if (state->in) { - fclose (state->in); - state->in = NULL; + p->b_o_s = p->e_o_s = 0; + p->granulepos = p->packetno = 0; +} + +static bool +ogg_packet_dup_data (ogg_packet *p) +{ + unsigned char *tmp; + + tmp = malloc (p->bytes); + if (!tmp) + return false; + + memcpy (tmp, p->packet, p->bytes); + p->packet = tmp; + + return true; +} + +static void +vcedit_state_free (vcedit_state *s) +{ + free (s->vendor); + + if (s->in) { + fclose (s->in); + s->in = NULL; } - free (state); + free (s); } static bool -vcedit_state_init (vcedit_state *state, const char *filename) +vcedit_state_init (vcedit_state *s, const char *filename) { - state->refcount = 1; + s->refcount = 1; + + ogg_packet_init (&s->packet_main, NULL, 0); + ogg_packet_init (&s->packet_code_books, NULL, 0); - strcpy (state->filename, filename); + strcpy (s->filename, filename); return true; } @@ -83,73 +114,68 @@ vcedit_state_init (vcedit_state *state, const char *filename) vcedit_state * vcedit_state_new (const char *filename) { - vcedit_state *state; + vcedit_state *s; size_t len; len = strlen (filename); if (len > PATH_MAX) return NULL; - state = malloc (sizeof (vcedit_state) + len + 1); - if (!state) + s= malloc (sizeof (vcedit_state) + len + 1); + if (!s) return NULL; - memset (state, 0, sizeof (vcedit_state)); + memset (s, 0, sizeof (vcedit_state)); - if (!vcedit_state_init (state, filename)) { - vcedit_state_free (state); + if (!vcedit_state_init (s, filename)) { + vcedit_state_free (s); return NULL; } - return state; + return s; } vorbis_comment * -vcedit_comments (vcedit_state *state) +vcedit_comments (vcedit_state *s) { - return state->opened ? &state->vc : NULL; + return s->opened ? &s->vc : NULL; } static void -vcedit_clear_internals (vcedit_state *state) +vcedit_clear_internals (vcedit_state *s) { - ogg_stream_clear (&state->os); - ogg_sync_clear (&state->oy); - - vorbis_info_clear (&state->vi); - vorbis_comment_clear (&state->vc); + ogg_stream_clear (&s->os); + ogg_sync_clear (&s->oy); - free (state->vendor); - state->vendor = NULL; + vorbis_info_clear (&s->vi); + vorbis_comment_clear (&s->vc); - free (state->mainbuf); - state->mainbuf = NULL; - state->mainlen = 0; + free (s->vendor); + s->vendor = NULL; - free (state->bookbuf); - state->bookbuf = NULL; - state->booklen = 0; + ogg_packet_clear (&s->packet_main); + ogg_packet_clear (&s->packet_code_books); - state->serial = 0; - state->opened = false; + s->serial = 0; + s->opened = false; } void -vcedit_state_ref (vcedit_state *state) +vcedit_state_ref (vcedit_state *s) { - state->refcount++; + s->refcount++; } void -vcedit_state_unref (vcedit_state *state) +vcedit_state_unref (vcedit_state *s) { - if (--state->refcount) + if (--s->refcount) return; - if (state->opened) - vcedit_clear_internals (state); + if (s->opened) + vcedit_clear_internals (s); - vcedit_state_free (state); + vcedit_state_free (s); } /* Next two functions pulled straight from libvorbis, apart from one change @@ -158,17 +184,18 @@ vcedit_state_unref (vcedit_state *state) static void _v_writestring (oggpack_buffer *o, char *s, int len) { - while (len--) { + while (len--) oggpack_write (o, *s++, 8); - } } -static int -_commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op) +static bool +write_comments (vcedit_state *s, ogg_packet *packet) { + oggpack_buffer opb; + size_t len; int i; - oggpack_buffer opb; + ogg_packet_init (packet, NULL, 0); oggpack_writeinit (&opb); @@ -177,35 +204,36 @@ _commentheader_out (vorbis_comment *vc, char *vendor, ogg_packet *op) _v_writestring (&opb, "vorbis", 6); /* vendor */ - oggpack_write (&opb, strlen (vendor), 32); - _v_writestring (&opb, vendor, strlen (vendor)); + len = strlen (s->vendor); + oggpack_write (&opb, len, 32); + _v_writestring (&opb, s->vendor, len); /* comments */ - oggpack_write (&opb, vc->comments, 32); + oggpack_write (&opb, s->vc.comments, 32); - for (i = 0; i < vc->comments; i++) { - if (!vc->user_comments[i]) + for (i = 0; i < s->vc.comments; i++) { + if (!s->vc.user_comments[i]) oggpack_write (&opb, 0, 32); else { - oggpack_write (&opb, vc->comment_lengths[i], 32); - _v_writestring (&opb, vc->user_comments[i], - vc->comment_lengths[i]); + oggpack_write (&opb, s->vc.comment_lengths[i], 32); + _v_writestring (&opb, s->vc.user_comments[i], + s->vc.comment_lengths[i]); } } oggpack_write (&opb, 1, 1); - op->packet = _ogg_malloc (oggpack_bytes (&opb)); - memcpy (op->packet, opb.buffer, oggpack_bytes (&opb)); + packet->bytes = oggpack_bytes (&opb); - op->bytes = oggpack_bytes (&opb); - op->b_o_s = 0; - op->e_o_s = 0; - op->granulepos = 0; + packet->packet = _ogg_malloc (packet->bytes); + if (!packet->packet) + return false; + + memcpy (packet->packet, opb.buffer, packet->bytes); oggpack_writeclear (&opb); - return 0; + return true; } static int @@ -230,27 +258,27 @@ _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page) int result, bytes; result = ogg_stream_packetout (&s->os, p); - - if (result > 0) + if (result == 1) return 1; - if (s->eosin) + if (s->eos) return 0; - while (ogg_sync_pageout (&s->oy, page) <= 0) { + while (ogg_sync_pageout (&s->oy, page) != 1) { buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE); bytes = fread (buffer, 1, CHUNKSIZE, s->in); ogg_sync_wrote (&s->oy, bytes); - if (!bytes) + if (!bytes && (feof (s->in) || ferror (s->in))) return 0; } if (ogg_page_eos (page)) - s->eosin = 1; + s->eos = true; else if (ogg_page_serialno (page) != s->serial) { - s->eosin = 1; - s->extrapage = 1; + s->eos = true; + s->extra_page = true; + return 0; } @@ -260,320 +288,302 @@ _fetch_next_packet (vcedit_state *s, ogg_packet *p, ogg_page *page) } vcedit_error -vcedit_open (vcedit_state *state) +vcedit_open (vcedit_state *s) { vcedit_error ret; + ogg_packet packet_comments, *header = &packet_comments; + ogg_page page; + struct stat st; char *buffer; - int bytes, i; - int chunks = 0; - ogg_packet *header; - ogg_packet header_main, header_comments, header_codebooks; - ogg_page og; - - state->in = fopen (state->filename, "rb"); - if (!state->in) - return VCEDIT_ERR_OPEN; - - ogg_sync_init (&state->oy); + size_t bytes, total = 0; + int i = 0; - while (1) { - buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE); - bytes = fread (buffer, 1, CHUNKSIZE, state->in); + s->in = fopen (s->filename, "rb"); + if (!s->in) + return VCEDIT_ERR_OPEN; - ogg_sync_wrote (&state->oy, bytes); + s->file_mode = stat (s->filename, &st) ? 0664 : st.st_mode; - if (ogg_sync_pageout (&state->oy, &og) == 1) - break; + ogg_sync_init (&s->oy); + do { /* Bail if we don't find data in the first 40 kB */ - if (chunks++ >= 10) { - ogg_sync_clear (&state->oy); + if (feof (s->in) || ferror (s->in) || total >= (CHUNKSIZE * 10)) { + ogg_sync_clear (&s->oy); return VCEDIT_ERR_INVAL; } - } - state->serial = ogg_page_serialno (&og); + buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE); + + bytes = fread (buffer, 1, CHUNKSIZE, s->in); + total += bytes; + + ogg_sync_wrote (&s->oy, bytes); + } while (ogg_sync_pageout (&s->oy, &page) != 1); - ogg_stream_init (&state->os, state->serial); - vorbis_info_init (&state->vi); - vorbis_comment_init (&state->vc); + s->serial = ogg_page_serialno (&page); - if (ogg_stream_pagein (&state->os, &og) < 0) { + ogg_stream_init (&s->os, s->serial); + vorbis_info_init (&s->vi); + vorbis_comment_init (&s->vc); + + if (ogg_stream_pagein (&s->os, &page) < 0) { ret = VCEDIT_ERR_INVAL; goto err; } - if (ogg_stream_packetout (&state->os, &header_main) != 1) { + if (ogg_stream_packetout (&s->os, &s->packet_main) != 1) { ret = VCEDIT_ERR_INVAL; goto err; } - if (vorbis_synthesis_headerin (&state->vi, &state->vc, &header_main) < 0) { + if (!ogg_packet_dup_data (&s->packet_main)) { + s->packet_main.packet = NULL; ret = VCEDIT_ERR_INVAL; goto err; } - state->mainlen = header_main.bytes; - state->mainbuf = malloc (state->mainlen); - memcpy (state->mainbuf, header_main.packet, header_main.bytes); + if (vorbis_synthesis_headerin (&s->vi, &s->vc, &s->packet_main) < 0) { + ret = VCEDIT_ERR_INVAL; + goto err; + } - i = 0; - header = &header_comments; + ogg_packet_init (&packet_comments, NULL, 0); while (i < 2) { + if (feof (s->in) || ferror (s->in)) { + ret = VCEDIT_ERR_INVAL; + goto err; + } + while (i < 2) { - int result = ogg_sync_pageout (&state->oy, &og); + int result; + result = ogg_sync_pageout (&s->oy, &page); if (!result) break; /* Too little data so far */ - if (result == 1) { - ogg_stream_pagein (&state->os, &og); + if (result != 1) + continue; - while (i < 2) { - result = ogg_stream_packetout (&state->os, header); + ogg_stream_pagein (&s->os, &page); - if (!result) - break; + while (i < 2) { + result = ogg_stream_packetout (&s->os, header); + if (!result) + break; - if (result == -1) { - ret = VCEDIT_ERR_INVAL; - goto err; - } + if (result != 1) { + ret = VCEDIT_ERR_INVAL; + goto err; + } - vorbis_synthesis_headerin (&state->vi, &state->vc, header); + if (i++ == 1 && !ogg_packet_dup_data (header)) { + header->packet = NULL; + ret = VCEDIT_ERR_INVAL; + goto err; + } - if (i == 1) { - state->booklen = header->bytes; - state->bookbuf = malloc (state->booklen); - memcpy (state->bookbuf, header->packet, header->bytes); - } + vorbis_synthesis_headerin (&s->vi, &s->vc, header); - i++; - header = &header_codebooks; - } + header = &s->packet_code_books; } } - buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE); - bytes = fread (buffer, 1, CHUNKSIZE, state->in); - - if (bytes == 0 && i < 2) { - ret = VCEDIT_ERR_INVAL; - goto err; - } + buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE); - ogg_sync_wrote (&state->oy, bytes); + bytes = fread (buffer, 1, CHUNKSIZE, s->in); + ogg_sync_wrote (&s->oy, bytes); } /* Copy the vendor tag */ - state->vendor = strdup (state->vc.vendor); + s->vendor = strdup (s->vc.vendor); /* Headers are done! */ - state->opened = true; + s->opened = true; return VCEDIT_ERR_SUCCESS; err: - vcedit_clear_internals (state); + vcedit_clear_internals (s); return ret; } +static bool +write_data (const void *buf, size_t size, size_t nmemb, FILE *stream) +{ + while (nmemb > 0) { + size_t w; + + w = fwrite (buf, size, nmemb, stream); + if (!w && ferror (stream)) + return false; + + nmemb -= w; + buf += size * w; + } + + return true; +} + +static bool +write_page (FILE *f, ogg_page *p) +{ + return write_data (p->header, 1, p->header_len, f) && + write_data (p->body, 1, p->body_len, f); +} + vcedit_error -vcedit_write (vcedit_state *state) +vcedit_write (vcedit_state *s) { - ogg_stream_state streamout; - ogg_packet header_main, header_comments, header_codebooks, op; - ogg_page ogout, ogin; + ogg_stream_state stream; + ogg_packet packet; + ogg_page page_out, page_in; ogg_int64_t granpos = 0; FILE *out; char *buffer, tmpfile[PATH_MAX]; - int s, result, bytes, needflush = 0, needout = 0; - size_t tmp; + bool success = false, need_flush = false, need_out = false; + int fd, result, bytes; - if (!state->opened) + if (!s->opened) return VCEDIT_ERR_INVAL; - strcpy (tmpfile, state->filename); + strcpy (tmpfile, s->filename); strcat (tmpfile, ".XXXXXX"); - s = mkstemp (tmpfile); - if (s == -1) + fd = mkstemp (tmpfile); + if (fd == -1) return VCEDIT_ERR_TMPFILE; - out = fdopen (s, "wb"); + out = fdopen (fd, "wb"); if (!out) { unlink (tmpfile); - close (s); + close (fd); return VCEDIT_ERR_TMPFILE; } - state->prevW = state->extrapage = state->eosin = 0; + s->prevW = 0; + s->extra_page = s->eos = false; - 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; + ogg_stream_init (&stream, s->serial); - 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; + /* write "main" packet */ + s->packet_main.b_o_s = 1; + ogg_stream_packetin (&stream, &s->packet_main); + s->packet_main.b_o_s = 0; - ogg_stream_init (&streamout, state->serial); + /* prepare and write comments */ + if (!write_comments (s, &packet)) { + ogg_stream_clear (&stream); + unlink (tmpfile); + fclose (out); - _commentheader_out (&state->vc, state->vendor, &header_comments); + return VCEDIT_ERR_INVAL; + } - ogg_stream_packetin (&streamout, &header_main); - ogg_stream_packetin (&streamout, &header_comments); - ogg_stream_packetin (&streamout, &header_codebooks); + ogg_stream_packetin (&stream, &packet); + ogg_packet_clear (&packet); - while ((result = ogg_stream_flush (&streamout, &ogout))) { - tmp = fwrite (ogout.header, 1, ogout.header_len, out); - if (tmp != (size_t) ogout.header_len) - goto cleanup; + /* write codebooks */ + ogg_stream_packetin (&stream, &s->packet_code_books); - tmp = fwrite (ogout.body, 1, ogout.body_len, out); - if (tmp != (size_t) ogout.body_len) + while (ogg_stream_flush (&stream, &page_out)) + if (!write_page (out, &page_out)) goto cleanup; - } - while (_fetch_next_packet (state, &op, &ogin)) { + while (_fetch_next_packet (s, &packet, &page_in)) { + bool write = false; int size; - size = _blocksize (state, &op); + size = _blocksize (s, &packet); granpos += size; - if (needflush) { - if (ogg_stream_flush (&streamout, &ogout)) { - tmp = fwrite (ogout.header, 1, ogout.header_len, out); - if (tmp != (size_t) ogout.header_len) - goto cleanup; - - tmp = fwrite (ogout.body, 1, ogout.body_len, out); - if (tmp != (size_t) ogout.body_len) - goto cleanup; - } - } else if (needout) { - if (ogg_stream_pageout (&streamout, &ogout)) { - tmp = fwrite (ogout.header, 1, ogout.header_len, out); - if (tmp != (size_t) ogout.header_len) - goto cleanup; - - tmp = fwrite (ogout.body, 1, ogout.body_len, out); - if (tmp != (size_t) ogout.body_len) - goto cleanup; - } + if (need_flush) { + write = ogg_stream_flush (&stream, &page_out); + } else if (need_out) { + write = ogg_stream_pageout (&stream, &page_out); } - needflush = needout = 0; + if (write && !write_page (out, &page_out)) + goto cleanup; + + need_flush = need_out = false; - if (op.granulepos == -1) { - op.granulepos = granpos; - ogg_stream_packetin (&streamout, &op); + if (packet.granulepos == -1) { + packet.granulepos = granpos; + ogg_stream_packetin (&stream, &packet); } 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; + if (granpos > packet.granulepos) { + granpos = packet.granulepos; + ogg_stream_packetin (&stream, &packet); + need_flush = true; } else { - ogg_stream_packetin (&streamout, &op); - needout = 1; + ogg_stream_packetin (&stream, &packet); + need_out = true; } } } - streamout.e_o_s = 1; + stream.e_o_s = 1; - while (ogg_stream_flush (&streamout, &ogout)) { - tmp = fwrite (ogout.header, 1, ogout.header_len, out); - if (tmp != (size_t) ogout.header_len) + while (ogg_stream_flush (&stream, &page_out)) + if (!write_page (out, &page_out)) goto cleanup; - tmp = fwrite (ogout.body, 1, ogout.body_len, out); - if (tmp != (size_t) ogout.body_len) - goto cleanup; - } - - if (state->extrapage) { - tmp = fwrite (ogin.header, 1, ogin.header_len, out); - if (tmp != (size_t) ogin.header_len) - goto cleanup; - - tmp = fwrite (ogin.body, 1, ogin.body_len, out); - if (tmp != (size_t) ogin.body_len) - goto cleanup; - } + if (s->extra_page && !write_page (out, &page_in)) + goto cleanup; /* clear it, because not all paths to here do */ - state->eosin = 0; + s->eos = false; - while (!state->eosin) { /* We reached eos, not eof */ + do { /* We copy the rest of the stream (other logical streams) * through, a page at a time. */ - while (1) { - result = ogg_sync_pageout (&state->oy, &ogout); - - if (!result) - break; - - if (result >= 0) { - /* Don't bother going through the rest, we can just - * write the page out now - */ - tmp = fwrite (ogout.header, 1, ogout.header_len, out); - if (tmp != (size_t) ogout.header_len) - goto cleanup; - - tmp = fwrite (ogout.body, 1, ogout.body_len, out); - if (tmp != (size_t) ogout.body_len) - goto cleanup; - } + while ((result = ogg_sync_pageout (&s->oy, &page_out))) { + /* Don't bother going through the rest, we can just + * write the page out now + */ + if (result == 1 && !write_page (out, &page_out)) + goto cleanup; } - buffer = ogg_sync_buffer (&state->oy, CHUNKSIZE); - bytes = fread (buffer, 1, CHUNKSIZE, state->in); - ogg_sync_wrote (&state->oy, bytes); - - if (!bytes) { - state->eosin = 1; - break; - } - } + buffer = ogg_sync_buffer (&s->oy, CHUNKSIZE); + bytes = fread (buffer, 1, CHUNKSIZE, s->in); + ogg_sync_wrote (&s->oy, bytes); - fclose (out); - fclose (state->in); + if (ferror (s->in)) + goto cleanup; + } while (bytes || !feof (s->in)); - unlink (state->filename); - rename (tmpfile, state->filename); + s->eos = success = true; cleanup: - ogg_stream_clear (&streamout); + fclose (s->in); - /* 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->mainbuf); - free (state->bookbuf); + if (!success) { + unlink (tmpfile); + fclose (out); + } else { + fclose (out); + unlink (s->filename); + rename (tmpfile, s->filename); + chmod (s->filename, s->file_mode); + } - state->mainbuf = state->bookbuf = NULL; + ogg_stream_clear (&stream); - if (!state->eosin) + if (!s->eos) return VCEDIT_ERR_INVAL; - vcedit_clear_internals (state); + vcedit_clear_internals (s); - return (vcedit_open (state) == VCEDIT_ERR_SUCCESS) ? + return (vcedit_open (s) == VCEDIT_ERR_SUCCESS) ? VCEDIT_ERR_SUCCESS : VCEDIT_ERR_REOPEN; }