[xiph-commits] r12967 - in trunk/theora-tools: . theoracomment
giles at svn.xiph.org
giles at svn.xiph.org
Mon May 21 17:25:52 PDT 2007
Author: giles
Date: 2007-05-21 17:25:51 -0700 (Mon, 21 May 2007)
New Revision: 12967
Added:
trunk/theora-tools/theoracomment/
trunk/theora-tools/theoracomment/Makefile.am
trunk/theora-tools/theoracomment/commenter.c
trunk/theora-tools/theoracomment/commenter.h
trunk/theora-tools/theoracomment/theoracomment.c
Modified:
trunk/theora-tools/Makefile.am
trunk/theora-tools/configure.ac
Log:
Theora comment editor, contributed by Daniel Kraft.
Modified: trunk/theora-tools/Makefile.am
===================================================================
--- trunk/theora-tools/Makefile.am 2007-05-21 12:08:40 UTC (rev 12966)
+++ trunk/theora-tools/Makefile.am 2007-05-22 00:25:51 UTC (rev 12967)
@@ -2,7 +2,7 @@
AUTOMAKE_OPTIONS = foreign dist-zip
-SUBDIRS = debian theora123 theora_splayer theoraenc theora_transcoder theora_dumpvideo
+SUBDIRS = debian theora123 theora_splayer theoraenc theora_transcoder theora_dumpvideo theoracomment
EXTRA_DIST = COPYING autogen.sh win32
Modified: trunk/theora-tools/configure.ac
===================================================================
--- trunk/theora-tools/configure.ac 2007-05-21 12:08:40 UTC (rev 12966)
+++ trunk/theora-tools/configure.ac 2007-05-22 00:25:51 UTC (rev 12967)
@@ -132,6 +132,7 @@
theoraenc/Makefile
theora_transcoder/Makefile
theora_dumpvideo/Makefile
+ theoracomment/Makefile
debian/Makefile
])
AC_OUTPUT
Added: trunk/theora-tools/theoracomment/Makefile.am
===================================================================
--- trunk/theora-tools/theoracomment/Makefile.am (rev 0)
+++ trunk/theora-tools/theoracomment/Makefile.am 2007-05-22 00:25:51 UTC (rev 12967)
@@ -0,0 +1,7 @@
+## Process this file with automake to produce Makefile.in
+
+AUTOMAKE_OPTIONS = foreign
+
+bin_PROGRAMS = theoracomment
+
+theoracomment_SOURCES = theoracomment.c commenter.c
Added: trunk/theora-tools/theoracomment/commenter.c
===================================================================
--- trunk/theora-tools/theoracomment/commenter.c (rev 0)
+++ trunk/theora-tools/theoracomment/commenter.c 2007-05-22 00:25:51 UTC (rev 12967)
@@ -0,0 +1,371 @@
+/* XXX: Header comment to come */
+
+#include "commenter.h"
+
+#include <stdlib.h>
+#include <assert.h>
+
+#define BUFFER_SIZE 4096
+#define _(str) str
+
+/* ************************************************************************** */
+/* Constructor and destructor */
+
+TheoraCommenter* newTheoraCommenter(TheoraCommenterInput in,
+ TheoraCommenterOutput out)
+{
+ TheoraCommenter* ret=malloc(sizeof(TheoraCommenter));
+ if(!ret)
+ return NULL;
+
+ ret->input=in;
+ ret->output=out;
+
+ ret->error=NULL;
+
+ ret->syncState=NULL;
+ ret->streamNo=-1;
+ ret->stream=NULL;
+
+ theora_comment_init(&ret->comments);
+ theora_info_init(&ret->info);
+
+ /* Do this last so an eventual destructor-call will not find us invalid */
+ ret->syncState=malloc(sizeof(ogg_stream_state));
+ if(!ret->syncState)
+ {
+ deleteTheoraCommenter(ret);
+ return NULL;
+ }
+ ogg_sync_init(ret->syncState);
+
+ return ret;
+}
+
+void deleteTheoraCommenter(TheoraCommenter* me)
+{
+ if(me->syncState)
+ ogg_sync_destroy(me->syncState);
+ if(me->stream)
+ ogg_stream_destroy(me->stream);
+ theora_comment_clear(&me->comments);
+ theora_info_clear(&me->info);
+ free(me);
+}
+
+/* ************************************************************************** */
+/* Manipulation */
+
+void theoraCommenter_add(TheoraCommenter* me, char* c)
+{
+ theora_comment_add(&me->comments, c);
+}
+
+void theoraCommenter_addTag(TheoraCommenter* me, char* tag, char* val)
+{
+ theora_comment_add_tag(&me->comments, tag, val);
+}
+
+void theoraCommenter_clear(TheoraCommenter* me)
+{
+ /* Free everything except vendor-string */
+ int i;
+ for(i=0; i!=me->comments.comments; ++i)
+ free(me->comments.user_comments[i]);
+ free(me->comments.user_comments);
+ me->comments.user_comments=NULL;
+ free(me->comments.comment_lengths);
+ me->comments.comment_lengths=NULL;
+ me->comments.comments=0;
+}
+
+/* ************************************************************************** */
+/* Higher-level reading */
+
+int theoraCommenter_read(TheoraCommenter* me)
+{
+ ogg_packet packet;
+
+ /* Find the stream */
+ if(theoraCommenter_findTheoraStream(me))
+ return 1;
+
+ /* Read the next packet */
+ while(!ogg_stream_packetout(me->stream, &packet))
+ {
+ ogg_page op;
+ if(theoraCommenter_readTheoraPage(me, &op))
+ return 1;
+ if(ogg_stream_pagein(me->stream, &op))
+ {
+ me->error=_("Error submitting page to stream");
+ return 1;
+ }
+ }
+
+ /* Initialize the comments */
+ if(theora_decode_header(&me->info, &me->comments, &packet))
+ {
+ me->error=_("Error decoding comment header");
+ return 1;
+ }
+
+ return 0;
+}
+
+int theoraCommenter_findTheoraStream(TheoraCommenter* me)
+{
+ ogg_page op;
+
+ assert(!me->stream);
+ assert(me->streamNo==-1);
+
+ /* Find the Theora stream */
+ while(1)
+ {
+ int serialno;
+
+ /* Read the next page */
+ if(theoraCommenter_readPage(me, &op))
+ return 1;
+ serialno=ogg_page_serialno(&op);
+
+ /* If not BOS, there's no Theora at all here */
+ if(!ogg_page_bos(&op))
+ {
+ me->error=_("No Theora stream in file");
+ return 1;
+ }
+
+ /* Initialize the stream for this serial */
+ if(!me->stream)
+ {
+ me->stream=malloc(sizeof(ogg_stream_state));
+ if(!me->stream)
+ {
+ me->error=_("Out of memory");
+ return 1;
+ }
+ } else
+ ogg_stream_clear(me->stream);
+ ogg_stream_init(me->stream, serialno);
+
+ /* Try if this is Theora */
+ {
+ ogg_packet packet;
+
+ if(ogg_stream_pagein(me->stream, &op))
+ {
+ me->error=_("Error submitting page to stream");
+ return 1;
+ }
+ if(ogg_stream_packetout(me->stream, &packet)!=1)
+ {
+ me->error=_("Error getting packet out of stream");
+ return 1;
+ }
+
+ if(!theora_decode_header(&me->info, &me->comments, &packet))
+ {
+ /* Found it. */
+ me->streamNo=serialno;
+ return 0;
+ }
+ }
+ }
+ /* Never reached */
+}
+
+/* ************************************************************************** */
+/* High-level writing */
+
+int theoraCommenter_write(TheoraCommenter* me)
+{
+ ogg_page op;
+ ogg_stream_state outStream; /* This stream is used for output-packetizing */
+ int theoraPackets=0; /* How many theora packets have yet been found */
+
+ assert(me->stream);
+ assert(me->streamNo!=-1);
+
+ /* Reset syncState as we seeked to the beginning */
+ ogg_sync_reset(me->syncState);
+ ogg_stream_reset(me->stream);
+
+ ogg_stream_init(&outStream, me->streamNo);
+ #define THROW_ERROR \
+ { \
+ ogg_stream_clear(&outStream); \
+ return 1; \
+ }
+
+ /* Copy with Theora-packetizing */
+ while(1)
+ {
+ ogg_packet packet;
+
+ /* Read a page in */
+ if(theoraCommenter_readPage(me, &op))
+ break;
+
+ /* If this is a non-BOS-page and we changed the comments, flush the
+ * outStream and continue non-packetizing. */
+ if(!ogg_page_bos(&op) && theoraPackets>=2)
+ {
+ ogg_page pg2;
+ while(1)
+ {
+ if(!ogg_stream_pageout(&outStream, &pg2))
+ if(!ogg_stream_flush(&outStream, &pg2))
+ break;
+ if(theoraCommenter_writePage(me, &pg2))
+ THROW_ERROR
+ }
+ if(theoraCommenter_writePage(me, &op))
+ THROW_ERROR
+ break;
+ }
+
+ /* For Theora, pull it through packetizing */
+ /* Replacing the second one with our new comment packet */
+ if(ogg_page_serialno(&op)==me->streamNo)
+ {
+ if(ogg_stream_pagein(me->stream, &op))
+ {
+ me->error=_("Error submitting page to stream");
+ THROW_ERROR
+ }
+
+ while(ogg_stream_packetout(me->stream, &packet))
+ {
+ int isComment;
+ ++theoraPackets;
+ isComment=(theoraPackets==2);
+
+ /* Replace the packet with our comments */
+ if(isComment)
+ if(theora_encode_comment(&me->comments, &packet))
+ {
+ me->error=_("Error encoding comment-header");
+ THROW_ERROR
+ }
+
+ /* Write the eventually modified packet out */
+ if(ogg_stream_packetin(&outStream, &packet))
+ {
+ me->error=_("Error writing out packet");
+ THROW_ERROR
+ }
+
+ /* Free the packet when it was encoded */
+ if(isComment)
+ ogg_packet_clear(&packet);
+ }
+
+ if(!ogg_stream_flush(&outStream, &op))
+ {
+ me->error=_("Unexpected error, there should be packets to flush!");
+ THROW_ERROR
+ }
+ }
+
+ /* Write the page, modified or not */
+ if(theoraCommenter_writePage(me, &op))
+ {
+ me->error=_("Error writing page out");
+ THROW_ERROR
+ }
+ }
+
+ /* Clean up output stream, no longer needed */
+ ogg_stream_clear(&outStream);
+ #undef THROW_ERROR
+
+ /* Simply copy until the end is reached */
+ while(1)
+ {
+ if(theoraCommenter_readPage(me, &op))
+ break;
+ if(theoraCommenter_writePage(me, &op))
+ {
+ me->error=_("Error writing page out");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* ************************************************************************** */
+/* Low-level io */
+
+int theoraCommenter_readTheoraPage(TheoraCommenter* me, ogg_page* op)
+{
+ assert(me->streamNo!=-1);
+
+ /* Find a page which matches the serial-no */
+ do
+ {
+ if(theoraCommenter_readPage(me, op))
+ return 1;
+ } while(ogg_page_serialno(op)!=me->streamNo);
+
+ return 0;
+}
+
+int theoraCommenter_readPage(TheoraCommenter* me, ogg_page* op)
+{
+ while(ogg_sync_pageout(me->syncState, op)!=1)
+ {
+ char* buf;
+ size_t n;
+
+ buf=ogg_sync_buffer(me->syncState, BUFFER_SIZE);
+ n=me->input(buf, BUFFER_SIZE);
+
+ /* No input available */
+ if(!n)
+ {
+ me->error=_("Need more input");
+ return -1;
+ }
+
+ if(ogg_sync_wrote(me->syncState, n))
+ {
+ me->error=_("Overflowed sync_state-buffer");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int theoraCommenter_writePage(TheoraCommenter* me, ogg_page* op)
+{
+ size_t n;
+
+ /* Is output configured? */
+ if(!me->output)
+ {
+ me->error=_("Can only output when we are in output-mode");
+ return 1;
+ }
+
+ /* Write header */
+ n=me->output(op->header, op->header_len);
+ if(n!=op->header_len)
+ {
+ me->error=_("Error writing output");
+ return 1;
+ }
+
+ /* Write body */
+ n=me->output(op->body, op->body_len);
+ if(n!=op->body_len)
+ {
+ me->error=_("Error writing output");
+ return 1;
+ }
+
+ return 0;
+}
Added: trunk/theora-tools/theoracomment/commenter.h
===================================================================
--- trunk/theora-tools/theoracomment/commenter.h (rev 0)
+++ trunk/theora-tools/theoracomment/commenter.h 2007-05-22 00:25:51 UTC (rev 12967)
@@ -0,0 +1,143 @@
+/* XXX: Header comment to come */
+
+/**
+ * This is the backend-library for theoracomment, defining an object to handle
+ * all comment-modifications.
+ */
+
+#ifndef THEORACOMMENTER_COMMENTER_H
+#define THEORACOMMENTER_COMMENTER_H
+
+#ifdef HAVE_CONFIG_H
+ #include <config.h>
+#endif
+
+#include <stddef.h>
+#include <ogg/ogg.h>
+#include <theora/theora.h>
+
+/**
+ * An input-callback as used by the commenter.
+ * @param buf The buffer to store the data.
+ * @param n How many bytes to read.
+ * @return Number bytes read.
+ */
+typedef size_t (*TheoraCommenterInput)(char* buf, size_t n);
+
+/**
+ * An output-callback used by the commenter.
+ * @param buf The data to write.
+ * @param n How many bytes to write.
+ * @return Number bytes written.
+ */
+typedef size_t (*TheoraCommenterOutput)(char* buf, size_t n);
+
+/**
+ * An object controlling comment-editing for theora-streams.
+ */
+typedef struct
+{
+ /** The input used */
+ TheoraCommenterInput input;
+ /** The output used */
+ TheoraCommenterOutput output;
+
+ /** The libogg-sync-state */
+ ogg_sync_state* syncState;
+
+ /** The serialno of the theora-stream if yet found */
+ int streamNo;
+ /** The theora-stream itself */
+ ogg_stream_state* stream;
+
+ /** The comments found */
+ theora_comment comments;
+ /** The info header */
+ theora_info info;
+
+ /** The last error occured or NULL if none */
+ const char* error;
+} TheoraCommenter;
+
+/**
+ * Create a new TheoraCommenter.
+ * @param in The input to use.
+ * @param out The output to use (may be null if no output is needed)
+ * @return The created TheoraCommenter.
+ */
+TheoraCommenter* newTheoraCommenter(TheoraCommenterInput in,
+ TheoraCommenterOutput out);
+
+/**
+ * Destroy and free a TheoraCommenter.
+ * @param me The commenter to free.
+ */
+void deleteTheoraCommenter(TheoraCommenter* me);
+
+/**
+ * Read in the input and store the found comments.
+ * @param me The this-pointer
+ * @return 0 if it succeeded.
+ */
+int theoraCommenter_read(TheoraCommenter* me);
+
+/**
+ * Write the whole thing out with possibly modified comments; before doing so,
+ * ensure that the input-stream has been seeked back to the very beginning!
+ * @param me this
+ * @return 0 on success
+ */
+int theoraCommenter_write(TheoraCommenter* me);
+
+/**
+ * Clear the comment-structure, i.e., remove all comments but preserve the
+ * vendor-string.
+ */
+void theoraCommenter_clear(TheoraCommenter* me);
+
+/**
+ * Append a comment to the structure. Always succeeds.
+ * @param me this
+ * @param c The comment as "TAG=value"
+ */
+void theoraCommenter_add(TheoraCommenter* me, char* c);
+/**
+ * Append a comment as tag and value, always succeeds.
+ * @param me this
+ * @param tag The comment's tag
+ * @param val The comment's value
+ */
+void theoraCommenter_addTag(TheoraCommenter* me, char* tag, char* val);
+
+/**
+ * Find the Theora stream.
+ * @param me this
+ * @return 0 if success
+ */
+int theoraCommenter_findTheoraStream(TheoraCommenter* me);
+
+/**
+ * Read the next Theora-page in.
+ * @param me this
+ * @param op The page to be filld.
+ * @return 0 on success.
+ */
+int theoraCommenter_readTheoraPage(TheoraCommenter* me, ogg_page* op);
+
+/**
+ * Read a page from the input.
+ * @param me this
+ * @param op The page to be filled.
+ * @return 0 if success.
+ */
+int theoraCommenter_readPage(TheoraCommenter* me, ogg_page* op);
+
+/**
+ * Write a page to the output.
+ * @param me this
+ * @param op The page to be output.
+ * @return 0 on success
+ */
+int theoraCommenter_writePage(TheoraCommenter* me, ogg_page* op);
+
+#endif /* Headerguard */
Added: trunk/theora-tools/theoracomment/theoracomment.c
===================================================================
--- trunk/theora-tools/theoracomment/theoracomment.c (rev 0)
+++ trunk/theora-tools/theoracomment/theoracomment.c 2007-05-22 00:25:51 UTC (rev 12967)
@@ -0,0 +1,603 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <assert.h>
+#include <getopt.h>
+
+#if HAVE_STAT && HAVE_CHMOD
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+/*
+#include "getopt.h"
+#include "utf8.h"
+#include "i18n.h"
+*/
+
+#define _(str) str
+
+#include "commenter.h"
+
+#define utf8_encode(str, dest) str
+#define utf8_decode(str, dest) str
+
+/* getopt format struct */
+struct option long_options[] = {
+ {"list",0,0,'l'},
+ {"append",0,0,'a'},
+ {"tag",0,0,'t'},
+ {"write",0,0,'w'},
+ {"help",0,0,'h'},
+ {"quiet",0,0,'q'}, /* unused */
+ {"version", 0, 0, 'V'},
+ {"commentfile",1,0,'c'},
+ {"raw", 0,0,'R'},
+ {NULL,0,0,0}
+};
+
+/* local parameter storage from parsed options */
+typedef struct {
+ /* mode and flags */
+ int mode;
+ int raw;
+
+ /* file names and handles */
+ char *infilename, *outfilename;
+ char *commentfilename;
+ FILE *commentfile;
+ int tempoutfile;
+
+ /* comments */
+ int commentcount;
+ char **comments;
+} param_t;
+
+#define MODE_NONE 0
+#define MODE_LIST 1
+#define MODE_WRITE 2
+#define MODE_APPEND 3
+
+/* prototypes */
+void usage(void);
+void print_comments(FILE *out, TheoraCommenter* com, int raw);
+int add_comment(char *line, TheoraCommenter* com, 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 close_files(param_t *p, int output_written);
+
+/** The input file */
+static FILE* inputFile;
+/** The output file */
+static FILE* outputFile;
+
+/**
+ * The input for the commenter, reading the input file.
+ * @see TheoraCommenterInput
+ */
+size_t inputFunc(char* buf, size_t n)
+{
+ assert(inputFile);
+ return fread(buf, 1, n, inputFile);
+}
+
+/**
+ * The output for the commenter, writing to the output file.
+ * @see TheoraCommenterOutput
+ */
+size_t outputFunc(char* buf, size_t n)
+{
+ assert(outputFile);
+ return fwrite(buf, 1, n, outputFile);
+}
+
+/**********
+ main.c
+
+ This is the main function where options are read and written
+ you should be able to just read this function and see how
+ to call the vcedit routines. Details of how to pack/unpack the
+ vorbis_comment structure itself are in the following two routines.
+ The rest of the file is ui dressing so make the program minimally
+ useful as a command line utility and can generally be ignored.
+
+***********/
+
+int main(int argc, char **argv)
+{
+ param_t *param;
+ int i;
+ TheoraCommenter* com;
+
+ /*
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ */
+
+ /* initialize the cmdline interface */
+ param = new_param();
+ parse_options(argc, argv, param);
+
+ /* take care of opening the requested files */
+ /* relevent file pointers are returned in the param struct */
+ open_files(param);
+
+ #define FINALLY \
+ { \
+ deleteTheoraCommenter(com); \
+ close_files(param, 0); \
+ free_param(param); \
+ }
+
+ /* Create the commenter */
+ com=newTheoraCommenter(&inputFunc, &outputFunc);
+ if(theoraCommenter_read(com))
+ {
+ fprintf(stderr, "%s\n", com->error);
+ FINALLY
+ return EXIT_FAILURE;
+ }
+
+ /* which mode are we in? */
+
+ if (param->mode == MODE_LIST) {
+ print_comments(param->commentfile, com, param->raw);
+ FINALLY
+ return 0;
+ }
+
+ if (param->mode == MODE_WRITE || param->mode == MODE_APPEND)
+ {
+ /* Clear comments when not appending */
+ if(param->mode!=MODE_APPEND)
+ theoraCommenter_clear(com);
+
+ /* Add the comments */
+ for(i=0; i!=param->commentcount; ++i)
+ {
+ if(add_comment(param->comments[i], com, param->raw))
+ fprintf(stderr, _("Bad comment: \"%s\"\n"), param->comments[i]);
+ }
+
+ /* Seek input to beginning */
+ fseek(inputFile, 0, SEEK_SET);
+
+ /* Write out */
+ if(theoraCommenter_write(com))
+ {
+ fprintf(stderr, "%s\n", com->error);
+ FINALLY
+ return EXIT_FAILURE;
+ }
+
+ FINALLY
+ return EXIT_SUCCESS;
+ }
+
+ /* should never reach this point */
+ fprintf(stderr, _("no action specified\n"));
+ FINALLY
+ return 1;
+}
+
+/**********
+
+ Print out the comments from the vorbis structure
+
+ this version just dumps the raw strings
+ a more elegant version would use vorbis_comment_query()
+
+***********/
+
+void print_comments(FILE *out, TheoraCommenter* com, int raw)
+{
+ int i;
+ char *decoded_value;
+
+ for(i=0; i!=com->comments.comments; ++i)
+ if(!raw && utf8_decode(com->comments.user_comments[i], &decoded_value)>=0)
+ {
+ fprintf(out, "%s\n", decoded_value);
+ free(decoded_value);
+ } else
+ {
+ fprintf(out, "%s\n", com->comments.user_comments[i]);
+ }
+}
+
+/**********
+
+ Take a line of the form "TAG=value string", parse it, convert the
+ value to UTF-8, and add it to the
+ vorbis_comment structure. Error checking is performed.
+
+ Note that this assumes a null-terminated string, which may cause
+ problems with > 8-bit character sets!
+
+***********/
+
+int add_comment(char *line, TheoraCommenter* com, int raw)
+{
+ char *mark, *value, *utf8_value;
+
+ /* strip any terminal newline */
+ {
+ int len = strlen(line);
+ if (line[len-1] == '\n') line[len-1] = '\0';
+ }
+
+ /* validation: basically, we assume it's a tag
+ * if it has an '=' after one or more valid characters,
+ * as the comment spec requires. For the moment, we
+ * also restrict ourselves to 0-terminated values */
+
+ mark = strchr(line, '=');
+ if (mark == NULL) return -1;
+
+ value = line;
+ while (value < mark) {
+ if(*value < 0x20 || *value > 0x7d || *value == 0x3d) return -1;
+ value++;
+ }
+
+ /* split the line by turning the '=' in to a null */
+ *mark = '\0';
+ value++;
+
+ if(raw)
+ {
+ theoraCommenter_addTag(com, line, value);
+ return 0;
+ }
+ /* convert the value from the native charset to UTF-8 */
+ else if (utf8_encode(value, &utf8_value) >= 0)
+ {
+ /* append the comment and return */
+ theoraCommenter_addTag(com, line, utf8_value);
+ free(utf8_value);
+ return 0;
+ } else
+ {
+ fprintf(stderr, _("Couldn't convert comment to UTF-8, "
+ "cannot add\n"));
+ return -1;
+ }
+}
+
+
+/*** ui-specific routines ***/
+
+/**********
+
+ Print out to usage summary for the cmdline interface (ui)
+
+***********/
+
+/* XXX: -q is unused
+ printf (_(" -q, --quiet Don't display comments while editing\n"));
+*/
+
+void usage(void)
+{
+
+ printf (_("vorbiscomment from %s %s\n"
+ " by the Xiph.Org Foundation (http://www.xiph.org/)\n\n"), PACKAGE, VERSION);
+
+ printf (_("List or edit comments in Ogg Vorbis files.\n"));
+ printf ("\n");
+
+ printf (_("Usage: \n"
+ " vorbiscomment [-Vh]\n"
+ " vorbiscomment [-lR] file\n"
+ " vorbiscomment [-R] [-c file] [-t tag] <-a|-w> inputfile [outputfile]\n"));
+ printf ("\n");
+
+ printf (_("Listing options\n"));
+ printf (_(" -l, --list List the comments (default if no options are given)\n"));
+ printf ("\n");
+
+ printf (_("Editing options\n"));
+ printf (_(" -a, --append Append comments\n"));
+ printf (_(" -t \"name=value\", --tag \"name=value\"\n"
+ " Specify a comment tag on the commandline\n"));
+ printf (_(" -w, --write Write comments, replacing the existing ones\n"));
+ printf ("\n");
+
+ printf (_("Miscellaneous options\n"));
+ printf (_(" -c file, --commentfile file\n"
+ " 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 ("\n");
+
+ printf (_(" -h, --help Display this help\n"));
+ printf (_(" -V, --version Display ogg123 version\n"));
+ printf ("\n");
+
+ printf (_("If no output file is specified, vorbiscomment will modify the input file. This\n"
+ "is handled via temporary file, such that the input file is not modified if any\n"
+ "errors are encountered during processing.\n"));
+ printf ("\n");
+
+ printf (_("vorbiscomment handles comments in the format \"name=value\", one per line. By\n"
+ "default, comments are written to stdout when listing, and read from stdin when\n"
+ "editing. Alternatively, a file can be specified with the -c option, or tags\n"
+ "can be given on the commandline with -t \"name=value\". Use of either -c or -t\n"
+ "disables reading from stdin.\n"));
+ printf ("\n");
+
+ printf (_("Examples:\n"
+ " vorbiscomment -a in.ogg -c comments.txt\n"
+ " vorbiscomment -a in.ogg -t \"ARTIST=Some Guy\" -t \"TITLE=A Title\"\n"));
+ printf ("\n");
+
+ printf (_("NOTE: Raw mode (--raw, -R) will read and write comments in UTF-8 rather than\n"
+ "converting to the user's character set, which is useful in scripts. However,\n"
+ "this is not sufficient for general round-tripping of comments in all cases.\n"));
+}
+
+void free_param(param_t *param)
+{
+ int i;
+ for(i=0; i!=param->commentcount; ++i)
+ free(param->comments[i]);
+ free(param->comments);
+ free(param->infilename);
+ free(param->outfilename);
+ free(param);
+}
+
+/**********
+
+ allocate and initialize a the parameter struct
+
+***********/
+
+param_t *new_param(void)
+{
+ param_t *param = (param_t *)malloc(sizeof(param_t));
+
+ /* mode and flags */
+ param->mode = MODE_LIST;
+ param->raw = 0;
+
+ /* filenames */
+ param->infilename = NULL;
+ param->outfilename = NULL;
+ param->commentfilename = "-"; /* default */
+
+ /* file pointers */
+ inputFile = outputFile = NULL;
+ param->commentfile = NULL;
+ param->tempoutfile=0;
+
+ /* comments */
+ param->commentcount=0;
+ param->comments=NULL;
+
+ return param;
+}
+
+/**********
+ parse_options()
+
+ This function takes care of parsing the command line options
+ with getopt() and fills out the param struct with the mode,
+ flags, and filenames.
+
+***********/
+
+void parse_options(int argc, char *argv[], param_t *param)
+{
+ int ret;
+ int option_index = 1;
+
+ setlocale(LC_ALL, "");
+
+ while ((ret = getopt_long(argc, argv, "alwhqVc:t:R",
+ long_options, &option_index)) != -1) {
+ switch (ret) {
+ case 0:
+ fprintf(stderr, _("Internal error parsing command options\n"));
+ exit(1);
+ break;
+ case 'l':
+ param->mode = MODE_LIST;
+ break;
+ case 'R':
+ param->raw = 1;
+ break;
+ case 'w':
+ param->mode = MODE_WRITE;
+ break;
+ case 'a':
+ param->mode = MODE_APPEND;
+ break;
+ case 'V':
+ fprintf(stderr, "vorbiscomment from vorbis-tools " VERSION "\n");
+ exit(0);
+ break;
+ case 'h':
+ usage();
+ exit(0);
+ break;
+ case 'q':
+ /* set quiet flag: unused */
+ break;
+ case 'c':
+ param->commentfilename = strdup(optarg);
+ break;
+ case 't':
+ param->comments = realloc(param->comments,
+ (param->commentcount+1)*sizeof(char *));
+ param->comments[param->commentcount++] = strdup(optarg);
+ break;
+ default:
+ usage();
+ exit(1);
+ }
+ }
+
+ /* remaining bits must be the filenames */
+ if((param->mode == MODE_LIST && (argc-optind) != 1) ||
+ ((param->mode == MODE_WRITE || param->mode == MODE_APPEND) &&
+ ((argc-optind) < 1 || (argc-optind) > 2))) {
+ usage();
+ exit(1);
+ }
+
+ param->infilename = strdup(argv[optind]);
+ if (param->mode == MODE_WRITE || param->mode == MODE_APPEND)
+ {
+ if(argc-optind == 1)
+ {
+ param->tempoutfile = 1;
+ param->outfilename = malloc(strlen(param->infilename)+8);
+ strcpy(param->outfilename, param->infilename);
+ strcat(param->outfilename, ".vctemp");
+ }
+ else
+ param->outfilename = strdup(argv[optind+1]);
+ }
+}
+
+/**********
+ open_files()
+
+ This function 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.
+
+ The idea is just to hide the tedious checking so main()
+ is easier to follow as an example.
+
+***********/
+
+void open_files(param_t *p)
+{
+ /* for all modes, open the input file */
+
+ if (strncmp(p->infilename,"-",2) == 0) {
+ fprintf(stderr, _("Input from stdin is not supported at the moment.\n"));
+ exit(EXIT_FAILURE);
+ } else {
+ inputFile = fopen(p->infilename, "rb");
+ }
+ if (!inputFile) {
+ fprintf(stderr,
+ _("Error opening input file '%s'.\n"),
+ p->infilename);
+ exit(1);
+ }
+
+ 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"));
+ exit(1);
+ }
+
+ if (strncmp(p->outfilename,"-",2) == 0) {
+ outputFile = stdout;
+ } else {
+ outputFile = fopen(p->outfilename, "wb");
+ }
+ if(!outputFile) {
+ fprintf(stderr,
+ _("Error opening output file '%s'.\n"),
+ p->outfilename);
+ exit(1);
+ }
+
+ /* commentfile is input */
+
+ if ((p->commentfilename == NULL) ||
+ (strncmp(p->commentfilename,"-",2) == 0)) {
+ p->commentfile = stdin;
+ } else {
+ p->commentfile = fopen(p->commentfilename, "r");
+ }
+ if (!p->commentfile) {
+ fprintf(stderr,
+ _("Error opening comment file '%s'.\n"),
+ p->commentfilename);
+ exit(1);
+ }
+
+ } else {
+
+ /* in list mode, commentfile is output */
+
+ if ((!p->commentfilename) ||
+ (strncmp(p->commentfilename,"-",2) == 0)) {
+ p->commentfile = stdout;
+ } else {
+ p->commentfile = fopen(p->commentfilename, "w");
+ }
+ if (!p->commentfile) {
+ fprintf(stderr,
+ _("Error opening comment file '%s'\n"),
+ p->commentfilename);
+ exit(1);
+ }
+ }
+
+ /* all done */
+}
+
+/**********
+ close_files()
+
+ Do some quick clean-up.
+
+***********/
+
+void close_files(param_t *p, int output_written)
+{
+ if (inputFile && inputFile != stdin) fclose(inputFile);
+ if (outputFile && outputFile != stdout) fclose(outputFile);
+ if (p->commentfile && p->commentfile != stdout && p->commentfile != stdin)
+ fclose(p->commentfile);
+
+ if(p->tempoutfile) {
+#if HAVE_STAT && HAVE_CHMOD
+ struct stat st;
+ stat (p->infilename, &st);
+#endif
+
+ if(output_written) {
+ /* Some platforms fail to rename a file if the new name already
+ * exists, so we need to remove, then rename. How stupid.
+ */
+ if(rename(p->outfilename, p->infilename)) {
+ if(remove(p->infilename))
+ fprintf(stderr, _("Error removing old file %s\n"), p->infilename);
+ else if(rename(p->outfilename, p->infilename))
+ fprintf(stderr, _("Error renaming %s to %s\n"), p->outfilename,
+ p->infilename);
+ } else {
+#if HAVE_STAT && HAVE_CHMOD
+ chmod (p->infilename, st.st_mode);
+#endif
+ }
+ }
+ else {
+ if(remove(p->outfilename)) {
+ fprintf(stderr, _("Error removing erroneous temporary file %s\n"),
+ p->outfilename);
+ }
+ }
+ }
+}
+
More information about the commits
mailing list