Initial commit
This commit is contained in:
commit
262601edb8
2025 files changed
+218754
No files matched your search
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
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) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47cdd5f88d9090727bd3e17f4f9c0893
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,782 @@
|
||||
//
|
||||
// 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.Image;
|
||||
using TagLib.Xmp;
|
||||
|
||||
namespace TagLib.Gif
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This class extends <see cref="TagLib.Image.ImageBlockFile" /> to provide tagging
|
||||
/// and property support for Gif files.
|
||||
/// </summary>
|
||||
[SupportedMimeType ("taglib/gif", "gif")]
|
||||
[SupportedMimeType ("image/gif")]
|
||||
public class File : ImageBlockFile
|
||||
{
|
||||
|
||||
#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>
|
||||
static readonly string XMP_IDENTIFIER = "XMP Data";
|
||||
|
||||
/// <summary>
|
||||
/// Application Authentication Extension Code for an XMP Block
|
||||
/// </summary>
|
||||
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>
|
||||
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>
|
||||
int width;
|
||||
|
||||
/// <summary>
|
||||
/// The height of the image
|
||||
/// </summary>
|
||||
int height;
|
||||
|
||||
/// <summary>
|
||||
/// The Properties of the image
|
||||
/// </summary>
|
||||
Properties properties;
|
||||
|
||||
/// <summary>
|
||||
/// The version of the file format
|
||||
/// </summary>
|
||||
string version;
|
||||
|
||||
/// <summary>
|
||||
/// The start of the first block in file after the header.
|
||||
/// </summary>
|
||||
long start_of_blocks = -1;
|
||||
|
||||
#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 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 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="TagLib.File.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 (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="TagLib.File.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 ()
|
||||
{
|
||||
// Boilerplate
|
||||
PreSave ();
|
||||
|
||||
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>
|
||||
void Read (ReadStyle propertiesStyle)
|
||||
{
|
||||
Mode = AccessMode.Read;
|
||||
try {
|
||||
ImageTag = new CombinedImageTag (TagTypes.XMP | TagTypes.GifComment);
|
||||
|
||||
ReadHeader ();
|
||||
ReadMetadata ();
|
||||
|
||||
TagTypesOnDisk = TagTypes;
|
||||
|
||||
if ((propertiesStyle & ReadStyle.Average) != 0)
|
||||
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>
|
||||
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>
|
||||
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 (
|
||||
$"Expected a GIF signature at start of file, but found: {data.Mid (0, 3)}");
|
||||
|
||||
// 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 (
|
||||
$"Only GIF versions 87a and 89a are currently supported, but not: {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>
|
||||
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 (
|
||||
$"Do not know what to do with byte 0x{identifier:X2} at the beginning of a block ({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>
|
||||
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>
|
||||
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), this));
|
||||
|
||||
// 2 bytes where read before
|
||||
AddMetadataBlock (position - 2, 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>
|
||||
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
|
||||
AddMetadataBlock (position - 2, Tell - position + 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
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>
|
||||
string ReadSubBlocks ()
|
||||
{
|
||||
// Sub Block
|
||||
// Starts with one byte with the number of data bytes
|
||||
// following. The last sub block is terminated by length 0
|
||||
var builder = new System.Text.StringBuilder ();
|
||||
|
||||
byte length = 0;
|
||||
|
||||
do {
|
||||
|
||||
if (length >= 0)
|
||||
builder.Append (ReadBlock (length));
|
||||
|
||||
// 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>
|
||||
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>
|
||||
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);
|
||||
}
|
||||
|
||||
// now, only metadata is stored at the beginning of the file, and we can overwrite it.
|
||||
var metadata_blocks = new ByteVector {
|
||||
comment_block,
|
||||
xmp_block
|
||||
};
|
||||
|
||||
SaveMetadata (metadata_blocks, start_of_blocks);
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
ByteVector RenderXMPBlock ()
|
||||
{
|
||||
// Check, if XmpTag is contained
|
||||
XmpTag xmp = ImageTag.Xmp;
|
||||
if (xmp == null)
|
||||
return null;
|
||||
|
||||
var xmp_data = new ByteVector {
|
||||
|
||||
// Add Extension Introducer (0x21), Application Extension Label (0xFF) and
|
||||
// the Block Size (0x0B
|
||||
new byte[] { 0x21, 0xFF, 0x0B },
|
||||
|
||||
// Application Identifier and Appl. Auth. Code
|
||||
XMP_IDENTIFIER,
|
||||
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.Render (),
|
||||
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>
|
||||
ByteVector RenderGifCommentBlock ()
|
||||
{
|
||||
// Check, if GifCommentTag is contained
|
||||
if (!(GetTag (TagTypes.GifComment) is GifCommentTag comment_tag))
|
||||
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>
|
||||
Properties ExtractProperties ()
|
||||
{
|
||||
if (width > 0 && height > 0)
|
||||
return new Properties (TimeSpan.Zero, new Codec (width, height));
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ccc0f5856d987773a8ff481376f547e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// 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 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90b0deeab3e1f5525bfc3333391201f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user