static MagickBooleanType WritePS3Image(const ImageInfo *image_info,Image *image,
  ExceptionInfo *exception)
{
  static const char
    *const PostscriptProlog[]=
    {
      "/ByteStreamDecodeFilter",
      "{",
      "  /z exch def",
      "  /r exch def",
      "  /c exch def",
      "  z " PS3_NoCompression " eq { /ASCII85Decode filter } if",
      "  z " PS3_FaxCompression " eq",
      "  {",
      "    <<",
      "      /K " CCITTParam,
      "      /Columns c",
      "      /Rows r",
      "    >>",
      "    /CCITTFaxDecode filter",
      "  } if",
      "  z " PS3_JPEGCompression " eq { /DCTDecode filter } if",
      "  z " PS3_LZWCompression " eq { /LZWDecode filter } if",
      "  z " PS3_RLECompression " eq { /RunLengthDecode filter } if",
      "  z " PS3_ZipCompression " eq { /FlateDecode filter } if",
      "} bind def",
      "",
      "/DirectClassImageDict",
      "{",
      "  colorspace " PS3_RGBColorspace " eq",
      "  {",
      "    /DeviceRGB setcolorspace",
      "    <<",
      "      /ImageType 1",
      "      /Width columns",
      "      /Height rows",
      "      /BitsPerComponent 8",
      "      /DataSource pixel_stream",
      "      /MultipleDataSources false",
      "      /ImageMatrix [columns 0 0 rows neg 0 rows]",
      "      /Decode [0 1 0 1 0 1]",
      "    >>",
      "  }",
      "  {",
      "    /DeviceCMYK setcolorspace",
      "    <<",
      "      /ImageType 1",
      "      /Width columns",
      "      /Height rows",
      "      /BitsPerComponent 8",
      "      /DataSource pixel_stream",
      "      /MultipleDataSources false",
      "      /ImageMatrix [columns 0 0 rows neg 0 rows]",
      "      /Decode",
      "        compression " PS3_JPEGCompression " eq",
      "        { [1 0 1 0 1 0 1 0] }",
      "        { [0 1 0 1 0 1 0 1] }",
      "        ifelse",
      "    >>",
      "  }",
      "  ifelse",
      "} bind def",
      "",
      "/PseudoClassImageDict",
      "{",
      "  % Colors in colormap image.",
      "  currentfile buffer readline pop",
      "  token pop /colors exch def pop",
      "  colors 0 eq",
      "  {",
      "    % Depth of grayscale image.",
      "    currentfile buffer readline pop",
      "    token pop /bits exch def pop",
      "    /DeviceGray setcolorspace",
      "    <<",
      "      /ImageType 1",
      "      /Width columns",
      "      /Height rows",
      "      /BitsPerComponent bits",
      "      /Decode [0 1]",
      "      /ImageMatrix [columns 0 0 rows neg 0 rows]",
      "      /DataSource pixel_stream",
      "    >>",
      "  }",
      "  {",
      "    % RGB colormap.",
      "    /colormap colors 3 mul string def",
      "    compression " PS3_NoCompression " eq",
      "    { currentfile /ASCII85Decode filter colormap readstring pop pop }",
      "    { currentfile colormap readstring pop pop }",
      "    ifelse",
      "    [ /Indexed /DeviceRGB colors 1 sub colormap ] setcolorspace",
      "    <<",
      "      /ImageType 1",
      "      /Width columns",
      "      /Height rows",
      "      /BitsPerComponent 8",
      "      /Decode [0 255]",
      "      /ImageMatrix [columns 0 0 rows neg 0 rows]",
      "      /DataSource pixel_stream",
      "    >>",
      "  }",
      "  ifelse",
      "} bind def",
      "",
      "/NonMaskedImageDict",
      "{",
      "  class " PS3_PseudoClass " eq",
      "  { PseudoClassImageDict }",
      "  { DirectClassImageDict }",
      "  ifelse",
      "} bind def",
      "",
      "/MaskedImageDict",
      "{",
      "  <<",
      "    /ImageType 3",
      "    /InterleaveType 3",
      "    /DataDict NonMaskedImageDict",
      "    /MaskDict",
      "    <<",
      "      /ImageType 1",
      "      /Width columns",
      "      /Height rows",
      "      /BitsPerComponent 1",
      "      /DataSource mask_stream",
      "      /MultipleDataSources false",
      "      /ImageMatrix [ columns 0 0 rows neg 0 rows]",
      "      /Decode [ 0 1 ]",
      "    >>",
      "  >>",
      "} bind def",
      "",
      "/ClipImage",
      "{} def",
      "",
      "/DisplayImage",
      "{",
      "  gsave",
      "  /buffer 512 string def",
      "  % Translation.",
      "  currentfile buffer readline pop",
      "  token pop /x exch def",
      "  token pop /y exch def pop",
      "  x y translate",
      "  % Image size and font size.",
      "  currentfile buffer readline pop",
      "  token pop /x exch def",
      "  token pop /y exch def pop",
      "  currentfile buffer readline pop",
      "  token pop /pointsize exch def pop",
      (const char *) NULL
    },
    *const PostscriptEpilog[]=
    {
      "  x y scale",
      "  % Clipping path.",
      "  currentfile buffer readline pop",
      "  token pop /clipped exch def pop",
      "  % Showpage.",
      "  currentfile buffer readline pop",
      "  token pop /sp exch def pop",
      "  % Image pixel size.",
      "  currentfile buffer readline pop",
      "  token pop /columns exch def",
      "  token pop /rows exch def pop",
      "  % Colorspace (RGB/CMYK).",
      "  currentfile buffer readline pop",
      "  token pop /colorspace exch def pop",
      "  % Transparency.",
      "  currentfile buffer readline pop",
      "  token pop /alpha exch def pop",
      "  % Stencil mask?",
      "  currentfile buffer readline pop",
      "  token pop /stencil exch def pop",
      "  % Image class (direct/pseudo).",
      "  currentfile buffer readline pop",
      "  token pop /class exch def pop",
      "  % Compression type.",
      "  currentfile buffer readline pop",
      "  token pop /compression exch def pop",
      "  % Clip and render.",
      "  /pixel_stream currentfile columns rows compression ByteStreamDecodeFilter def",
      "  clipped { ClipImage } if",
      "  alpha stencil not and",
      "  { MaskedImageDict mask_stream resetfile }",
      "  { NonMaskedImageDict }",
      "  ifelse",
      "  stencil { 0 setgray imagemask } { image } ifelse",
      "  grestore",
      "  sp { showpage } if",
      "} bind def",
      (const char *) NULL
    };

  char
    buffer[MagickPathExtent],
    date[MagickPathExtent],
    **labels,
    page_geometry[MagickPathExtent];

  CompressionType
    compression;

  const char
    *option,
    *const *q,
    *value;

  double
    pointsize;

  GeometryInfo
    geometry_info;

  MagickBooleanType
    status;

  MagickOffsetType
    offset,
    scene,
    start,
    stop;

  MagickStatusType
    flags;

  MemoryInfo
    *pixel_info;

  PointInfo
    delta,
    resolution,
    scale;

  RectangleInfo
    geometry,
    media_info,
    page_info;

  register ssize_t
    i;

  SegmentInfo
    bounds;

  size_t
    imageListLength,
    length,
    page,
    pixel,
    text_size;

  ssize_t
    j;

  time_t
    timer;

  unsigned char
    *pixels;

  /*
    Open output image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
  if (status == MagickFalse)
    return(MagickFalse);
  compression=image->compression;
  if (image_info->compression != UndefinedCompression)
    compression=image_info->compression;
  switch (compression)
  {
    case FaxCompression:
    case Group4Compression:
    {
      if ((SetImageMonochrome(image,exception) == MagickFalse) ||
          (image->alpha_trait != UndefinedPixelTrait))
        compression=RLECompression;
      break;
    }
#if !defined(MAGICKCORE_JPEG_DELEGATE)
    case JPEGCompression:
    {
      compression=RLECompression;
      (void) ThrowMagickException(exception,GetMagickModule(),
        MissingDelegateError,"DelegateLibrarySupportNotBuiltIn","`%s' (JPEG)",
        image->filename);
      break;
    }
#endif
#if !defined(MAGICKCORE_ZLIB_DELEGATE)
    case ZipCompression:
    {
      compression=RLECompression;
      (void) ThrowMagickException(exception,GetMagickModule(),
        MissingDelegateError,"DelegateLibrarySupportNotBuiltIn","`%s' (ZLIB)",
        image->filename);
      break;
    }
#endif
    default:
      break;
  }
  (void) memset(&bounds,0,sizeof(bounds));
  page=0;
  scene=0;
  imageListLength=GetImageListLength(image);
  do
  {
    /*
      Scale relative to dots-per-inch.
    */
    delta.x=DefaultResolution;
    delta.y=DefaultResolution;
    resolution.x=image->resolution.x;
    resolution.y=image->resolution.y;
    if ((resolution.x == 0.0) || (resolution.y == 0.0))
      {
        flags=ParseGeometry(PSDensityGeometry,&geometry_info);
        resolution.x=geometry_info.rho;
        resolution.y=geometry_info.sigma;
        if ((flags & SigmaValue) == 0)
          resolution.y=resolution.x;
      }
    if (image_info->density != (char *) NULL)
      {
        flags=ParseGeometry(image_info->density,&geometry_info);
        resolution.x=geometry_info.rho;
        resolution.y=geometry_info.sigma;
        if ((flags & SigmaValue) == 0)
          resolution.y=resolution.x;
      }
    if (image->units == PixelsPerCentimeterResolution)
      {
        resolution.x=(100.0*2.54*resolution.x+0.5)/100.0;
        resolution.y=(100.0*2.54*resolution.y+0.5)/100.0;
      }
    SetGeometry(image,&geometry);
    (void) FormatLocaleString(page_geometry,MagickPathExtent,"%.20gx%.20g",
      (double) image->columns,(double) image->rows);
    if (image_info->page != (char *) NULL)
      (void) CopyMagickString(page_geometry,image_info->page,MagickPathExtent);
    else
      if ((image->page.width != 0) && (image->page.height != 0))
        (void) FormatLocaleString(page_geometry,MagickPathExtent,
          "%.20gx%.20g%+.20g%+.20g",(double) image->page.width,(double)
          image->page.height,(double) image->page.x,(double) image->page.y);
      else
        if ((image->gravity != UndefinedGravity) &&
            (LocaleCompare(image_info->magick,"PS") == 0))
          (void) CopyMagickString(page_geometry,PSPageGeometry,
            MagickPathExtent);
    (void) ConcatenateMagickString(page_geometry,">",MagickPathExtent);
    (void) ParseMetaGeometry(page_geometry,&geometry.x,&geometry.y,
      &geometry.width,&geometry.height);
    scale.x=PerceptibleReciprocal(resolution.x)*geometry.width*delta.x;
    geometry.width=(size_t) floor(scale.x+0.5);
    scale.y=PerceptibleReciprocal(resolution.y)*geometry.height*delta.y;
    geometry.height=(size_t) floor(scale.y+0.5);
    (void) ParseAbsoluteGeometry(page_geometry,&media_info);
    (void) ParseGravityGeometry(image,page_geometry,&page_info,exception);
    if (image->gravity != UndefinedGravity)
      {
        geometry.x=(-page_info.x);
        geometry.y=(ssize_t) (media_info.height+page_info.y-image->rows);
      }
    pointsize=12.0;
    if (image_info->pointsize != 0.0)
      pointsize=image_info->pointsize;
    text_size=0;
    value=GetImageProperty(image,"label",exception);
    if (value != (const char *) NULL)
      text_size=(size_t) (MultilineCensus(value)*pointsize+12);
    page++;
    if (page == 1)
      {
        /*
          Postscript header on the first page.
        */
        if (LocaleCompare(image_info->magick,"PS3") == 0)
          (void) CopyMagickString(buffer,"%!PS-Adobe-3.0\n",MagickPathExtent);
        else
          (void) CopyMagickString(buffer,"%!PS-Adobe-3.0 EPSF-3.0\n",
            MagickPathExtent);
        (void) WriteBlobString(image,buffer);
        (void) FormatLocaleString(buffer,MagickPathExtent,
          "%%%%Creator: ImageMagick %s\n",MagickLibVersionText);
        (void) WriteBlobString(image,buffer);
        (void) FormatLocaleString(buffer,MagickPathExtent,"%%%%Title: %s\n",
          image->filename);
        (void) WriteBlobString(image,buffer);
        timer=GetMagickTime();
        (void) FormatMagickTime(timer,MagickPathExtent,date);
        (void) FormatLocaleString(buffer,MagickPathExtent,
          "%%%%CreationDate: %s\n",date);
        (void) WriteBlobString(image,buffer);
        bounds.x1=(double) geometry.x;
        bounds.y1=(double) geometry.y;
        bounds.x2=(double) geometry.x+scale.x;
        bounds.y2=(double) geometry.y+scale.y+text_size;
        if ((image_info->adjoin != MagickFalse) &&
            (GetNextImageInList(image) != (Image *) NULL))
          {
            (void) WriteBlobString(image,"%%BoundingBox: (atend)\n");
            (void) WriteBlobString(image,"%%HiResBoundingBox: (atend)\n");
          }
        else
          {
            (void) FormatLocaleString(buffer,MagickPathExtent,
              "%%%%BoundingBox: %g %g %g %g\n",ceil(bounds.x1-0.5),
              ceil(bounds.y1-0.5),floor(bounds.x2+0.5),floor(bounds.y2+0.5));
            (void) WriteBlobString(image,buffer);
            (void) FormatLocaleString(buffer,MagickPathExtent,
              "%%%%HiResBoundingBox: %g %g %g %g\n",bounds.x1,
              bounds.y1,bounds.x2,bounds.y2);
            (void) WriteBlobString(image,buffer);
            if (image->colorspace == CMYKColorspace)
              (void) WriteBlobString(image,
                "%%DocumentProcessColors: Cyan Magenta Yellow Black\n");
            else
              if (SetImageGray(image,exception) != MagickFalse)
                (void) WriteBlobString(image,
                  "%%DocumentProcessColors: Black\n");
          }
        /*
          Font resources
        */
        value=GetImageProperty(image,"label",exception);
        if (value != (const char *) NULL)
          (void) WriteBlobString(image,
            "%%DocumentNeededResources: font Helvetica\n");
        (void) WriteBlobString(image,"%%LanguageLevel: 3\n");
        /*
          Pages, orientation and order.
        */
        if (LocaleCompare(image_info->magick,"PS3") != 0)
          (void) WriteBlobString(image,"%%Pages: 1\n");
        else
          {
            (void) WriteBlobString(image,"%%Orientation: Portrait\n");
            (void) WriteBlobString(image,"%%PageOrder: Ascend\n");
            if (image_info->adjoin == MagickFalse)
              (void) CopyMagickString(buffer,"%%Pages: 1\n",MagickPathExtent);
            else
              (void) FormatLocaleString(buffer,MagickPathExtent,
                "%%%%Pages: %.20g\n",(double) imageListLength);
            (void) WriteBlobString(image,buffer);
          }
        if (image->colorspace == CMYKColorspace)
          (void) WriteBlobString(image,
            "%%DocumentProcessColors: Cyan Magenta Yellow Black\n");
        (void) WriteBlobString(image,"%%EndComments\n");
        /*
          The static postscript procedures prolog.
        */
        (void)WriteBlobString(image,"%%BeginProlog\n");
        for (q=PostscriptProlog; *q; q++)
        {
          (void) WriteBlobString(image,*q);
          (void) WriteBlobByte(image,'\n');
        }
        /*
          One label line for each line in label string.
        */
        value=GetImageProperty(image,"label",exception);
        if (value != (const char *) NULL)
          {
              (void) WriteBlobString(image,"\n  %% Labels.\n  /Helvetica "
              " findfont pointsize scalefont setfont\n");
            for (i=(ssize_t) MultilineCensus(value)-1; i >= 0; i--)
            {
              (void) WriteBlobString(image,
                "  currentfile buffer readline pop token pop\n");
              (void) FormatLocaleString(buffer,MagickPathExtent,
                "  0 y %g add moveto show pop\n",i*pointsize+12);
              (void) WriteBlobString(image,buffer);
            }
          }
        /*
          The static postscript procedures epilog.
        */
        for (q=PostscriptEpilog; *q; q++)
        {
          (void) WriteBlobString(image,*q);
          (void) WriteBlobByte(image,'\n');
        }
        (void)WriteBlobString(image,"%%EndProlog\n");
      }
    (void) FormatLocaleString(buffer,MagickPathExtent,"%%%%Page: 1 %.20g\n",
      (double) page);
    (void) WriteBlobString(image,buffer);
    /*
      Page bounding box.
    */
    (void) FormatLocaleString(buffer,MagickPathExtent,
      "%%%%PageBoundingBox: %.20g %.20g %.20g %.20g\n",(double) geometry.x,
       (double) geometry.y,geometry.x+(double) geometry.width,geometry.y+
       (double) (geometry.height+text_size));
    (void) WriteBlobString(image,buffer);
    /*
      Page process colors if not RGB.
    */
    if (image->colorspace == CMYKColorspace)
      (void) WriteBlobString(image,
        "%%PageProcessColors: Cyan Magenta Yellow Black\n");
    else
      if (SetImageGray(image,exception) != MagickFalse)
        (void) WriteBlobString(image,"%%PageProcessColors: Black\n");
    /*
      Adjust document bounding box to bound page bounding box.
    */
    if ((double) geometry.x < bounds.x1)
      bounds.x1=(double) geometry.x;
    if ((double) geometry.y < bounds.y1)
      bounds.y1=(double) geometry.y;
    if ((double) (geometry.x+scale.x) > bounds.x2)
      bounds.x2=(double) geometry.x+scale.x;
    if ((double) (geometry.y+scale.y+text_size) > bounds.y2)
      bounds.y2=(double) geometry.y+scale.y+text_size;
    /*
      Page font resource if there's a label.
    */
    value=GetImageProperty(image,"label",exception);
    if (value != (const char *) NULL)
      (void) WriteBlobString(image,"%%PageResources: font Helvetica\n");
    /*
      PS clipping path from Photoshop clipping path.
    */
    if (((image->channels & WriteMaskChannel) != 0) ||
        (LocaleNCompare("8BIM:",image->magick_filename,5) != 0))
      (void) WriteBlobString(image,"/ClipImage {} def\n");
    else
      {
        const char
          *value;

        value=GetImageProperty(image,image->magick_filename,exception);
        if (value == (const char *) NULL)
          return(MagickFalse);
        (void) WriteBlobString(image,value);
        (void) WriteBlobByte(image,'\n');
      }
    /*
      Push a dictionary for our own def's if this an EPS.
    */
    if (LocaleCompare(image_info->magick,"PS3") != 0)
      (void) WriteBlobString(image,"userdict begin\n");
    /*
      Image mask.
    */
    if ((image->alpha_trait != UndefinedPixelTrait) &&
        (WritePS3MaskImage(image_info,image,compression,exception) == MagickFalse))
      {
        (void) CloseBlob(image);
        return(MagickFalse);
      }
    /*
      Remember position of BeginData comment so we can update it.
    */
    start=TellBlob(image);
    if (start < 0)
      ThrowWriterException(CorruptImageError,"ImproperImageHeader");
    (void) FormatLocaleString(buffer,MagickPathExtent,
      "%%%%BeginData:%13ld %s Bytes\n",0L,
      compression == NoCompression ? "ASCII" : "BINARY");
    (void) WriteBlobString(image,buffer);
    stop=TellBlob(image);
    if (stop < 0)
      ThrowWriterException(CorruptImageError,"ImproperImageHeader");
    (void) WriteBlobString(image,"DisplayImage\n");
    /*
      Translate, scale, and font point size.
    */
    (void) FormatLocaleString(buffer,MagickPathExtent,"%.20g %.20g\n%g %g\n%g\n",
      (double) geometry.x,(double) geometry.y,scale.x,scale.y,pointsize);
    (void) WriteBlobString(image,buffer);
    /*
      Output labels.
    */
    labels=(char **) NULL;
    value=GetImageProperty(image,"label",exception);
    if (value != (const char *) NULL)
      labels=StringToList(value);
    if (labels != (char **) NULL)
      {
        for (i=0; labels[i] != (char *) NULL; i++)
        {
          if (compression != NoCompression)
            {
              for (j=0; labels[i][j] != '\0'; j++)
                (void) WriteBlobByte(image,(unsigned char) labels[i][j]);
              (void) WriteBlobByte(image,'\n');
            }
          else
            {
              (void) WriteBlobString(image,"<~");
              Ascii85Initialize(image);
              for (j=0; labels[i][j] != '\0'; j++)
                Ascii85Encode(image,(unsigned char) labels[i][j]);
              Ascii85Flush(image);
            }
          labels[i]=DestroyString(labels[i]);
        }
        labels=(char **) RelinquishMagickMemory(labels);
      }
    /*
      Photoshop clipping path active?
    */
    if (((image->channels & WriteMaskChannel) != 0) &&
        (LocaleNCompare("8BIM:",image->magick_filename,5) == 0))
        (void) WriteBlobString(image,"true\n");
      else
        (void) WriteBlobString(image,"false\n");
    /*
      Showpage for non-EPS.
    */
    (void) WriteBlobString(image, LocaleCompare(image_info->magick,"PS3") == 0 ?
      "true\n" : "false\n");
    /*
      Image columns, rows, and color space.
    */
    (void) FormatLocaleString(buffer,MagickPathExtent,"%.20g %.20g\n%s\n",
      (double) image->columns,(double) image->rows,image->colorspace ==
      CMYKColorspace ? PS3_CMYKColorspace : PS3_RGBColorspace);
    (void) WriteBlobString(image,buffer);
    /*
      Masked image?
    */
    (void) WriteBlobString(image,image->alpha_trait != UndefinedPixelTrait ?
      "true\n" : "false\n");
    /*
      Render with imagemask operator?
    */
    option=GetImageOption(image_info,"ps3:imagemask");
    (void) WriteBlobString(image,((option != (const char *) NULL) &&
      (SetImageMonochrome(image,exception) != MagickFalse)) ?
      "true\n" : "false\n");
    /*
      Output pixel data.
    */
    pixels=(unsigned char *) NULL;
    length=0;
    if ((image_info->type != TrueColorType) &&
        (image_info->type != TrueColorAlphaType) &&
        (image_info->type != ColorSeparationType) &&
        (image_info->type != ColorSeparationAlphaType) &&
        (image->colorspace != CMYKColorspace) &&
        ((SetImageGray(image,exception) != MagickFalse) ||
         (SetImageMonochrome(image,exception) != MagickFalse)))
      {
        /*
          Gray images.
        */
        (void) WriteBlobString(image,PS3_PseudoClass"\n");
        switch (compression)
        {
          case NoCompression:
          default:
          {
            (void) WriteBlobString(image,PS3_NoCompression"\n");
            break;
          }
          case FaxCompression:
          case Group4Compression:
          {
            (void) WriteBlobString(image,PS3_FaxCompression"\n");
            break;
          }
          case JPEGCompression:
          {
            (void) WriteBlobString(image,PS3_JPEGCompression"\n");
            break;
          }
          case LZWCompression:
          {
            (void) WriteBlobString(image,PS3_LZWCompression"\n");
            break;
          }
          case RLECompression:
          {
            (void) WriteBlobString(image,PS3_RLECompression"\n");
            break;
          }
          case ZipCompression:
          {
            (void) WriteBlobString(image,PS3_ZipCompression"\n");
            break;
          }
        }
        /*
          Number of colors -- 0 for single component non-color mapped data.
        */
        (void) WriteBlobString(image,"0\n");
        /*
          1 bit or 8 bit components?
        */
        (void) FormatLocaleString(buffer,MagickPathExtent,"%d\n",
          SetImageMonochrome(image,exception) != MagickFalse ? 1 : 8);
        (void) WriteBlobString(image,buffer);
        /*
          Image data.
        */
        if (compression == JPEGCompression)
          status=InjectImageBlob(image_info,image,image,"jpeg",exception);
        else
          if ((compression == FaxCompression) ||
              (compression == Group4Compression))
            {
              if (LocaleCompare(CCITTParam,"0") == 0)
                status=HuffmanEncodeImage(image_info,image,image,exception);
              else
                status=Huffman2DEncodeImage(image_info,image,image,exception);
            }
          else
            {
              status=SerializeImageChannel(image_info,image,&pixel_info,&length,
                exception);
              if (status == MagickFalse)
                {
                  (void) CloseBlob(image);
                  return(MagickFalse);
                }
              pixels=(unsigned char *) GetVirtualMemoryBlob(pixel_info);
              switch (compression)
              {
                case NoCompression:
                default:
                {
                  Ascii85Initialize(image);
                  for (i=0; i < (ssize_t) length; i++)
                    Ascii85Encode(image,pixels[i]);
                  Ascii85Flush(image);
                  status=MagickTrue;
                  break;
                }
                case LZWCompression:
                {
                  status=LZWEncodeImage(image,length,pixels,exception);
                  break;
                }
                case RLECompression:
                {
                  status=PackbitsEncodeImage(image,length,pixels,exception);
                  break;
                }
                case ZipCompression:
                {
                  status=ZLIBEncodeImage(image,length,pixels,exception);
                  break;
                }
              }
              pixel_info=RelinquishVirtualMemory(pixel_info);
            }
      }
    else
      if ((image->storage_class == DirectClass) || (image->colors > 256) ||
          (compression == JPEGCompression))
        {
          /*
            Truecolor image.
          */
          (void) WriteBlobString(image,PS3_DirectClass"\n");
          switch (compression)
          {
            case NoCompression:
            default:
            {
              (void) WriteBlobString(image,PS3_NoCompression"\n");
              break;
            }
            case RLECompression:
            {
              (void) WriteBlobString(image,PS3_RLECompression"\n");
              break;
            }
            case JPEGCompression:
            {
              (void) WriteBlobString(image,PS3_JPEGCompression"\n");
              break;
            }
            case LZWCompression:
            {
              (void) WriteBlobString(image,PS3_LZWCompression"\n");
              break;
            }
            case ZipCompression:
            {
              (void) WriteBlobString(image,PS3_ZipCompression"\n");
              break;
            }
          }
          /*
            Image data.
          */
          if (compression == JPEGCompression)
            status=InjectImageBlob(image_info,image,image,"jpeg",exception);
          else
            {
              /*
                Stream based compressions.
              */
              status=SerializeImage(image_info,image,&pixel_info,&length,
                exception);
              if (status == MagickFalse)
                {
                  (void) CloseBlob(image);
                  return(MagickFalse);
                }
              pixels=(unsigned char *) GetVirtualMemoryBlob(pixel_info);
              switch (compression)
              {
                case NoCompression:
                default:
                {
                  Ascii85Initialize(image);
                  for (i=0; i < (ssize_t) length; i++)
                    Ascii85Encode(image,pixels[i]);
                  Ascii85Flush(image);
                  status=MagickTrue;
                  break;
                }
                case RLECompression:
                {
                  status=PackbitsEncodeImage(image,length,pixels,exception);
                  break;
                }
                case LZWCompression:
                {
                  status=LZWEncodeImage(image,length,pixels,exception);
                  break;
                }
                case ZipCompression:
                {
                  status=ZLIBEncodeImage(image,length,pixels,exception);
                  break;
                }
              }
              pixel_info=RelinquishVirtualMemory(pixel_info);
            }
          }
        else
          {
            /*
              Colormapped images.
            */
            (void) WriteBlobString(image,PS3_PseudoClass"\n");
            switch (compression)
            {
              case NoCompression:
              default:
              {
                (void) WriteBlobString(image,PS3_NoCompression"\n");
                break;
              }
              case RLECompression:
              {
                (void) WriteBlobString(image,PS3_RLECompression"\n");
                break;
              }
              case LZWCompression:
              {
                (void) WriteBlobString(image,PS3_LZWCompression"\n");
                break;
              }
              case ZipCompression:
              {
                (void) WriteBlobString(image,PS3_ZipCompression"\n");
                break;
              }
            }
            /*
              Number of colors in color map.
            */
            (void) FormatLocaleString(buffer,MagickPathExtent,"%.20g\n",
              (double) image->colors);
            (void) WriteBlobString(image,buffer);
            /*
              Color map - uncompressed.
            */
            if ((compression != NoCompression) &&
                (compression != UndefinedCompression))
              {
                for (i=0; i < (ssize_t) image->colors; i++)
                {
                  pixel=ScaleQuantumToChar(image->colormap[i].red);
                  (void) WriteBlobByte(image,(unsigned char) pixel);
                  pixel=ScaleQuantumToChar(image->colormap[i].green);
                  (void) WriteBlobByte(image,(unsigned char) pixel);
                  pixel=ScaleQuantumToChar(image->colormap[i].blue);
                  (void) WriteBlobByte(image,(unsigned char) pixel);
                }
              }
            else
              {
                Ascii85Initialize(image);
                for (i=0; i < (ssize_t) image->colors; i++)
                {
                  pixel=ScaleQuantumToChar(image->colormap[i].red);
                  Ascii85Encode(image,(unsigned char) pixel);
                  pixel=ScaleQuantumToChar(image->colormap[i].green);
                  Ascii85Encode(image,(unsigned char) pixel);
                  pixel=ScaleQuantumToChar(image->colormap[i].blue);
                  Ascii85Encode(image,(unsigned char) pixel);
                }
                Ascii85Flush(image);
              }
            status=SerializeImageIndexes(image_info,image,&pixel_info,&length,
              exception);
            if (status == MagickFalse)
              {
                (void) CloseBlob(image);
                return(MagickFalse);
              }
            pixels=(unsigned char *) GetVirtualMemoryBlob(pixel_info);
            switch (compression)
            {
              case NoCompression:
              default:
              {
                Ascii85Initialize(image);
                for (i=0; i < (ssize_t) length; i++)
                  Ascii85Encode(image,pixels[i]);
                Ascii85Flush(image);
                status=MagickTrue;
                break;
              }
              case RLECompression:
              {
                status=PackbitsEncodeImage(image,length,pixels,exception);
                break;
              }
              case LZWCompression:
              {
                status=LZWEncodeImage(image,length,pixels,exception);
                break;
              }
              case ZipCompression:
              {
                status=ZLIBEncodeImage(image,length,pixels,exception);
                break;
              }
            }
            pixel_info=RelinquishVirtualMemory(pixel_info);
          }
    (void) WriteBlobByte(image,'\n');
    if (status == MagickFalse)
      {
        (void) CloseBlob(image);
        return(MagickFalse);
      }
    /*
      Update BeginData now that we know the data size.
    */
    length=(size_t) (TellBlob(image)-stop);
    stop=TellBlob(image);
    if (stop < 0)
      ThrowWriterException(CorruptImageError,"ImproperImageHeader");
    offset=SeekBlob(image,start,SEEK_SET);
    if (offset < 0)
      ThrowWriterException(CorruptImageError,"ImproperImageHeader");
    (void) FormatLocaleString(buffer,MagickPathExtent,
      "%%%%BeginData:%13ld %s Bytes\n",(long) length,
      compression == NoCompression ? "ASCII" : "BINARY");
    (void) WriteBlobString(image,buffer);
    offset=SeekBlob(image,stop,SEEK_SET);
    (void) WriteBlobString(image,"%%EndData\n");
    /*
      End private dictionary if this an EPS.
    */
    if (LocaleCompare(image_info->magick,"PS3") != 0)
      (void) WriteBlobString(image,"end\n");
    (void) WriteBlobString(image,"%%PageTrailer\n");
    if (GetNextImageInList(image) == (Image *) NULL)
      break;
    image=SyncNextImageInList(image);
    status=SetImageProgress(image,SaveImagesTag,scene++,imageListLength);
    if (status == MagickFalse)
      break;
  } while (image_info->adjoin != MagickFalse);
  (void) WriteBlobString(image,"%%Trailer\n");
  if (page > 1)
    {
      (void) FormatLocaleString(buffer,MagickPathExtent,
        "%%%%BoundingBox: %g %g %g %g\n",ceil(bounds.x1-0.5),
        ceil(bounds.y1-0.5),floor(bounds.x2+0.5),floor(bounds.y2+0.5));
      (void) WriteBlobString(image,buffer);
      (void) FormatLocaleString(buffer,MagickPathExtent,
        "%%%%HiResBoundingBox: %g %g %g %g\n",bounds.x1,bounds.y1,bounds.x2,
        bounds.y2);
      (void) WriteBlobString(image,buffer);
    }
  (void) WriteBlobString(image,"%%EOF\n");
  (void) CloseBlob(image);
  return(MagickTrue);
}

Fixed memory leaks reported in #1557.

#define PS3_NoCompression "0"

#define PS3_FaxCompression "1"

#define PS3_JPEGCompression "2"

#define PS3_LZWCompression "3"

#define PS3_RLECompression "4"

#define PS3_ZipCompression "5"

#define PS3_RGBColorspace "0"

#define PS3_CMYKColorspace "1"

#define PS3_DirectClass "0"

#define PS3_PseudoClass "1"

#define CCITTParam  "-1"

#define CCITTParam  "0"

static MagickBooleanType
  WritePS3Image(const ImageInfo *,Image *,ExceptionInfo *);


static MagickBooleanType SerializeImage(const ImageInfo *image_info,
  Image *image,MemoryInfo **pixel_info,size_t *length,ExceptionInfo *exception)
{
  MagickBooleanType
    status;

  register const Quantum
    *p;

  register ssize_t
    x;

  register unsigned char
    *q;

  ssize_t
    y;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickCoreSignature);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  status=MagickTrue;
  *length=(image->colorspace == CMYKColorspace ? 4 : 3)*(size_t)
    image->columns*image->rows;
  *pixel_info=AcquireVirtualMemory(*length,sizeof(*q));
  if (*pixel_info == (MemoryInfo *) NULL)
    ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
  q=(unsigned char *) GetVirtualMemoryBlob(*pixel_info);
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
    if (p == (const Quantum *) NULL)
      break;
    if (image->colorspace != CMYKColorspace)
      for (x=0; x < (ssize_t) image->columns; x++)
      {
        *q++=ScaleQuantumToChar(GetPixelRed(image,p));
        *q++=ScaleQuantumToChar(GetPixelGreen(image,p));
        *q++=ScaleQuantumToChar(GetPixelBlue(image,p));
        p+=GetPixelChannels(image);
      }
    else
      for (x=0; x < (ssize_t) image->columns; x++)
      {
        *q++=ScaleQuantumToChar(GetPixelRed(image,p));
        *q++=ScaleQuantumToChar(GetPixelGreen(image,p));
        *q++=ScaleQuantumToChar(GetPixelBlue(image,p));
        *q++=ScaleQuantumToChar(GetPixelBlack(image,p));
        p+=GetPixelChannels(image);
      }
    if (image->previous == (Image *) NULL)
      {
        status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
          image->rows);
        if (status == MagickFalse)
          break;
      }
  }
  if (status == MagickFalse)
    *pixel_info=RelinquishVirtualMemory(*pixel_info);
  return(status);
}

Fixed memory leaks reported in #1557.

static MagickBooleanType
  WritePS3Image(const ImageInfo *,Image *,ExceptionInfo *);


char *secsTOtime(double seconds) {
  ap_time time_duration = {0};
  uint32_t whole_secs = (uint32_t)(seconds / 1);

  time_duration.rem_millisecs = seconds - (double)whole_secs;
  time_duration.hours = whole_secs / 3600;
  whole_secs -= time_duration.hours * 3600;
  time_duration.minutes = whole_secs / 60;
  whole_secs -= time_duration.minutes * 60;
  time_duration.seconds = whole_secs;

  static char hhmmss_time[20];
  memset(hhmmss_time, 0, 20);
  char milli[5];
  memset(milli, 0, 5);

  uint8_t time_offset = 0;
  if (time_duration.hours > 0) {
    if (time_duration.hours < 10) {
      sprintf(hhmmss_time, "0%u:", time_duration.hours);
    } else {
      sprintf(hhmmss_time, "%u:", time_duration.hours);
    }
    time_offset += 3;
  }
  if (time_duration.minutes > 0) {
    if (time_duration.minutes < 10) {
      sprintf(hhmmss_time + time_offset, "0%u:", time_duration.minutes);
    } else {
      sprintf(hhmmss_time + time_offset, "%u:", time_duration.minutes);
    }
    time_offset += 3;
  } else {
    memcpy(hhmmss_time + time_offset, "0:", 2);
    time_offset += 2;
  }
  if (time_duration.seconds > 0) {
    if (time_duration.seconds < 10) {
      sprintf(hhmmss_time + time_offset, "0%u", time_duration.seconds);
    } else {
      sprintf(hhmmss_time + time_offset, "%u", time_duration.seconds);
    }
    time_offset += 2;
  } else {
    memcpy(hhmmss_time + time_offset, "0.", 2);
    time_offset += 1;
  }

  sprintf(
      milli,
      "%.2lf",
      time_duration.rem_millisecs); // sprintf the double float into a new
                                    // string because I don't know if there is a
                                    // way to print without a leading zero
  memcpy(hhmmss_time + time_offset, milli + 1, 3);

  return *&hhmmss_time;
}

Avoid stack overflow

refs: https://github.com/wez/atomicparsley/issues/32

MovieInfo movie_info = {0};

iods_OD iods_info = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};


