Initial commit
This commit is contained in:
commit
262601edb8
2025 files changed
+218754
No files matched your search
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a12be478792ef0b798d11f431269c2c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fed920fee9cd6fea7ba74ef626454b6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb042ad18bd78a508b883d944a2d08d0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bdaa91990d5161d185b4e54c843718a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
Reference in new issue
Block a user