[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