void APar_ExtractMovieDetails(char *uint32_buffer,
                              FILE *isofile,
                              AtomicInfo *mvhd_atom) {
  if (mvhd_atom->AtomicVerFlags & 0x01000000) {
    movie_info.creation_time =
        APar_read64(uint32_buffer, isofile, mvhd_atom->AtomicStart + 12);
    movie_info.modified_time =
        APar_read64(uint32_buffer, isofile, mvhd_atom->AtomicStart + 20);
    movie_info.timescale =
        APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 28);
    movie_info.duration =
        APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 32);
    movie_info.timescale =
        APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 36);
    movie_info.duration =
        APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 40);
    movie_info.playback_rate =
        APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 44);
    movie_info.volume =
        APar_read16(uint32_buffer, isofile, mvhd_atom->AtomicStart + 48);
  } else {
    movie_info.creation_time = (uint64_t)APar_read32(
        uint32_buffer, isofile, mvhd_atom->AtomicStart + 12);
    movie_info.modified_time = (uint64_t)APar_read32(
        uint32_buffer, isofile, mvhd_atom->AtomicStart + 16);
    movie_info.timescale =
        APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 20);
    movie_info.duration =
        APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 24);
    movie_info.playback_rate =
        APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 28);
    movie_info.volume =
        APar_read16(uint32_buffer, isofile, mvhd_atom->AtomicStart + 32);
  }

  movie_info.seconds = (float)movie_info.duration / (float)movie_info.timescale;
#if defined(_MSC_VER)
  __int64 media_bits = (__int64)mdatData * 8;
#else
  uint64_t media_bits = (uint64_t)mdatData * 8;
#endif
  movie_info.simple_bitrate_calc =
      ((double)media_bits / movie_info.seconds) / 1000.0;

  return;
}


void APar_Extract_devc_Info(FILE *isofile,
                            short track_level_atom,
                            TrackInfo *track_info) {
  uint64_t offset_into_devc = 8;
  APar_readX(track_info->encoder_name,
             isofile,
             parsedAtoms[track_level_atom].AtomicStart + offset_into_devc,
             4);
  return;
}


char *uint32tochar4(uint32_t lnum, char *data) {
  data[0] = (lnum >> 24) & 0xff;
  data[1] = (lnum >> 16) & 0xff;
  data[2] = (lnum >> 8) & 0xff;
  data[3] = (lnum >> 0) & 0xff;
  return data;
}


void APar_Extract_d263_Info(char *uint32_buffer,
                            FILE *isofile,
                            short track_level_atom,
                            TrackInfo *track_info) {
  uint64_t offset_into_d263 = 8;
  APar_readX(track_info->encoder_name,
             isofile,
             parsedAtoms[track_level_atom].AtomicStart + offset_into_d263,
             4);
  track_info->level = APar_read8(isofile,
                                 parsedAtoms[track_level_atom].AtomicStart +
                                     offset_into_d263 + 4 + 1);
  track_info->profile = APar_read8(isofile,
                                   parsedAtoms[track_level_atom].AtomicStart +
                                       offset_into_d263 + 4 + 2);
  // possible 'bitr' bitrate box afterwards
  return;
}


void APar_ExtractBrands(char *filepath) {
  FILE *a_file = APar_OpenISOBaseMediaFile(filepath, true);
  char buffer[16] = {};
  uint32_t atom_length = 0;
  uint8_t file_type_offset = 0;
  uint32_t compatible_brand = 0;
  bool cb_V2ISOBMFF = false;

  APar_read32(buffer, a_file, 4);
  if (memcmp(buffer, "ftyp", 4) == 0) {
    atom_length = APar_read32(buffer, a_file, 0);
  } else {
    APar_readX(buffer, a_file, 0, 12);
    if (memcmp(buffer,
               "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A",
               12) == 0) {
      APar_readX(buffer, a_file, 12, 12);
      if (memcmp(buffer + 4, "ftypmjp2", 8) == 0 ||
          memcmp(buffer + 4, "ftypmj2s", 8) == 0) {
        atom_length = UInt32FromBigEndian(buffer);
        file_type_offset = 12;
      }
    }
  }

  if (atom_length > 0) {
    memset(buffer, 0, 16);
    APar_readX(buffer, a_file, 8 + file_type_offset, 4);
    printBOM();
    fprintf(stdout, " Major Brand: %s", buffer);
    APar_IdentifyBrand(buffer);

    if (memcmp(buffer, "isom", 4) == 0) {
      APar_ScanAtoms(filepath); // scan_file = true;
    }

    uint32_t minor_version = APar_read32(buffer, a_file, 12 + file_type_offset);
    fprintf(stdout, "  -  version %" PRIu32 "\n", minor_version);

    fprintf(stdout, " Compatible Brands:");
    for (uint64_t i = 16 + file_type_offset; i < atom_length; i += 4) {
      APar_readX(buffer, a_file, i, 4);
      compatible_brand = UInt32FromBigEndian(buffer);
      if (compatible_brand != 0) {
        fprintf(stdout, " %s", buffer);
        if (compatible_brand == 0x6D703432 || compatible_brand == 0x69736F32) {
          cb_V2ISOBMFF = true;
        }
      }
    }
    fprintf(stdout, "\n");
  }

  APar_OpenISOBaseMediaFile(filepath, false);

  fprintf(stdout, " Tagging schemes available:\n");
  switch (metadata_style) {
  case ITUNES_STYLE: {
    fprintf(stdout, "   iTunes-style metadata allowed.\n");
    break;
  }
  case THIRD_GEN_PARTNER:
  case THIRD_GEN_PARTNER_VER1_REL6:
  case THIRD_GEN_PARTNER_VER1_REL7:
  case THIRD_GEN_PARTNER_VER2: {
    fprintf(stdout, "   3GP-style asset metadata allowed.\n");
    break;
  }
  case THIRD_GEN_PARTNER_VER2_REL_A: {
    fprintf(stdout,
            "   3GP-style asset metadata allowed [& unimplemented GAD "
            "(Geographical Area Description) asset].\n");
    break;
  }
  }
  if (cb_V2ISOBMFF || metadata_style == THIRD_GEN_PARTNER_VER1_REL7) {
    fprintf(stdout,
            "   ID3 tags on ID32 atoms @ file/movie/track level allowed.\n");
  }
  fprintf(stdout,
          "   ISO-copyright notices @ movie and/or track level "
          "allowed.\n   uuid private user extension tags allowed.\n");
}


uint8_t APar_ExtractChannelInfo(FILE *isofile, uint32_t pos) {
  uint8_t packed_channels = APar_read8(isofile, pos);
  uint8_t unpacked_channels =
      (packed_channels << 1); // just shift the first bit off the table
  unpacked_channels =
      (unpacked_channels >> 4); // and slide it on over back on the uint8_t
  return unpacked_channels;
}


void APar_TrackLevelInfo(Trackage *track, const char *track_search_atom_name) {
  uint8_t track_tally = 0;
  short iter = 0;

  while (parsedAtoms[iter].NextAtomNumber != 0) {

    if (strncmp(parsedAtoms[iter].AtomicName, "trak", 4) == 0) {
      track_tally += 1;
      if (track->track_num == 0) {
        track->total_tracks += 1;

      } else if (track->track_num == track_tally) {

        short next_atom = parsedAtoms[iter].NextAtomNumber;
        while (parsedAtoms[next_atom].AtomicLevel >
               parsedAtoms[iter].AtomicLevel) {

          if (strncmp(parsedAtoms[next_atom].AtomicName,
                      track_search_atom_name,
                      4) == 0) {

            track->track_atom = parsedAtoms[next_atom].AtomicNumber;
            return;
          } else {
            next_atom = parsedAtoms[next_atom].NextAtomNumber;
          }
          if (parsedAtoms[next_atom].AtomicLevel ==
              parsedAtoms[iter].AtomicLevel) {
            track->track_atom = 0;
          }
        }
      }
    }
    iter = parsedAtoms[iter].NextAtomNumber;
  }
  return;
}


void APar_ShowObjectProfileInfo(uint8_t track_type, TrackInfo *track_info) {
  if (track_info->contains_esds) {
    switch (track_info->ObjectTypeIndication) {
    // 0x00 es Lambada/Verboten/Forbidden
    case 0x01:
    case 0x02: {
      fprintf(stdout, "  MPEG-4 Systems (BIFS/ObjDesc)");
      break;
    }
    case 0x03: {
      fprintf(stdout, "  Interaction Stream");
      break;
    }
    case 0x04: {
      fprintf(stdout, "  MPEG-4 Systems Extended BIFS");
      break;
    }
    case 0x05: {
      fprintf(stdout, "  MPEG-4 Systems AFX");
      break;
    }
    case 0x06: {
      fprintf(stdout, "  Font Data Stream");
      break;
    }
    case 0x08: {
      fprintf(stdout, "  Synthesized Texture Stream");
      break;
    }
    case 0x07: {
      fprintf(stdout, "  Streaming Text Stream");
      break;
    }
    // 0x09-0x1F reserved
    case 0x20: {
      APar_ShowMPEG4VisualProfileInfo(track_info);
      break;
    }

    case 0x40: { // vererable mpeg-4 aac
      APar_ShowMPEG4AACProfileInfo(track_info);
      break;
    }

    // 0x41-0x5F reserved
    case 0x60: {
      fprintf(stdout,
              "  MPEG-2 Visual Simple Profile"); //'Visual ISO/IEC 13818-2
                                                 // Simple Profile'
      break;
    }
    case 0x61: {
      fprintf(stdout, "  MPEG-2 Visual Main Profile"); //'Visual ISO/IEC 13818-2
                                                       // Main Profile'
      break;
    }
    case 0x62: {
      fprintf(
          stdout,
          "  MPEG-2 Visual SNR Profile"); //'Visual ISO/IEC 13818-2 SNR Profile'
      break;
    }
    case 0x63: {
      fprintf(stdout,
              "  MPEG-2 Visual Spatial Profile"); //'Visual ISO/IEC 13818-2
                                                  // Spatial Profile'
      break;
    }
    case 0x64: {
      fprintf(stdout, "  MPEG-2 Visual High Profile"); //'Visual ISO/IEC 13818-2
                                                       // High Profile'
      break;
    }
    case 0x65: {
      fprintf(stdout, "  MPEG-2 Visual 4:2:2 Profile"); //'Visual ISO/IEC
                                                        // 13818-2 422 Profile'
      break;
    }
    case 0x66: {
      fprintf(
          stdout,
          "  MPEG-2 AAC Main Profile"); //'Audio ISO/IEC 13818-7 Main Profile'
      break;
    }
    case 0x67: {
      fprintf(stdout,
              "  MPEG-2 AAC Low Complexity Profile"); // Audio ISO/IEC 13818-7
                                                      // LowComplexity Profile
      break;
    }
    case 0x68: {
      fprintf(
          stdout,
          "  MPEG-2 AAC Scaleable Sample Rate Profile"); //'Audio ISO/IEC
                                                         // 13818-7 Scaleable
                                                         // Sampling Rate
                                                         // Profile'
      break;
    }
    case 0x69: {
      fprintf(stdout, "  MPEG-2 Audio"); //'Audio ISO/IEC 13818-3'
      break;
    }
    case 0x6A: {
      fprintf(stdout, "  MPEG-1 Visual"); //'Visual ISO/IEC 11172-2'
      break;
    }
    case 0x6B: {
      fprintf(stdout, "  MPEG-1 Audio"); //'Audio ISO/IEC 11172-3'
      break;
    }
    case 0x6C: {
      fprintf(stdout, "  JPEG"); //'Visual ISO/IEC 10918-1'
      break;
    }
    case 0x6D: {
      fprintf(stdout, "  PNG"); // http://www.mp4ra.org/object.html
      break;
    }
    case 0x6E: {
      fprintf(stdout, "  JPEG2000"); //'Visual ISO/IEC 15444-1'
      break;
    }
    case 0xA0: {
      fprintf(stdout, "  3GPP2 EVRC Voice"); // http://www.mp4ra.org/object.html
      break;
    }
    case 0xA1: {
      fprintf(stdout, "  3GPP2 SMV Voice"); // http://www.mp4ra.org/object.html
      break;
    }
    case 0xA2: {
      fprintf(
          stdout,
          "  3GPP2 Compact Multimedia Format"); // http://www.mp4ra.org/object.html
      break;
    }

    // 0xC0-0xE0 user private
    case 0xE1: {
      fprintf(stdout,
              "  3GPP2 QCELP (14K Voice)"); // http://www.mp4ra.org/object.html
      break;
    }
      // 0xE2-0xFE user private
      // 0xFF no object type specified

    default: {
      // so many profiles, so little desire to list them all (in 14496-2 which I
      // don't have)
      if (movie_info.contains_iods && iods_info.audio_profile == 0xFE) {
        fprintf(stdout,
                "  Private user object: 0x%X",
                track_info->ObjectTypeIndication);
      } else {
        fprintf(
            stdout,
            "  Object Type Indicator: 0x%X  Description Ojbect Type ID: 0x%X\n",
            track_info->ObjectTypeIndication,
            track_info->descriptor_object_typeID);
      }
      break;
    }
    }

  } else if (track_type == AVC1_TRACK) {
    // profiles & levels are in the 14496-10 pdf (which I don't have access to),
    // so... http://lists.mpegif.org/pipermail/mp4-tech/2006-January/006255.html
    // http://iphome.hhi.de/suehring/tml/doc/lenc/html/configfile_8c-source.html
    // 66=baseline, 77=main, 88=extended; 100=High, 110=High 10, 122=High 4:2:2,
    // 144=High 4:4:4

    switch (track_info->profile) {
    case 66: {
      fprintf(stdout, "  AVC Baseline Profile");
      break;
    }
    case 77: {
      fprintf(stdout, "  AVC Main Profile");
      break;
    }
    case 88: {
      fprintf(stdout, "  AVC Extended Profile");
      break;
    }
    case 100: {
      fprintf(stdout, "  AVC High Profile");
      break;
    }
    case 110: {
      fprintf(stdout, "  AVC High 10 Profile");
      break;
    }
    case 122: {
      fprintf(stdout, "  AVC High 4:2:2 Profile");
      break;
    }
    case 144: {
      fprintf(stdout, "  AVC High 4:4:4 Profile");
      break;
    }
    default: {
      fprintf(stdout, "  Unknown Profile: %u", track_info->profile);
      break;
    }
    } // end profile switch

    // Don't have access to levels either, but working off of:
    // http://iphome.hhi.de/suehring/tml/doc/lenc/html/configfile_8c-source.html

    // and the 15 levels it says here:
    // http://www.chiariglione.org/mpeg/technologies/mp04-avc/index.htm (1b in
    // http://en.wikipedia.org/wiki/H.264 seems nonsensical) working backwards,
    // we get... a simple 2 digit number (with '20' just drop the 0; with 21,
    // put in a decimal)
    if (track_info->level > 0) {
      switch (track_info->level) {
      case 10:
      case 20:
      case 30:
      case 40:
      case 50: {
        fprintf(stdout, ",  Level %u", track_info->level / 10);
        break;
      }
      case 11:
      case 12:
      case 13:
      case 21:
      case 22:
      case 31:
      case 32:
      case 41:
      case 42:
      case 51: {
        fprintf(stdout,
                ",  Level %u.%u",
                track_info->level / 10,
                track_info->level % 10);
        break;
      }
      default: {
        fprintf(stdout,
                ", Unknown level %u.%u",
                track_info->level / 10,
                track_info->level % 10);
      }

      } // end switch
    }   // end level if
  } else if (track_type == S_AMR_TRACK) {
    char amr_modes[500] = {};
    if (track_info->track_codec == 0x73616D72 ||
        track_info->track_codec == 0x73617762) {
      if (track_info->amr_modes & 0x0001)
        mem_append("0", amr_modes);
      if (track_info->amr_modes & 0x0002)
        mem_append("1", amr_modes);
      if (track_info->amr_modes & 0x0004)
        mem_append("2", amr_modes);
      if (track_info->amr_modes & 0x0008)
        mem_append("3", amr_modes);
      if (track_info->amr_modes & 0x0010)
        mem_append("4", amr_modes);
      if (track_info->amr_modes & 0x0020)
        mem_append("5", amr_modes);
      if (track_info->amr_modes & 0x0040)
        mem_append("6", amr_modes);
      if (track_info->amr_modes & 0x0080)
        mem_append("7", amr_modes);
      if (track_info->amr_modes & 0x0100)
        mem_append("8", amr_modes);
      if (strlen(amr_modes) == 0)
        memcpy(amr_modes, "none", 4);
    } else if (track_info->track_codec == 0x73766D72) {
      if (track_info->amr_modes & 0x0001)
        mem_append("VMR-WB Mode 0, ", amr_modes);
      if (track_info->amr_modes & 0x0002)
        mem_append("VMR-WB Mode 1, ", amr_modes);
      if (track_info->amr_modes & 0x0004)
        mem_append("VMR-WB Mode 2, ", amr_modes);
      if (track_info->amr_modes & 0x0008)
        mem_append("VMR-WB Mode 3 (AMR-WB interoperable mode), ", amr_modes);
      if (track_info->amr_modes & 0x0010)
        mem_append("VMR-WB Mode 4, ", amr_modes);
      if (track_info->amr_modes & 0x0020)
        mem_append("VMR-WB Mode 2 with maximum half-rate, ", amr_modes);
      if (track_info->amr_modes & 0x0040)
        mem_append("VMR-WB Mode 4 with maximum half-rate, ", amr_modes);
      uint16_t amr_modes_len = strlen(amr_modes);
      if (amr_modes_len > 0)
        memset(amr_modes + (amr_modes_len - 1), 0, 2);
    }

    if (track_info->track_codec == 0x73616D72) { // samr
      fprintf(stdout,
              "  AMR Narrow-Band. Modes: %s. Encoder vendor code: %s\n",
              amr_modes,
              track_info->encoder_name);
    } else if (track_info->track_codec == 0x73617762) { // sawb
      fprintf(stdout,
              "  AMR Wide-Band. Modes: %s. Encoder vendor code: %s\n",
              amr_modes,
              track_info->encoder_name);
    } else if (track_info->track_codec == 0x73617770) { // sawp
      fprintf(stdout,
              "  AMR Wide-Band WB+. Encoder vendor code: %s\n",
              track_info->encoder_name);
    } else if (track_info->track_codec == 0x73766D72) { // svmr
      fprintf(stdout,
              "  AMR VBR Wide-Band. Encoder vendor code: %s\n",
              track_info->encoder_name);
    }
  } else if (track_type == EVRC_TRACK) {
    fprintf(stdout,
            "  EVRC (Enhanced Variable Rate Coder). Encoder vendor code: %s\n",
            track_info->encoder_name);

  } else if (track_type == QCELP_TRACK) {
    fprintf(stdout,
            "  QCELP (Qualcomm Code Excited Linear Prediction). Encoder vendor "
            "code: %s\n",
            track_info->encoder_name);

  } else if (track_type == S263_TRACK) {
    if (track_info->profile == 0) {
      fprintf(stdout,
              "  H.263 Baseline Profile, Level %u. Encoder vendor code: %s",
              track_info->level,
              track_info->encoder_name);
    } else {
      fprintf(stdout,
              "  H.263 Profile: %u, Level %u. Encoder vendor code: %s",
              track_info->profile,
              track_info->level,
              track_info->encoder_name);
    }
  }
  if (track_type == AUDIO_TRACK) {
    if (track_info->section5_length == 0) {
      fprintf(stdout, "    channels: (%u)\n", track_info->channels);
    } else {
      fprintf(stdout, "    channels: [%u]\n", track_info->channels);
    }
  }
}


void mem_append(const char *add_string, char *dest_string) {
  uint8_t str_len = strlen(dest_string);
  if (str_len > 0) {
    memcpy(dest_string + str_len, ", ", 2);
    memcpy(dest_string + str_len + 2, add_string, strlen(add_string));
  } else {
    memcpy(dest_string, add_string, strlen(add_string));
  }
  return;
}


uint16_t purge_extraneous_characters(char *data) {
  uint16_t purgings = 0;
  uint16_t str_len = strlen(data);
  for (uint16_t str_offset = 0; str_offset < str_len; str_offset++) {
    if (data[str_offset] < 32 || data[str_offset] == 127) {
      data[str_offset] = 19;
      purgings++;
      break;
    }
  }
  return purgings;
}


void APar_Extract_iods_Info(FILE *isofile, AtomicInfo *iods_atom) {
  uint64_t iods_offset = iods_atom->AtomicStart + 8;
  if (iods_atom->AtomicVerFlags == 0 &&
      APar_read8(isofile, iods_offset + 4) == 0x10) {
    iods_offset += 5;
    iods_offset += APar_skip_filler(isofile, iods_offset);
    uint8_t iods_objdescrip_len = APar_read8(isofile, iods_offset);
    iods_offset++;
    if (iods_objdescrip_len >= 7) {
      iods_info.od_profile_level = APar_read8(isofile, iods_offset + 2);
      iods_info.scene_profile_level = APar_read8(isofile, iods_offset + 3);
      iods_info.audio_profile = APar_read8(isofile, iods_offset + 4);
      iods_info.video_profile_level = APar_read8(isofile, iods_offset + 5);
      iods_info.graphics_profile_level = APar_read8(isofile, iods_offset + 6);
    }
  }
  return;
}

iods_OD iods_info = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};


uint8_t APar_skip_filler(FILE *isofile, uint32_t start_position) {
  uint8_t skip_bytes = 0;

  while (true) {
    uint8_t eval_byte = APar_read8(isofile, start_position + skip_bytes);

    if (eval_byte == 0x80 || eval_byte == 0x81 ||
        eval_byte == 0xFE) { // seems sometimes QT writes 0x81
      skip_bytes++;
    } else {
      break;
    }
  }
  return skip_bytes;
}


void APar_ExtractDetails(FILE *isofile, uint8_t optional_output) {
  char uint32_buffer[8];
  Trackage track = {0};

  AtomicInfo *mvhdAtom = APar_FindAtom("moov.mvhd", false, VERSIONED_ATOM, 0);
  if (mvhdAtom != NULL) {
    APar_ExtractMovieDetails(uint32_buffer, isofile, mvhdAtom);
    fprintf(stdout,
            "Movie duration: %.3lf seconds (%s) - %.2lf* kbp/sec bitrate "
            "(*=approximate)\n",
            movie_info.seconds,
            secsTOtime(movie_info.seconds),
            movie_info.simple_bitrate_calc);
    if (optional_output & SHOW_DATE_INFO) {
      fprintf(stdout,
              "  Presentation Creation Date (UTC):     %s\n",
              APar_extract_UTC(movie_info.creation_time));
      fprintf(stdout,
              "  Presentation Modification Date (UTC): %s\n",
              APar_extract_UTC(movie_info.modified_time));
    }
  }

  AtomicInfo *iodsAtom = APar_FindAtom("moov.iods", false, VERSIONED_ATOM, 0);
  if (iodsAtom != NULL) {
    movie_info.contains_iods = true;
    APar_Extract_iods_Info(isofile, iodsAtom);
  }

  if (optional_output & SHOW_TRACK_INFO) {
    APar_TrackLevelInfo(&track,
                        NULL); // With track_num set to 0, it will return the
                               // total trak atom into total_tracks here.

    fprintf(
        stdout, "Low-level details. Total tracks: %u\n", track.total_tracks);
    fprintf(stdout,
            "Trk  Type  Handler                    Kind  Lang  Bytes\n");

    if (track.total_tracks > 0) {
      while (track.total_tracks > track.track_num) {
        track.track_num += 1;
        TrackInfo track_info = {0};

        // tracknum, handler type, handler name
        APar_ExtractTrackDetails(uint32_buffer, isofile, &track, &track_info);
        uint16_t more_whitespace =
            purge_extraneous_characters(track_info.track_hdlr_name);

        if (strlen(track_info.track_hdlr_name) == 0) {
          memcpy(track_info.track_hdlr_name, "[none listed]", 13);
        }
        fprintf(stdout,
                "%u    %s  %s",
                track.track_num,
                uint32tochar4(track_info.track_type, uint32_buffer),
                track_info.track_hdlr_name);

        uint16_t handler_len = strlen(track_info.track_hdlr_name);
        if (handler_len < 25 + more_whitespace) {
          for (uint16_t i = handler_len; i < 25 + more_whitespace; i++) {
            fprintf(stdout, " ");
          }
        }

        // codec, language
        fprintf(stdout,
                "  %s  %s   %" PRIu64,
                uint32tochar4(track_info.track_codec, uint32_buffer),
                track_info.unpacked_lang,
                track_info.sample_aggregate);

        if (track_info.encoder_name[0] != 0 && track_info.contains_esds) {
          purge_extraneous_characters(track_info.encoder_name);
          fprintf(stdout, "   Encoder: %s", track_info.encoder_name);
        }
        if (track_info.type_of_track & DRM_PROTECTED_TRACK) {
          fprintf(stdout,
                  " (protected %s)",
                  uint32tochar4(track_info.protected_codec, uint32_buffer));
        }

        fprintf(stdout, "\n");
        /*---------------------------------*/

        if (track_info.type_of_track & VIDEO_TRACK ||
            track_info.type_of_track & AUDIO_TRACK) {
          APar_Print_TrackDetails(&track_info);
        }

        if (optional_output & SHOW_DATE_INFO) {
          fprintf(stdout,
                  "       Creation Date (UTC):     %s\n",
                  APar_extract_UTC(track_info.creation_time));
          fprintf(stdout,
                  "       Modification Date (UTC): %s\n",
                  APar_extract_UTC(track_info.modified_time));
        }
      }
    }
  }
}


void APar_Extract_esds_Info(char *uint32_buffer,
                            FILE *isofile,
                            short track_level_atom,
                            TrackInfo *track_info) {
  uint64_t offset_into_stsd = 0;

  while (offset_into_stsd < parsedAtoms[track_level_atom].AtomicLength) {
    offset_into_stsd++;
    if (APar_read32(uint32_buffer,
                    isofile,
                    parsedAtoms[track_level_atom].AtomicStart +
                        offset_into_stsd) == 0x65736473) {
      track_info->contains_esds = true;

      uint64_t esds_start =
          parsedAtoms[track_level_atom].AtomicStart + offset_into_stsd - 4;
      uint64_t esds_length = APar_read32(uint32_buffer, isofile, esds_start);
      uint64_t offset_into_esds =
          12; // 4bytes length + 4 bytes name + 4bytes null

      if (APar_read8(isofile, esds_start + offset_into_esds) == 0x03) {
        offset_into_esds++;
        offset_into_esds +=
            APar_skip_filler(isofile, esds_start + offset_into_esds);
      }

      uint8_t section3_length =
          APar_read8(isofile, esds_start + offset_into_esds);
      if (section3_length <= esds_length && section3_length != 0) {
        track_info->section3_length = section3_length;
      } else {
        break;
      }

      // for whatever reason, when mp4box muxes in ogg into an mp4 container,
      // section 3 gets a 0x9D byte (which doesn't fall inline with what AP
      // considers 'filler') then again, I haven't *completely* read the ISO
      // specifications, so I could just be missing it the the ->voluminous<-
      // 14496-X specifications.
      uint8_t test_byte =
          APar_read8(isofile, esds_start + offset_into_esds + 1);
      if (test_byte != 0) {
        offset_into_esds++;
      }

      offset_into_esds += 4; // 1 bytes section 0x03 length + 2 bytes + 1 byte

      if (APar_read8(isofile, esds_start + offset_into_esds) == 0x04) {
        offset_into_esds++;
        offset_into_esds +=
            APar_skip_filler(isofile, esds_start + offset_into_esds);
      }

      uint8_t section4_length =
          APar_read8(isofile, esds_start + offset_into_esds);
      if (section4_length <= section3_length && section4_length != 0) {
        track_info->section4_length = section4_length;

        if (section4_length == 0x9D)
          offset_into_esds++; // upper limit? when gpac puts an ogg in, section
                              // 3 is 9D - so is sec4 (section 4 real length
                              // with ogg = 0x0E86)

        offset_into_esds++;
        track_info->ObjectTypeIndication =
            APar_read8(isofile, esds_start + offset_into_esds);

        // this is just so that ogg in mp4 won't have some bizarre high bitrate
        // of like 2.8megabits/sec
        uint8_t a_v_flag =
            APar_read8(isofile,
                       esds_start + offset_into_esds +
                           1); // mp4box with ogg will set this to DD,
                               // mp4a has it as 0x40, mp4v has 0x20

        if (track_info->ObjectTypeIndication < 0xC0 &&
            a_v_flag < 0xA0) { // 0xC0 marks user streams; but things below that
                               // might still be wrong (like 0x6D - png)
          offset_into_esds += 5;
          track_info->max_bitrate = APar_read32(
              uint32_buffer, isofile, esds_start + offset_into_esds);
          offset_into_esds += 4;
          track_info->avg_bitrate = APar_read32(
              uint32_buffer, isofile, esds_start + offset_into_esds);
          offset_into_esds += 4;
        }
      } else {
        break;
      }

      if (APar_read8(isofile, esds_start + offset_into_esds) == 0x05) {
        offset_into_esds++;
        offset_into_esds +=
            APar_skip_filler(isofile, esds_start + offset_into_esds);

        uint8_t section5_length =
            APar_read8(isofile, esds_start + offset_into_esds);
        if ((section5_length <= section4_length || section4_length == 1) &&
            section5_length != 0) {
          track_info->section5_length = section5_length;
          offset_into_esds += 1;

          if (track_info->type_of_track & AUDIO_TRACK) {
            uint8_t packed_objID = APar_read8(
                isofile,
                esds_start + offset_into_esds); // its packed with channel, but
                                                // channel is fetched separately
            track_info->descriptor_object_typeID = packed_objID >> 3;
            offset_into_esds += 1;

            track_info->channels = (uint16_t)APar_ExtractChannelInfo(
                isofile, esds_start + offset_into_esds);

          } else if (track_info->type_of_track & VIDEO_TRACK) {
            // technically, visual_object_sequence_start_code should be tested
            // aginst 0x000001B0
            if (APar_read16(uint32_buffer,
                            isofile,
                            esds_start + offset_into_esds + 2) == 0x01B0) {
              track_info->m4v_profile =
                  APar_read8(isofile, esds_start + offset_into_esds + 2 + 2);
            }
          }
        }
        break; // uh, I've extracted the pertinent info
      }
    }
    if (offset_into_stsd > parsedAtoms[track_level_atom].AtomicLength) {
      break;
    }
  }
  if ((track_info->section5_length == 0 &&
       track_info->type_of_track & AUDIO_TRACK) ||
      track_info->channels == 0) {
    track_info->channels = APar_read16(
        uint32_buffer, isofile, parsedAtoms[track_level_atom].AtomicStart + 40);
  }
  return;
}


void APar_ShowMPEG4AACProfileInfo(TrackInfo *track_info) {
  if (track_info->descriptor_object_typeID == 1) {
    fprintf(stdout, "  MPEG-4 AAC Main Profile");
  } else if (track_info->descriptor_object_typeID == 2) {
    fprintf(
        stdout,
        "  MPEG-4 AAC Low Complexity/LC Profile"); // most files will land here
  } else if (track_info->descriptor_object_typeID == 3) {
    fprintf(stdout, "  MPEG-4 AAC Scaleable Sample Rate/SSR Profile");
  } else if (track_info->descriptor_object_typeID == 4) {
    fprintf(stdout, "  MPEG-4 AAC Long Term Prediction Profile");
  } else if (track_info->descriptor_object_typeID == 5) {
    fprintf(stdout, "  MPEG-4 AAC High Efficiency/HE Profile");
  } else if (track_info->descriptor_object_typeID == 6) {
    fprintf(stdout, "  MPEG-4 AAC Scalable Profile");
  } else if (track_info->descriptor_object_typeID == 7) {
    fprintf(stdout,
            "  MPEG-4 AAC Transform domain Weighted INterleave Vector "
            "Quantization/TwinVQ Profile");
  } else if (track_info->descriptor_object_typeID == 8) {
    fprintf(stdout, "  MPEG-4 AAC Code Excited Linear Predictive/CELP Profile");
  } else if (track_info->descriptor_object_typeID == 9) {
    fprintf(stdout, "  MPEG-4 AAC HVXC Profile");

  } else if (track_info->descriptor_object_typeID == 12) {
    fprintf(stdout, "  MPEG-4 AAC TTSI Profile");
  } else if (track_info->descriptor_object_typeID == 13) {
    fprintf(stdout, "  MPEG-4 AAC Main Synthesis Profile");
  } else if (track_info->descriptor_object_typeID == 14) {
    fprintf(stdout, "  MPEG-4 AAC Wavetable Synthesis Profile");
  } else if (track_info->descriptor_object_typeID == 15) {
    fprintf(stdout, "  MPEG-4 AAC General MIDI Profile");
  } else if (track_info->descriptor_object_typeID == 16) {
    fprintf(stdout, "  MPEG-4 AAC Algorithmic Synthesis & Audio FX Profile");
  } else if (track_info->descriptor_object_typeID == 17) {
    fprintf(stdout,
            "  MPEG-4 AAC AAC Low Complexity/LC (+error recovery) Profile");

  } else if (track_info->descriptor_object_typeID == 19) {
    fprintf(stdout,
            "  MPEG-4 AAC Long Term Prediction (+error recovery) Profile");
  } else if (track_info->descriptor_object_typeID == 20) {
    fprintf(stdout, "  MPEG-4 AAC Scalable (+error recovery) Profile");
  } else if (track_info->descriptor_object_typeID == 21) {
    fprintf(stdout,
            "  MPEG-4 AAC Transform domain Weighted INterleave Vector "
            "Quantization/TwinVQ (+error recovery) Profile");
  } else if (track_info->descriptor_object_typeID == 22) {
    fprintf(stdout,
            "  MPEG-4 AAC Bit Sliced Arithmetic Coding/BSAC (+error "
            "recovery) Profile");
  } else if (track_info->descriptor_object_typeID == 23) {
    fprintf(stdout, "  MPEG-4 AAC Low Delay/LD (+error recovery) Profile");
  } else if (track_info->descriptor_object_typeID == 24) {
    fprintf(stdout,
            "  MPEG-4 AAC Code Excited Linear Predictive/CELP (+error "
            "recovery) Profile");
  } else if (track_info->descriptor_object_typeID == 25) {
    fprintf(stdout, "  MPEG-4 AAC HXVC (+error recovery) Profile");
  } else if (track_info->descriptor_object_typeID == 26) {
    fprintf(stdout,
            "  MPEG-4 AAC Harmonic and Individual Lines plus "
            "Noise/HILN (+error recovery) Profile");
  } else if (track_info->descriptor_object_typeID == 27) {
    fprintf(stdout, "  MPEG-4 AAC Parametric (+error recovery) Profile");

  } else if (track_info->descriptor_object_typeID == 31) {
    fprintf(
        stdout,
        "  MPEG-4 ALS Audio Lossless Coding"); // I think that mp4alsRM18 writes
                                               // the channels wrong after
                                               // objectedID: 0xF880 has 0
                                               // channels; 0xF890 is 2ch
  } else {
    fprintf(stdout,
            "  MPEG-4 Unknown profile: 0x%X",
            track_info->descriptor_object_typeID);
  }
  return;
}


void APar_ExtractTrackDetails(char *uint32_buffer,
                              FILE *isofile,
                              Trackage *track,
                              TrackInfo *track_info) {
  uint64_t _offset = 0;

  APar_TrackLevelInfo(track, "tkhd");
  if (APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 8) ==
      0) {
    if (APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 11) &
        1) {
      track_info->track_enabled = true;
    }
    track_info->creation_time =
        APar_read32(uint32_buffer,
                    isofile,
                    parsedAtoms[track->track_atom].AtomicStart + 12);
    track_info->modified_time =
        APar_read32(uint32_buffer,
                    isofile,
                    parsedAtoms[track->track_atom].AtomicStart + 16);
    track_info->duration =
        APar_read32(uint32_buffer,
                    isofile,
                    parsedAtoms[track->track_atom].AtomicStart + 28);
  } else {
    track_info->creation_time =
        APar_read64(uint32_buffer,
                    isofile,
                    parsedAtoms[track->track_atom].AtomicStart + 12);
    track_info->modified_time =
        APar_read64(uint32_buffer,
                    isofile,
                    parsedAtoms[track->track_atom].AtomicStart + 20);
    track_info->duration =
        APar_read64(uint32_buffer,
                    isofile,
                    parsedAtoms[track->track_atom].AtomicStart + 36);
  }

  // language code
  APar_TrackLevelInfo(track, "mdhd");
  memset(uint32_buffer, 0, 5);
  uint16_t packed_language = APar_read16(
      uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 28);
  memset(track_info->unpacked_lang, 0, 4);
  APar_UnpackLanguage(
      track_info->unpacked_lang,
      packed_language); // http://www.w3.org/WAI/ER/IG/ert/iso639.htm

  // track handler type
  APar_TrackLevelInfo(track, "hdlr");
  memset(uint32_buffer, 0, 5);
  track_info->track_type = APar_read32(
      uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 16);
  if (track_info->track_type == 0x736F756E) { // soun
    track_info->type_of_track = AUDIO_TRACK;
  } else if (track_info->track_type == 0x76696465) { // vide
    track_info->type_of_track = VIDEO_TRACK;
  }
  if (parsedAtoms[track->track_atom].AtomicLength > 34) {
    memset(track_info->track_hdlr_name, 0, sizeof(track_info->track_hdlr_name));
    APar_readX(track_info->track_hdlr_name,
               isofile,
               parsedAtoms[track->track_atom].AtomicStart + 32,
               std::min((uint64_t)sizeof(track_info->track_hdlr_name),
                        parsedAtoms[track->track_atom].AtomicLength - 32));
  }

  // codec section
  APar_TrackLevelInfo(track, "stsd");
  memset(uint32_buffer, 0, 5);
  track_info->track_codec = APar_read32(
      uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 20);

  if (track_info->type_of_track & VIDEO_TRACK) { // vide
    track_info->video_width =
        APar_read16(uint32_buffer,
                    isofile,
                    parsedAtoms[track->track_atom + 1].AtomicStart + 32);
    track_info->video_height =
        APar_read16(uint32_buffer,
                    isofile,
                    parsedAtoms[track->track_atom + 1].AtomicStart + 34);
    track_info->macroblocks =
        (track_info->video_width / 16) * (track_info->video_height / 16);

    // avc profile & level
    if (track_info->track_codec == 0x61766331 ||
        track_info->track_codec == 0x64726D69) { // avc1 or drmi
      track_info->contains_esds = false;
      APar_TrackLevelInfo(track, "avcC");
      // get avc1 profile/level; atom 'avcC' is :
      // byte 1	configurationVersion    byte 2	AVCProfileIndication byte 3
      // profile_compatibility    byte 4	AVCLevelIndication
      track_info->avc_version =
          APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 8);
      if (track_info->avc_version == 1) {
        track_info->profile =
            APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 9);
        // uint8_t profile_compatibility = APar_read8(isofile,
        // parsedAtoms[track.track_atom].AtomicStart + 10); /* is this reserved
        // ?? */
        track_info->level = APar_read8(
            isofile, parsedAtoms[track->track_atom].AtomicStart + 11);
      }

      // avc1 doesn't have a hardcoded bitrate, so calculate it (off of stsz
      // table summing) later
    } else if (track_info->track_codec == 0x73323633) { // s263
      APar_TrackLevelInfo(track, "d263");
      if (memcmp(parsedAtoms[track->track_atom].AtomicName, "d263", 4) == 0) {
        APar_Extract_d263_Info(
            uint32_buffer, isofile, track->track_atom, track_info);
      }

    } else { // mp4v
      APar_TrackLevelInfo(track, "esds");
      if (memcmp(parsedAtoms[track->track_atom].AtomicName, "esds", 4) == 0) {
        APar_Extract_esds_Info(
            uint32_buffer,
            isofile,
            track->track_atom - 1,
            track_info); // right, backtrack to the atom before 'esds' so we can
                         // offset_into_stsd++
      } else if (track_info->track_codec == 0x73323633) { // s263
        track_info->type_of_track = VIDEO_TRACK;
      } else if (track_info->track_codec == 0x73616D72 ||
                 track_info->track_codec == 0x73617762 ||
                 track_info->track_codec == 0x73617770 ||
                 track_info->track_codec ==
                     0x73766D72) { // samr, sawb, sawp & svmr
        track_info->type_of_track = AUDIO_TRACK;
      } else {
        track_info->type_of_track = OTHER_TRACK; // a 'jpeg' track will fall
                                                 // here
      }
    }

  } else if (track_info->type_of_track & AUDIO_TRACK) {
    if (track_info->track_codec == 0x73616D72 ||
        track_info->track_codec == 0x73617762 ||
        track_info->track_codec == 0x73617770 ||
        track_info->track_codec ==
            0x73766D72) { // samr,sawb, svmr (sawp doesn't contain modes)
      APar_Extract_AMR_Info(
          uint32_buffer, isofile, track->track_atom + 2, track_info);

    } else if (track_info->track_codec == 0x73657663) { // sevc
      APar_TrackLevelInfo(track, "devc");
      if (memcmp(parsedAtoms[track->track_atom].AtomicName, "devc", 4) == 0) {
        APar_Extract_devc_Info(isofile, track->track_atom, track_info);
      }

    } else if (track_info->track_codec == 0x73716370) { // sqcp
      APar_TrackLevelInfo(track, "dqcp");
      if (memcmp(parsedAtoms[track->track_atom].AtomicName, "dqcp", 4) == 0) {
        APar_Extract_devc_Info(isofile,
                               track->track_atom,
                               track_info); // its the same thing
      }

    } else if (track_info->track_codec == 0x73736D76) { // ssmv
      APar_TrackLevelInfo(track, "dsmv");
      if (memcmp(parsedAtoms[track->track_atom].AtomicName, "dsmv", 4) == 0) {
        APar_Extract_devc_Info(isofile,
                               track->track_atom,
                               track_info); // its the same thing
      }

    } else {
      APar_Extract_esds_Info(
          uint32_buffer, isofile, track->track_atom, track_info);
    }
  }

  // in case bitrate isn't found, manually determine it off of stsz summing
  if ((track_info->type_of_track & AUDIO_TRACK ||
       track_info->type_of_track & VIDEO_TRACK) &&
      track_info->avg_bitrate == 0) {
    if (track_info->track_codec == 0x616C6163) { // alac
      track_info->channels =
          APar_read16(uint32_buffer,
                      isofile,
                      parsedAtoms[track->track_atom + 1].AtomicStart + 24);
    }
  }

  APar_TrackLevelInfo(track, "stsz");
  if (memcmp(parsedAtoms[track->track_atom].AtomicName, "stsz", 4) == 0) {
    track_info->sample_aggregate =
        calcuate_sample_size(uint32_buffer, isofile, track->track_atom);
  }

  // get what exactly 'drmX' stands in for
  if (track_info->track_codec >= 0x64726D00 &&
      track_info->track_codec <= 0x64726DFF) {
    track_info->type_of_track += DRM_PROTECTED_TRACK;
    APar_TrackLevelInfo(track, "frma");
    memset(uint32_buffer, 0, 5);
    track_info->protected_codec = APar_read32(
        uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 8);
  }

  // Encoder string; occasionally, it appears under stsd for a video track; it
  // is typcally preceded by ' ' (1st char is unprintable) or 0x01B2
  if (track_info->contains_esds) {
    APar_TrackLevelInfo(track, "esds");

    // technically, user_data_start_code should be tested aginst 0x000001B2;
    // TODO: it should only be read up to section 3's length too
    _offset = APar_FindValueInAtom(
        uint32_buffer, isofile, track->track_atom, 24, 0x01B2);

    if (_offset > 0 && _offset < parsedAtoms[track->track_atom].AtomicLength) {
      _offset += 2;
      memset(track_info->encoder_name,
             0,
             parsedAtoms[track->track_atom].AtomicLength - _offset);
      APar_readX(track_info->encoder_name,
                 isofile,
                 parsedAtoms[track->track_atom].AtomicStart + _offset,
                 parsedAtoms[track->track_atom].AtomicLength - _offset);
    }
  }
  return;
}


void APar_Print_TrackDetails(TrackInfo *track_info) {
  if (track_info->max_bitrate > 0 && track_info->avg_bitrate > 0) {
    fprintf(stdout, "     %.2f kbp/s", (float)track_info->avg_bitrate / 1000.0);
  } else { // some ffmpeg encodings have avg_bitrate set to 0, but an inexact
           // max_bitrate - actually, their esds seems a mess to me
#if defined(_MSC_VER)
    fprintf(stdout,
            "     %.2lf* kbp/s",
            ((double)((__int64)track_info->sample_aggregate) /
             ((double)((__int64)track_info->duration) /
              (double)((__int64)movie_info.timescale))) /
                1000.0 * 8);
    fprintf(stdout,
            "  %.3f sec",
            (float)track_info->duration / (float)movie_info.timescale);
#else
    fprintf(stdout,
            "     %.2lf* kbp/s",
            ((double)track_info->sample_aggregate /
             ((double)track_info->duration / (double)movie_info.timescale)) /
                1000.0 * 8);
    fprintf(stdout,
            "  %.3f sec",
            (float)track_info->duration / (float)movie_info.timescale);
#endif
  }

  if (track_info->track_codec == 0x6D703476) { // mp4v profile
    APar_ShowObjectProfileInfo(MP4V_TRACK, track_info);
  } else if (track_info->track_codec == 0x6D703461 ||
             track_info->protected_codec == 0x6D703461) { // mp4a profile
    APar_ShowObjectProfileInfo(AUDIO_TRACK, track_info);
  } else if (track_info->track_codec ==
             0x616C6163) { // alac - can't figure out a hardcoded bitrate either
    fprintf(
        stdout, "  Apple Lossless    channels: [%u]\n", track_info->channels);
  } else if (track_info->track_codec == 0x61766331 ||
             track_info->protected_codec == 0x61766331) {
    if (track_info->avc_version == 1) { // avc profile & level
      APar_ShowObjectProfileInfo(AVC1_TRACK, track_info);
    }
  } else if (track_info->track_codec == 0x73323633) { // s263 in 3gp
    APar_ShowObjectProfileInfo(S263_TRACK, track_info);
  } else if (track_info->track_codec == 0x73616D72 ||
             track_info->track_codec == 0x73617762 ||
             track_info->track_codec == 0x73617770 ||
             track_info->track_codec ==
                 0x73766D72) { // samr,sawb,sawp & svmr in 3gp
    track_info->type_of_track = S_AMR_TRACK;
    APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
  } else if (track_info->track_codec == 0x73657663) { // evrc in 3gp
    track_info->type_of_track = EVRC_TRACK;
    APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
  } else if (track_info->track_codec == 0x73716370) { // qcelp in 3gp
    track_info->type_of_track = QCELP_TRACK;
    APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
  } else if (track_info->track_codec == 0x73736D76) { // smv in 3gp
    track_info->type_of_track = SMV_TRACK;
    APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
  } else { // unknown everything, 0 hardcoded bitrate
    APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
    fprintf(stdout, "\n");
  }

  if (track_info->type_of_track & VIDEO_TRACK &&
      ((track_info->max_bitrate > 0 &&
        track_info->ObjectTypeIndication == 0x20) ||
       track_info->avc_version == 1 || track_info->protected_codec != 0)) {
    fprintf(stdout,
            "  %ux%u  (%" PRIu32 " macroblocks)\n",
            track_info->video_width,
            track_info->video_height,
            track_info->macroblocks);
  } else if (track_info->type_of_track & VIDEO_TRACK) {
    fprintf(stdout, "\n");
  }
  return;
}


