Initial commit

This commit is contained in:
Thastertyn committed 2024-06-07 00:47:07 +02:00
commit 262601edb8
2025 files changed
+218754

No files matched your search

+60
View File
@@ -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) { }
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 47cdd5f88d9090727bd3e17f4f9c0893
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+782
View File
@@ -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
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ccc0f5856d987773a8ff481376f547e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+102
View File
@@ -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: