shithub: libopusenc

Download patch

ref: 150387346b6503725de51f835fb137746fdc1430
parent: 04b6487308d2f981afe7c94147cf58c56aaf0615
author: Jean-Marc Valin <jmvalin@jmvalin.ca>
date: Mon May 1 13:18:46 EDT 2017

Add picture code (completely untested)

--- a/Makefile.am
+++ b/Makefile.am
@@ -13,6 +13,7 @@
 libopusenc_la_SOURCES = \
 	src/opus_header.c \
 	src/opusenc.c \
+	src/picture.c \
 	src/resample.c
 libopusenc_la_LIBADD = $(DEPS_LIBS) $(lrintf_lib)
 libopusenc_la_LDFLAGS = -no-undefined \
--- a/include/opusenc.h
+++ b/include/opusenc.h
@@ -113,6 +113,9 @@
 /** Add a comment to the file (can only be called before encoding samples). */
 OPE_EXPORT int ope_add_comment(OggOpusEnc *enc, const char *tag, const char *val);
 
+/** Add a picture to the file (can only be called before encoding samples). */
+OPE_EXPORT int ope_add_picture(OggOpusEnc *enc, const char *spec);
+
 /** Sets the Opus comment vendor string (optional, defaults to library info). */
 OPE_EXPORT int ope_set_vendor_string(OggOpusEnc *enc, const char *vendor);
 
--- a/src/opusenc.c
+++ b/src/opusenc.c
@@ -42,6 +42,7 @@
 #include "opusenc.h"
 #include "opus_header.h"
 #include "speex_resampler.h"
+#include "picture.h"
 
 #define MAX_CHANNELS 8
 
@@ -93,6 +94,7 @@
   int comment_padding;
   char *comment;
   int comment_length;
+  int seen_file_icons;
   ogg_stream_state os;
   int stream_is_init;
   int packetno;
@@ -176,6 +178,7 @@
   enc->decision_delay = 96000;
   enc->max_ogg_delay = 48000;
   enc->comment_padding = 512;
+  enc->seen_file_icons = 0;
   enc->header.channels=channels;
   enc->header.channel_mapping=family;
   enc->header.input_sample_rate=rate;
@@ -445,6 +448,20 @@
 /* Add a comment to the file (can only be called before encoding samples). */
 int ope_add_comment(OggOpusEnc *enc, const char *tag, const char *val) {
   if (comment_add(&enc->comment, &enc->comment_length, tag, val)) return OPE_INTERNAL_ERROR;
+  return OPE_OK;
+}
+
+int ope_add_picture(OggOpusEnc *enc, const char *spec) {
+  const char *error_message;
+  char *picture_data;
+  picture_data = parse_picture_specification(spec, &error_message, &enc->seen_file_icons);
+  if(picture_data==NULL){
+    /* FIXME: return proper errors rather than printing a message. */
+    fprintf(stderr,"Error parsing picture option: %s\n",error_message);
+    return OPE_BAD_ARG;
+  }
+  comment_add(&enc->comment, &enc->comment_length, "METADATA_BLOCK_PICTURE", picture_data);
+  free(picture_data);
   return OPE_OK;
 }
 
--- /dev/null
+++ b/src/picture.c
@@ -1,0 +1,499 @@
+/* Copyright (C)2007-2013 Xiph.Org Foundation
+   File: picture.c
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   - Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   - Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "picture.h"
+
+static const char BASE64_TABLE[64]={
+  'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+  'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+  'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+  'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
+};
+
+/*Utility function for base64 encoding METADATA_BLOCK_PICTURE tags.
+  Stores BASE64_LENGTH(len)+1 bytes in dst (including a terminating NUL).*/
+void base64_encode(char *dst, const char *src, int len){
+  unsigned s0;
+  unsigned s1;
+  unsigned s2;
+  int      ngroups;
+  int      i;
+  ngroups=len/3;
+  for(i=0;i<ngroups;i++){
+    s0=(unsigned char)src[3*i+0];
+    s1=(unsigned char)src[3*i+1];
+    s2=(unsigned char)src[3*i+2];
+    dst[4*i+0]=BASE64_TABLE[s0>>2];
+    dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
+    dst[4*i+2]=BASE64_TABLE[(s1&15)<<2|s2>>6];
+    dst[4*i+3]=BASE64_TABLE[s2&63];
+  }
+  len-=3*i;
+  if(len==1){
+    s0=(unsigned char)src[3*i+0];
+    dst[4*i+0]=BASE64_TABLE[s0>>2];
+    dst[4*i+1]=BASE64_TABLE[(s0&3)<<4];
+    dst[4*i+2]='=';
+    dst[4*i+3]='=';
+    i++;
+  }
+  else if(len==2){
+    s0=(unsigned char)src[3*i+0];
+    s1=(unsigned char)src[3*i+1];
+    dst[4*i+0]=BASE64_TABLE[s0>>2];
+    dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
+    dst[4*i+2]=BASE64_TABLE[(s1&15)<<2];
+    dst[4*i+3]='=';
+    i++;
+  }
+  dst[4*i]='\0';
+}
+
+/*A version of strncasecmp() that is guaranteed to only ignore the case of
+   ASCII characters.*/
+int oi_strncasecmp(const char *a, const char *b, int n){
+  int i;
+  for(i=0;i<n;i++){
+    int aval;
+    int bval;
+    int diff;
+    aval=a[i];
+    bval=b[i];
+    if(aval>='a'&&aval<='z') {
+      aval-='a'-'A';
+    }
+    if(bval>='a'&&bval<='z'){
+      bval-='a'-'A';
+    }
+    diff=aval-bval;
+    if(diff){
+      return diff;
+    }
+  }
+  return 0;
+}
+
+int is_jpeg(const unsigned char *buf, size_t length){
+  return length>=11&&memcmp(buf,"\xFF\xD8\xFF\xE0",4)==0
+   &&(buf[4]<<8|buf[5])>=16&&memcmp(buf+6,"JFIF",5)==0;
+}
+
+int is_png(const unsigned char *buf, size_t length){
+  return length>=8&&memcmp(buf,"\x89PNG\x0D\x0A\x1A\x0A",8)==0;
+}
+
+int is_gif(const unsigned char *buf, size_t length){
+  return length>=6
+   &&(memcmp(buf,"GIF87a",6)==0||memcmp(buf,"GIF89a",6)==0);
+}
+
+#define READ_U32_BE(buf) \
+    (((buf)[0]<<24)|((buf)[1]<<16)|((buf)[2]<<8)|((buf)[3]&0xff))
+
+/*Tries to extract the width, height, bits per pixel, and palette size of a
+   PNG.
+  On failure, simply leaves its outputs unmodified.*/
+void extract_png_params(const unsigned char *data, size_t data_length,
+                        ogg_uint32_t *width, ogg_uint32_t *height,
+                        ogg_uint32_t *depth, ogg_uint32_t *colors,
+                        int *has_palette){
+  if(is_png(data,data_length)){
+    size_t offs;
+    offs=8;
+    while(data_length-offs>=12){
+      ogg_uint32_t chunk_len;
+      chunk_len=READ_U32_BE(data+offs);
+      if(chunk_len>data_length-(offs+12))break;
+      else if(chunk_len==13&&memcmp(data+offs+4,"IHDR",4)==0){
+        int color_type;
+        *width=READ_U32_BE(data+offs+8);
+        *height=READ_U32_BE(data+offs+12);
+        color_type=data[offs+17];
+        if(color_type==3){
+          *depth=24;
+          *has_palette=1;
+        }
+        else{
+          int sample_depth;
+          sample_depth=data[offs+16];
+          if(color_type==0)*depth=sample_depth;
+          else if(color_type==2)*depth=sample_depth*3;
+          else if(color_type==4)*depth=sample_depth*2;
+          else if(color_type==6)*depth=sample_depth*4;
+          *colors=0;
+          *has_palette=0;
+          break;
+        }
+      }
+      else if(*has_palette>0&&memcmp(data+offs+4,"PLTE",4)==0){
+        *colors=chunk_len/3;
+        break;
+      }
+      offs+=12+chunk_len;
+    }
+  }
+}
+
+/*Tries to extract the width, height, bits per pixel, and palette size of a
+   GIF.
+  On failure, simply leaves its outputs unmodified.*/
+void extract_gif_params(const unsigned char *data, size_t data_length,
+                        ogg_uint32_t *width, ogg_uint32_t *height,
+                        ogg_uint32_t *depth, ogg_uint32_t *colors,
+                        int *has_palette){
+  if(is_gif(data,data_length)&&data_length>=14){
+    *width=data[6]|data[7]<<8;
+    *height=data[8]|data[9]<<8;
+    /*libFLAC hard-codes the depth to 24.*/
+    *depth=24;
+    *colors=1<<((data[10]&7)+1);
+    *has_palette=1;
+  }
+}
+
+
+/*Tries to extract the width, height, bits per pixel, and palette size of a
+   JPEG.
+  On failure, simply leaves its outputs unmodified.*/
+void extract_jpeg_params(const unsigned char *data, size_t data_length,
+                         ogg_uint32_t *width, ogg_uint32_t *height,
+                         ogg_uint32_t *depth, ogg_uint32_t *colors,
+                         int *has_palette){
+  if(is_jpeg(data,data_length)){
+    size_t offs;
+    offs=2;
+    for(;;){
+      size_t segment_len;
+      int    marker;
+      while(offs<data_length&&data[offs]!=0xFF)offs++;
+      while(offs<data_length&&data[offs]==0xFF)offs++;
+      marker=data[offs];
+      offs++;
+      /*If we hit EOI* (end of image), or another SOI* (start of image),
+         or SOS (start of scan), then stop now.*/
+      if(offs>=data_length||(marker>=0xD8&&marker<=0xDA))break;
+      /*RST* (restart markers): skip (no segment length).*/
+      else if(marker>=0xD0&&marker<=0xD7)continue;
+      /*Read the length of the marker segment.*/
+      if(data_length-offs<2)break;
+      segment_len=data[offs]<<8|data[offs+1];
+      if(segment_len<2||data_length-offs<segment_len)break;
+      if(marker==0xC0||(marker>0xC0&&marker<0xD0&&(marker&3)!=0)){
+        /*Found a SOFn (start of frame) marker segment:*/
+        if(segment_len>=8){
+          *height=data[offs+3]<<8|data[offs+4];
+          *width=data[offs+5]<<8|data[offs+6];
+          *depth=data[offs+2]*data[offs+7];
+          *colors=0;
+          *has_palette=0;
+        }
+        break;
+      }
+      /*Other markers: skip the whole marker segment.*/
+      offs+=segment_len;
+    }
+  }
+}
+
+#define IMAX(a,b) ((a) > (b) ? (a) : (b))
+
+/*Parse a picture SPECIFICATION as given on the command-line.
+  spec: The specification.
+  error_message: Returns an error message on error.
+  seen_file_icons: Bit flags used to track if any pictures of type 1 or type 2
+   have already been added, to ensure only one is allowed.
+  Return: A Base64-encoded string suitable for use in a METADATA_BLOCK_PICTURE
+   tag.*/
+char *parse_picture_specification(const char *spec,
+                                  const char **error_message,
+                                  int *seen_file_icons){
+  FILE          *picture_file;
+  unsigned long  picture_type;
+  unsigned long  width;
+  unsigned long  height;
+  unsigned long  depth;
+  unsigned long  colors;
+  const char    *mime_type;
+  const char    *mime_type_end;
+  const char    *description;
+  const char    *description_end;
+  const char    *filename;
+  unsigned char *buf;
+  char          *out;
+  size_t         cbuf;
+  size_t         nbuf;
+  size_t         data_offset;
+  size_t         data_length;
+  size_t         b64_length;
+  int            is_url;
+  /*If a filename has a '|' in it, there's no way we can distinguish it from a
+     full specification just from the spec string.
+    Instead, try to open the file.
+    If it exists, the user probably meant the file.*/
+  picture_type=3;
+  width=height=depth=colors=0;
+  mime_type=mime_type_end=description=description_end=filename=spec;
+  is_url=0;
+  picture_file=fopen(filename,"rb");
+  if(picture_file==NULL&&strchr(spec,'|')){
+    const char *p;
+    char       *q;
+    /*We don't have a plain file, and there is a pipe character: assume it's
+       the full form of the specification.*/
+    picture_type=strtoul(spec,&q,10);
+    if(*q!='|'||picture_type>20){
+      *error_message="invalid picture type";
+      return NULL;
+    }
+    if(picture_type>=1&&picture_type<=2&&(*seen_file_icons&picture_type)){
+      *error_message=picture_type==1?
+       "only one picture of type 1 (32x32 icon) allowed":
+       "only one picture of type 2 (icon) allowed";
+      return NULL;
+    }
+    /*An empty field implies a default of 'Cover (front)'.*/
+    if(spec==q)picture_type=3;
+    mime_type=q+1;
+    mime_type_end=mime_type+strcspn(mime_type,"|");
+    if(*mime_type_end!='|'){
+      *error_message="invalid picture specification: not enough fields";
+      return NULL;
+    }
+    /*The media type must be composed of ASCII printable characters 0x20-0x7E.*/
+    for(p=mime_type;p<mime_type_end;p++)if(*p<0x20||*p>0x7E){
+      *error_message="invalid characters in media type";
+      return NULL;
+    }
+    is_url=mime_type_end-mime_type==3
+     &&strncmp("-->",mime_type,mime_type_end-mime_type)==0;
+    description=mime_type_end+1;
+    description_end=description+strcspn(description,"|");
+    if(*description_end!='|'){
+      *error_message="invalid picture specification: not enough fields";
+      return NULL;
+    }
+    p=description_end+1;
+    if(*p!='|'){
+      width=strtoul(p,&q,10);
+      if(*q!='x'){
+        *error_message=
+         "invalid picture specification: can't parse resolution/color field";
+        return NULL;
+      }
+      p=q+1;
+      height=strtoul(p,&q,10);
+      if(*q!='x'){
+        *error_message=
+         "invalid picture specification: can't parse resolution/color field";
+        return NULL;
+      }
+      p=q+1;
+      depth=strtoul(p,&q,10);
+      if(*q=='/'){
+        p=q+1;
+        colors=strtoul(p,&q,10);
+      }
+      if(*q!='|'){
+        *error_message=
+         "invalid picture specification: can't parse resolution/color field";
+        return NULL;
+      }
+      p=q;
+    }
+    filename=p+1;
+    if(!is_url)picture_file=fopen(filename,"rb");
+  }
+  /*Buffer size: 8 static 4-byte fields plus 2 dynamic fields, plus the
+     file/URL data.
+    We reserve at least 10 bytes for the media type, in case we still need to
+     extract it from the file.*/
+  data_offset=32+(description_end-description)+IMAX(mime_type_end-mime_type,10);
+  buf=NULL;
+  if(is_url){
+    /*Easy case: just stick the URL at the end.
+      We don't do anything to verify it's a valid URL.*/
+    data_length=strlen(filename);
+    cbuf=nbuf=data_offset+data_length;
+    buf=(unsigned char *)malloc(cbuf);
+    memcpy(buf+data_offset,filename,data_length);
+  }
+  else{
+    ogg_uint32_t file_width;
+    ogg_uint32_t file_height;
+    ogg_uint32_t file_depth;
+    ogg_uint32_t file_colors;
+    int          has_palette;
+    /*Complicated case: we have a real file.
+      Read it in, attempt to parse the media type and image dimensions if
+       necessary, and validate what the user passed in.*/
+    if(picture_file==NULL){
+      *error_message="error opening picture file";
+      return NULL;
+    }
+    nbuf=data_offset;
+    /*Add a reasonable starting image file size.*/
+    cbuf=data_offset+65536;
+    for(;;){
+      unsigned char *new_buf;
+      size_t         nread;
+      new_buf=realloc(buf,cbuf);
+      if(new_buf==NULL){
+        fclose(picture_file);
+        free(buf);
+        *error_message="insufficient memory";
+        return NULL;
+      }
+      buf=new_buf;
+      nread=fread(buf+nbuf,1,cbuf-nbuf,picture_file);
+      nbuf+=nread;
+      if(nbuf<cbuf){
+        int error;
+        error=ferror(picture_file);
+        fclose(picture_file);
+        if(error){
+          free(buf);
+          *error_message="error reading picture file";
+          return NULL;
+        }
+        break;
+      }
+      if(cbuf==0xFFFFFFFF){
+        fclose(picture_file);
+        free(buf);
+        *error_message="file too large";
+        return NULL;
+      }
+      else if(cbuf>0x7FFFFFFFU)cbuf=0xFFFFFFFFU;
+      else cbuf=cbuf<<1|1;
+    }
+    data_length=nbuf-data_offset;
+    /*If there was no media type, try to extract it from the file data.*/
+    if(mime_type_end==mime_type){
+      if(is_jpeg(buf+data_offset,data_length)){
+        mime_type="image/jpeg";
+        mime_type_end=mime_type+10;
+      }
+      else if(is_png(buf+data_offset,data_length)){
+        mime_type="image/png";
+        mime_type_end=mime_type+9;
+      }
+      else if(is_gif(buf+data_offset,data_length)){
+        mime_type="image/gif";
+        mime_type_end=mime_type+9;
+      }
+      else{
+        free(buf);
+        *error_message="unable to guess media type from file, "
+         "must set it explicitly";
+        return NULL;
+      }
+    }
+    /*Try to extract the image dimensions/color information from the file.*/
+    file_width=file_height=file_depth=file_colors=0;
+    has_palette=-1;
+    if(mime_type_end-mime_type==9
+     &&oi_strncasecmp("image/png",mime_type,mime_type_end-mime_type)==0){
+      extract_png_params(buf+data_offset,data_length,
+       &file_width,&file_height,&file_depth,&file_colors,&has_palette);
+    }
+    else if(mime_type_end-mime_type==9
+     &&oi_strncasecmp("image/gif",mime_type,mime_type_end-mime_type)==0){
+      extract_gif_params(buf+data_offset,data_length,
+       &file_width,&file_height,&file_depth,&file_colors,&has_palette);
+    }
+    else if(mime_type_end-mime_type==10
+     &&oi_strncasecmp("image/jpeg",mime_type,mime_type_end-mime_type)==0){
+      extract_jpeg_params(buf+data_offset,data_length,
+       &file_width,&file_height,&file_depth,&file_colors,&has_palette);
+    }
+    if(!width)width=file_width;
+    if(!height)height=file_height;
+    if(!depth)depth=file_depth;
+    if(!colors)colors=file_colors;
+    if((file_width&&width!=file_width)
+     ||(file_height&&height!=file_height)
+     ||(file_depth&&depth!=file_depth)
+     /*We use has_palette to ensure we also reject non-0 user color counts for
+        images we've positively identified as non-paletted.*/
+     ||(has_palette>=0&&colors!=file_colors)){
+      free(buf);
+      *error_message="invalid picture specification: "
+       "resolution/color field does not match file";
+      return NULL;
+    }
+  }
+  /*These fields MUST be set correctly OR all set to zero.
+    So if any of them (except colors, for which 0 is a valid value) are still
+     zero, clear the rest to zero.*/
+  if(width==0||height==0||depth==0)width=height=depth=colors=0;
+  if(picture_type==1&&(width!=32||height!=32
+   ||mime_type_end-mime_type!=9
+   ||oi_strncasecmp("image/png",mime_type,mime_type_end-mime_type)!=0)){
+    free(buf);
+    *error_message="pictures of type 1 MUST be 32x32 PNGs";
+    return NULL;
+  }
+  /*Build the METADATA_BLOCK_PICTURE buffer.
+    We do this backwards from data_offset, because we didn't necessarily know
+     how big the media type string was before we read the data in.*/
+  data_offset-=4;
+  WRITE_U32_BE(buf+data_offset,(unsigned long)data_length);
+  data_offset-=4;
+  WRITE_U32_BE(buf+data_offset,colors);
+  data_offset-=4;
+  WRITE_U32_BE(buf+data_offset,depth);
+  data_offset-=4;
+  WRITE_U32_BE(buf+data_offset,height);
+  data_offset-=4;
+  WRITE_U32_BE(buf+data_offset,width);
+  data_offset-=description_end-description;
+  memcpy(buf+data_offset,description,description_end-description);
+  data_offset-=4;
+  WRITE_U32_BE(buf+data_offset,(unsigned long)(description_end-description));
+  data_offset-=mime_type_end-mime_type;
+  memcpy(buf+data_offset,mime_type,mime_type_end-mime_type);
+  data_offset-=4;
+  WRITE_U32_BE(buf+data_offset,(unsigned long)(mime_type_end-mime_type));
+  data_offset-=4;
+  WRITE_U32_BE(buf+data_offset,picture_type);
+  data_length=nbuf-data_offset;
+  b64_length=BASE64_LENGTH(data_length);
+  out=(char *)malloc(b64_length+1);
+  if(out!=NULL){
+    base64_encode(out,(char *)buf+data_offset,data_length);
+    if(picture_type>=1&&picture_type<=2)*seen_file_icons|=picture_type;
+  }
+  free(buf);
+  return out;
+}
--- /dev/null
+++ b/src/picture.h
@@ -1,0 +1,50 @@
+#ifndef __PICTURE_H
+#define __PICTURE_H
+
+#include <ogg/ogg.h>
+
+typedef enum{
+  PIC_FORMAT_JPEG,
+  PIC_FORMAT_PNG,
+  PIC_FORMAT_GIF
+}picture_format;
+
+#define BASE64_LENGTH(len) (((len)+2)/3*4)
+
+/*Utility function for base64 encoding METADATA_BLOCK_PICTURE tags.
+  Stores BASE64_LENGTH(len)+1 bytes in dst (including a terminating NUL).*/
+void base64_encode(char *dst, const char *src, int len);
+
+int oi_strncasecmp(const char *a, const char *b, int n);
+
+int is_jpeg(const unsigned char *buf, size_t length);
+int is_png(const unsigned char *buf, size_t length);
+int is_gif(const unsigned char *buf, size_t length);
+
+void extract_png_params(const unsigned char *data, size_t data_length,
+                        ogg_uint32_t *width, ogg_uint32_t *height,
+                        ogg_uint32_t *depth, ogg_uint32_t *colors,
+                        int *has_palette);
+void extract_gif_params(const unsigned char *data, size_t data_length,
+                        ogg_uint32_t *width, ogg_uint32_t *height,
+                        ogg_uint32_t *depth, ogg_uint32_t *colors,
+                        int *has_palette);
+void extract_jpeg_params(const unsigned char *data, size_t data_length,
+                         ogg_uint32_t *width, ogg_uint32_t *height,
+                         ogg_uint32_t *depth, ogg_uint32_t *colors,
+                         int *has_palette);
+
+char *parse_picture_specification(const char *spec,
+                                  const char **error_message,
+                                  int *seen_file_icons);
+
+#define WRITE_U32_BE(buf, val) \
+  do{ \
+    (buf)[0]=(unsigned char)((val)>>24); \
+    (buf)[1]=(unsigned char)((val)>>16); \
+    (buf)[2]=(unsigned char)((val)>>8); \
+    (buf)[3]=(unsigned char)(val); \
+  } \
+  while(0);
+
+#endif /* __PICTURE_H */