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

+209
View File
@@ -0,0 +1,209 @@
//
// Bitstream.cs:
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Copyright (C) 2007 Brian Nickel
//
// 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.Ogg
{
/// <summary>
/// This class accepts a sequence of pages belonging to a single
/// logical bitstream, processes them, and extracts the tagging and
/// media information.
/// </summary>
public class Bitstream
{
#region Private Fields
/// <summary>
/// Contains the last packet of the previous page in case it
/// is continued in the next frame.
/// </summary>
ByteVector previous_packet;
/// <summary>
/// Contains the index of the next packet to be processed.
/// </summary>
int packet_index;
/// <summary>
/// Contains the absolute granular position of the first
/// page.
/// </summary>
readonly long first_absolute_granular_position;
#endregion
#region Constructors
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="Bitstream" /> capable of processing a specified
/// page.
/// </summary>
/// <param name="page">
/// The first <see cref="Page" /> object of the stream to be
/// processed by the new instance.
/// </param>
/// <remarks>
/// The constructor only sets the new instance up to read the
/// packet, but doesn't actually read it.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// <paramref name="page" /> is <see langword="null" />.
/// </exception>
/// <exception cref="UnsupportedFormatException">
/// No registered codec capable of processing <paramref
/// name="page" /> could be found.
/// </exception>
public Bitstream (Page page)
{
if (page == null)
throw new ArgumentNullException (nameof (page));
// Assume that the first packet is completely enclosed.
// This should be sufficient for codec recognition.
Codec = Codec.GetCodec (page.Packets[0]);
first_absolute_granular_position = page.Header.AbsoluteGranularPosition;
}
#endregion
#region Public Methods
/// <summary>
/// Reads the next logical page in the stream.
/// </summary>
/// <param name="page">
/// The next logical <see cref="Page" /> object in the
/// stream.
/// </param>
/// <returns>
/// <see langword="true" /> if the codec has read all the
/// necessary packets for the stream and does not need to be
/// called again, typically once the Xiph comment has been
/// found. Otherwise <see langword="false" />.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="page" /> is <see langword="null" />.
/// </exception>
public bool ReadPage (Page page)
{
if (page == null)
throw new ArgumentNullException (nameof (page));
ByteVector[] packets = page.Packets;
for (int i = 0; i < packets.Length; i++) {
ByteVector packet = packets[i];
// If we're at the first packet of the page, and
// we're continuing an old packet, combine the
// old with the new.
if (i == 0 && (page.Header.Flags &
PageFlags.FirstPacketContinued) != 0 &&
previous_packet != null) {
previous_packet.Add (packet);
packet = previous_packet;
}
previous_packet = null;
if (i == packets.Length - 1 && !page.Header.LastPacketComplete) {
// If we're at the last packet of the
// page and it's continued on the next page, store it.
previous_packet = new ByteVector (packet);
} else if (ReadPacket (packet)) {
// Otherwise, we need to process it.
return true;
}
}
return false;
}
/// <summary>
/// Gets the duration of the stream represented by the
/// current instance.
/// </summary>
/// <param name="lastAbsoluteGranularPosition">
/// A <see cref="long" /> value containing the absolute
/// granular position of the last page in the bitstream.
/// </param>
/// <returns>
/// A <see cref="TimeSpan" /> object containing the duration
/// of the stream represented by the current instance.
/// </returns>
public TimeSpan GetDuration (long lastAbsoluteGranularPosition)
{
return Codec.GetDuration (first_absolute_granular_position, lastAbsoluteGranularPosition);
}
#endregion
#region Public Properties
/// <summary>
/// Gets the codec object used to interpret the stream
/// represented by the current instance.
/// </summary>
/// <value>
/// The <see cref="Codec" /> object used by the current
/// instance.
/// </value>
public Codec Codec { get; private set; }
#endregion
#region Public Properties
/// <summary>
/// Sents a packet to the codec processor to read it.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing the next
/// packet in the stream.
/// </param>
/// <returns>
/// <see langword="true" /> if the codec has read all the
/// necessary packets for the stream and does not need to be
/// called again, typically once the Xiph comment has been
/// found. Otherwise <see langword="false" />.
/// </returns>
bool ReadPacket (ByteVector packet)
{
return Codec.ReadPacket (packet, packet_index++);
}
#endregion
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a12be478792ef0b798d11f431269c2c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+288
View File
@@ -0,0 +1,288 @@
//
// Codec.cs:
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Copyright (C) 2007 Brian Nickel
//
// 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.Collections.Generic;
namespace TagLib.Ogg
{
/// <summary>
/// This abstract class implements <see cref="ICodec" /> to provide
/// support for processing packets from an Ogg logical bitstream.
/// </summary>
/// <remarks>
/// Unsupported Ogg codecs can be added by creating child classes and
/// registering them using <see cref="AddCodecProvider" />.
/// </remarks>
/// <seealso cref="CodecProvider" />
/// <seealso cref="AddCodecProvider" />
public abstract class Codec : ICodec
{
#region Public Delegates
/// <summary>
/// Represents a method capable of checking an Ogg header
/// packet to see it is matches a given codec.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing an Ogg
/// header packet.
/// </param>
/// <returns>
/// A <see cref="Codec" /> object capable of processing the
/// stream from which the packet was generated; otherwise
/// <see langword="null" />.
/// </returns>
/// <remarks>
/// <para>In order to add support for a new Ogg codec in
/// TagLib#, a derivative class of <see cref="Codec" /> needs
/// to be created. The class then needs to be added to the
/// codec detection system by creating a <see
/// cref="CodecProvider" /> and registering it with <see
/// cref="AddCodecProvider"/>.</para>
/// <para>A method implementing <see cref="CodecProvider" />
/// should read <paramref name="packet" /> to determine if
/// it's subclass can handle the data. If it can, it should
/// return a new instance of that class, but in no way act
/// upon the data. If the class cannot be used to read the
/// packet, <see langref="null" /> indicates to the system
/// that it needs to try anther codec provider.</para>
/// </remarks>
/// <example>
/// <para>The following example would check for a Speex
/// packet and return a Speex codec:</para>
/// <code lang="C++">
/// Codec.AddCodecProvider (delegate (ByteVector packet) {
/// return packet.StartsWith ("Speex ") ? new MySpeexCodec () : null;
/// });
/// </code>
/// </example>
public delegate Codec CodecProvider (ByteVector packet);
#endregion
#region Private Static Fields
/// <summary>
/// Contains registered codec providers.
/// </summary>
static readonly List<CodecProvider> providers = new List<CodecProvider> ();
#endregion
#region Private Static Methods
/// <summary>
/// Determines the correct codec to use for a stream header
/// packet.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing the first
/// packet of an Ogg logical bitstream.
/// </param>
/// <returns>
/// A <see cref="Codec" /> object capable of handling
/// <paramref name="packet" /> and subsequent packets from
/// the same stream.
/// </returns>
/// <exception cref="UnsupportedFormatException">
/// No registered codec capable of processing <paramref
/// name="packet" /> could be found.
/// </exception>
/// <remarks>
/// This method will first use <see cref="CodecProvider" />
/// delegates registered with <see cref="AddCodecProvider" />
/// and then attempt to use the built-in codecs.
/// </remarks>
public static Codec GetCodec (ByteVector packet)
{
Codec c = null;
foreach (CodecProvider p in providers) {
c = p (packet);
if (c != null) return c;
}
c = Codecs.Vorbis.FromPacket (packet);
if (c != null)
return c;
c = Codecs.Theora.FromPacket (packet);
if (c != null)
return c;
c = Codecs.Opus.FromPacket (packet);
if (c != null)
return c;
throw new UnsupportedFormatException ("Unknown codec.");
}
/// <summary>
/// Adds a codec
/// </summary>
/// <param name="provider">
/// A <see cref="CodecProvider"/>
/// </param>
/// <remarks>
/// A <see cref="CodecProvider" /> delegate is used to add
/// support for new <see cref="Codec" /> subclasses in <see
/// cref="GetCodec" />.
/// </remarks>
/// <seealso cref="CodecProvider" />
public static void AddCodecProvider (CodecProvider provider)
{
providers.Insert (0, provider);
}
#endregion
#region Private Properties
/// <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 abstract string Description { get; }
/// <summary>
/// Gets the types of media represented by the current
/// instance.
/// </summary>
/// <value>
/// A bitwise combined <see cref="MediaTypes" /> containing
/// the types of media represented by the current instance.
/// </value>
public abstract MediaTypes MediaTypes { get; }
/// <summary>
/// Gets the raw Xiph comment data contained in the codec.
/// </summary>
/// <value>
/// A <see cref="ByteVector" /> object containing a raw Xiph
/// comment or <see langword="null"/> if none was found.
/// </value>
public abstract ByteVector CommentData { get; }
/// <summary>
/// Gets the duration of the media represented by the current
/// instance.
/// </summary>
/// <value>
/// Always <see cref="TimeSpan.Zero" />.
/// </value>
/// <remarks>
/// In order to determine the duration of an Ogg stream, the
/// first and last granular positions will be passed to <see
/// cref="GetDuration" />.
/// </remarks>
public TimeSpan Duration {
get { return TimeSpan.Zero; }
}
#endregion
#region Private Methods
/// <summary>
/// Reads a Ogg packet that has been encountered in the
/// stream.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing a packet to
/// be read by the current instance.
/// </param>
/// <param name="index">
/// A <see cref="int" /> value containing the index of the
/// packet in the stream.
/// </param>
/// <returns>
/// <see langword="true" /> if the codec has read all the
/// necessary packets for the stream and does not need to be
/// called again, typically once the Xiph comment has been
/// found. Otherwise <see langword="false" />.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="packet" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index" /> is less than zero.
/// </exception>
/// <exception cref="CorruptFileException">
/// The data does not conform to the specificiation for the
/// codec represented by the current instance.
/// </exception>
public abstract bool ReadPacket (ByteVector packet, int index);
/// <summary>
/// Computes the duration of the stream using the first and
/// last granular positions of the stream.
/// </summary>
/// <param name="firstGranularPosition">
/// A <see cref="long" /> value containing the first granular
/// position of the stream.
/// </param>
/// <param name="lastGranularPosition">
/// A <see cref="long" /> value containing the last granular
/// position of the stream.
/// </param>
/// <returns>
/// A <see cref="TimeSpan" /> value containing the duration
/// of the stream.
/// </returns>
public abstract TimeSpan GetDuration (long firstGranularPosition, long lastGranularPosition);
/// <summary>
/// Replaces the comment packet in a collection of packets
/// with the rendered version of a Xiph comment or inserts a
/// comment packet if the stream lacks one.
/// </summary>
/// <param name="packets">
/// A <see cref="ByteVectorCollection" /> object containing
/// a collection of packets.
/// </param>
/// <param name="comment">
/// A <see cref="XiphComment" /> object to store the rendered
/// version of in <paramref name="packets" />.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="packets" /> or <paramref name="comment"
/// /> is <see langword="null" />.
/// </exception>
public abstract void SetCommentPacket (ByteVectorCollection packets, XiphComment comment);
#endregion
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fed920fee9cd6fea7ba74ef626454b6e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eb042ad18bd78a508b883d944a2d08d0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+374
View File
@@ -0,0 +1,374 @@
//
// Opus.cs:
//
// Author:
// Les De Ridder (les@lesderid.net)
//
// Copyright (C) 2007 Brian Nickel
// Copyright (C) 2015 Les De Ridder
//
// 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.Ogg.Codecs
{
/// <summary>
/// This class extends <see cref="Codec" /> and implements <see
/// cref="IAudioCodec" /> to provide support for processing Ogg
/// Opus bitstreams.
/// </summary>
public class Opus : Codec, IAudioCodec
{
#region Private Static Fields
/// <summary>
/// Contains the file identifier.
/// </summary>
static readonly ByteVector magic_signature_base = "Opus";
static readonly ByteVector magic_signature_header = "OpusHead";
static readonly ByteVector magic_signature_comment = "OpusTags";
static readonly int magic_signature_length = 8;
#endregion
#region Private Fields
/// <summary>
/// Contains the header packet.
/// </summary>
HeaderPacket header;
/// <summary>
/// Contains the comment data.
/// </summary>
ByteVector comment_data;
#endregion
#region Constructors
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="Opus" />.
/// </summary>
Opus ()
{
}
#endregion
#region Public Methods
/// <summary>
/// Reads a Ogg packet that has been encountered in the
/// stream.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing a packet to
/// be read by the current instance.
/// </param>
/// <param name="index">
/// A <see cref="int" /> value containing the index of the
/// packet in the stream.
/// </param>
/// <returns>
/// <see langword="true" /> if the codec has read all the
/// necessary packets for the stream and does not need to be
/// called again, typically once the Xiph comment has been
/// found. Otherwise <see langword="false" />.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="packet" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index" /> is less than zero.
/// </exception>
/// <exception cref="CorruptFileException">
/// The data does not conform to the specificiation for the
/// codec represented by the current instance.
/// </exception>
public override bool ReadPacket (ByteVector packet, int index)
{
if (packet == null)
throw new ArgumentNullException (nameof (packet));
if (index < 0)
throw new ArgumentOutOfRangeException (nameof (index), "index must be at least zero.");
ByteVector signature = MagicSignature (packet);
if (signature != magic_signature_header && index == 0)
throw new CorruptFileException ("Stream does not begin with opus header.");
if (comment_data == null) {
if (signature == magic_signature_header)
header = new HeaderPacket (packet);
else if (signature == magic_signature_comment)
comment_data = packet.Mid (magic_signature_length);
else
return true;
}
return comment_data != null;
}
/// <summary>
/// Computes the duration of the stream using the first and
/// last granular positions of the stream.
/// </summary>
/// <param name="firstGranularPosition">
/// A <see cref="long" /> value containing the first granular
/// position of the stream.
/// </param>
/// <param name="lastGranularPosition">
/// A <see cref="long" /> value containing the last granular
/// position of the stream.
/// </param>
/// <returns>
/// A <see cref="TimeSpan" /> value containing the duration
/// of the stream.
/// </returns>
public override TimeSpan GetDuration (long firstGranularPosition, long lastGranularPosition)
{
return TimeSpan.FromSeconds ((lastGranularPosition - firstGranularPosition - 2 * header.pre_skip) / (double)48000);
}
/// <summary>
/// Replaces the comment packet in a collection of packets
/// with the rendered version of a Xiph comment or inserts a
/// comment packet if the stream lacks one.
/// </summary>
/// <param name="packets">
/// A <see cref="ByteVectorCollection" /> object containing
/// a collection of packets.
/// </param>
/// <param name="comment">
/// A <see cref="XiphComment" /> object to store the rendered
/// version of in <paramref name="packets" />.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="packets" /> or <paramref name="comment"
/// /> is <see langword="null" />.
/// </exception>
public override void SetCommentPacket (ByteVectorCollection packets, XiphComment comment)
{
if (packets == null)
throw new ArgumentNullException (nameof (packets));
if (comment == null)
throw new ArgumentNullException (nameof (comment));
ByteVector data = new ByteVector ();
data.Add (magic_signature_comment);
data.Add (comment.Render (true));
if (packets.Count > 1 && MagicSignature (packets[1]) == magic_signature_comment)
packets[1] = data;
else
packets.Insert (1, data);
}
#endregion
#region Public Properties
/// <summary>
/// Gets the bitrate of the audio represented by the current
/// instance.
/// </summary>
/// <value>
/// A <see cref="int" /> value containing a bitrate of the
/// audio represented by the current instance.
/// </value>
/// <remarks>
/// Always returns zero, since bitrate is variable and no
/// information is stored in the Ogg header (unlike e.g. Vorbis).
/// </remarks>
public int AudioBitrate {
get { return 0; }
}
/// <summary>
/// Gets the sample rate of the audio represented by the
/// current instance.
/// </summary>
/// <value>
/// A <see cref="int" /> value containing the original
/// sample rate of the audio represented by the current instance.
/// </value>
public int AudioSampleRate {
get { return (int)header.input_sample_rate; }
}
/// <summary>
/// Gets the number of channels in the audio represented by
/// the current instance.
/// </summary>
/// <value>
/// A <see cref="int" /> value containing the number of
/// channels in the audio represented by the current
/// instance.
/// </value>
public int AudioChannels {
get { return (int)header.channel_count; }
}
/// <summary>
/// Gets the types of media represented by the current
/// instance.
/// </summary>
/// <value>
/// Always <see cref="MediaTypes.Audio" />.
/// </value>
public override MediaTypes MediaTypes {
get { return MediaTypes.Audio; }
}
/// <summary>
/// Gets the raw Xiph comment data contained in the codec.
/// </summary>
/// <value>
/// A <see cref="ByteVector" /> object containing a raw Xiph
/// comment or <see langword="null"/> if none was found.
/// </value>
public override ByteVector CommentData {
get { return comment_data; }
}
/// <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 $"Opus Version {header.opus_version} Audio";
}
}
#endregion
#region Public Static Methods
/// <summary>
/// Implements the <see cref="T:CodecProvider" /> delegate to
/// provide support for recognizing a Opus stream from the
/// header packet.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing the stream
/// header packet.
/// </param>
/// <returns>
/// A <see cref="Codec"/> object containing a codec capable
/// of parsing the stream of <see langref="null" /> if the
/// stream is not a Opus stream.
/// </returns>
public static Codec FromPacket (ByteVector packet)
{
return (MagicSignature (packet) == magic_signature_header) ? new Opus () : null;
}
#endregion
#region Private Static Methods
/// <summary>
/// Gets the magic signature for a specified Opus packet.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing a Opus
/// packet.
/// </param>
/// <returns>
/// A <see cref="ByteVector" /> value containing the magic
/// signature or null if the packet is invalid.
/// </returns>
static ByteVector MagicSignature (ByteVector packet)
{
if (packet.Count < magic_signature_length)
return null;
for (int i = 0; i < magic_signature_base.Count; i++)
if (packet[i] != magic_signature_base[i])
return null;
return packet.Mid (0, magic_signature_length);
}
#endregion
/// <summary>
/// This structure represents a Opus header packet.
/// </summary>
struct HeaderPacket
{
public uint opus_version;
public uint channel_count;
public uint pre_skip;
public uint input_sample_rate;
public uint output_gain;
public uint channel_map;
public uint stream_count;
public uint two_channel_stream_count;
public uint[] channel_mappings;
public HeaderPacket (ByteVector data)
{
opus_version = data[8];
channel_count = data[9];
pre_skip = data.Mid (10, 2).ToUInt (false);
input_sample_rate = data.Mid (12, 4).ToUInt (false);
output_gain = data.Mid (16, 2).ToUInt (false);
channel_map = data[18];
if (channel_map == 0) {
stream_count = 1;
two_channel_stream_count = channel_count - 1;
channel_mappings = new uint[channel_count];
channel_mappings[0] = 0;
if (channel_count == 2) {
channel_mappings[1] = 1;
}
} else {
stream_count = data[19];
two_channel_stream_count = data[20];
channel_mappings = new uint[channel_count];
for (int i = 0; i < channel_count; i++) {
channel_mappings[i] = data[21 + i];
}
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a14c8220a5c3b244faae861945f167a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+371
View File
@@ -0,0 +1,371 @@
//
// Theora.cs:
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Copyright (C) 2007 Brian Nickel
//
// 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.Ogg.Codecs
{
/// <summary>
/// This class extends <see cref="Codec" /> and implements <see
/// cref="IVideoCodec" /> to provide support for processing Ogg
/// Theora bitstreams.
/// </summary>
public class Theora : Codec, IVideoCodec
{
#region Private Static Fields
/// <summary>
/// Contains the file identifier.
/// </summary>
static readonly ByteVector id = "theora";
#endregion
#region Private Fields
/// <summary>
/// Contains the header packet.
/// </summary>
HeaderPacket header;
/// <summary>
/// Contains the comment data.
/// </summary>
ByteVector comment_data;
#endregion
#region Constructors
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="Theora" />.
/// </summary>
Theora ()
{
}
#endregion
#region Public Methods
/// <summary>
/// Reads a Ogg packet that has been encountered in the
/// stream.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing a packet to
/// be read by the current instance.
/// </param>
/// <param name="index">
/// A <see cref="int" /> value containing the index of the
/// packet in the stream.
/// </param>
/// <returns>
/// <see langword="true" /> if the codec has read all the
/// necessary packets for the stream and does not need to be
/// called again, typically once the Xiph comment has been
/// found. Otherwise <see langword="false" />.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="packet" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index" /> is less than zero.
/// </exception>
/// <exception cref="CorruptFileException">
/// The data does not conform to the specificiation for the
/// codec represented by the current instance.
/// </exception>
public override bool ReadPacket (ByteVector packet, int index)
{
if (packet == null)
throw new ArgumentNullException (nameof (packet));
if (index < 0)
throw new ArgumentOutOfRangeException (nameof (index), "index must be at least zero.");
int type = PacketType (packet);
if (type != 0x80 && index == 0)
throw new CorruptFileException ("Stream does not begin with theora header.");
if (comment_data == null) {
if (type == 0x80)
header = new HeaderPacket (packet);
else if (type == 0x81)
comment_data = packet.Mid (7);
else
return true;
}
return comment_data != null;
}
/// <summary>
/// Computes the duration of the stream using the first and
/// last granular positions of the stream.
/// </summary>
/// <param name="firstGranularPosition">
/// A <see cref="long" /> value containing the first granular
/// position of the stream.
/// </param>
/// <param name="lastGranularPosition">
/// A <see cref="long" /> value containing the last granular
/// position of the stream.
/// </param>
/// <returns>
/// A <see cref="TimeSpan" /> value containing the duration
/// of the stream.
/// </returns>
public override TimeSpan GetDuration (long firstGranularPosition, long lastGranularPosition)
{
return TimeSpan.FromSeconds (
header.GranuleTime (lastGranularPosition) -
header.GranuleTime (firstGranularPosition));
}
/// <summary>
/// Replaces the comment packet in a collection of packets
/// with the rendered version of a Xiph comment or inserts a
/// comment packet if the stream lacks one.
/// </summary>
/// <param name="packets">
/// A <see cref="ByteVectorCollection" /> object containing
/// a collection of packets.
/// </param>
/// <param name="comment">
/// A <see cref="XiphComment" /> object to store the rendered
/// version of in <paramref name="packets" />.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="packets" /> or <paramref name="comment"
/// /> is <see langword="null" />.
/// </exception>
public override void SetCommentPacket (ByteVectorCollection packets, XiphComment comment)
{
if (packets == null)
throw new ArgumentNullException (nameof (packets));
if (comment == null)
throw new ArgumentNullException (nameof (comment));
ByteVector data = new ByteVector ((byte)0x81);
data.Add (id);
data.Add (comment.Render (true));
if (packets.Count > 1 && PacketType (packets[1]) == 0x81)
packets[1] = data;
else
packets.Insert (1, data);
}
#endregion
#region Public Properties
/// <summary>
/// Gets the width of the video represented by the current
/// instance.
/// </summary>
/// <value>
/// A <see cref="int" /> value containing the width of the
/// video represented by the current instance.
/// </value>
public int VideoWidth {
get { return header.width; }
}
/// <summary>
/// Gets the height of the video represented by the current
/// instance.
/// </summary>
/// <value>
/// A <see cref="int" /> value containing the height of the
/// video represented by the current instance.
/// </value>
public int VideoHeight {
get { return header.height; }
}
/// <summary>
/// Gets the types of media represented by the current
/// instance.
/// </summary>
/// <value>
/// Always <see cref="MediaTypes.Video" />.
/// </value>
public override MediaTypes MediaTypes {
get { return MediaTypes.Video; }
}
/// <summary>
/// Gets the raw Xiph comment data contained in the codec.
/// </summary>
/// <value>
/// A <see cref="ByteVector" /> object containing a raw Xiph
/// comment or <see langword="null"/> if none was found.
/// </value>
public override ByteVector CommentData {
get { return comment_data; }
}
/// <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 $"Theora Version {header.major_version}.{header.minor_version} Video";
}
}
#endregion
#region Public Static Methods
/// <summary>
/// Implements the <see cref="T:CodecProvider" /> delegate to
/// provide support for recognizing a Theora stream from the
/// header packet.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing the stream
/// header packet.
/// </param>
/// <returns>
/// A <see cref="Codec"/> object containing a codec capable
/// of parsing the stream of <see langref="null" /> if the
/// stream is not a Theora stream.
/// </returns>
public static Codec FromPacket (ByteVector packet)
{
return (PacketType (packet) == 0x80) ? new Theora () : null;
}
#endregion
#region Private Static Methods
/// <summary>
/// Gets the packet type for a specified Theora packet.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing a Theora
/// packet.
/// </param>
/// <returns>
/// A <see cref="int" /> value containing the packet type or
/// -1 if the packet is invalid.
/// </returns>
static int PacketType (ByteVector packet)
{
if (packet.Count <= id.Count || packet[0] < 0x80)
return -1;
for (int i = 0; i < id.Count; i++)
if (packet[i + 1] != id[i])
return -1;
return packet[0];
}
#endregion
/// <summary>
/// This structure represents a Theora header packet.
/// </summary>
struct HeaderPacket
{
public byte major_version;
public byte minor_version;
public byte revision_version;
public int width;
public int height;
public int fps_numerator;
public int fps_denominator;
public int keyframe_granule_shift;
public HeaderPacket (ByteVector data)
{
major_version = data[7];
minor_version = data[8];
revision_version = data[9];
// width = data.Mid (10, 2).ToShort () << 4;
// height = data.Mid (12, 2).ToShort () << 4;
width = (int)data.Mid (14, 3).ToUInt (); // Frame Width.
height = (int)data.Mid (17, 3).ToUInt (); // Frame Height.
// Offset X.
// Offset Y.
fps_numerator = (int)data.Mid (22, 4).ToUInt ();
fps_denominator = (int)data.Mid (26, 4).ToUInt ();
// Aspect Numerator.
// Aspect Denominator.
// Colorspace.
// Target bitrate.
ushort last_bits = data.Mid (40, 2).ToUShort ();
keyframe_granule_shift = (last_bits >> 5) & 0x1F;
}
/// <summary>
/// Converts an absolute granular position into a
/// seconds.
/// </summary>
/// <param name="granularPosition">
/// A <see cref="long" /> value containing the
/// absolute granular position.
/// </param>
/// <returns>
/// A <see cref="double" /> value containing the time
/// at <paramref name="granularPosition" /> in
/// seconds.</returns>
/// <remarks>
/// Many thanks to the good people at
/// irc://irc.freenode.net#theora for making this
/// code a reality.
/// </remarks>
public double GranuleTime (long granularPosition)
{
long iframe = granularPosition >> keyframe_granule_shift;
long pframe = granularPosition - (iframe << keyframe_granule_shift);
return (iframe + pframe) * (fps_denominator / (double)fps_numerator);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4101a3ed56e2ee3c5ad91051eeca7af3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+346
View File
@@ -0,0 +1,346 @@
//
// Vorbis.cs:
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Copyright (C) 2007 Brian Nickel
//
// 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.Ogg.Codecs
{
/// <summary>
/// This class extends <see cref="Codec" /> and implements <see
/// cref="IAudioCodec" /> to provide support for processing Ogg
/// Vorbis bitstreams.
/// </summary>
public class Vorbis : Codec, IAudioCodec
{
#region Private Static Fields
/// <summary>
/// Contains the file identifier.
/// </summary>
static readonly ByteVector id = "vorbis";
#endregion
#region Private Fields
/// <summary>
/// Contains the header packet.
/// </summary>
HeaderPacket header;
/// <summary>
/// Contains the comment data.
/// </summary>
ByteVector comment_data;
#endregion
#region Constructors
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="Vorbis" />.
/// </summary>
Vorbis ()
{
}
#endregion
#region Public Methods
/// <summary>
/// Reads a Ogg packet that has been encountered in the
/// stream.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing a packet to
/// be read by the current instance.
/// </param>
/// <param name="index">
/// A <see cref="int" /> value containing the index of the
/// packet in the stream.
/// </param>
/// <returns>
/// <see langword="true" /> if the codec has read all the
/// necessary packets for the stream and does not need to be
/// called again, typically once the Xiph comment has been
/// found. Otherwise <see langword="false" />.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="packet" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="index" /> is less than zero.
/// </exception>
/// <exception cref="CorruptFileException">
/// The data does not conform to the specificiation for the
/// codec represented by the current instance.
/// </exception>
public override bool ReadPacket (ByteVector packet, int index)
{
if (packet == null)
throw new ArgumentNullException (nameof (packet));
if (index < 0)
throw new ArgumentOutOfRangeException (nameof (index), "index must be at least zero.");
int type = PacketType (packet);
if (type != 1 && index == 0)
throw new CorruptFileException ("Stream does not begin with vorbis header.");
if (comment_data == null) {
if (type == 1)
header = new HeaderPacket (packet);
else if (type == 3)
comment_data = packet.Mid (7);
else
return true;
}
return comment_data != null;
}
/// <summary>
/// Computes the duration of the stream using the first and
/// last granular positions of the stream.
/// </summary>
/// <param name="firstGranularPosition">
/// A <see cref="long" /> value containing the first granular
/// position of the stream.
/// </param>
/// <param name="lastGranularPosition">
/// A <see cref="long" /> value containing the last granular
/// position of the stream.
/// </param>
/// <returns>
/// A <see cref="TimeSpan" /> value containing the duration
/// of the stream.
/// </returns>
public override TimeSpan GetDuration (long firstGranularPosition, long lastGranularPosition)
{
return header.sample_rate == 0 ? TimeSpan.Zero :
TimeSpan.FromSeconds ((lastGranularPosition - firstGranularPosition) / (double)header.sample_rate);
}
/// <summary>
/// Replaces the comment packet in a collection of packets
/// with the rendered version of a Xiph comment or inserts a
/// comment packet if the stream lacks one.
/// </summary>
/// <param name="packets">
/// A <see cref="ByteVectorCollection" /> object containing
/// a collection of packets.
/// </param>
/// <param name="comment">
/// A <see cref="XiphComment" /> object to store the rendered
/// version of in <paramref name="packets" />.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="packets" /> or <paramref name="comment"
/// /> is <see langword="null" />.
/// </exception>
public override void SetCommentPacket (ByteVectorCollection packets, XiphComment comment)
{
if (packets == null)
throw new ArgumentNullException (nameof (packets));
if (comment == null)
throw new ArgumentNullException (nameof (comment));
ByteVector data = new ByteVector ((byte)0x03);
data.Add (id);
data.Add (comment.Render (true));
if (packets.Count > 1 && PacketType (packets[1]) == 0x03)
packets[1] = data;
else
packets.Insert (1, data);
}
#endregion
#region Public Properties
/// <summary>
/// Gets the bitrate of the audio represented by the current
/// instance.
/// </summary>
/// <value>
/// A <see cref="int" /> value containing a bitrate of the
/// audio represented by the current instance.
/// </value>
public int AudioBitrate {
get {
return (int)(header.bitrate_nominal / 1000f + 0.5);
}
}
/// <summary>
/// Gets the sample rate of the audio represented by the
/// current instance.
/// </summary>
/// <value>
/// A <see cref="int" /> value containing the sample rate of
/// the audio represented by the current instance.
/// </value>
public int AudioSampleRate {
get { return (int)header.sample_rate; }
}
/// <summary>
/// Gets the number of channels in the audio represented by
/// the current instance.
/// </summary>
/// <value>
/// A <see cref="int" /> value containing the number of
/// channels in the audio represented by the current
/// instance.
/// </value>
public int AudioChannels {
get { return (int)header.channels; }
}
/// <summary>
/// Gets the types of media represented by the current
/// instance.
/// </summary>
/// <value>
/// Always <see cref="MediaTypes.Audio" />.
/// </value>
public override MediaTypes MediaTypes {
get { return MediaTypes.Audio; }
}
/// <summary>
/// Gets the raw Xiph comment data contained in the codec.
/// </summary>
/// <value>
/// A <see cref="ByteVector" /> object containing a raw Xiph
/// comment or <see langword="null"/> if none was found.
/// </value>
public override ByteVector CommentData {
get { return comment_data; }
}
/// <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 $"Vorbis Version {header.vorbis_version} Audio";
}
}
#endregion
#region Public Static Methods
/// <summary>
/// Implements the <see cref="T:CodecProvider" /> delegate to
/// provide support for recognizing a Vorbis stream from the
/// header packet.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing the stream
/// header packet.
/// </param>
/// <returns>
/// A <see cref="Codec"/> object containing a codec capable
/// of parsing the stream of <see langref="null" /> if the
/// stream is not a Vorbis stream.
/// </returns>
public static Codec FromPacket (ByteVector packet)
{
return (PacketType (packet) == 1) ? new Vorbis () : null;
}
#endregion
#region Private Static Methods
/// <summary>
/// Gets the packet type for a specified Vorbis packet.
/// </summary>
/// <param name="packet">
/// A <see cref="ByteVector" /> object containing a Vorbis
/// packet.
/// </param>
/// <returns>
/// A <see cref="int" /> value containing the packet type or
/// -1 if the packet is invalid.
/// </returns>
static int PacketType (ByteVector packet)
{
if (packet.Count <= id.Count)
return -1;
for (int i = 0; i < id.Count; i++)
if (packet[i + 1] != id[i])
return -1;
return packet[0];
}
#endregion
/// <summary>
/// This structure represents a Vorbis header packet.
/// </summary>
struct HeaderPacket
{
public uint sample_rate;
public uint channels;
public uint vorbis_version;
public uint bitrate_maximum;
public uint bitrate_nominal;
public uint bitrate_minimum;
public HeaderPacket (ByteVector data)
{
vorbis_version = data.Mid (7, 4).ToUInt (false);
channels = data[11];
sample_rate = data.Mid (12, 4).ToUInt (false);
bitrate_maximum = data.Mid (16, 4).ToUInt (false);
bitrate_nominal = data.Mid (20, 4).ToUInt (false);
bitrate_minimum = data.Mid (24, 4).ToUInt (false);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eb1a3113ffc3c33888579ca93ea7b089
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+422
View File
@@ -0,0 +1,422 @@
//
// File.cs:
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Original Source:
// oggfile.cpp from TagLib
//
// Copyright (C) 2005-2007 Brian Nickel
// Copyright (C) 2003 Scott Wheeler (Original Implementation)
//
// 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.Collections.Generic;
namespace TagLib.Ogg
{
/// <summary>
/// This class extends <see cref="TagLib.File" /> to provide tagging
/// and properties support for Ogg files.
/// </summary>
[SupportedMimeType ("taglib/ogg", "ogg")]
[SupportedMimeType ("taglib/oga", "oga")]
[SupportedMimeType ("taglib/ogv", "ogv")]
[SupportedMimeType ("taglib/opus", "opus")]
[SupportedMimeType ("application/ogg")]
[SupportedMimeType ("application/x-ogg")]
[SupportedMimeType ("audio/vorbis")]
[SupportedMimeType ("audio/x-vorbis")]
[SupportedMimeType ("audio/x-vorbis+ogg")]
[SupportedMimeType ("audio/ogg")]
[SupportedMimeType ("audio/x-ogg")]
[SupportedMimeType ("video/ogg")]
[SupportedMimeType ("video/x-ogm+ogg")]
[SupportedMimeType ("video/x-theora+ogg")]
[SupportedMimeType ("video/x-theora")]
[SupportedMimeType ("audio/opus")]
[SupportedMimeType ("audio/x-opus")]
[SupportedMimeType ("audio/x-opus+ogg")]
public class File : TagLib.File
{
#region Private Fields
/// <summary>
/// Contains the tags for the file.
/// </summary>
readonly GroupedComment tag;
/// <summary>
/// Contains the media properties.
/// </summary>
Properties 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 with an average read style.
/// </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)
{
Mode = AccessMode.Read;
try {
tag = new GroupedComment ();
Read (propertiesStyle);
TagTypesOnDisk = TagTypes;
} finally {
Mode = AccessMode.Closed;
}
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="File" /> for a specified file abstraction with an
/// average read style.
/// </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>
public 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 {
var pages = new List<Page> ();
var streams = ReadStreams (pages, out var end);
var paginators = new Dictionary<uint, Paginator> ();
var new_pages = new List<List<Page>> ();
var shifts = new Dictionary<uint, int> ();
foreach (var page in pages) {
uint id = page.Header.StreamSerialNumber;
if (!paginators.ContainsKey (id))
paginators.Add (id, new Paginator (streams[id].Codec));
paginators[id].AddPage (page);
}
foreach (uint id in paginators.Keys) {
paginators[id].SetComment (tag.GetComment (id));
new_pages.Add (new List<Page> (paginators[id].Paginate (out var shift)));
shifts.Add (id, shift);
}
var output = new ByteVector ();
bool empty;
do {
empty = true;
foreach (var stream_pages in new_pages) {
if (stream_pages.Count == 0)
continue;
output.Add (stream_pages[0].Render ());
stream_pages.RemoveAt (0);
if (stream_pages.Count != 0)
empty = false;
}
} while (!empty);
Insert (output, 0, end);
InvariantStartPosition = output.Count;
InvariantEndPosition = Length;
TagTypesOnDisk = TagTypes;
Page.OverwriteSequenceNumbers (this, output.Count, shifts);
} finally {
Mode = AccessMode.Closed;
}
}
/// <summary>
/// Removes a set of tag types from the current instance.
/// </summary>
/// <param name="types">
/// A bitwise combined <see cref="TagTypes" /> value
/// containing tag types to be removed from the file.
/// </param>
/// <remarks>
/// In order to remove all tags from a file, pass <see
/// cref="TagTypes.AllTags" /> as <paramref name="types" />.
/// </remarks>
public override void RemoveTags (TagTypes types)
{
if ((types & TagTypes.Xiph) != TagTypes.None)
tag.Clear ();
}
/// <summary>
/// Gets a tag of a specified type from the current instance,
/// optionally creating a new tag if possible.
/// </summary>
/// <param name="type">
/// A <see cref="TagTypes" /> value indicating the
/// type of tag to read.
/// </param>
/// <param name="create">
/// A <see cref="bool" /> value specifying whether or not to
/// try and create the tag if one is not found.
/// </param>
/// <returns>
/// A <see cref="Tag" /> object containing the tag that was
/// found in or added to the current instance. If no
/// matching tag was found and none was created, <see
/// langword="null" /> is returned.
/// </returns>
public override Tag GetTag (TagTypes type, bool create)
{
if (type == TagTypes.Xiph)
foreach (var comment in tag.Comments)
return comment;
return null;
}
#endregion
#region Public Properties
/// <summary>
/// Gets a abstract representation of all tags stored in the
/// current instance.
/// </summary>
/// <value>
/// A <see cref="TagLib.Tag" /> object representing all tags
/// stored in the current instance.
/// </value>
public override Tag Tag {
get { return tag; }
}
/// <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 Private Methods
/// <summary>
/// Reads the 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)
{
var streams = ReadStreams (null, out var end);
var codecs = new List<ICodec> ();
InvariantStartPosition = end;
InvariantEndPosition = Length;
foreach (uint id in streams.Keys) {
tag.AddComment (id, streams[id].Codec.CommentData);
codecs.Add (streams[id].Codec);
}
if ((propertiesStyle & ReadStyle.Average) == 0)
return;
var last_header = LastPageHeader;
var duration = streams[last_header.StreamSerialNumber].GetDuration (last_header.AbsoluteGranularPosition);
properties = new Properties (duration, codecs);
}
/// <summary>
/// Reads the file until all streams have finished their
/// property and tagging data.
/// </summary>
/// <param name="pages">
/// A <see cref="T:System.Collections.Generic.List`1"/>
/// object to be filled with <see cref="Page" /> objects as
/// they are read, or <see langword="null"/> if the pages
/// are not to be stored.
/// </param>
/// <param name="end">
/// A <see cref="long" /> value reference to be updated to
/// the postion of the first page not read by the current
/// instance.
/// </param>
/// <returns>
/// A <see cref="T:System.Collections.Generic.Dictionary`2"
/// /> object containing stream serial numbers as the keys
/// <see cref="Bitstream" /> objects as the values.
/// </returns>
Dictionary<uint, Bitstream> ReadStreams (List<Page> pages, out long end)
{
var streams = new Dictionary<uint, Bitstream> ();
var active_streams = new List<Bitstream> ();
long position = 0;
do {
Bitstream stream = null;
var page = new Page (this, position);
if ((page.Header.Flags & PageFlags.FirstPageOfStream) != 0) {
stream = new Bitstream (page);
streams.Add (page.Header.StreamSerialNumber, stream);
active_streams.Add (stream);
}
if (stream == null)
stream = streams[page.Header.StreamSerialNumber];
if (active_streams.Contains (stream) && stream.ReadPage (page))
active_streams.Remove (stream);
pages?.Add (page);
position += page.Size;
} while (active_streams.Count > 0);
end = position;
return streams;
}
#endregion
#region Private Properties
/// <summary>
/// Gets the last page header in the file.
/// </summary>
/// <value>
/// A <see cref="PageHeader" /> object containing the last
/// page header in the file.
/// </value>
/// <remarks>
/// The last page header is used to determine the last
/// absolute granular position of a stream so the duration
/// can be calculated.
/// </remarks>
PageHeader LastPageHeader {
get {
long last_page_header_offset = RFind ("OggS");
if (last_page_header_offset < 0)
throw new CorruptFileException ("Could not find last header.");
return new PageHeader (this, last_page_header_offset);
}
}
#endregion
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d0629b431af30065c9564b78babcba44
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d740fd9b0d9d57ee78bdbcac627a720d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+290
View File
@@ -0,0 +1,290 @@
//
// PageHeader.cs:
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Original Source:
// oggpage.cpp from TagLib
//
// Copyright (C) 2005-2007 Brian Nickel
// Copyright (C) 2003 Scott Wheeler (Original Implementation)
//
// 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.Collections.Generic;
namespace TagLib.Ogg
{
/// <summary>
/// This class provides a representation of an Ogg page.
/// </summary>
public class Page
{
#region Private Properties
/// <summary>
/// Contains the packets.
/// </summary>
readonly ByteVectorCollection packets;
#endregion
#region Constructors
/// <summary>
/// Constructs and intializes a new instance of <see
/// cref="Page" /> with a specified header and no packets.
/// </summary>
/// <param name="header">
/// A <see cref="PageHeader"/> object to use as the header of
/// the new instance.
/// </param>
protected Page (PageHeader header)
{
Header = header;
packets = new ByteVectorCollection ();
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="Page" /> by reading a raw Ogg page from a specified
/// position in a specified file.
/// </summary>
/// <param name="file">
/// A <see cref="File" /> object containing the file from
/// which the contents of the new instance are to be read.
/// </param>
/// <param name="position">
/// A <see cref="long" /> value specify at what position to
/// read.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="file" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="position" /> is less than zero or greater
/// than the size of the file.
/// </exception>
/// <exception cref="CorruptFileException">
/// The Ogg identifier could not be found at the correct
/// location.
/// </exception>
public Page (File file, long position)
: this (new PageHeader (file, position))
{
file.Seek (position + Header.Size);
foreach (int packet_size in Header.PacketSizes)
packets.Add (file.ReadBlock (packet_size));
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="Page" /> with a specified header and packets.
/// </summary>
/// <param name="packets">
/// A <see cref="ByteVectorCollection" /> object containing
/// packets to use for the new instance.
/// </param>
/// <param name="header">
/// A <see cref="PageHeader"/> object to use as the header of
/// the new instance.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="packets" /> is <see langword="null" />.
/// </exception>
public Page (ByteVectorCollection packets, PageHeader header)
: this (header)
{
if (packets == null)
throw new ArgumentNullException (nameof (packets));
this.packets = new ByteVectorCollection (packets);
List<int> packet_sizes = new List<int> ();
// Build a page from the list of packets.
foreach (ByteVector v in packets)
packet_sizes.Add (v.Count);
header.PacketSizes = packet_sizes.ToArray ();
}
#endregion
#region Public Methods
/// <summary>
/// Renders the current instance as a raw Ogg page.
/// </summary>
/// <returns>
/// A <see cref="ByteVector" /> object containing the
/// rendered version of the current instance.
/// </returns>
public ByteVector Render ()
{
ByteVector data = Header.Render ();
foreach (ByteVector v in packets)
data.Add (v);
// Compute and set the checksum for the Ogg page. The
// checksum is taken over the entire page with the 4
// bytes reserved for the checksum zeroed and then
// inserted in bytes 22-25 of the page header.
ByteVector checksum = ByteVector.FromUInt (
data.Checksum, false);
for (int i = 0; i < 4; i++)
data[i + 22] = checksum[i];
return data;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the header of the current instance.
/// </summary>
/// <value>
/// A <see cref="PageHeader" /> object that applies to the
/// current instance.
/// </value>
public PageHeader Header { get; private set; }
/// <summary>
/// Gets the packets contained in the current instance.
/// </summary>
/// <value>
/// A <see cref="T:ByteVector[]" /> containing the packets
/// contained in the current instance.
/// </value>
public ByteVector[] Packets {
get { return packets.ToArray (); }
}
/// <summary>
/// Gets the total size of the current instance as it
/// appeared on disk.
/// </summary>
/// <value>
/// A <see cref="uint" /> value containing the size of the
/// page, including the header, as it appeared on disk.
/// </value>
public uint Size {
get { return Header.Size + Header.DataSize; }
}
#endregion
#region Public Static Methods
/// <summary>
/// Overwrites all page headers in a file starting at a
/// specified position, shifting the page sequence numbers
/// a set amount.
/// </summary>
/// <param name="file">
/// A <see cref="File" /> object containing the file to
/// update.
/// </param>
/// <param name="position">
/// A <see cref="long" /> value specify at what position to
/// start updating.
/// </param>
/// <param name="shiftTable">
/// A <see cref="T:System.Collections.Generic.IDictionary`2"
/// /> object where the key is the serial number of the
/// stream to update and the value is the amount to offset
/// the page sequence numbers in the stream.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="file" /> or <paramref name="shiftTable"
/// /> is <see langword="null" />.
/// </exception>
/// <remarks>
/// When the number of pages in a stream changes, all
/// subsequent pages in the stream need to have their page
/// sequence number update in order to remain valid.
/// Additionally, when the page sequence number changes, the
/// page needs to have its checksum recomputed. This makes
/// for a costly recalculation if large comment data is
/// added.
/// </remarks>
public static void OverwriteSequenceNumbers (File file, long position, IDictionary<uint, int> shiftTable)
{
if (file == null)
throw new ArgumentNullException (nameof (file));
if (shiftTable == null)
throw new ArgumentNullException (nameof (shiftTable));
// Check to see if there are no changes to be made.
bool done = true;
foreach (var pair in shiftTable)
if (pair.Value != 0) {
done = false;
break;
}
// If the file is fine, quit.
if (done)
return;
while (position < file.Length - 27) {
PageHeader header = new PageHeader (file, position);
int size = (int)(header.Size + header.DataSize);
if (shiftTable.ContainsKey (header.StreamSerialNumber)
&& shiftTable[header.StreamSerialNumber] != 0) {
file.Seek (position);
ByteVector page_data = file.ReadBlock (size);
ByteVector new_data = ByteVector.FromUInt (
(uint)(header.PageSequenceNumber +
shiftTable[header.StreamSerialNumber]),
false);
for (int i = 18; i < 22; i++)
page_data[i] = new_data[i - 18];
for (int i = 22; i < 26; i++)
page_data[i] = 0;
new_data.Add (ByteVector.FromUInt (
page_data.Checksum, false));
file.Seek (position + 18);
file.WriteBlock (new_data);
}
position += size;
}
}
#endregion
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3bdaa91990d5161d185b4e54c843718a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+522
View File
@@ -0,0 +1,522 @@
//
// PageHeader.cs:
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Original Source:
// oggpageheader.cpp from TagLib
//
// Copyright (C) 2005-2007 Brian Nickel
// Copyright (C) 2003 Scott Wheeler (Original Implementation)
//
// 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.Collections.Generic;
namespace TagLib.Ogg
{
/// <summary>
/// Indicates the special properties of a <see cref="Page" />.
/// </summary>
[Flags]
public enum PageFlags : byte
{
/// <summary>
/// The page is a normal page.
/// </summary>
None = 0,
/// <summary>
/// The first packet of the page is continued from the
/// previous page.
/// </summary>
FirstPacketContinued = 1,
/// <summary>
/// The page is the first page of the stream.
/// </summary>
FirstPageOfStream = 2,
/// <summary>
/// The page is the last page of the stream.
/// </summary>
LastPageOfStream = 4
}
/// <summary>
/// This structure provides a representation of an Ogg page header.
/// </summary>
public struct PageHeader
{
#region Private Propertis
/// <summary>
/// Contains the sizes of the packets contained in the
/// current instance.
/// </summary>
readonly List<int> packet_sizes;
/// <summary>
/// Contains the OGG version.
/// </summary>
readonly byte version;
/// <summary>
/// Contains the page absolute granular postion.
/// </summary>
readonly ulong absolute_granular_position;
#endregion
#region Constructors
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="PageHeader" /> with a given serial number, page
/// number, and flags.
/// </summary>
/// <param name="streamSerialNumber">
/// A <see cref="uint" /> value containing the serial number
/// for the stream containing the page described by the new
/// instance.
/// </param>
/// <param name="pageNumber">
/// A <see cref="uint" /> value containing the index of the
/// page described by the new instance in the stream.
/// </param>
/// <param name="flags">
/// A <see cref="PageFlags" /> object containing the flags
/// that apply to the page described by the new instance.
/// </param>
public PageHeader (uint streamSerialNumber, uint pageNumber, PageFlags flags)
{
version = 0;
Flags = flags;
absolute_granular_position = 0;
StreamSerialNumber = streamSerialNumber;
PageSequenceNumber = pageNumber;
Size = 0;
DataSize = 0;
packet_sizes = new List<int> ();
LastPacketComplete = false;
if (pageNumber == 0 && (flags & PageFlags.FirstPacketContinued) == 0)
Flags |= PageFlags.FirstPageOfStream;
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="PageHeader" /> by reading a raw Ogg page header
/// from a specified position in a specified file.
/// </summary>
/// <param name="file">
/// A <see cref="File" /> object containing the file from
/// which the contents of the new instance are to be read.
/// </param>
/// <param name="position">
/// A <see cref="long" /> value specify at what position to
/// read.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="file" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="position" /> is less than zero or greater
/// than the size of the file.
/// </exception>
/// <exception cref="CorruptFileException">
/// The Ogg identifier could not be found at the correct
/// location.
/// </exception>
public PageHeader (File file, long position)
{
if (file == null)
throw new ArgumentNullException (nameof (file));
if (position < 0 || position > file.Length - 27)
throw new ArgumentOutOfRangeException (nameof (position));
file.Seek (position);
// An Ogg page header is at least 27 bytes, so we'll go
// ahead and read that much and then get the rest when
// we're ready for it.
ByteVector data = file.ReadBlock (27);
if (data.Count < 27 || !data.StartsWith ("OggS"))
throw new CorruptFileException ("Error reading page header");
version = data[4];
Flags = (PageFlags)data[5];
absolute_granular_position = data.Mid (6, 8).ToULong (false);
StreamSerialNumber = data.Mid (14, 4).ToUInt (false);
PageSequenceNumber = data.Mid (18, 4).ToUInt (false);
// Byte number 27 is the number of page segments, which
// is the only variable length portion of the page
// header. After reading the number of page segments
// we'll then read in the coresponding data for this
// count.
int page_segment_count = data[26];
ByteVector page_segments = file.ReadBlock (page_segment_count);
// Another sanity check.
if (page_segment_count < 1 || page_segments.Count != page_segment_count)
throw new CorruptFileException ("Incorrect number of page segments");
// The base size of an Ogg page 27 bytes plus the number
// of lacing values.
Size = (uint)(27 + page_segment_count);
packet_sizes = new List<int> ();
int packet_size = 0;
DataSize = 0;
for (int i = 0; i < page_segment_count; i++) {
DataSize += page_segments[i];
packet_size += page_segments[i];
if (page_segments[i] < 255) {
packet_sizes.Add (packet_size);
packet_size = 0;
}
}
if (packet_size > 0)
packet_sizes.Add (packet_size);
LastPacketComplete = page_segments[page_segment_count - 1] < 255;
}
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="PageHeader" /> by copying the values from another
/// instance, offsetting the page number and applying new
/// flags.
/// </summary>
/// <param name="original">
/// A <see cref="PageHeader"/> object to copy the values
/// from.
/// </param>
/// <param name="offset">
/// A <see cref="uint"/> value specifying how much to offset
/// the page sequence number in the new instance.
/// </param>
/// <param name="flags">
/// A <see cref="PageFlags"/> value specifying the flags to
/// use in the new instance.
/// </param>
public PageHeader (PageHeader original, uint offset, PageFlags flags)
{
version = original.version;
Flags = flags;
absolute_granular_position = original.absolute_granular_position;
StreamSerialNumber = original.StreamSerialNumber;
PageSequenceNumber = original.PageSequenceNumber + offset;
Size = original.Size;
DataSize = original.DataSize;
packet_sizes = new List<int> ();
LastPacketComplete = false;
if (PageSequenceNumber == 0 && (flags & PageFlags.FirstPacketContinued) == 0)
Flags |= PageFlags.FirstPageOfStream;
}
#endregion
#region Public Properties
/// <summary>
/// Gets and sets the sizes for the packets in the page
/// described by the current instance.
/// </summary>
/// <value>
/// A <see cref="T:int[]" /> containing the packet sizes.
/// </value>
public int[] PacketSizes {
get { return packet_sizes.ToArray (); }
set {
packet_sizes.Clear ();
packet_sizes.AddRange (value);
}
}
/// <summary>
/// Indicates whether the final packet is continued on the next page
/// </summary>
/// <value>
/// true if the final packet is complete and not continued on the next page
/// </value>
public bool LastPacketComplete { get; private set; }
/// <summary>
/// Gets the flags for the page described by the current
/// instance.
/// </summary>
/// <value>
/// A <see cref="PageFlags" /> value containing the page
/// flags.
/// </value>
public PageFlags Flags { get; private set; }
/// <summary>
/// Gets the absolute granular position of the page described
/// by the current instance.
/// </summary>
/// <value>
/// A <see cref="long" /> value containing the absolute
/// granular position of the page.
/// </value>
public long AbsoluteGranularPosition {
get { return (long)absolute_granular_position; }
}
/// <summary>
/// Gets the sequence number of the page described by the
/// current instance.
/// </summary>
/// <value>
/// A <see cref="uint" /> value containing the sequence
/// number of the page.
/// </value>
public uint PageSequenceNumber { get; private set; }
/// <summary>
/// Gets the serial number of stream that the page described
/// by the current instance belongs to.
/// </summary>
/// <value>
/// A <see cref="uint" /> value containing the stream serial
/// number.
/// </value>
public uint StreamSerialNumber { get; private set; }
/// <summary>
/// Gets the size of the header as it appeared on disk.
/// </summary>
/// <value>
/// A <see cref="uint" /> value containing the header size.
/// </value>
public uint Size { get; private set; }
/// <summary>
/// Gets the size of the data portion of the page described
/// by the current instance as it appeared on disk.
/// </summary>
/// <value>
/// A <see cref="uint" /> value containing the data size.
/// </value>
public uint DataSize { get; private set; }
#endregion
#region Public Methods
/// <summary>
/// Renders the current instance as a raw Ogg page header.
/// </summary>
/// <returns>
/// A <see cref="ByteVector" /> object containing the
/// rendered version of the current instance.
/// </returns>
public ByteVector Render ()
{
ByteVector data = new ByteVector {
"OggS",
version, // stream structure version
(byte)Flags,
ByteVector.FromULong (
absolute_granular_position, false),
ByteVector.FromUInt (
StreamSerialNumber, false),
ByteVector.FromUInt (
PageSequenceNumber, false),
new ByteVector (4, 0) // checksum, to be filled in later.
};
ByteVector page_segments = LacingValues;
data.Add ((byte)page_segments.Count);
data.Add (page_segments);
return data;
}
#endregion
#region Private Properties
/// <summary>
/// Gets the rendered lacing values for the current instance.
/// </summary>
/// <value>
/// A <see cref="ByteVector" /> object containing the
/// rendered lacing values.
/// </value>
ByteVector LacingValues {
get {
ByteVector data = new ByteVector ();
int[] sizes = PacketSizes;
for (int i = 0; i < sizes.Length; i++) {
// The size of a packet in an Ogg page
// is indicated by a series of "lacing
// values" where the sum of the values
// is the packet size in bytes. Each of
// these values is a byte. A value of
// less than 255 (0xff) indicates the
// end of the packet.
int quot = sizes[i] / 255;
int rem = sizes[i] % 255;
for (int j = 0; j < quot; j++)
data.Add (255);
if (i < sizes.Length - 1 ||
(packet_sizes[i] % 255) != 0)
data.Add ((byte)rem);
}
return data;
}
}
#endregion
#region IEquatable
/// <summary>
/// Generates a hash code for the current instance.
/// </summary>
/// <returns>
/// A <see cref="int" /> value containing the hash code for
/// the current instance.
/// </returns>
public override int GetHashCode ()␍ {
unchecked {
return (int)(LacingValues.GetHashCode () ^
version ^ (int)Flags ^
(int)absolute_granular_position ^
StreamSerialNumber ^
PageSequenceNumber ^ Size ^
DataSize);
}
}
/// <summary>
/// Checks whether or not the current instance is equal to
/// another object.
/// </summary>
/// <param name="other">
/// A <see cref="object" /> to compare to the current
/// instance.
/// </param>
/// <returns>
/// A <see cref="bool" /> value indicating whether or not the
/// current instance is equal to <paramref name="other" />.
/// </returns>
/// <seealso cref="M:System.IEquatable`1.Equals" />
public override bool Equals (object other)
{
if (!(other is PageHeader))
return false;
return Equals ((PageHeader)other);
}
/// <summary>
/// Checks whether or not the current instance is equal to
/// another instance of <see cref="PageHeader" />.
/// </summary>
/// <param name="other">
/// A <see cref="PageHeader" /> object to compare to the
/// current instance.
/// </param>
/// <returns>
/// A <see cref="bool" /> value indicating whether or not the
/// current instance is equal to <paramref name="other" />.
/// </returns>
/// <seealso cref="M:System.IEquatable`1.Equals" />
public bool Equals (PageHeader other)
{
return packet_sizes == other.packet_sizes &&
version == other.version &&
Flags == other.Flags &&
absolute_granular_position ==
other.absolute_granular_position &&
StreamSerialNumber ==
other.StreamSerialNumber &&
PageSequenceNumber ==
other.PageSequenceNumber &&
Size == other.Size &&
DataSize == other.DataSize;
}
/// <summary>
/// Gets whether or not two instances of <see
/// cref="PageHeader" /> are equal to eachother.
/// </summary>
/// <param name="first">
/// A <see cref="PageHeader" /> object to compare.
/// </param>
/// <param name="second">
/// A <see cref="PageHeader" /> object to compare.
/// </param>
/// <returns>
/// <see langword="true" /> if <paramref name="first" /> is
/// equal to <paramref name="second" />. Otherwise, <see
/// langword="false" />.
/// </returns>
public static bool operator == (PageHeader first, PageHeader second)
{
return first.Equals (second);
}
/// <summary>
/// Gets whether or not two instances of <see
/// cref="PageHeader" /> differ.
/// </summary>
/// <param name="first">
/// A <see cref="PageHeader" /> object to compare.
/// </param>
/// <param name="second">
/// A <see cref="PageHeader" /> object to compare.
/// </param>
/// <returns>
/// <see langword="true" /> if <paramref name="first" /> is
/// unequal to <paramref name="second" />. Otherwise, <see
/// langword="false" />.
/// </returns>
public static bool operator != (PageHeader first, PageHeader second)
{
return !first.Equals (second);
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 803e4ace08266f732819ed7516c7c764
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+269
View File
@@ -0,0 +1,269 @@
//
// Paginator.cs:
//
// Author:
// Brian Nickel (brian.nickel@gmail.com)
//
// Original Source:
// oggpage.cpp from TagLib
//
// Copyright (C) 2006-2007 Brian Nickel
// Copyright (C) 2003 Scott Wheeler (Original Implementation)
//
// 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.Collections.Generic;
namespace TagLib.Ogg
{
/// <summary>
/// This class accepts a sequence of pages for a single Ogg stream,
/// accepts changes, and produces a new sequence of pages to write to
/// disk.
/// </summary>
public class Paginator
{
#region Private Fields
/// <summary>
/// Contains the packets to paginate.
/// </summary>
readonly ByteVectorCollection packets =
new ByteVectorCollection ();
/// <summary>
/// Contains the first page header.
/// </summary>
PageHeader? first_page_header;
/// <summary>
/// Contains the codec to use.
/// </summary>
readonly Codec codec;
/// <summary>
/// contains the number of pages read.
/// </summary>
int pages_read;
#endregion
#region Constructors
/// <summary>
/// Constructs and initializes a new instance of <see
/// cref="Paginator" /> for a given <see cref="Codec" />
/// object.
/// </summary>
/// <param name="codec">
/// A <see cref="Codec"/> object to use when processing
/// packets.
/// </param>
public Paginator (Codec codec)
{
this.codec = codec;
}
#endregion
#region Public Methods
/// <summary>
/// Adds the next page to the current instance.
/// </summary>
/// <param name="page">
/// The next <see cref="Page" /> object found in the stream.
/// </param>
public void AddPage (Page page)
{
pages_read++;
if (first_page_header == null)
first_page_header = page.Header;
if (page.Packets.Length == 0)
return;
ByteVector[] page_packets = page.Packets;
for (int i = 0; i < page_packets.Length; i++) {
if ((page.Header.Flags & PageFlags.FirstPacketContinued) != 0 && i == 0 && packets.Count > 0)
packets[packets.Count - 1].Add (page_packets[0]);
else
packets.Add (page_packets[i]);
}
}
/// <summary>
/// Stores a Xiph comment in the codec-specific comment
/// packet.
/// </summary>
/// <param name="comment">
/// A <see cref="XiphComment" /> object to store in the
/// comment packet.
/// </param>
public void SetComment (XiphComment comment)
{
codec.SetCommentPacket (packets, comment);
}
/// <summary>
/// Repaginates the pages passed into the current instance to
/// handle changes made to the Xiph comment.
/// </summary>
/// <returns>
/// A <see cref="T:Page[]" /> containing the new page
/// collection.
/// </returns>
[Obsolete ("Use Paginator.Paginate(out int)")]
public Page[] Paginate ()
{
return Paginate (out var dummy);
}
/// <summary>
/// Repaginates the pages passed into the current instance to
/// handle changes made to the Xiph comment.
/// </summary>
/// <param name="change">
/// A <see cref="int" /> value reference containing the
/// the difference between the number of pages returned and
/// the number of pages that were added to the class.
/// </param>
/// <returns>
/// A <see cref="T:Page[]" /> containing the new page
/// collection.
/// </returns>
public Page[] Paginate (out int change)
{
// Ogg Pagination: Welcome to sucksville!
// If you don't understand this, you're not alone.
// It is confusing as Hell.
// TODO: Document this method, in the mean time, there
// is always http://xiph.org/ogg/doc/framing.html
if (pages_read == 0) {
change = 0;
return new Page[0];
}
int count = pages_read;
ByteVectorCollection packets = new ByteVectorCollection (this.packets);
PageHeader first_header = (PageHeader)first_page_header;
List<Page> pages = new List<Page> ();
uint index = 0;
bool bos = first_header.PageSequenceNumber == 0;
if (bos) {
pages.Add (new Page (new ByteVectorCollection (packets[0]), first_header));
index++;
packets.RemoveAt (0);
count--;
}
int lacing_per_page = 0xfc;
if (count > 0) {
int total_lacing_bytes = 0;
for (int i = 0; i < packets.Count; i++)
total_lacing_bytes += GetLacingValueLength (packets, i);
lacing_per_page = Math.Min (total_lacing_bytes / count + 1, lacing_per_page);
}
int lacing_bytes_used = 0;
ByteVectorCollection page_packets = new ByteVectorCollection ();
bool first_packet_continued = false;
while (packets.Count > 0) {
int packet_bytes = GetLacingValueLength (packets, 0);
int remaining = lacing_per_page - lacing_bytes_used;
bool whole_packet = packet_bytes <= remaining;
if (whole_packet) {
page_packets.Add (packets[0]);
lacing_bytes_used += packet_bytes;
packets.RemoveAt (0);
} else {
page_packets.Add (packets[0].Mid (0, remaining * 0xff));
packets[0] = packets[0].Mid (remaining * 0xff);
lacing_bytes_used += remaining;
}
if (lacing_bytes_used == lacing_per_page) {
pages.Add (new Page (page_packets,
new PageHeader (first_header,
index, first_packet_continued ?
PageFlags.FirstPacketContinued :
PageFlags.None)));
page_packets = new ByteVectorCollection ();
lacing_bytes_used = 0;
index++;
count--;
first_packet_continued = !whole_packet;
}
}
if (page_packets.Count > 0) {
pages.Add (new Page (page_packets,
new PageHeader (
first_header.StreamSerialNumber,
index, first_packet_continued ?
PageFlags.FirstPacketContinued :
PageFlags.None)));
index++;
count--;
}
change = -count;
return pages.ToArray ();
}
#endregion
#region Private Methods
/// <summary>
/// Gets the number of lacing value bytes that would be
/// required for a given packet.
/// </summary>
/// <param name="packets">
/// A <see cref="ByteVectorCollection" /> object containing
/// the packet.
/// </param>
/// <param name="index">
/// A <see cref="int" /> value containing the index of the
/// packet to compute.
/// </param>
/// <returns>
/// A <see cref="int" /> value containing the number of bytes
/// needed to store the length.
/// </returns>
static int GetLacingValueLength (ByteVectorCollection packets, int index)
{
int size = packets[index].Count;
return size / 0xff + ((index + 1 < packets.Count ||
size % 0xff > 0) ? 1 : 0);
}
#endregion
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 50b6b0d864afd6908ae2a9aaee0d0af0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c51c529d4ffacffbd893864ce06a64dc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: