[f-spot/taglib-metadata] Taglib# sync f6a3428d8b86aeee51b972a1ffbeb26e9afb048d



commit 016a93b2ac4f1d76802c7735de3b2730371116b9
Author: Ruben Vermeersch <ruben savanne be>
Date:   Sun May 23 15:18:45 2010 +0200

    Taglib# sync f6a3428d8b86aeee51b972a1ffbeb26e9afb048d
    
    Adds, among other things, GIF support.
    
        commit f6a3428d8b86aeee51b972a1ffbeb26e9afb048d
        Author: Ruben Vermeersch <ruben savanne be>
        Date:   Sun May 23 15:02:07 2010 +0200
    
            Remove a bit of unreadable code.
    
        commit 065c3d8e865dff12708223d1dc059c5512b21ae6
        Author: Mike Gemünde <mike gemuende de>
        Date:   Sun May 23 14:23:38 2010 +0200
    
            Add tests for GIF support
    
            This commit adds a bunch of tests for the GIF support. The sample files
            are created with GIMP and modified with exiftool. Some sample files are
            hand-edited to provide input data for some special tests.
    
            https://bugzilla.gnome.org/show_bug.cgi?id=619432
    
        commit 05da6a12c6263eb24c6cfdca8d919049dca9193f
        Author: Mike Gemünde <mike gemuende de>
        Date:   Sun May 23 14:21:53 2010 +0200
    
            Add Validator to test removal of tags from files
    
            A new Validator for the image tests is added which can be used to
            test if tags are correctly removed from files.
    
            https://bugzilla.gnome.org/show_bug.cgi?id=619432
    
        commit 52399c37397069345211e64fac47015b2443b200
        Author: Mike Gemünde <mike gemuende de>
        Date:   Sun May 23 14:06:24 2010 +0200
    
            Extend Validators to specify an own comment string for the test
    
            The current implementation of CommentModificationValidator and
            TagCommentModificationValidator uses a fixed comment for testing.
            This commit extends the Validators that the comment string can be
            changed and an arbitrary comment can be used for the test.
    
            https://bugzilla.gnome.org/show_bug.cgi?id=619432
    
        commit be86da1d7145a43b788be0fce8357f87d09ffbe8
        Author: Mike Gemünde <mike gemuende de>
        Date:   Sun May 23 14:03:07 2010 +0200
    
            Introduce support for GIF files
    
            This commit introduces support for GIF files. A GIF file is parsed
            completely and XMP metadata and GIF comments are extracted. Additionally,
            the size of the image is extracted. Since GIF files can be used as
            an animation, it would be reasonable to determine some Properties
            which are related to Audio/Video files. This is currently not covered.
    
            https://bugzilla.gnome.org/show_bug.cgi?id=619432
    
        commit 55b341eca74d9f4f31dfcadd20216656f422cbdc
        Author: Mike Gemünde <mike gemuende de>
        Date:   Thu May 20 16:32:54 2010 +0200
    
            Remove uneccessary if condition in CombinedImageTag
    
            https://bugzilla.gnome.org/show_bug.cgi?id=619200

 lib/TagLib/Makefile.am                      |    3 +
 lib/TagLib/TagLib/FileTypes.cs              |    1 +
 lib/TagLib/TagLib/Gif/Codec.cs              |   62 ++
 lib/TagLib/TagLib/Gif/File.cs               |  863 +++++++++++++++++++++++++++
 lib/TagLib/TagLib/Gif/GifCommentTag.cs      |  103 ++++
 lib/TagLib/TagLib/Image/CombinedImageTag.cs |    1 -
 lib/TagLib/TagLib/Image/File.cs             |    5 +
 lib/TagLib/TagLib/Tag.cs                    |    5 +
 lib/TagLib/TagLib/TagLib.sources            |    3 +
 9 files changed, 1045 insertions(+), 1 deletions(-)
---
diff --git a/lib/TagLib/Makefile.am b/lib/TagLib/Makefile.am
index 604a8b6..819de35 100644
--- a/lib/TagLib/Makefile.am
+++ b/lib/TagLib/Makefile.am
@@ -45,6 +45,9 @@ ASSEMBLY_SOURCES = \
 	TagLib/Flac/StreamHeader.cs \
 	TagLib/Flac/Picture.cs \
 	TagLib/Flac/File.cs \
+	TagLib/Gif/Codec.cs \
+	TagLib/Gif/File.cs \
+	TagLib/Gif/GifCommentTag.cs \
 	TagLib/Image/Codec.cs \
 	TagLib/Image/CombinedImageTag.cs \
 	TagLib/Image/File.cs \
diff --git a/lib/TagLib/TagLib/FileTypes.cs b/lib/TagLib/TagLib/FileTypes.cs
index ff1c7f8..6d65b83 100644
--- a/lib/TagLib/TagLib/FileTypes.cs
+++ b/lib/TagLib/TagLib/FileTypes.cs
@@ -63,6 +63,7 @@ namespace TagLib {
 			typeof(TagLib.Ape.File),
 			typeof(TagLib.Asf.File),
 			typeof(TagLib.Flac.File),
+			typeof(TagLib.Gif.File),
 			typeof(TagLib.MusePack.File),
 			typeof(TagLib.Mpeg4.File),
 			typeof(TagLib.Mpeg.File),
diff --git a/lib/TagLib/TagLib/Gif/Codec.cs b/lib/TagLib/TagLib/Gif/Codec.cs
new file mode 100644
index 0000000..8b4b646
--- /dev/null
+++ b/lib/TagLib/TagLib/Gif/Codec.cs
@@ -0,0 +1,62 @@
+//
+// Codec.cs:
+//
+// Author:
+//   Mike Gemuende (mike gemuende be)
+//
+// Copyright (C) 2010 Mike Gemuende
+//
+// This library is free software; you can redistribute it and/or modify
+// it  under the terms of the GNU Lesser General Public License version
+// 2.1 as published by the Free Software Foundation.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+// USA
+//
+
+using System;
+
+namespace TagLib.Gif
+{
+
+	/// <summary>
+	///    A Gif photo codec. Contains basic photo details.
+	/// </summary>
+	public class Codec : Image.Codec
+	{
+
+		/// <summary>
+		///    Gets a text description of the media represented by the
+		///    current instance.
+		/// </summary>
+		/// <value>
+		///    A <see cref="string" /> object containing a description
+		///    of the media represented by the current instance.
+		/// </value>
+		public override string Description { get { return "GIF File"; } }
+
+
+		/// <summary>
+		///    Constructs a new <see cref="Codec" /> with the given width
+		///    and height.
+		/// </summary>
+		/// <param name="width">
+		///    The width of the photo.
+		/// </param>
+		/// <param name="height">
+		///    The height of the photo.
+		/// </param>
+		/// <returns>
+		///    A new <see cref="Codec" /> instance.
+		/// </returns>
+		public Codec (int width, int height)
+			: base (width, height) {}
+	}
+}
diff --git a/lib/TagLib/TagLib/Gif/File.cs b/lib/TagLib/TagLib/Gif/File.cs
new file mode 100644
index 0000000..655b2ae
--- /dev/null
+++ b/lib/TagLib/TagLib/Gif/File.cs
@@ -0,0 +1,863 @@
+//
+// File.cs: Provides tagging for GIF files
+//
+// Author:
+//   Mike Gemuende (mike gemuende be)
+//
+// Copyright (C) 2010 Mike Gemuende
+//
+// This library is free software; you can redistribute it and/or modify
+// it  under the terms of the GNU Lesser General Public License version
+// 2.1 as published by the Free Software Foundation.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+// USA
+//
+
+using System;
+using System.IO;
+
+using TagLib;
+using TagLib.Image;
+using TagLib.Xmp;
+
+
+namespace TagLib.Gif
+{
+
+	[SupportedMimeType("taglib/gif", "gif")]
+	[SupportedMimeType("image/gif")]
+	public class File : TagLib.Image.File
+	{
+
+#region GIF specific constants
+
+		/// <summary>
+		///    Gif file signature which occurs at the begin of the file
+		/// </summary>
+		protected static readonly string SIGNATURE = "GIF";
+
+		/// <summary>
+		///    String which is used to indicate version the gif file format version 87a
+		/// </summary>
+		protected static readonly string VERSION_87A = "87a";
+
+		/// <summary>
+		///    String which is used to indicate version the gif file format version 89a
+		/// </summary>
+		protected static readonly string VERSION_89A = "89a";
+
+		/// <summary>
+		///    Application Extension Identifier for an XMP Block
+		/// </summary>
+		private static readonly string XMP_IDENTIFIER = "XMP Data";
+
+		/// <summary>
+		///    Application Authentication Extension Code for an XMP Block
+		/// </summary>
+		private static readonly string XMP_AUTH_CODE = "XMP";
+
+		/// <summary>
+		///    The Magic Trailer for XMP Data
+		/// </summary>
+		/// <remarks>
+		///    The storage of XMP data in GIF does not follow the GIF specification. According to the
+		///    specification, extension data is stored in so-called sub-blocks, which start with a length
+		///    byte which specifies the number of data bytes contained in the sub block. So a block can at
+		///    most contain 256 data bytes. After a sub-block, the next sub-block begins. The sequence ends,
+		///    when a sub-block starts with 0. So readers, which are not aware of the XMP data not following
+		///    this scheme, will get confused by the XMP data. To fix this, this trailer is added to the end.
+		///    It has a length of 258 bytes, so that it is ensured that a reader which tries to skip the
+		///    XMP data reads one of this bytes as length of a sub-block. But, each byte points with its length
+		///    to the last one. Therefoe, independent of the byte, the reader reads as sub-block length, it is
+		///    redirected to the last byte of the trailer and therfore to the end of the XMP data.
+		/// </remarks>
+		private static readonly byte [] XMP_MAGIC_TRAILER = new byte [] {
+			0x01, 0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1,
+			0xF0, 0xEF, 0xEE, 0xED, 0xEC, 0xEB, 0xEA, 0xE9, 0xE8, 0xE7, 0xE6, 0xE5, 0xE4, 0xE3, 0xE2, 0xE1,
+			0xE0, 0xDF, 0xDE, 0xDD, 0xDC, 0xDB, 0xDA, 0xD9, 0xD8, 0xD7, 0xD6, 0xD5, 0xD4, 0xD3, 0xD2, 0xD1,
+			0xD0, 0xCF, 0xCE, 0xCD, 0xCC, 0xCB, 0xCA, 0xC9, 0xC8, 0xC7, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1,
+			0xC0, 0xBF, 0xBE, 0xBD, 0xBC, 0xBB, 0xBA, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1,
+			0xB0, 0xAF, 0xAE, 0xAD, 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7, 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1,
+			0xA0, 0x9F, 0x9E, 0x9D, 0x9C, 0x9B, 0x9A, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91,
+			0x90, 0x8F, 0x8E, 0x8D, 0x8C, 0x8B, 0x8A, 0x89, 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81,
+			0x80, 0x7F, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71,
+			0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61,
+			0x60, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51,
+			0x50, 0x4F, 0x4E, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41,
+			0x40, 0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31,
+			0x30, 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21,
+			0x20, 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11,
+			0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
+			0x00, 0x00
+		};
+
+#endregion
+
+#region private fields
+
+		/// <summary>
+		///    The width of the image
+		/// </summary>
+		private int width;
+
+		/// <summary>
+		///    The height of the image
+		/// </summary>
+		private int height;
+
+		/// <summary>
+		///    The Properties of the image
+		/// </summary>
+		private Properties properties;
+
+		/// <summary>
+		///    The version of the file format
+		/// </summary>
+		private string version;
+
+		/// <summary>
+		///    The start of the first block in file after the header.
+		/// </summary>
+		private long start_of_blocks = -1;
+
+		/// <summary>
+		///    The start of the xmp block, if there is one. This value is used
+		///    to overwrite the block when metadata is saved.
+		/// </summary>
+		private long xmp_block_start = -1;
+
+		/// <summary>
+		///    The length of the xmp block, if there is one. This value is used
+		///    to overwrite the block when metadata is saved.
+		/// </summary>
+		private int xmp_block_length = 0;
+
+		/// <summary>
+		///    The start of the comment block, if there is one. This value is used
+		///    to overwrite the block when metadata is saved.
+		/// </summary>
+		private long comment_block_start = -1;
+
+		/// <summary>
+		///    The length of the comment block, if there is one. This value is used
+		///    to overwrite the block when metadata is saved.
+		/// </summary>
+		private int comment_block_length = 0;
+
+#endregion
+
+#region public Properties
+
+		/// <summary>
+		///    Gets the media properties of the file represented by the
+		///    current instance.
+		/// </summary>
+		/// <value>
+		///    A <see cref="TagLib.Properties" /> object containing the
+		///    media properties of the file represented by the current
+		///    instance.
+		/// </value>
+		public override TagLib.Properties Properties {
+			get { return properties; }
+		}
+
+#endregion
+
+#region constructors
+
+		/// <summary>
+		///    Constructs and initializes a new instance of <see
+		///    cref="File" /> for a specified path in the local file
+		///    system and specified read style.
+		/// </summary>
+		/// <param name="path">
+		///    A <see cref="string" /> object containing the path of the
+		///    file to use in the new instance.
+		/// </param>
+		/// <param name="propertiesStyle">
+		///    A <see cref="ReadStyle" /> value specifying at what level
+		///    of accuracy to read the media properties, or <see
+		///    cref="ReadStyle.None" /> to ignore the properties.
+		/// </param>
+		/// <exception cref="ArgumentNullException">
+		///    <paramref name="path" /> is <see langword="null" />.
+		/// </exception>
+		public File (string path, ReadStyle propertiesStyle)
+			: this (new File.LocalFileAbstraction (path),
+				propertiesStyle)
+		{
+		}
+
+		/// <summary>
+		///    Constructs and initializes a new instance of <see
+		///    cref="File" /> for a specified path in the local file
+		///    system.
+		/// </summary>
+		/// <param name="path">
+		///    A <see cref="string" /> object containing the path of the
+		///    file to use in the new instance.
+		/// </param>
+		/// <exception cref="ArgumentNullException">
+		///    <paramref name="path" /> is <see langword="null" />.
+		/// </exception>
+		public File (string path) : this (path, ReadStyle.Average)
+		{
+		}
+
+		/// <summary>
+		///    Constructs and initializes a new instance of <see
+		///    cref="File" /> for a specified file abstraction and
+		///    specified read style.
+		/// </summary>
+		/// <param name="abstraction">
+		///    A <see cref="IFileAbstraction" /> object to use when
+		///    reading from and writing to the file.
+		/// </param>
+		/// <param name="propertiesStyle">
+		///    A <see cref="ReadStyle" /> value specifying at what level
+		///    of accuracy to read the media properties, or <see
+		///    cref="ReadStyle.None" /> to ignore the properties.
+		/// </param>
+		/// <exception cref="ArgumentNullException">
+		///    <paramref name="abstraction" /> is <see langword="null"
+		///    />.
+		/// </exception>
+		public File (File.IFileAbstraction abstraction,
+		             ReadStyle propertiesStyle) : base (abstraction)
+		{
+			Read (propertiesStyle);
+		}
+
+		/// <summary>
+		///    Constructs and initializes a new instance of <see
+		///    cref="File" /> for a specified file abstraction.
+		/// </summary>
+		/// <param name="abstraction">
+		///    A <see cref="IFileAbstraction" /> object to use when
+		///    reading from and writing to the file.
+		/// </param>
+		/// <exception cref="ArgumentNullException">
+		///    <paramref name="abstraction" /> is <see langword="null"
+		///    />.
+		/// </exception>
+		protected File (IFileAbstraction abstraction)
+			: this (abstraction, ReadStyle.Average)
+		{
+		}
+
+#endregion
+
+
+#region Public Methods
+
+		/// <summary>
+		///    Saves the changes made in the current instance to the
+		///    file it represents.
+		/// </summary>
+		public override void Save ()
+		{
+			Mode = AccessMode.Write;
+			try {
+				SaveMetadata ();
+
+				TagTypesOnDisk = TagTypes;
+			} finally {
+				Mode = AccessMode.Closed;
+			}
+		}
+
+#endregion
+
+
+#region Private Methods
+
+		/// <summary>
+		///    Reads the information from file with a specified read style.
+		/// </summary>
+		/// <param name="propertiesStyle">
+		///    A <see cref="ReadStyle" /> value specifying at what level
+		///    of accuracy to read the media properties, or <see
+		///    cref="ReadStyle.None" /> to ignore the properties.
+		/// </param>
+		private void Read (ReadStyle propertiesStyle)
+		{
+			Mode = AccessMode.Read;
+			try {
+				ImageTag = new CombinedImageTag (TagTypes.XMP | TagTypes.GifComment);
+
+				ReadHeader ();
+				ReadMetadata ();
+
+				TagTypesOnDisk = TagTypes;
+
+				if (propertiesStyle != ReadStyle.None)
+					properties = ExtractProperties ();
+
+			} finally {
+				Mode = AccessMode.Closed;
+			}
+		}
+
+
+		/// <summary>
+		///   Reads a single byte form file. This is needed often for Gif files.
+		/// </summary>
+		/// <returns>
+		///   A <see cref="System.Byte"/> with the read data.
+		/// </returns>
+		private byte ReadByte ()
+		{
+			ByteVector data = ReadBlock (1);
+
+			if (data.Count != 1)
+				throw new CorruptFileException ("Unexpected end of file");
+
+			return data[0];
+		}
+
+
+		/// <summary>
+		///    Reads the Header and the Logical Screen Descriptor of the GIF file and,
+		///    if there is one, skips the global color table. It also extracts the
+		///    image width and height from it.
+		/// </summary>
+		private void ReadHeader ()
+		{
+			// The header consists of:
+			//
+			// 3 Bytes        Signature
+			// 3 Bytes        Version
+			//
+			// The Logical Screen Descriptor of:
+			//
+			// 2 Bytes        Width  (little endian)
+			// 2 Bytes        Height (little endian)
+			// 1 Byte         Screen and Color Map (packed field)
+			// 1 Byte         Background Color
+			// 1 Byte         Aspect Ratio
+			//
+			// Whereas the bits of the packed field contains some special information.
+			ByteVector data = ReadBlock (13);
+
+			if (data.Count != 13)
+				throw new CorruptFileException ("Unexpected end of Header");
+
+			if (data.Mid (0, 3).ToString () != SIGNATURE)
+				throw new CorruptFileException (String.Format ("Expected a GIF signature at start of file, but found: {0}", data.Mid (0, 3).ToString ()));
+
+			// We do not care about the version here, because we can read both versions in the same way.
+			// We just care when writing metadata, that, if necessary, the version is increased to 89a.
+			var read_version = data.Mid (3, 3).ToString ();
+			if (read_version == VERSION_87A || read_version == VERSION_89A)
+				version = read_version;
+			else
+				throw new UnsupportedFormatException (
+					String.Format ("Only GIF versions 87a and 89a are currently supported, but not: {0}", read_version));
+
+			// Read Image Size (little endian)
+			width = data.Mid (6, 2).ToUShort (false);
+			height = data.Mid (8, 2).ToUShort (false);
+
+			// Skip optional global color table
+			SkipColorTable (data [10]);
+		}
+
+
+		/// <summary>
+		///    Reads the metadata from file. The current position must point to the
+		///    start of the first block after the Header and Logical Screen
+		///    Descriptor (and, if there is one, the Global Color Table)
+		/// </summary>
+		private void ReadMetadata ()
+		{
+			start_of_blocks = Tell;
+
+			// Read Blocks until end of file is reached.
+			while (true) {
+				byte identifier = ReadByte ();
+
+				switch (identifier) {
+				case 0x2c:
+					SkipImage ();
+					break;
+
+				case 0x21:
+					ReadExtensionBlock ();
+					break;
+
+				case 0x3B:
+					return;
+
+				default:
+					throw new CorruptFileException (
+						String.Format ("Do not know what to do with byte 0x{0:X2} at the beginning of a block ({1}).", identifier, Tell - 1));
+				}
+			}
+		}
+
+		/// <summary>
+		///    Reads an Extension Block at the current position. The current position must
+		///    point to the 2nd byte of the comment block. (The other byte is usually
+		///    read before to identify the comment block)
+		/// </summary>
+		private void ReadExtensionBlock ()
+		{
+			// Extension Block
+			//
+			// 1 Byte       Extension Introducer (0x21)
+			// 1 Byte       Extension Identifier
+			// ....
+			//
+			// Note, the Extension Introducer was read before to
+			// identify the Extension Block. Therefore, it has not
+			// to be handled here.
+			byte identifier = ReadByte ();
+
+			switch (identifier) {
+			case 0xFE:
+				ReadCommentBlock ();
+				break;
+
+			case 0xFF:
+				ReadApplicationExtensionBlock ();
+				break;
+
+			// Control Extension Block, ...
+			case 0xF9:
+			// ... Plain Text Extension ...
+			case 0x01:
+			// ... and all other unknown blocks can be skipped by just
+			// reading sub-blocks.
+			default:
+				SkipSubBlocks ();
+				break;
+			}
+		}
+
+
+		/// <summary>
+		///    Reads an Application Extension Block at the current position. The current
+		///    position must point to the 3rd byte of the comment block. (The other 2 bytes
+		///    are usually read before to identify the comment block)
+		/// </summary>
+		private void ReadApplicationExtensionBlock ()
+		{
+			// Application Extension Block
+			//
+			// 1 Byte       Extension Introducer (0x21)
+			// 1 Byte       Application Extension Label (0xFF)
+			// 1 Byte       Block Size (0x0B - 11)
+			// 8 Bytes      Application Identifier
+			// 3 Bytes      Application Auth. Code
+			// N Bytes      Application Data (sub blocks)
+			// 1 Byte       Block Terminator (0x00)
+			//
+			// Note, the first 2 bytes are still read to identify the Comment Block.
+			// Therefore, we only need to read the sub blocks and extract the data.
+			long position = Tell;
+			ByteVector data = ReadBlock (12);
+
+			if (data.Count != 12)
+				throw new CorruptFileException ("");
+
+			// Contains XMP data
+			if (data.Mid (1, 8) == XMP_IDENTIFIER &&
+			    data.Mid (9, 3) == XMP_AUTH_CODE) {
+				// XMP Data is not organized in sub-blocks
+
+				// start of xmp data
+				long data_start = Tell;
+
+				// start of trailer start
+				// FIXME: Since File.Find is still buggy, the following call does not work to find the end of the
+				// XMP data. Therfore, we use here a different way for now.
+				//long xmp_trailer_start = Find (new ByteVector (0x00), data_start);
+
+				// Since searching just one byte is save, we search for the end of the xmp trailer which
+				// consists of two 0x00 bytes and compute the expected start.
+				long xmp_trailer_start = Find (new byte [] {0x00}, data_start) - XMP_MAGIC_TRAILER.Length + 2;
+
+				Seek (data_start, SeekOrigin.Begin);
+
+				if (xmp_trailer_start <= data_start)
+					throw new CorruptFileException ("No End of XMP data found");
+
+				// length of xmp data
+				int data_length = (int) (xmp_trailer_start - data_start);
+
+				ByteVector xmp_data = ReadBlock (data_length);
+				ImageTag.AddTag (new XmpTag (xmp_data.ToString (StringType.UTF8)));
+
+				// 2 bytes where read before
+				xmp_block_start = position - 2;
+				xmp_block_length = 14 + data_length + XMP_MAGIC_TRAILER.Length;
+
+				// set position behind the XMP block
+				Seek (xmp_trailer_start + XMP_MAGIC_TRAILER.Length, SeekOrigin.Begin);
+
+			} else {
+				SkipSubBlocks ();
+			}
+		}
+
+
+		/// <summary>
+		///    Reads a Comment Block at the current position. The current position must
+		///    point to the 3rd byte of the comment block. (The other 2 bytes are usually
+		///    read before to identify the comment block)
+		/// </summary>
+		private void ReadCommentBlock ()
+		{
+			long position = Tell;
+
+			// Comment Extension
+			//
+			// 1 Byte       Extension Introducer (0x21)
+			// 1 Byte       Comment Label (0xFE)
+			// N Bytes      Comment Data (Sub Blocks)
+			// 1 Byte       Block Terminator (0x00)
+			//
+			// Note, the first 2 bytes are still read to identify the Comment Block.
+			// Therefore, we only need to read the sub blocks and extract the data.
+
+			string comment = ReadSubBlocks ();
+
+			// Only add the tag, if no one is still contained.
+			if ((TagTypes & TagTypes.GifComment) == 0x00) {
+				ImageTag.AddTag (new GifCommentTag (comment));
+
+				// 2 bytes where read before
+				comment_block_start = position - 2;
+				comment_block_length =  (int) (Tell - comment_block_start);
+			}
+		}
+
+
+		/// <summary>
+		///    Skips the color table if there is one
+		/// </summary>
+		/// <param name="packed_data">
+		///    A <see cref="System.Byte"/> with the packed data which is
+		///    contained Logical Screen Descriptor or in the Image Descriptor.
+		/// </param>
+		/// <remarks>
+		///    The data contained in the packed data is different for the Logical
+		///    Screen Descriptor and for the Image Descriptor. But fortunately,
+		///    the bits which are used do identifying the exitstance and the size
+		///    of the color table are at the same position.
+		/// </remarks>
+		private void SkipColorTable (byte packed_data)
+		{
+			// Packed Field (Information with Bit 0 is  LSB)
+			//
+			// Bit 0-2      Size of Color Table
+			// Bit 3-6      Other stuff
+			// Bit 7        (Local|Global) Color Table Flag
+			//
+			// We are interested in Bit 7 which indicates if a global color table is
+			// present or not and the Bits 0-2 which indicate the size of the color
+			// table.
+			if ((packed_data & 0x80) == 0x80) {
+				// 2^(size + 1) for each color.
+				int table_size = 3 * (1 << ((packed_data & 0x07) + 1));
+
+				// and simply skip the color table
+				ByteVector color_table = ReadBlock (table_size);
+
+				if (color_table.Count != table_size)
+					throw new CorruptFileException ("Unexpected end of Color Table");
+
+			}
+		}
+
+
+		/// <summary>
+		///    Skip over the image data at the current position. The current position must
+		///    point to 2nd byte of the Image Descriptor. (First byte is usually read before
+		///    to identify the image descriptor.)
+		/// </summary>
+		private void SkipImage ()
+		{
+			// Image Descriptor
+			//
+			// 1 Byte         Separator (0x2C)
+			// 2 Bytes        Image Left Position   (little endian)
+			// 2 Bytes        Image Right Position  (little endian)
+			// 2 Bytes        Image Witdh           (little endian)
+			// 2 Bytes        Image Height          (little endian)
+			// 1 Byte         Packed Data
+			//
+			// Note, the Separator was read before to identify the Image Block
+			// Therefore, we only need to read 9 bytes here.
+			ByteVector data = ReadBlock (9);
+
+			if (data.Count != 9)
+				throw new CorruptFileException ("Unexpected end of Image Descriptor");
+
+			// Skip an optional local color table
+			SkipColorTable (data [8]);
+
+
+			// Image Data
+			//
+			// 1 Byte         LZW Minimum Code Size
+			// N Bytes        Image Data (Sub-Blocks)
+			//
+			// Before the image data, one byte for LZW encoding information is used.
+			// This byte is read first, then the sub-blocks are skipped.
+			ReadBlock (1);
+			SkipSubBlocks ();
+		}
+
+
+		/// <summary>
+		///    Reads a sequence of sub-blocks from the current position and concatenates the data
+		///    from the sub-blocks to a string. The current position must point to the size-byte
+		///    of the first subblock to skip.
+		/// </summary>
+		/// <returns>
+		///    A <see cref="System.String"/> with the data contained in the sub-blocks.
+		/// </returns>
+		private string ReadSubBlocks ()
+		{
+			// Sub Block
+			// Starts with one byte with the number of data bytes
+			// following. The last sub block is terminated by length 0
+			System.Text.StringBuilder builder = new System.Text.StringBuilder ();
+
+			byte length = 0;
+
+			do {
+
+				if (length >= 0)
+					builder.Append (ReadBlock (length).ToString ());
+
+				// read new length byte
+				length = ReadByte ();
+
+			// The sub-blocks are terminated with 0
+			} while (length != 0);
+
+			return builder.ToString ();
+		}
+
+
+		/// <summary>
+		///    Skips over a sequence of sub-blocks from the current position in the file.
+		///    The current position must point to the size-byte of the first subblock to skip.
+		/// </summary>
+		private void SkipSubBlocks ()
+		{
+			// Sub Block
+			// Starts with one byte with the number of data bytes
+			// following. The last sub block is terminated by length 0
+			byte length = 0;
+
+			do {
+
+				if (Tell + length >= Length)
+					throw new CorruptFileException ("Unexpected end of Sub-Block");
+
+				// Seek to end of sub-block and update the position
+				Seek (Tell + length, SeekOrigin.Begin);
+
+
+				// read new length byte
+				length = ReadByte ();
+
+			// The sub-blocks are terminated with 0
+			} while (length != 0);
+		}
+
+
+		/// <summary>
+		///    Save the metadata to file.
+		/// </summary>
+		private void SaveMetadata ()
+		{
+			ByteVector comment_block = RenderGifCommentBlock ();
+			ByteVector xmp_block = RenderXMPBlock ();
+
+			// If we write metadata and the version is not 89a, bump the format version
+			// because application extension blocks and comment extension blocks are
+			// specified in 89a.
+			// If we do not write metadata or if metadata is deleted, we do not care
+			// about the version, because it may be wrong before.
+			if (comment_block != null && xmp_block != null && version != VERSION_89A) {
+				Insert (VERSION_89A, 3, VERSION_89A.Length);
+			}
+
+			// We store the metadata at the beginning of the file starting from the first block.
+			// Therefore, we can overwrite those metadata blocks which are still at this place and
+			// we delete the ones which are placed somewhere.
+			int metadata_length = 0;
+
+			// first, deetermine the length of metadata blocks stored at the beginning
+			if (xmp_block_start == start_of_blocks) {
+				if (xmp_block_start + xmp_block_length == comment_block_start)
+					metadata_length = xmp_block_length + comment_block_length;
+				else
+					metadata_length = xmp_block_length;
+			} else if (comment_block_start == start_of_blocks) {
+				if (comment_block_start + comment_block_length == xmp_block_start)
+					metadata_length = xmp_block_length + comment_block_length;
+				else
+					metadata_length = comment_block_length;
+			}
+
+			// second, delete block which are somwhere in the file
+			if ((xmp_block_start > comment_block_start) && (xmp_block_start > start_of_blocks + metadata_length)) {
+
+				// delete XMP Block
+				Insert (new ByteVector (), xmp_block_start, xmp_block_length);
+
+				// and delete Comment Block, if necessary
+				if (comment_block_start > start_of_blocks + metadata_length)
+					Insert (new ByteVector (), comment_block_start, comment_block_length);
+
+			} else if ((comment_block_start > xmp_block_start) && (comment_block_start > start_of_blocks + metadata_length)) {
+
+				// delete Comment Block
+				Insert (new ByteVector (), comment_block_start, comment_block_length);
+
+				// and delete XMP Block, if necessary
+				if (xmp_block_start > start_of_blocks + metadata_length)
+					Insert (new ByteVector (), xmp_block_start, xmp_block_length);
+			}
+
+			// now, only metadata is stored at the beginning of the file, and we can overwrite it.
+			ByteVector metadata_blocks = new ByteVector ();
+			metadata_blocks.Add (comment_block);
+			metadata_blocks.Add (xmp_block);
+
+			Insert (metadata_blocks, start_of_blocks, metadata_length);
+
+			if (comment_block == null) {
+				comment_block_start = -1;
+				comment_block_length = 0;
+			} else {
+				comment_block_start = start_of_blocks;
+				comment_block_length = comment_block.Count;
+			}
+
+			if (xmp_block == null) {
+				xmp_block_start = -1;
+				xmp_block_length = 0;
+			} else {
+
+				xmp_block_length = xmp_block.Count;
+
+				if (comment_block == null)
+					xmp_block_start = start_of_blocks;
+				else
+					xmp_block_start = comment_block_start + comment_block_length;
+
+			}
+		}
+
+
+		/// <summary>
+		///    Renders the XMP data to a Application Extension Block which can be
+		///    embedded in a Gif file.
+		/// </summary>
+		/// <returns>
+		///    A <see cref="ByteVector"/> with the Application Extension Block for the
+		///    XMP data, or <see langword="null" /> if the file does not have XMP data.
+		/// </returns>
+		private ByteVector RenderXMPBlock ()
+		{
+			// Check, if XmpTag is contained
+			XmpTag xmp = ImageTag.Xmp;
+			if (xmp == null)
+				return null;
+
+			ByteVector xmp_data = new ByteVector ();
+
+			// Add Extension Introducer (0x21), Application Extension Label (0xFF) and
+			// the Block Size (0x0B
+			xmp_data.Add (new byte [] {0x21, 0xFF, 0x0B});
+
+			// Application Identifier and Appl. Auth. Code
+			xmp_data.Add (XMP_IDENTIFIER);
+			xmp_data.Add (XMP_AUTH_CODE);
+
+			// Add XMP data and Magic Trailer
+			// For XMP, we do not need to store the data in sub-blocks, therfore we
+			// can just add the whole rendered data. (The trailer fixes this)
+			xmp_data.Add (xmp.Render ());
+			xmp_data.Add (XMP_MAGIC_TRAILER);
+
+			return xmp_data;
+		}
+
+
+		/// <summary>
+		///    Renders the Gif Comment to a Comment Extension Block which can be
+		///    embedded in a Gif file.
+		/// </summary>
+		/// <returns>
+		///    A <see cref="ByteVector"/> with the Comment Extension Block for the
+		///    Gif Comment, or <see langword="null" /> if the file does not have
+		///    a Gif Comment.
+		/// </returns>
+		private ByteVector RenderGifCommentBlock ()
+		{
+			// Check, if GifCommentTag is contained
+			GifCommentTag comment_tag = GetTag (TagTypes.GifComment) as GifCommentTag;
+			if (comment_tag == null)
+				return null;
+
+			string comment = comment_tag.Comment;
+			if (comment == null)
+				return null;
+
+			ByteVector comment_data = new ByteVector ();
+
+			// Add Extension Introducer (0x21) and Comment Label (0xFE)
+			comment_data.Add (new byte [] {0x21, 0xFE});
+
+			// Add data of comment in sub-blocks of max length 256.
+			ByteVector comment_bytes = new ByteVector (comment);
+			byte block_max = 255;
+			for (int start = 0; start < comment_bytes.Count; start += block_max) {
+				byte block_length = (byte) Math.Min (comment_bytes.Count - start, block_max);
+
+				comment_data.Add (block_length);
+				comment_data.Add (comment_bytes.Mid (start, block_length));
+			}
+			comment_data.Add (new byte [] {0x00});
+
+			return comment_data;
+		}
+
+
+		/// <summary>
+		///    Attempts to extract the media properties of the main
+		///    photo.
+		/// </summary>
+		/// <returns>
+		///    A <see cref="Properties" /> object with a best effort guess
+		///    at the right values. When no guess at all can be made,
+		///    <see langword="null" /> is returned.
+		/// </returns>
+		private Properties ExtractProperties ()
+		{
+			if (width > 0 && height > 0)
+				return new Properties (TimeSpan.Zero, new Codec (width, height));
+
+			return null;
+
+		}
+
+#endregion
+
+	}
+}
diff --git a/lib/TagLib/TagLib/Gif/GifCommentTag.cs b/lib/TagLib/TagLib/Gif/GifCommentTag.cs
new file mode 100644
index 0000000..648c16e
--- /dev/null
+++ b/lib/TagLib/TagLib/Gif/GifCommentTag.cs
@@ -0,0 +1,103 @@
+//
+// GifCommentTag.cs:
+//
+// Author:
+//   Ruben Vermeersch (ruben savanne be)
+//   Mike Gemuende (mike gemuende de)
+//
+// Copyright (C) 2009 Ruben Vermeersch
+// Copyright (C) 2010 Mike Gemuende
+//
+// This library is free software; you can redistribute it and/or modify
+// it  under the terms of the GNU Lesser General Public License version
+// 2.1 as published by the Free Software Foundation.
+//
+// This library is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+// USA
+//
+
+using System;
+
+using TagLib.Image;
+
+namespace TagLib.Gif
+{
+	/// <summary>
+	///    Contains the Gif comment.
+	/// </summary>
+	public class GifCommentTag : ImageTag
+	{
+#region Constructors
+
+		/// <summary>
+		///    Constructor.
+		/// </summary>
+		/// <param name="value">
+		///    The value of the comment.
+		/// </param>
+		public GifCommentTag (string value)
+		{
+			Value = value;
+		}
+
+		/// <summary>
+		///    Constructor. Creates a new empty comment.
+		/// </summary>
+		public GifCommentTag () {
+			Value = null;
+		}
+
+#endregion
+
+#region Public Properties
+
+		/// <summary>
+		///    The value of the comment represented by the current instance.
+		/// </summary>
+		public string Value { get; set; }
+
+		/// <summary>
+		///    Gets or sets the comment for the image described
+		///    by the current instance.
+		/// </summary>
+		/// <value>
+		///    A <see cref="string" /> containing the comment of the
+		///    current instace.
+		/// </value>
+		public override string Comment {
+			get { return Value; }
+			set { Value = value; }
+		}
+
+#endregion
+
+#region Public Methods
+
+		/// <summary>
+		///    Gets the tag types contained in the current instance.
+		/// </summary>
+		/// <value>
+		///    Always <see cref="TagTypes.GifComment" />.
+		/// </value>
+		public override TagTypes TagTypes {
+			get { return TagTypes.GifComment; }
+		}
+
+		/// <summary>
+		///    Clears the values stored in the current instance.
+		/// </summary>
+		public override void Clear ()
+		{
+			Value = null;
+		}
+
+#endregion
+	}
+}
diff --git a/lib/TagLib/TagLib/Image/CombinedImageTag.cs b/lib/TagLib/TagLib/Image/CombinedImageTag.cs
index 619f27f..cf57a21 100644
--- a/lib/TagLib/TagLib/Image/CombinedImageTag.cs
+++ b/lib/TagLib/TagLib/Image/CombinedImageTag.cs
@@ -519,7 +519,6 @@ namespace TagLib.Image
 				foreach (ImageTag tag in AllTags) {
 					string value = tag.Model;
 					
-					if (value != null)
 					if (!string.IsNullOrEmpty(value))
 						return value;
 				}
diff --git a/lib/TagLib/TagLib/Image/File.cs b/lib/TagLib/TagLib/Image/File.cs
index c76ee75..2e343c8 100644
--- a/lib/TagLib/TagLib/Image/File.cs
+++ b/lib/TagLib/TagLib/Image/File.cs
@@ -25,6 +25,7 @@ using System;
 using System.Collections.Generic;
 
 using TagLib.Jpeg;
+using TagLib.Gif;
 using TagLib.IFD;
 using TagLib.Xmp;
 
@@ -161,6 +162,10 @@ namespace TagLib.Image
 			case TagTypes.JpegComment:
 				new_tag = new JpegCommentTag ();
 				break;
+
+			case TagTypes.GifComment:
+				new_tag = new GifCommentTag ();
+				break;
 				
 			case TagTypes.TiffIFD:
 				new_tag = new IFDTag ();
diff --git a/lib/TagLib/TagLib/Tag.cs b/lib/TagLib/TagLib/Tag.cs
index e6a2ce7..ae69c0e 100644
--- a/lib/TagLib/TagLib/Tag.cs
+++ b/lib/TagLib/TagLib/Tag.cs
@@ -106,6 +106,11 @@ namespace TagLib {
 		JpegComment = 0x00001000,
 		
 		/// <summary>
+		///    Gif Comment Tag
+		/// </summary>
+		GifComment = 0x00002000,
+
+		/// <summary>
 		///    All tag types.
 		/// </summary>
 		AllTags      = 0xFFFFFFFF
diff --git a/lib/TagLib/TagLib/TagLib.sources b/lib/TagLib/TagLib/TagLib.sources
index b5b80d3..22c6ecb 100644
--- a/lib/TagLib/TagLib/TagLib.sources
+++ b/lib/TagLib/TagLib/TagLib.sources
@@ -41,6 +41,9 @@ TAGLIB_CSFILES = \
 	$(srcdir)/TagLib/Flac/StreamHeader.cs \
 	$(srcdir)/TagLib/Flac/Picture.cs \
 	$(srcdir)/TagLib/Flac/File.cs \
+	$(srcdir)/TagLib/Gif/Codec.cs \
+	$(srcdir)/TagLib/Gif/File.cs \
+	$(srcdir)/TagLib/Gif/GifCommentTag.cs \
 	$(srcdir)/TagLib/Image/Codec.cs \
 	$(srcdir)/TagLib/Image/CombinedImageTag.cs \
 	$(srcdir)/TagLib/Image/File.cs \



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]