shithub: opusfile

Download patch

ref: 0221ca95fc5829a9fd7d5348507ba2be0dea8264
parent: 2bb99f3cf0119a43884cb7c04fb5dc8c3aec61fc
author: Timothy B. Terriberry <tterribe@xiph.org>
date: Tue Dec 29 13:10:07 EST 2015

Add API to access and preserve binary metadata.

This adds support for accessing any binary metadata at the end of
 the comment header, as first specified in
 <https://tools.ietf.org/html/draft-ietf-codec-oggopus-05>.
It also allows the data to be set and preserves the data when
 doing deep copies.

--- a/examples/opusfile_example.c
+++ b/examples/opusfile_example.c
@@ -259,6 +259,7 @@
       if(li!=prev_li){
         const OpusHead *head;
         const OpusTags *tags;
+        int             binary_suffix_len;
         int             ci;
         /*We found a new link.
           Print out some information.*/
@@ -306,6 +307,10 @@
             else fprintf(stderr,"<error parsing picture tag>\n");
           }
           else fprintf(stderr,"  %s\n",tags->user_comments[ci]);
+        }
+        if(opus_tags_get_binary_suffix(tags,&binary_suffix_len)!=NULL){
+          fprintf(stderr,"<%u bytes of unknown binary metadata>\n",
+           binary_suffix_len);
         }
         fprintf(stderr,"\n");
         if(!op_seekable(of)){
--- a/include/opusfile.h
+++ b/include/opusfile.h
@@ -503,6 +503,22 @@
 int opus_tags_add_comment(OpusTags *_tags,const char *_comment)
  OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
 
+/**Replace the binary suffix data at the end of the packet (if any).
+   \param _tags An initialized #OpusTags structure.
+   \param _data A buffer of binary data to append after the encoded user
+                 comments.
+                The least significant bit of the first byte of this data must
+                 be set (to ensure the data is preserved by other editors).
+   \param _len  The number of bytes of binary data to append.
+                This may be zero to remove any existing binary suffix data.
+   \return 0 on success, or a negative value on error.
+   \retval #OP_EINVAL \a _len was negative, or \a _len was positive but
+                       \a _data was <code>NULL</code> or the least significant
+                       bit of the first byte was not set.
+   \retval #OP_EFAULT An internal memory allocation failed.*/
+int opus_tags_set_binary_suffix(OpusTags *_tags,
+ const unsigned char *_data,int _len) OP_ARG_NONNULL(1);
+
 /**Look up a comment value by its tag.
    \param _tags  An initialized #OpusTags structure.
    \param _tag   The tag to look up.
@@ -530,6 +546,14 @@
    \return The number of instances of this particular tag.*/
 int opus_tags_query_count(const OpusTags *_tags,const char *_tag)
  OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
+
+/**Retrieve the binary suffix data at the end of the packet (if any).
+   \param      _tags An initialized #OpusTags structure.
+   \param[out] _len  Returns the number of bytes of binary suffix data returned.
+   \return A pointer to the binary suffix data, or <code>NULL</code> if none
+            was present.*/
+const unsigned char *opus_tags_get_binary_suffix(const OpusTags *_tags,
+ int *_len) OP_ARG_NONNULL(1) OP_ARG_NONNULL(2);
 
 /**Get the album gain from an R128_ALBUM_GAIN tag, if one was specified.
    This searches for the first R128_ALBUM_GAIN tag with a valid signed,
--- a/src/info.c
+++ b/src/info.c
@@ -92,8 +92,11 @@
 }
 
 void opus_tags_clear(OpusTags *_tags){
+  int ncomments;
   int ci;
-  for(ci=_tags->comments;ci-->0;)_ogg_free(_tags->user_comments[ci]);
+  ncomments=_tags->comments;
+  if(_tags->user_comments!=NULL)ncomments++;
+  for(ci=ncomments;ci-->0;)_ogg_free(_tags->user_comments[ci]);
   _ogg_free(_tags->user_comments);
   _ogg_free(_tags->comment_lengths);
   _ogg_free(_tags->vendor);
@@ -103,19 +106,27 @@
 static int op_tags_ensure_capacity(OpusTags *_tags,size_t _ncomments){
   char   **user_comments;
   int     *comment_lengths;
+  int      cur_ncomments;
+  char    *binary_suffix_data;
+  int      binary_suffix_len;
   size_t   size;
   if(OP_UNLIKELY(_ncomments>=(size_t)INT_MAX))return OP_EFAULT;
   size=sizeof(*_tags->comment_lengths)*(_ncomments+1);
   if(size/sizeof(*_tags->comment_lengths)!=_ncomments+1)return OP_EFAULT;
+  cur_ncomments=_tags->comments;
+  comment_lengths=_tags->comment_lengths;
+  binary_suffix_len=comment_lengths==NULL?0:comment_lengths[cur_ncomments];
   comment_lengths=(int *)_ogg_realloc(_tags->comment_lengths,size);
   if(OP_UNLIKELY(comment_lengths==NULL))return OP_EFAULT;
-  comment_lengths[_ncomments]=0;
+  comment_lengths[_ncomments]=binary_suffix_len;
   _tags->comment_lengths=comment_lengths;
   size=sizeof(*_tags->user_comments)*(_ncomments+1);
   if(size/sizeof(*_tags->user_comments)!=_ncomments+1)return OP_EFAULT;
+  user_comments=_tags->user_comments;
+  binary_suffix_data=user_comments==NULL?NULL:user_comments[cur_ncomments];
   user_comments=(char **)_ogg_realloc(_tags->user_comments,size);
   if(OP_UNLIKELY(user_comments==NULL))return OP_EFAULT;
-  user_comments[_ncomments]=NULL;
+  user_comments[_ncomments]=binary_suffix_data;
   _tags->user_comments=user_comments;
   return 0;
 }
@@ -192,6 +203,13 @@
     _data+=count;
     len-=count;
   }
+  if(len>0&&(_data[0]&1)){
+    if(len>(opus_uint32)INT_MAX)return OP_EFAULT;
+    _tags->user_comments[ncomments]=(char *)_ogg_malloc(len);
+    if(OP_UNLIKELY(_tags->user_comments[ncomments]==NULL))return OP_EFAULT;
+    memcpy(_tags->user_comments[ncomments],_data,len);
+    _tags->comment_lengths[ncomments]=(int)len;
+  }
   return 0;
 }
 
@@ -232,6 +250,16 @@
     _dst->comment_lengths[ci]=len;
     _dst->comments=ci+1;
   }
+  if(_src->comment_lengths!=NULL){
+    int len;
+    len=_src->comment_lengths[ncomments];
+    if(len>0){
+      _dst->user_comments[ncomments]=(char *)_ogg_malloc(len);
+      if(OP_UNLIKELY(_dst->user_comments[ncomments]==NULL))return OP_EFAULT;
+      memcpy(_dst->user_comments[ncomments],_src->user_comments[ncomments],len);
+      _dst->comment_lengths[ncomments]=len;
+    }
+  }
   return 0;
 }
 
@@ -257,13 +285,12 @@
   tag_len=strlen(_tag);
   value_len=strlen(_value);
   /*+2 for '=' and '\0'.*/
-  _tags->comment_lengths[ncomments]=0;
-  _tags->user_comments[ncomments]=comment=
-   (char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2));
+  comment=(char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2));
   if(OP_UNLIKELY(comment==NULL))return OP_EFAULT;
   memcpy(comment,_tag,sizeof(*comment)*tag_len);
   comment[tag_len]='=';
   memcpy(comment+tag_len+1,_value,sizeof(*comment)*(value_len+1));
+  _tags->user_comments[ncomments]=comment;
   _tags->comment_lengths[ncomments]=tag_len+value_len+1;
   _tags->comments=ncomments+1;
   return 0;
@@ -270,21 +297,40 @@
 }
 
 int opus_tags_add_comment(OpusTags *_tags,const char *_comment){
-  int comment_len;
-  int ncomments;
-  int ret;
+  char *comment;
+  int   comment_len;
+  int   ncomments;
+  int   ret;
   ncomments=_tags->comments;
   ret=op_tags_ensure_capacity(_tags,ncomments+1);
   if(OP_UNLIKELY(ret<0))return ret;
   comment_len=(int)strlen(_comment);
-  _tags->comment_lengths[ncomments]=0;
-  _tags->user_comments[ncomments]=op_strdup_with_len(_comment,comment_len);
+  comment=op_strdup_with_len(_comment,comment_len);
   if(OP_UNLIKELY(_tags->user_comments[ncomments]==NULL))return OP_EFAULT;
+  _tags->user_comments[ncomments]=comment;
   _tags->comment_lengths[ncomments]=comment_len;
   _tags->comments=ncomments+1;
   return 0;
 }
 
+int opus_tags_set_binary_suffix(OpusTags *_tags,
+ const unsigned char *_data,int _len){
+  unsigned char *binary_suffix_data;
+  int            ncomments;
+  int            ret;
+  if(_len<0||_len>0&&(_data==NULL||!(_data[0]&1)))return OP_EINVAL;
+  ncomments=_tags->comments;
+  ret=op_tags_ensure_capacity(_tags,ncomments);
+  if(OP_UNLIKELY(ret<0))return ret;
+  binary_suffix_data=
+   (unsigned char *)_ogg_realloc(_tags->user_comments[ncomments],_len);
+  if(OP_UNLIKELY(binary_suffix_data==NULL))return OP_EFAULT;
+  memcpy(binary_suffix_data,_data,_len);
+  _tags->user_comments[ncomments]=(char *)binary_suffix_data;
+  _tags->comment_lengths[ncomments]=_len;
+  return 0;
+}
+
 int opus_tagcompare(const char *_tag_name,const char *_comment){
   return opus_tagncompare(_tag_name,strlen(_tag_name),_comment);
 }
@@ -330,6 +376,17 @@
     if(!opus_tagncompare(_tag,tag_len,user_comments[ci]))found++;
   }
   return found;
+}
+
+const unsigned char *opus_tags_get_binary_suffix(const OpusTags *_tags,
+ int *_len){
+  int ncomments;
+  int len;
+  ncomments=_tags->comments;
+  len=_tags->comment_lengths==NULL?0:_tags->comment_lengths[ncomments];
+  *_len=len;
+  OP_ASSERT(len==0||_tags->user_comments!=NULL);
+  return len>0?(const unsigned char *)_tags->user_comments[ncomments]:NULL;
 }
 
 static int opus_tags_get_gain(const OpusTags *_tags,int *_gain_q8,