void APar_ShowMPEG4VisualProfileInfo(TrackInfo *track_info) {
  fprintf(stdout, "  MPEG-4 Visual ");
  uint8_t mp4v_profile = 0;
  if (movie_info.contains_iods) {
    mp4v_profile = iods_info.video_profile_level;
  } else {
    mp4v_profile = track_info->m4v_profile;
  }

  // unparalleled joy - Annex G table g1 - a binary listing (this from
  // 14496-2:2001)
  if (mp4v_profile == 0x01) {
    fprintf(stdout, "Simple Profile, Level 1"); // 00000001
  } else if (mp4v_profile == 0x02) {
    fprintf(stdout, "Simple Profile, Level 2"); // 00000010
  } else if (mp4v_profile == 0x03) {
    fprintf(stdout,
            "Simple Profile, Level 3"); // most files will land here  //00000011

  } else if (mp4v_profile == 0x08) { // Compressor can create these in 3gp files
    fprintf(stdout, "Simple Profile, Level 0"); // ISO 14496-2:2004(e)
                                                // //00001000

    // Reserved 00000100 - 00000111
  } else if (mp4v_profile == 0x10) {
    fprintf(stdout, "Simple Scalable Profile, Level 0"); // 00010000
  } else if (mp4v_profile == 0x11) {
    fprintf(stdout, "Simple Scalable Profile, Level 1"); // 00010001
  } else if (mp4v_profile == 0x12) {
    fprintf(stdout, "Simple Scalable Profile, Level 2"); // 00010010

    // Reserved 00010011 - 00100000
  } else if (mp4v_profile == 0x21) {
    fprintf(stdout, "Core Profile, Level 1"); // 00100001
  } else if (mp4v_profile == 0x22) {
    fprintf(stdout, "Core Profile, Level 2"); // 00100010

    // Reserved 00100011 - 00110001
  } else if (mp4v_profile == 0x32) {
    fprintf(stdout, "Main Profile, Level 2"); // 00110010
  } else if (mp4v_profile == 0x33) {
    fprintf(stdout, "Main Profile, Level 3"); // 00110011
  } else if (mp4v_profile == 0x34) {
    fprintf(stdout, "Main Profile, Level 4"); // 00110100

    // Reserved 00110101 - 01000001
  } else if (mp4v_profile == 0x42) {
    fprintf(stdout, "N-bit Profile, Level 2"); // 01000010

    // Reserved 01000011 - 01010000
  } else if (mp4v_profile == 0x51) {
    fprintf(stdout, "Scalable Texture Profile, Level 1"); // 01010001

    // Reserved 01010010 - 01100000
  } else if (mp4v_profile == 0x61) {
    fprintf(stdout, "Simple Face Animation, Level 1"); // 01100001
  } else if (mp4v_profile == 0x62) {
    fprintf(stdout, "Simple Face Animation, Level 2"); // 01100010

  } else if (mp4v_profile == 0x63) {
    fprintf(stdout, "Simple FBA Profile, Level 1"); // 01100011
  } else if (mp4v_profile == 0x64) {
    fprintf(stdout, "Simple FBA Profile, Level 2"); // 01100100

    // Reserved 01100101 - 01110000
  } else if (mp4v_profile == 0x71) {
    fprintf(stdout, "Basic Animated Texture Profile, Level 1"); // 01110001
  } else if (mp4v_profile == 0x72) {
    fprintf(stdout, "Basic Animated Texture Profile, Level 2"); // 01110010

    // Reserved 01110011 - 10000000
  } else if (mp4v_profile == 0x81) {
    fprintf(stdout, "Hybrid Profile, Level 1"); // 10000001
  } else if (mp4v_profile == 0x82) {
    fprintf(stdout, "Hybrid Profile, Level 2"); // 10000010

    // Reserved 10000011 - 10010000
  } else if (mp4v_profile == 0x91) {
    fprintf(stdout, "Advanced Real Time Simple Profile, Level 1"); // 10010001
  } else if (mp4v_profile == 0x92) {
    fprintf(stdout, "Advanced Real Time Simple Profile, Level 2"); // 10010010
  } else if (mp4v_profile == 0x93) {
    fprintf(stdout, "Advanced Real Time Simple Profile, Level 3"); // 10010011
  } else if (mp4v_profile == 0x94) {
    fprintf(stdout, "Advanced Real Time Simple Profile, Level 4"); // 10010100

    // Reserved 10010101 - 10100000
  } else if (mp4v_profile == 0xA1) {
    fprintf(stdout, "Core Scalable Profile, Level 1"); // 10100001
  } else if (mp4v_profile == 0xA2) {
    fprintf(stdout, "Core Scalable Profile, Level 2"); // 10100010
  } else if (mp4v_profile == 0xA3) {
    fprintf(stdout, "Core Scalable Profile, Level 3"); // 10100011

    // Reserved 10100100 - 10110000
  } else if (mp4v_profile == 0xB1) {
    fprintf(stdout, "Advanced Coding Efficiency Profile, Level 1"); // 10110001
  } else if (mp4v_profile == 0xB2) {
    fprintf(stdout, "Advanced Coding Efficiency Profile, Level 2"); // 10110010
  } else if (mp4v_profile == 0xB3) {
    fprintf(stdout, "Advanced Coding Efficiency Profile, Level 3"); // 10110011
  } else if (mp4v_profile == 0xB4) {
    fprintf(stdout, "Advanced Coding Efficiency Profile, Level 4"); // 10110100

    // Reserved 10110101  11000000
  } else if (mp4v_profile == 0xC1) {
    fprintf(stdout, "Advanced Core Profile, Level 1"); // 11000001
  } else if (mp4v_profile == 0xC2) {
    fprintf(stdout, "Advanced Core Profile, Level 2"); // 11000010

    // Reserved 11000011  11010000
  } else if (mp4v_profile == 0xD1) {
    fprintf(stdout, "Advanced Scalable Texture, Level 1"); // 11010001
  } else if (mp4v_profile == 0xD2) {
    fprintf(stdout, "Advanced Scalable Texture, Level 2"); // 11010010
  } else if (mp4v_profile == 0xD2) {
    fprintf(stdout, "Advanced Scalable Texture, Level 3"); // 11010011

    // from a draft document - 1999 (earlier than the 2000 above!!)
  } else if (mp4v_profile == 0xE1) {
    fprintf(stdout, "Simple Studio Profile, Level 1"); // 11100001
  } else if (mp4v_profile == 0xE2) {
    fprintf(stdout, "Simple Studio Profile, Level 2"); // 11100010
  } else if (mp4v_profile == 0xE3) {
    fprintf(stdout, "Simple Studio Profile, Level 3"); // 11100011
  } else if (mp4v_profile == 0xE4) {
    fprintf(stdout, "Simple Studio Profile, Level 4"); // 11100100

  } else if (mp4v_profile == 0xE5) {
    fprintf(stdout, "Core Studio Profile, Level 1"); // 11100101
  } else if (mp4v_profile == 0xE6) {
    fprintf(stdout, "Core Studio Profile, Level 2"); // 11100110
  } else if (mp4v_profile == 0xE7) {
    fprintf(stdout, "Core Studio Profile, Level 3"); // 11100111
  } else if (mp4v_profile == 0xE8) {
    fprintf(stdout, "Core Studio Profile, Level 4"); // 11101000

    // Reserved 11101001 - 11101111
    // ISO 14496-2:2004(e)
  } else if (mp4v_profile == 0xF0) {
    fprintf(stdout, "Advanced Simple Profile, Level 0"); // 11110000
  } else if (mp4v_profile == 0xF1) {
    fprintf(stdout, "Advanced Simple Profile, Level 1"); // 11110001
  } else if (mp4v_profile == 0xF2) {
    fprintf(
        stdout,
        "Advanced Simple Profile, Level 2"); // 11110010  ////3gp files that QT
                                             // says is H.263 have esds to 0xF2
                                             // & their ObjectType set to 0x20
                                             // (mpeg-4 visual)
                                             ////...and its been figured out -
                                             /// FILE EXTENSION of all things
                                             /// determines mpeg-4 ASP or H.263
  } else if (mp4v_profile == 0xF3) {
    fprintf(stdout, "Advanced Simple Profile, Level 3"); // 11110011
  } else if (mp4v_profile == 0xF4) {
    fprintf(stdout, "Advanced Simple Profile, Level 4"); // 11110100
  } else if (mp4v_profile == 0xF5) {
    fprintf(stdout, "Advanced Simple Profile, Level 5"); // 11110101

    // Reserved 11110110
  } else if (mp4v_profile == 0xF7) {
    fprintf(stdout, "Advanced Simple Profile, Level 3b"); // 11110111

  } else if (mp4v_profile == 0xF7) {
    fprintf(stdout, "Fine Granularity Scalable Profile/Level 0"); // 11111000
  } else if (mp4v_profile == 0xF7) {
    fprintf(stdout, "Fine Granularity Scalable Profile/Level 1"); // 11111001
  } else if (mp4v_profile == 0xF7) {
    fprintf(stdout, "Fine Granularity Scalable Profile/Level 2"); // 11111010
  } else if (mp4v_profile == 0xF7) {
    fprintf(stdout, "Fine Granularity Scalable Profile/Level 3"); // 11111011
  } else if (mp4v_profile == 0xF7) {
    fprintf(stdout, "Fine Granularity Scalable Profile/Level 4"); // 11111100
  } else if (mp4v_profile == 0xF7) {
    fprintf(stdout, "Fine Granularity Scalable Profile/Level 5"); // 11111101

    // Reserved 11111110
    // Reserved for Escape 11111111

  } else {
    fprintf(stdout, "Unknown profile: 0x%X", mp4v_profile);
  }
  return;
}


calcuate_sample_size(char *uint32_buffer, FILE *isofile, short stsz_atom) {
  uint32_t sample_size = 0;
  uint32_t sample_count = 0;
  uint64_t total_size = 0;

  sample_size = APar_read32(
      uint32_buffer, isofile, parsedAtoms[stsz_atom].AtomicStart + 12);
  sample_count = APar_read32(
      uint32_buffer, isofile, parsedAtoms[stsz_atom].AtomicStart + 16);

  if (sample_size == 0) {
    for (uint64_t atom_offset = 20;
         atom_offset < parsedAtoms[stsz_atom].AtomicLength;
         atom_offset += 4) {
      total_size +=
          APar_read32(uint32_buffer,
                      isofile,
                      parsedAtoms[stsz_atom].AtomicStart + atom_offset);
    }
  } else {
    total_size = sample_size * sample_count;
  }
  return total_size;
}


void APar_Extract_AMR_Info(char *uint32_buffer,
                           FILE *isofile,
                           short track_level_atom,
                           TrackInfo *track_info) {
  uint32_t amr_specific_offet = 8;
  APar_readX(track_info->encoder_name,
             isofile,
             parsedAtoms[track_level_atom].AtomicStart + amr_specific_offet,
             4);
  if (track_info->track_codec == 0x73616D72 ||
      track_info->track_codec == 0x73617762 ||
      track_info->track_codec ==
          0x73766D72) { // samr,sawb & svmr contain modes only
    track_info->amr_modes = APar_read16(
        uint32_buffer,
        isofile,
        parsedAtoms[track_level_atom].AtomicStart + amr_specific_offet + 4 + 1);
  }
  return;
}


is_write_comp_null (gnutls_session_t session)
{
  if (session->security_parameters.write_compression_algorithm ==
      GNUTLS_COMP_NULL)
    return 0;

  return 1;
}

Fix broken debug check for GNUTLS-SA-2008-1.


mac_deinit (digest_hd_st *td, opaque * res, int ver)
{
  if (ver == GNUTLS_SSL3)
    {				/* SSL 3.0 */
      _gnutls_mac_deinit_ssl3 (td, res);
    }
  else
    {
      _gnutls_hmac_deinit (td, res);
    }
}


mac_init (digest_hd_st* td, gnutls_mac_algorithm_t mac, opaque * secret, int secret_size,
	  int ver)
{
int ret =  0;

  if (mac == GNUTLS_MAC_NULL)
    {
      gnutls_assert();
      return GNUTLS_E_HASH_FAILED;
    }

  if (ver == GNUTLS_SSL3)
    {				/* SSL 3.0 */
      ret = _gnutls_mac_init_ssl3 (td, mac, secret, secret_size);
    }
  else
    {				/* TLS 1.x */
      ret = _gnutls_hmac_init (td, mac, secret, secret_size);
    }

  return ret;
}


calc_enc_length (gnutls_session_t session, int data_size,
		 int hash_size, uint8_t * pad, int random_pad,
		 cipher_type_t block_algo, uint16_t blocksize)
{
  uint8_t rnd;
  int length, ret;

  *pad = 0;

  switch (block_algo)
    {
    case CIPHER_STREAM:
      length = data_size + hash_size;

      break;
    case CIPHER_BLOCK:
      ret =_gnutls_rnd (RND_NONCE, &rnd, 1);
      if ( ret < 0)
	{
	  gnutls_assert ();
	  return ret;
	}

      /* make rnd a multiple of blocksize */
      if (session->security_parameters.version == GNUTLS_SSL3 ||
	  random_pad == 0)
	{
	  rnd = 0;
	}
      else
	{
	  rnd = (rnd / blocksize) * blocksize;
	  /* added to avoid the case of pad calculated 0
	   * seen below for pad calculation.
	   */
	  if (rnd > blocksize)
	    rnd -= blocksize;
	}

      length = data_size + hash_size;

      *pad = (uint8_t) (blocksize - (length % blocksize)) + rnd;

      length += *pad;
      if (session->security_parameters.version >= GNUTLS_TLS1_1)
	length += blocksize;	/* for the IV */

      break;
    default:
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }

  return length;
}


_gnutls_encrypt (gnutls_session_t session, const opaque * headers,
		 size_t headers_size, const opaque * data,
		 size_t data_size, opaque * ciphertext,
		 size_t ciphertext_size, content_type_t type, int random_pad)
{
  gnutls_datum_t plain;
  gnutls_datum_t comp;
  int ret;
  int free_comp = 1;

  plain.data = (opaque *) data;
  plain.size = data_size;

  if (plain.size == 0 || is_write_comp_null (session) == 0)
    {
      comp = plain;
      free_comp = 0;
    }
  else
    {
      /* Here comp is allocated and must be
       * freed.
       */
      ret = _gnutls_m_plaintext2compressed (session, &comp, &plain);
      if (ret < 0)
	{
	  gnutls_assert ();
	  return ret;
	}
    }

  ret = _gnutls_compressed2ciphertext (session, &ciphertext[headers_size],
				       ciphertext_size - headers_size,
				       comp, type, random_pad);

  if (free_comp)
    _gnutls_free_datum (&comp);

  if (ret < 0)
    {
      gnutls_assert ();
      return ret;
    }

  /* copy the headers */
  memcpy (ciphertext, headers, headers_size);
  _gnutls_write_uint16 (ret, &ciphertext[3]);

  return ret + headers_size;
}


is_read_comp_null (gnutls_session_t session)
{
  if (session->security_parameters.read_compression_algorithm ==
      GNUTLS_COMP_NULL)
    return 0;

  return 1;
}