diff --git a/.gitignore b/.gitignore
index 1598a7ac36..c74a54d71e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,8 @@ tmp/
*.bak
*.swp
*~.nib
+project.fragment.lock.json
+project.lock.json
local.properties
.classpath
.settings/
diff --git a/BDInfo/BDInfo.csproj b/BDInfo/BDInfo.csproj
new file mode 100644
index 0000000000..e7013f341b
--- /dev/null
+++ b/BDInfo/BDInfo.csproj
@@ -0,0 +1,77 @@
+
+
+
+
+ 11.0
+ Debug
+ AnyCPU
+ {88AE38DF-19D7-406F-A6A9-09527719A21E}
+ Library
+ Properties
+ BDInfo
+ BDInfo
+ en-US
+ 512
+ {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Profile7
+ v4.5
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}
+ MediaBrowser.Model
+
+
+
+
+
\ No newline at end of file
diff --git a/BDInfo/BDInfo.nuget.targets b/BDInfo/BDInfo.nuget.targets
new file mode 100644
index 0000000000..e69ce0e64f
--- /dev/null
+++ b/BDInfo/BDInfo.nuget.targets
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BDInfo/BDInfoSettings.cs b/BDInfo/BDInfoSettings.cs
new file mode 100644
index 0000000000..7abb67499f
--- /dev/null
+++ b/BDInfo/BDInfoSettings.cs
@@ -0,0 +1,105 @@
+
+namespace BDInfo
+{
+ class BDInfoSettings
+ {
+ public static bool GenerateStreamDiagnostics
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public static bool EnableSSIF
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public static bool AutosaveReport
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public static bool GenerateFrameDataFile
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public static bool FilterLoopingPlaylists
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public static bool FilterShortPlaylists
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public static int FilterShortPlaylistsValue
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ public static bool UseImagePrefix
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public static string UseImagePrefixValue
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Setting this to false throws an IComparer error on some discs.
+ ///
+ public static bool KeepStreamOrder
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public static bool GenerateTextSummary
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public static string LastPath
+ {
+ get
+ {
+ return string.Empty;
+ }
+ }
+ }
+}
diff --git a/BDInfo/BDROM.cs b/BDInfo/BDROM.cs
new file mode 100644
index 0000000000..97dbfbf3bd
--- /dev/null
+++ b/BDInfo/BDROM.cs
@@ -0,0 +1,437 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Text;
+
+namespace BDInfo
+{
+ public class BDROM
+ {
+ public FileSystemMetadata DirectoryRoot = null;
+ public FileSystemMetadata DirectoryBDMV = null;
+ public FileSystemMetadata DirectoryBDJO = null;
+ public FileSystemMetadata DirectoryCLIPINF = null;
+ public FileSystemMetadata DirectoryPLAYLIST = null;
+ public FileSystemMetadata DirectorySNP = null;
+ public FileSystemMetadata DirectorySSIF = null;
+ public FileSystemMetadata DirectorySTREAM = null;
+
+ public string VolumeLabel = null;
+ public ulong Size = 0;
+ public bool IsBDPlus = false;
+ public bool IsBDJava = false;
+ public bool IsDBOX = false;
+ public bool IsPSP = false;
+ public bool Is3D = false;
+ public bool Is50Hz = false;
+
+ private readonly IFileSystem _fileSystem;
+
+ public Dictionary PlaylistFiles =
+ new Dictionary();
+ public Dictionary StreamClipFiles =
+ new Dictionary();
+ public Dictionary StreamFiles =
+ new Dictionary();
+ public Dictionary InterleavedFiles =
+ new Dictionary();
+
+ private static List ExcludeDirs = new List { "ANY!", "AACS", "BDSVM", "ANYVM", "SLYVM" };
+
+ public delegate bool OnStreamClipFileScanError(
+ TSStreamClipFile streamClipFile, Exception ex);
+
+ public event OnStreamClipFileScanError StreamClipFileScanError;
+
+ public delegate bool OnStreamFileScanError(
+ TSStreamFile streamClipFile, Exception ex);
+
+ public event OnStreamFileScanError StreamFileScanError;
+
+ public delegate bool OnPlaylistFileScanError(
+ TSPlaylistFile playlistFile, Exception ex);
+
+ public event OnPlaylistFileScanError PlaylistFileScanError;
+
+ public BDROM(
+ string path, IFileSystem fileSystem, ITextEncoding textEncoding)
+ {
+ _fileSystem = fileSystem;
+ //
+ // Locate BDMV directories.
+ //
+
+ DirectoryBDMV =
+ GetDirectoryBDMV(path);
+
+ if (DirectoryBDMV == null)
+ {
+ throw new Exception("Unable to locate BD structure.");
+ }
+
+ DirectoryRoot =
+ _fileSystem.GetDirectoryInfo(Path.GetDirectoryName(DirectoryBDMV.FullName));
+ DirectoryBDJO =
+ GetDirectory("BDJO", DirectoryBDMV, 0);
+ DirectoryCLIPINF =
+ GetDirectory("CLIPINF", DirectoryBDMV, 0);
+ DirectoryPLAYLIST =
+ GetDirectory("PLAYLIST", DirectoryBDMV, 0);
+ DirectorySNP =
+ GetDirectory("SNP", DirectoryRoot, 0);
+ DirectorySTREAM =
+ GetDirectory("STREAM", DirectoryBDMV, 0);
+ DirectorySSIF =
+ GetDirectory("SSIF", DirectorySTREAM, 0);
+
+ if (DirectoryCLIPINF == null
+ || DirectoryPLAYLIST == null)
+ {
+ throw new Exception("Unable to locate BD structure.");
+ }
+
+ //
+ // Initialize basic disc properties.
+ //
+
+ VolumeLabel = GetVolumeLabel(DirectoryRoot);
+ Size = (ulong)GetDirectorySize(DirectoryRoot);
+
+ if (null != GetDirectory("BDSVM", DirectoryRoot, 0))
+ {
+ IsBDPlus = true;
+ }
+ if (null != GetDirectory("SLYVM", DirectoryRoot, 0))
+ {
+ IsBDPlus = true;
+ }
+ if (null != GetDirectory("ANYVM", DirectoryRoot, 0))
+ {
+ IsBDPlus = true;
+ }
+
+ if (DirectoryBDJO != null &&
+ _fileSystem.GetFiles(DirectoryBDJO.FullName).Any())
+ {
+ IsBDJava = true;
+ }
+
+ if (DirectorySNP != null &&
+ GetFiles(DirectorySNP.FullName, ".mnv").Any())
+ {
+ IsPSP = true;
+ }
+
+ if (DirectorySSIF != null &&
+ _fileSystem.GetFiles(DirectorySSIF.FullName).Any())
+ {
+ Is3D = true;
+ }
+
+ if (_fileSystem.FileExists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml")))
+ {
+ IsDBOX = true;
+ }
+
+ //
+ // Initialize file lists.
+ //
+
+ if (DirectoryPLAYLIST != null)
+ {
+ FileSystemMetadata[] files = GetFiles(DirectoryPLAYLIST.FullName, ".mpls").ToArray();
+ foreach (FileSystemMetadata file in files)
+ {
+ PlaylistFiles.Add(
+ file.Name.ToUpper(), new TSPlaylistFile(this, file, _fileSystem, textEncoding));
+ }
+ }
+
+ if (DirectorySTREAM != null)
+ {
+ FileSystemMetadata[] files = GetFiles(DirectorySTREAM.FullName, ".m2ts").ToArray();
+ foreach (FileSystemMetadata file in files)
+ {
+ StreamFiles.Add(
+ file.Name.ToUpper(), new TSStreamFile(file, _fileSystem));
+ }
+ }
+
+ if (DirectoryCLIPINF != null)
+ {
+ FileSystemMetadata[] files = GetFiles(DirectoryCLIPINF.FullName, ".clpi").ToArray();
+ foreach (FileSystemMetadata file in files)
+ {
+ StreamClipFiles.Add(
+ file.Name.ToUpper(), new TSStreamClipFile(file, _fileSystem, textEncoding));
+ }
+ }
+
+ if (DirectorySSIF != null)
+ {
+ FileSystemMetadata[] files = GetFiles(DirectorySSIF.FullName, ".ssif").ToArray();
+ foreach (FileSystemMetadata file in files)
+ {
+ InterleavedFiles.Add(
+ file.Name.ToUpper(), new TSInterleavedFile(file));
+ }
+ }
+ }
+
+ private IEnumerable GetFiles(string path, string extension)
+ {
+ return _fileSystem.GetFiles(path).Where(i => string.Equals(i.Extension, extension, StringComparison.OrdinalIgnoreCase));
+ }
+
+ public void Scan()
+ {
+ List errorStreamClipFiles = new List();
+ foreach (TSStreamClipFile streamClipFile in StreamClipFiles.Values)
+ {
+ try
+ {
+ streamClipFile.Scan();
+ }
+ catch (Exception ex)
+ {
+ errorStreamClipFiles.Add(streamClipFile);
+ if (StreamClipFileScanError != null)
+ {
+ if (StreamClipFileScanError(streamClipFile, ex))
+ {
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else throw ex;
+ }
+ }
+
+ foreach (TSStreamFile streamFile in StreamFiles.Values)
+ {
+ string ssifName = Path.GetFileNameWithoutExtension(streamFile.Name) + ".SSIF";
+ if (InterleavedFiles.ContainsKey(ssifName))
+ {
+ streamFile.InterleavedFile = InterleavedFiles[ssifName];
+ }
+ }
+
+ TSStreamFile[] streamFiles = new TSStreamFile[StreamFiles.Count];
+ StreamFiles.Values.CopyTo(streamFiles, 0);
+ Array.Sort(streamFiles, CompareStreamFiles);
+
+ List errorPlaylistFiles = new List();
+ foreach (TSPlaylistFile playlistFile in PlaylistFiles.Values)
+ {
+ try
+ {
+ playlistFile.Scan(StreamFiles, StreamClipFiles);
+ }
+ catch (Exception ex)
+ {
+ errorPlaylistFiles.Add(playlistFile);
+ if (PlaylistFileScanError != null)
+ {
+ if (PlaylistFileScanError(playlistFile, ex))
+ {
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else throw ex;
+ }
+ }
+
+ List errorStreamFiles = new List();
+ foreach (TSStreamFile streamFile in streamFiles)
+ {
+ try
+ {
+ List playlists = new List();
+ foreach (TSPlaylistFile playlist in PlaylistFiles.Values)
+ {
+ foreach (TSStreamClip streamClip in playlist.StreamClips)
+ {
+ if (streamClip.Name == streamFile.Name)
+ {
+ playlists.Add(playlist);
+ break;
+ }
+ }
+ }
+ streamFile.Scan(playlists, false);
+ }
+ catch (Exception ex)
+ {
+ errorStreamFiles.Add(streamFile);
+ if (StreamFileScanError != null)
+ {
+ if (StreamFileScanError(streamFile, ex))
+ {
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else throw ex;
+ }
+ }
+
+ foreach (TSPlaylistFile playlistFile in PlaylistFiles.Values)
+ {
+ playlistFile.Initialize();
+ if (!Is50Hz)
+ {
+ foreach (TSVideoStream videoStream in playlistFile.VideoStreams)
+ {
+ if (videoStream.FrameRate == TSFrameRate.FRAMERATE_25 ||
+ videoStream.FrameRate == TSFrameRate.FRAMERATE_50)
+ {
+ Is50Hz = true;
+ }
+ }
+ }
+ }
+ }
+
+ private FileSystemMetadata GetDirectoryBDMV(
+ string path)
+ {
+ FileSystemMetadata dir = _fileSystem.GetDirectoryInfo(path);
+
+ while (dir != null)
+ {
+ if (dir.Name == "BDMV")
+ {
+ return dir;
+ }
+ dir = _fileSystem.GetDirectoryInfo(Path.GetDirectoryName(dir.FullName));
+ }
+
+ return GetDirectory("BDMV", _fileSystem.GetDirectoryInfo(path), 0);
+ }
+
+ private FileSystemMetadata GetDirectory(
+ string name,
+ FileSystemMetadata dir,
+ int searchDepth)
+ {
+ if (dir != null)
+ {
+ FileSystemMetadata[] children = _fileSystem.GetDirectories(dir.FullName).ToArray();
+ foreach (FileSystemMetadata child in children)
+ {
+ if (child.Name == name)
+ {
+ return child;
+ }
+ }
+ if (searchDepth > 0)
+ {
+ foreach (FileSystemMetadata child in children)
+ {
+ GetDirectory(
+ name, child, searchDepth - 1);
+ }
+ }
+ }
+ return null;
+ }
+
+ private long GetDirectorySize(FileSystemMetadata directoryInfo)
+ {
+ long size = 0;
+
+ //if (!ExcludeDirs.Contains(directoryInfo.Name.ToUpper())) // TODO: Keep?
+ {
+ FileSystemMetadata[] pathFiles = _fileSystem.GetFiles(directoryInfo.FullName).ToArray();
+ foreach (FileSystemMetadata pathFile in pathFiles)
+ {
+ if (pathFile.Extension.ToUpper() == ".SSIF")
+ {
+ continue;
+ }
+ size += pathFile.Length;
+ }
+
+ FileSystemMetadata[] pathChildren = _fileSystem.GetDirectories(directoryInfo.FullName).ToArray();
+ foreach (FileSystemMetadata pathChild in pathChildren)
+ {
+ size += GetDirectorySize(pathChild);
+ }
+ }
+
+ return size;
+ }
+
+ private string GetVolumeLabel(FileSystemMetadata dir)
+ {
+ return dir.Name;
+ }
+
+ public static int CompareStreamFiles(
+ TSStreamFile x,
+ TSStreamFile y)
+ {
+ // TODO: Use interleaved file sizes
+
+ if ((x == null || x.FileInfo == null) && (y == null || y.FileInfo == null))
+ {
+ return 0;
+ }
+ else if ((x == null || x.FileInfo == null) && (y != null && y.FileInfo != null))
+ {
+ return 1;
+ }
+ else if ((x != null || x.FileInfo != null) && (y == null || y.FileInfo == null))
+ {
+ return -1;
+ }
+ else
+ {
+ if (x.FileInfo.Length > y.FileInfo.Length)
+ {
+ return 1;
+ }
+ else if (y.FileInfo.Length > x.FileInfo.Length)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+
+ }
+}
diff --git a/BDInfo/BitVector32.cs b/BDInfo/BitVector32.cs
new file mode 100644
index 0000000000..1ac94bfbeb
--- /dev/null
+++ b/BDInfo/BitVector32.cs
@@ -0,0 +1,308 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BDInfo
+{
+ using System.Diagnostics;
+ using System.Text;
+ using System;
+
+ ///
+ /// Provides a simple light bit vector with easy integer or Boolean access to
+ /// a 32 bit storage.
+ ///
+ public struct BitVector32
+ {
+ private uint data;
+
+ ///
+ /// Initializes a new instance of the BitVector32 structure with the specified internal data.
+ ///
+ public BitVector32(int data)
+ {
+ this.data = (uint)data;
+ }
+
+ ///
+ /// Initializes a new instance of the BitVector32 structure with the information in the specified
+ /// value.
+ ///
+ public BitVector32(BitVector32 value)
+ {
+ this.data = value.data;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether all the specified bits are set.
+ ///
+ public bool this[int bit]
+ {
+ get
+ {
+ return (data & bit) == (uint)bit;
+ }
+ set
+ {
+ if (value)
+ {
+ data |= (uint)bit;
+ }
+ else
+ {
+ data &= ~(uint)bit;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the value for the specified section.
+ ///
+ public int this[Section section]
+ {
+ get
+ {
+ return (int)((data & (uint)(section.Mask << section.Offset)) >> section.Offset);
+ }
+ set
+ {
+ value <<= section.Offset;
+ int offsetMask = (0xFFFF & (int)section.Mask) << section.Offset;
+ data = (data & ~(uint)offsetMask) | ((uint)value & (uint)offsetMask);
+ }
+ }
+
+ ///
+ /// returns the raw data stored in this bit vector...
+ ///
+ public int Data
+ {
+ get
+ {
+ return (int)data;
+ }
+ }
+
+ private static short CountBitsSet(short mask)
+ {
+
+ // yes, I know there are better algorithms, however, we know the
+ // bits are always right aligned, with no holes (i.e. always 00000111,
+ // never 000100011), so this is just fine...
+ //
+ short value = 0;
+ while ((mask & 0x1) != 0)
+ {
+ value++;
+ mask >>= 1;
+ }
+ return value;
+ }
+
+ ///
+ /// Creates the first mask in a series.
+ ///
+ public static int CreateMask()
+ {
+ return CreateMask(0);
+ }
+
+ ///
+ /// Creates the next mask in a series.
+ ///
+ public static int CreateMask(int previous)
+ {
+ if (previous == 0)
+ {
+ return 1;
+ }
+
+ if (previous == unchecked((int)0x80000000))
+ {
+ throw new InvalidOperationException("Bit vector full");
+ }
+
+ return previous << 1;
+ }
+
+ ///
+ /// Given a highValue, creates the mask
+ ///
+ private static short CreateMaskFromHighValue(short highValue)
+ {
+ short required = 16;
+ while ((highValue & 0x8000) == 0)
+ {
+ required--;
+ highValue <<= 1;
+ }
+
+ ushort value = 0;
+ while (required > 0)
+ {
+ required--;
+ value <<= 1;
+ value |= 0x1;
+ }
+
+ return unchecked((short)value);
+ }
+
+ ///
+ /// Creates the first section in a series, with the specified maximum value.
+ ///
+ public static Section CreateSection(short maxValue)
+ {
+ return CreateSectionHelper(maxValue, 0, 0);
+ }
+
+ ///
+ /// Creates the next section in a series, with the specified maximum value.
+ ///
+ public static Section CreateSection(short maxValue, Section previous)
+ {
+ return CreateSectionHelper(maxValue, previous.Mask, previous.Offset);
+ }
+
+ private static Section CreateSectionHelper(short maxValue, short priorMask, short priorOffset)
+ {
+ if (maxValue < 1)
+ {
+ throw new ArgumentOutOfRangeException("maxValue");
+ }
+#if DEBUG
+ int maskCheck = CreateMaskFromHighValue(maxValue);
+ int offsetCheck = priorOffset + CountBitsSet(priorMask);
+ Debug.Assert(maskCheck <= short.MaxValue && offsetCheck < 32, "Overflow on BitVector32");
+#endif
+ short offset = (short)(priorOffset + CountBitsSet(priorMask));
+ if (offset >= 32)
+ {
+ throw new InvalidOperationException("Bit vector full");
+ }
+ return new Section(CreateMaskFromHighValue(maxValue), offset);
+ }
+
+ public override bool Equals(object o)
+ {
+ if (!(o is BitVector32))
+ {
+ return false;
+ }
+
+ return data == ((BitVector32)o).data;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ ///
+ ///
+ public static string ToString(BitVector32 value)
+ {
+ StringBuilder sb = new StringBuilder(/*"BitVector32{".Length*/12 + /*32 bits*/32 + /*"}".Length"*/1);
+ sb.Append("BitVector32{");
+ int locdata = (int)value.data;
+ for (int i = 0; i < 32; i++)
+ {
+ if ((locdata & 0x80000000) != 0)
+ {
+ sb.Append("1");
+ }
+ else
+ {
+ sb.Append("0");
+ }
+ locdata <<= 1;
+ }
+ sb.Append("}");
+ return sb.ToString();
+ }
+
+ ///
+ ///
+ public override string ToString()
+ {
+ return BitVector32.ToString(this);
+ }
+
+ ///
+ ///
+ /// Represents an section of the vector that can contain a integer number.
+ ///
+ public struct Section
+ {
+ private readonly short mask;
+ private readonly short offset;
+
+ internal Section(short mask, short offset)
+ {
+ this.mask = mask;
+ this.offset = offset;
+ }
+
+ public short Mask
+ {
+ get
+ {
+ return mask;
+ }
+ }
+
+ public short Offset
+ {
+ get
+ {
+ return offset;
+ }
+ }
+
+ public override bool Equals(object o)
+ {
+ if (o is Section)
+ return Equals((Section)o);
+ else
+ return false;
+ }
+
+ public bool Equals(Section obj)
+ {
+ return obj.mask == mask && obj.offset == offset;
+ }
+
+ public static bool operator ==(Section a, Section b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(Section a, Section b)
+ {
+ return !(a == b);
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ ///
+ ///
+ public static string ToString(Section value)
+ {
+ return "Section{0x" + Convert.ToString(value.Mask, 16) + ", 0x" + Convert.ToString(value.Offset, 16) + "}";
+ }
+
+ ///
+ ///
+ public override string ToString()
+ {
+ return Section.ToString(this);
+ }
+
+ }
+ }
+}
diff --git a/BDInfo/LanguageCodes.cs b/BDInfo/LanguageCodes.cs
new file mode 100644
index 0000000000..90d0bccc43
--- /dev/null
+++ b/BDInfo/LanguageCodes.cs
@@ -0,0 +1,493 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//=============================================================================
+
+
+namespace BDInfo
+{
+ public abstract class LanguageCodes
+ {
+ public static string GetName(string code)
+ {
+ switch (code)
+ {
+ case "abk": return "Abkhazian";
+ case "ace": return "Achinese";
+ case "ach": return "Acoli";
+ case "ada": return "Adangme";
+ case "aar": return "Afar";
+ case "afh": return "Afrihili";
+ case "afr": return "Afrikaans";
+ case "afa": return "Afro-Asiatic (Other)";
+ case "aka": return "Akan";
+ case "akk": return "Akkadian";
+ case "alb": return "Albanian";
+ case "sqi": return "Albanian";
+ case "ale": return "Aleut";
+ case "alg": return "Algonquian languages";
+ case "tut": return "Altaic (Other)";
+ case "amh": return "Amharic";
+ case "apa": return "Apache languages";
+ case "ara": return "Arabic";
+ case "arc": return "Aramaic";
+ case "arp": return "Arapaho";
+ case "arn": return "Araucanian";
+ case "arw": return "Arawak";
+ case "arm": return "Armenian";
+ case "hye": return "Armenian";
+ case "art": return "Artificial (Other)";
+ case "asm": return "Assamese";
+ case "ath": return "Athapascan languages";
+ case "aus": return "Australian languages";
+ case "map": return "Austronesian (Other)";
+ case "ava": return "Avaric";
+ case "ave": return "Avestan";
+ case "awa": return "Awadhi";
+ case "aym": return "Aymara";
+ case "aze": return "Azerbaijani";
+ case "ban": return "Balinese";
+ case "bat": return "Baltic (Other)";
+ case "bal": return "Baluchi";
+ case "bam": return "Bambara";
+ case "bai": return "Bamileke languages";
+ case "bad": return "Banda";
+ case "bnt": return "Bantu (Other)";
+ case "bas": return "Basa";
+ case "bak": return "Bashkir";
+ case "baq": return "Basque";
+ case "eus": return "Basque";
+ case "btk": return "Batak (Indonesia)";
+ case "bej": return "Beja";
+ case "bel": return "Belarusian";
+ case "bem": return "Bemba";
+ case "ben": return "Bengali";
+ case "ber": return "Berber (Other)";
+ case "bho": return "Bhojpuri";
+ case "bih": return "Bihari";
+ case "bik": return "Bikol";
+ case "bin": return "Bini";
+ case "bis": return "Bislama";
+ case "bos": return "Bosnian";
+ case "bra": return "Braj";
+ case "bre": return "Breton";
+ case "bug": return "Buginese";
+ case "bul": return "Bulgarian";
+ case "bua": return "Buriat";
+ case "bur": return "Burmese";
+ case "mya": return "Burmese";
+ case "cad": return "Caddo";
+ case "car": return "Carib";
+ case "cat": return "Catalan";
+ case "cau": return "Caucasian (Other)";
+ case "ceb": return "Cebuano";
+ case "cel": return "Celtic (Other)";
+ case "cai": return "Central American Indian (Other)";
+ case "chg": return "Chagatai";
+ case "cmc": return "Chamic languages";
+ case "cha": return "Chamorro";
+ case "che": return "Chechen";
+ case "chr": return "Cherokee";
+ case "chy": return "Cheyenne";
+ case "chb": return "Chibcha";
+ case "chi": return "Chinese";
+ case "zho": return "Chinese";
+ case "chn": return "Chinook jargon";
+ case "chp": return "Chipewyan";
+ case "cho": return "Choctaw";
+ case "chu": return "Church Slavic";
+ case "chk": return "Chuukese";
+ case "chv": return "Chuvash";
+ case "cop": return "Coptic";
+ case "cor": return "Cornish";
+ case "cos": return "Corsican";
+ case "cre": return "Cree";
+ case "mus": return "Creek";
+ case "crp": return "Creoles and pidgins (Other)";
+ case "cpe": return "Creoles and pidgins,";
+ case "cpf": return "Creoles and pidgins,";
+ case "cpp": return "Creoles and pidgins,";
+ case "scr": return "Croatian";
+ case "hrv": return "Croatian";
+ case "cus": return "Cushitic (Other)";
+ case "cze": return "Czech";
+ case "ces": return "Czech";
+ case "dak": return "Dakota";
+ case "dan": return "Danish";
+ case "day": return "Dayak";
+ case "del": return "Delaware";
+ case "din": return "Dinka";
+ case "div": return "Divehi";
+ case "doi": return "Dogri";
+ case "dgr": return "Dogrib";
+ case "dra": return "Dravidian (Other)";
+ case "dua": return "Duala";
+ case "dut": return "Dutch";
+ case "nld": return "Dutch";
+ case "dum": return "Dutch, Middle (ca. 1050-1350)";
+ case "dyu": return "Dyula";
+ case "dzo": return "Dzongkha";
+ case "efi": return "Efik";
+ case "egy": return "Egyptian (Ancient)";
+ case "eka": return "Ekajuk";
+ case "elx": return "Elamite";
+ case "eng": return "English";
+ case "enm": return "English, Middle (1100-1500)";
+ case "ang": return "English, Old (ca.450-1100)";
+ case "epo": return "Esperanto";
+ case "est": return "Estonian";
+ case "ewe": return "Ewe";
+ case "ewo": return "Ewondo";
+ case "fan": return "Fang";
+ case "fat": return "Fanti";
+ case "fao": return "Faroese";
+ case "fij": return "Fijian";
+ case "fin": return "Finnish";
+ case "fiu": return "Finno-Ugrian (Other)";
+ case "fon": return "Fon";
+ case "fre": return "French";
+ case "fra": return "French";
+ case "frm": return "French, Middle (ca.1400-1600)";
+ case "fro": return "French, Old (842-ca.1400)";
+ case "fry": return "Frisian";
+ case "fur": return "Friulian";
+ case "ful": return "Fulah";
+ case "gaa": return "Ga";
+ case "glg": return "Gallegan";
+ case "lug": return "Ganda";
+ case "gay": return "Gayo";
+ case "gba": return "Gbaya";
+ case "gez": return "Geez";
+ case "geo": return "Georgian";
+ case "kat": return "Georgian";
+ case "ger": return "German";
+ case "deu": return "German";
+ case "nds": return "Saxon";
+ case "gmh": return "German, Middle High (ca.1050-1500)";
+ case "goh": return "German, Old High (ca.750-1050)";
+ case "gem": return "Germanic (Other)";
+ case "gil": return "Gilbertese";
+ case "gon": return "Gondi";
+ case "gor": return "Gorontalo";
+ case "got": return "Gothic";
+ case "grb": return "Grebo";
+ case "grc": return "Greek, Ancient (to 1453)";
+ case "gre": return "Greek";
+ case "ell": return "Greek";
+ case "grn": return "Guarani";
+ case "guj": return "Gujarati";
+ case "gwi": return "Gwich´in";
+ case "hai": return "Haida";
+ case "hau": return "Hausa";
+ case "haw": return "Hawaiian";
+ case "heb": return "Hebrew";
+ case "her": return "Herero";
+ case "hil": return "Hiligaynon";
+ case "him": return "Himachali";
+ case "hin": return "Hindi";
+ case "hmo": return "Hiri Motu";
+ case "hit": return "Hittite";
+ case "hmn": return "Hmong";
+ case "hun": return "Hungarian";
+ case "hup": return "Hupa";
+ case "iba": return "Iban";
+ case "ice": return "Icelandic";
+ case "isl": return "Icelandic";
+ case "ibo": return "Igbo";
+ case "ijo": return "Ijo";
+ case "ilo": return "Iloko";
+ case "inc": return "Indic (Other)";
+ case "ine": return "Indo-European (Other)";
+ case "ind": return "Indonesian";
+ case "ina": return "Interlingua (International";
+ case "ile": return "Interlingue";
+ case "iku": return "Inuktitut";
+ case "ipk": return "Inupiaq";
+ case "ira": return "Iranian (Other)";
+ case "gle": return "Irish";
+ case "mga": return "Irish, Middle (900-1200)";
+ case "sga": return "Irish, Old (to 900)";
+ case "iro": return "Iroquoian languages";
+ case "ita": return "Italian";
+ case "jpn": return "Japanese";
+ case "jav": return "Javanese";
+ case "jrb": return "Judeo-Arabic";
+ case "jpr": return "Judeo-Persian";
+ case "kab": return "Kabyle";
+ case "kac": return "Kachin";
+ case "kal": return "Kalaallisut";
+ case "kam": return "Kamba";
+ case "kan": return "Kannada";
+ case "kau": return "Kanuri";
+ case "kaa": return "Kara-Kalpak";
+ case "kar": return "Karen";
+ case "kas": return "Kashmiri";
+ case "kaw": return "Kawi";
+ case "kaz": return "Kazakh";
+ case "kha": return "Khasi";
+ case "khm": return "Khmer";
+ case "khi": return "Khoisan (Other)";
+ case "kho": return "Khotanese";
+ case "kik": return "Kikuyu";
+ case "kmb": return "Kimbundu";
+ case "kin": return "Kinyarwanda";
+ case "kir": return "Kirghiz";
+ case "kom": return "Komi";
+ case "kon": return "Kongo";
+ case "kok": return "Konkani";
+ case "kor": return "Korean";
+ case "kos": return "Kosraean";
+ case "kpe": return "Kpelle";
+ case "kro": return "Kru";
+ case "kua": return "Kuanyama";
+ case "kum": return "Kumyk";
+ case "kur": return "Kurdish";
+ case "kru": return "Kurukh";
+ case "kut": return "Kutenai";
+ case "lad": return "Ladino";
+ case "lah": return "Lahnda";
+ case "lam": return "Lamba";
+ case "lao": return "Lao";
+ case "lat": return "Latin";
+ case "lav": return "Latvian";
+ case "ltz": return "Letzeburgesch";
+ case "lez": return "Lezghian";
+ case "lin": return "Lingala";
+ case "lit": return "Lithuanian";
+ case "loz": return "Lozi";
+ case "lub": return "Luba-Katanga";
+ case "lua": return "Luba-Lulua";
+ case "lui": return "Luiseno";
+ case "lun": return "Lunda";
+ case "luo": return "Luo (Kenya and Tanzania)";
+ case "lus": return "Lushai";
+ case "mac": return "Macedonian";
+ case "mkd": return "Macedonian";
+ case "mad": return "Madurese";
+ case "mag": return "Magahi";
+ case "mai": return "Maithili";
+ case "mak": return "Makasar";
+ case "mlg": return "Malagasy";
+ case "may": return "Malay";
+ case "msa": return "Malay";
+ case "mal": return "Malayalam";
+ case "mlt": return "Maltese";
+ case "mnc": return "Manchu";
+ case "mdr": return "Mandar";
+ case "man": return "Mandingo";
+ case "mni": return "Manipuri";
+ case "mno": return "Manobo languages";
+ case "glv": return "Manx";
+ case "mao": return "Maori";
+ case "mri": return "Maori";
+ case "mar": return "Marathi";
+ case "chm": return "Mari";
+ case "mah": return "Marshall";
+ case "mwr": return "Marwari";
+ case "mas": return "Masai";
+ case "myn": return "Mayan languages";
+ case "men": return "Mende";
+ case "mic": return "Micmac";
+ case "min": return "Minangkabau";
+ case "mis": return "Miscellaneous languages";
+ case "moh": return "Mohawk";
+ case "mol": return "Moldavian";
+ case "mkh": return "Mon-Khmer (Other)";
+ case "lol": return "Mongo";
+ case "mon": return "Mongolian";
+ case "mos": return "Mossi";
+ case "mul": return "Multiple languages";
+ case "mun": return "Munda languages";
+ case "nah": return "Nahuatl";
+ case "nau": return "Nauru";
+ case "nav": return "Navajo";
+ case "nde": return "Ndebele, North";
+ case "nbl": return "Ndebele, South";
+ case "ndo": return "Ndonga";
+ case "nep": return "Nepali";
+ case "new": return "Newari";
+ case "nia": return "Nias";
+ case "nic": return "Niger-Kordofanian (Other)";
+ case "ssa": return "Nilo-Saharan (Other)";
+ case "niu": return "Niuean";
+ case "non": return "Norse, Old";
+ case "nai": return "North American Indian (Other)";
+ case "sme": return "Northern Sami";
+ case "nor": return "Norwegian";
+ case "nob": return "Norwegian Bokmål";
+ case "nno": return "Norwegian Nynorsk";
+ case "nub": return "Nubian languages";
+ case "nym": return "Nyamwezi";
+ case "nya": return "Nyanja";
+ case "nyn": return "Nyankole";
+ case "nyo": return "Nyoro";
+ case "nzi": return "Nzima";
+ case "oci": return "Occitan";
+ case "oji": return "Ojibwa";
+ case "ori": return "Oriya";
+ case "orm": return "Oromo";
+ case "osa": return "Osage";
+ case "oss": return "Ossetian";
+ case "oto": return "Otomian languages";
+ case "pal": return "Pahlavi";
+ case "pau": return "Palauan";
+ case "pli": return "Pali";
+ case "pam": return "Pampanga";
+ case "pag": return "Pangasinan";
+ case "pan": return "Panjabi";
+ case "pap": return "Papiamento";
+ case "paa": return "Papuan (Other)";
+ case "per": return "Persian";
+ case "fas": return "Persian";
+ case "peo": return "Persian, Old (ca.600-400 B.C.)";
+ case "phi": return "Philippine (Other)";
+ case "phn": return "Phoenician";
+ case "pon": return "Pohnpeian";
+ case "pol": return "Polish";
+ case "por": return "Portuguese";
+ case "pra": return "Prakrit languages";
+ case "pro": return "Provençal";
+ case "pus": return "Pushto";
+ case "que": return "Quechua";
+ case "roh": return "Raeto-Romance";
+ case "raj": return "Rajasthani";
+ case "rap": return "Rapanui";
+ case "rar": return "Rarotongan";
+ case "roa": return "Romance (Other)";
+ case "rum": return "Romanian";
+ case "ron": return "Romanian";
+ case "rom": return "Romany";
+ case "run": return "Rundi";
+ case "rus": return "Russian";
+ case "sal": return "Salishan languages";
+ case "sam": return "Samaritan Aramaic";
+ case "smi": return "Sami languages (Other)";
+ case "smo": return "Samoan";
+ case "sad": return "Sandawe";
+ case "sag": return "Sango";
+ case "san": return "Sanskrit";
+ case "sat": return "Santali";
+ case "srd": return "Sardinian";
+ case "sas": return "Sasak";
+ case "sco": return "Scots";
+ case "gla": return "Gaelic";
+ case "sel": return "Selkup";
+ case "sem": return "Semitic (Other)";
+ case "scc": return "Serbian";
+ case "srp": return "Serbian";
+ case "srr": return "Serer";
+ case "shn": return "Shan";
+ case "sna": return "Shona";
+ case "sid": return "Sidamo";
+ case "sgn": return "Sign languages";
+ case "bla": return "Siksika";
+ case "snd": return "Sindhi";
+ case "sin": return "Sinhalese";
+ case "sit": return "Sino-Tibetan (Other)";
+ case "sio": return "Siouan languages";
+ case "den": return "Slave (Athapascan)";
+ case "sla": return "Slavic (Other)";
+ case "slo": return "Slovak";
+ case "slk": return "Slovak";
+ case "slv": return "Slovenian";
+ case "sog": return "Sogdian";
+ case "som": return "Somali";
+ case "son": return "Songhai";
+ case "snk": return "Soninke";
+ case "wen": return "Sorbian languages";
+ case "nso": return "Sotho, Northern";
+ case "sot": return "Sotho, Southern";
+ case "sai": return "South American Indian (Other)";
+ case "spa": return "Spanish";
+ case "suk": return "Sukuma";
+ case "sux": return "Sumerian";
+ case "sun": return "Sundanese";
+ case "sus": return "Susu";
+ case "swa": return "Swahili";
+ case "ssw": return "Swati";
+ case "swe": return "Swedish";
+ case "syr": return "Syriac";
+ case "tgl": return "Tagalog";
+ case "tah": return "Tahitian";
+ case "tai": return "Tai (Other)";
+ case "tgk": return "Tajik";
+ case "tmh": return "Tamashek";
+ case "tam": return "Tamil";
+ case "tat": return "Tatar";
+ case "tel": return "Telugu";
+ case "ter": return "Tereno";
+ case "tet": return "Tetum";
+ case "tha": return "Thai";
+ case "tib": return "Tibetan";
+ case "bod": return "Tibetan";
+ case "tig": return "Tigre";
+ case "tir": return "Tigrinya";
+ case "tem": return "Timne";
+ case "tiv": return "Tiv";
+ case "tli": return "Tlingit";
+ case "tpi": return "Tok Pisin";
+ case "tkl": return "Tokelau";
+ case "tog": return "Tonga (Nyasa)";
+ case "ton": return "Tonga (Tonga Islands)";
+ case "tsi": return "Tsimshian";
+ case "tso": return "Tsonga";
+ case "tsn": return "Tswana";
+ case "tum": return "Tumbuka";
+ case "tur": return "Turkish";
+ case "ota": return "Turkish, Ottoman (1500-1928)";
+ case "tuk": return "Turkmen";
+ case "tvl": return "Tuvalu";
+ case "tyv": return "Tuvinian";
+ case "twi": return "Twi";
+ case "uga": return "Ugaritic";
+ case "uig": return "Uighur";
+ case "ukr": return "Ukrainian";
+ case "umb": return "Umbundu";
+ case "und": return "Undetermined";
+ case "urd": return "Urdu";
+ case "uzb": return "Uzbek";
+ case "vai": return "Vai";
+ case "ven": return "Venda";
+ case "vie": return "Vietnamese";
+ case "vol": return "Volapük";
+ case "vot": return "Votic";
+ case "wak": return "Wakashan languages";
+ case "wal": return "Walamo";
+ case "war": return "Waray";
+ case "was": return "Washo";
+ case "wel": return "Welsh";
+ case "cym": return "Welsh";
+ case "wol": return "Wolof";
+ case "xho": return "Xhosa";
+ case "sah": return "Yakut";
+ case "yao": return "Yao";
+ case "yap": return "Yapese";
+ case "yid": return "Yiddish";
+ case "yor": return "Yoruba";
+ case "ypk": return "Yupik languages";
+ case "znd": return "Zande";
+ case "zap": return "Zapotec";
+ case "zen": return "Zenaga";
+ case "zha": return "Zhuang";
+ case "zul": return "Zulu";
+ case "zun": return "Zuni";
+
+ default: return code;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Model.Portable/Properties/AssemblyInfo.cs b/BDInfo/Properties/AssemblyInfo.cs
similarity index 60%
rename from MediaBrowser.Model.Portable/Properties/AssemblyInfo.cs
rename to BDInfo/Properties/AssemblyInfo.cs
index a24dd014e4..aa44da1909 100644
--- a/MediaBrowser.Model.Portable/Properties/AssemblyInfo.cs
+++ b/BDInfo/Properties/AssemblyInfo.cs
@@ -1,15 +1,17 @@
using System.Resources;
using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("MediaBrowser.Model.Portable")]
+[assembly: AssemblyTitle("BDInfo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("MediaBrowser.Model.Portable")]
-[assembly: AssemblyCopyright("Copyright © 2013")]
+[assembly: AssemblyProduct("BDInfo")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
@@ -20,4 +22,8 @@ using System.Reflection;
// Minor Version
// Build Number
// Revision
-//
\ No newline at end of file
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.1")]
\ No newline at end of file
diff --git a/BDInfo/ReadMe.txt b/BDInfo/ReadMe.txt
new file mode 100644
index 0000000000..68326d560a
--- /dev/null
+++ b/BDInfo/ReadMe.txt
@@ -0,0 +1,5 @@
+The source is taken from the BDRom folder of this project:
+
+http://www.cinemasquid.com/blu-ray/tools/bdinfo
+
+BDInfoSettings was taken from the FormSettings class, and changed so that the settings all return defaults.
\ No newline at end of file
diff --git a/BDInfo/TSCodecAC3.cs b/BDInfo/TSCodecAC3.cs
new file mode 100644
index 0000000000..144141c302
--- /dev/null
+++ b/BDInfo/TSCodecAC3.cs
@@ -0,0 +1,309 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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
+//=============================================================================
+
+#undef DEBUG
+using System.IO;
+
+namespace BDInfo
+{
+ public abstract class TSCodecAC3
+ {
+ private static byte[] eac3_blocks = new byte[] { 1, 2, 3, 6 };
+
+ public static void Scan(
+ TSAudioStream stream,
+ TSStreamBuffer buffer,
+ ref string tag)
+ {
+ if (stream.IsInitialized) return;
+
+ byte[] sync = buffer.ReadBytes(2);
+ if (sync == null ||
+ sync[0] != 0x0B ||
+ sync[1] != 0x77)
+ {
+ return;
+ }
+
+ int sr_code = 0;
+ int frame_size = 0;
+ int frame_size_code = 0;
+ int channel_mode = 0;
+ int lfe_on = 0;
+ int dial_norm = 0;
+ int num_blocks = 0;
+
+ byte[] hdr = buffer.ReadBytes(4);
+ int bsid = (hdr[3] & 0xF8) >> 3;
+ buffer.Seek(-4, SeekOrigin.Current);
+ if (bsid <= 10)
+ {
+ byte[] crc = buffer.ReadBytes(2);
+ sr_code = buffer.ReadBits(2);
+ frame_size_code = buffer.ReadBits(6);
+ bsid = buffer.ReadBits(5);
+ int bsmod = buffer.ReadBits(3);
+
+ channel_mode = buffer.ReadBits(3);
+ int cmixlev = 0;
+ if (((channel_mode & 0x1) > 0) && (channel_mode != 0x1))
+ {
+ cmixlev = buffer.ReadBits(2);
+ }
+ int surmixlev = 0;
+ if ((channel_mode & 0x4) > 0)
+ {
+ surmixlev = buffer.ReadBits(2);
+ }
+ int dsurmod = 0;
+ if (channel_mode == 0x2)
+ {
+ dsurmod = buffer.ReadBits(2);
+ if (dsurmod == 0x2)
+ {
+ stream.AudioMode = TSAudioMode.Surround;
+ }
+ }
+ lfe_on = buffer.ReadBits(1);
+ dial_norm = buffer.ReadBits(5);
+ int compr = 0;
+ if (1 == buffer.ReadBits(1))
+ {
+ compr = buffer.ReadBits(8);
+ }
+ int langcod = 0;
+ if (1 == buffer.ReadBits(1))
+ {
+ langcod = buffer.ReadBits(8);
+ }
+ int mixlevel = 0;
+ int roomtyp = 0;
+ if (1 == buffer.ReadBits(1))
+ {
+ mixlevel = buffer.ReadBits(5);
+ roomtyp = buffer.ReadBits(2);
+ }
+ if (channel_mode == 0)
+ {
+ int dialnorm2 = buffer.ReadBits(5);
+ int compr2 = 0;
+ if (1 == buffer.ReadBits(1))
+ {
+ compr2 = buffer.ReadBits(8);
+ }
+ int langcod2 = 0;
+ if (1 == buffer.ReadBits(1))
+ {
+ langcod2 = buffer.ReadBits(8);
+ }
+ int mixlevel2 = 0;
+ int roomtyp2 = 0;
+ if (1 == buffer.ReadBits(1))
+ {
+ mixlevel2 = buffer.ReadBits(5);
+ roomtyp2 = buffer.ReadBits(2);
+ }
+ }
+ int copyrightb = buffer.ReadBits(1);
+ int origbs = buffer.ReadBits(1);
+ if (bsid == 6)
+ {
+ if (1 == buffer.ReadBits(1))
+ {
+ int dmixmod = buffer.ReadBits(2);
+ int ltrtcmixlev = buffer.ReadBits(3);
+ int ltrtsurmixlev = buffer.ReadBits(3);
+ int lorocmixlev = buffer.ReadBits(3);
+ int lorosurmixlev = buffer.ReadBits(3);
+ }
+ if (1 == buffer.ReadBits(1))
+ {
+ int dsurexmod = buffer.ReadBits(2);
+ int dheadphonmod = buffer.ReadBits(2);
+ if (dheadphonmod == 0x2)
+ {
+ // TODO
+ }
+ int adconvtyp = buffer.ReadBits(1);
+ int xbsi2 = buffer.ReadBits(8);
+ int encinfo = buffer.ReadBits(1);
+ if (dsurexmod == 2)
+ {
+ stream.AudioMode = TSAudioMode.Extended;
+ }
+ }
+ }
+ }
+ else
+ {
+ int frame_type = buffer.ReadBits(2);
+ int substreamid = buffer.ReadBits(3);
+ frame_size = (buffer.ReadBits(11) + 1) << 1;
+
+ sr_code = buffer.ReadBits(2);
+ if (sr_code == 3)
+ {
+ sr_code = buffer.ReadBits(2);
+ }
+ else
+ {
+ num_blocks = buffer.ReadBits(2);
+ }
+ channel_mode = buffer.ReadBits(3);
+ lfe_on = buffer.ReadBits(1);
+ }
+
+ switch (channel_mode)
+ {
+ case 0: // 1+1
+ stream.ChannelCount = 2;
+ if (stream.AudioMode == TSAudioMode.Unknown)
+ {
+ stream.AudioMode = TSAudioMode.DualMono;
+ }
+ break;
+ case 1: // 1/0
+ stream.ChannelCount = 1;
+ break;
+ case 2: // 2/0
+ stream.ChannelCount = 2;
+ if (stream.AudioMode == TSAudioMode.Unknown)
+ {
+ stream.AudioMode = TSAudioMode.Stereo;
+ }
+ break;
+ case 3: // 3/0
+ stream.ChannelCount = 3;
+ break;
+ case 4: // 2/1
+ stream.ChannelCount = 3;
+ break;
+ case 5: // 3/1
+ stream.ChannelCount = 4;
+ break;
+ case 6: // 2/2
+ stream.ChannelCount = 4;
+ break;
+ case 7: // 3/2
+ stream.ChannelCount = 5;
+ break;
+ default:
+ stream.ChannelCount = 0;
+ break;
+ }
+
+ switch (sr_code)
+ {
+ case 0:
+ stream.SampleRate = 48000;
+ break;
+ case 1:
+ stream.SampleRate = 44100;
+ break;
+ case 2:
+ stream.SampleRate = 32000;
+ break;
+ default:
+ stream.SampleRate = 0;
+ break;
+ }
+
+ if (bsid <= 10)
+ {
+ switch (frame_size_code >> 1)
+ {
+ case 18:
+ stream.BitRate = 640000;
+ break;
+ case 17:
+ stream.BitRate = 576000;
+ break;
+ case 16:
+ stream.BitRate = 512000;
+ break;
+ case 15:
+ stream.BitRate = 448000;
+ break;
+ case 14:
+ stream.BitRate = 384000;
+ break;
+ case 13:
+ stream.BitRate = 320000;
+ break;
+ case 12:
+ stream.BitRate = 256000;
+ break;
+ case 11:
+ stream.BitRate = 224000;
+ break;
+ case 10:
+ stream.BitRate = 192000;
+ break;
+ case 9:
+ stream.BitRate = 160000;
+ break;
+ case 8:
+ stream.BitRate = 128000;
+ break;
+ case 7:
+ stream.BitRate = 112000;
+ break;
+ case 6:
+ stream.BitRate = 96000;
+ break;
+ case 5:
+ stream.BitRate = 80000;
+ break;
+ case 4:
+ stream.BitRate = 64000;
+ break;
+ case 3:
+ stream.BitRate = 56000;
+ break;
+ case 2:
+ stream.BitRate = 48000;
+ break;
+ case 1:
+ stream.BitRate = 40000;
+ break;
+ case 0:
+ stream.BitRate = 32000;
+ break;
+ default:
+ stream.BitRate = 0;
+ break;
+ }
+ }
+ else
+ {
+ stream.BitRate = (long)
+ (4.0 * frame_size * stream.SampleRate / (num_blocks * 256));
+ }
+
+ stream.LFE = lfe_on;
+ if (stream.StreamType != TSStreamType.AC3_PLUS_AUDIO &&
+ stream.StreamType != TSStreamType.AC3_PLUS_SECONDARY_AUDIO)
+ {
+ stream.DialNorm = dial_norm - 31;
+ }
+ stream.IsVBR = false;
+ stream.IsInitialized = true;
+ }
+ }
+}
diff --git a/BDInfo/TSCodecAVC.cs b/BDInfo/TSCodecAVC.cs
new file mode 100644
index 0000000000..43c6d6f857
--- /dev/null
+++ b/BDInfo/TSCodecAVC.cs
@@ -0,0 +1,148 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//=============================================================================
+
+
+namespace BDInfo
+{
+ public abstract class TSCodecAVC
+ {
+ public static void Scan(
+ TSVideoStream stream,
+ TSStreamBuffer buffer,
+ ref string tag)
+ {
+ uint parse = 0;
+ byte accessUnitDelimiterParse = 0;
+ byte sequenceParameterSetParse = 0;
+ string profile = null;
+ string level = null;
+ byte constraintSet0Flag = 0;
+ byte constraintSet1Flag = 0;
+ byte constraintSet2Flag = 0;
+ byte constraintSet3Flag = 0;
+
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ parse = (parse << 8) + buffer.ReadByte();
+
+ if (parse == 0x00000109)
+ {
+ accessUnitDelimiterParse = 1;
+ }
+ else if (accessUnitDelimiterParse > 0)
+ {
+ --accessUnitDelimiterParse;
+ if (accessUnitDelimiterParse == 0)
+ {
+ switch ((parse & 0xFF) >> 5)
+ {
+ case 0: // I
+ case 3: // SI
+ case 5: // I, SI
+ tag = "I";
+ break;
+
+ case 1: // I, P
+ case 4: // SI, SP
+ case 6: // I, SI, P, SP
+ tag = "P";
+ break;
+
+ case 2: // I, P, B
+ case 7: // I, SI, P, SP, B
+ tag = "B";
+ break;
+ }
+ if (stream.IsInitialized) return;
+ }
+ }
+ else if (parse == 0x00000127 || parse == 0x00000167)
+ {
+ sequenceParameterSetParse = 3;
+ }
+ else if (sequenceParameterSetParse > 0)
+ {
+ --sequenceParameterSetParse;
+ switch (sequenceParameterSetParse)
+ {
+ case 2:
+ switch (parse & 0xFF)
+ {
+ case 66:
+ profile = "Baseline Profile";
+ break;
+ case 77:
+ profile = "Main Profile";
+ break;
+ case 88:
+ profile = "Extended Profile";
+ break;
+ case 100:
+ profile = "High Profile";
+ break;
+ case 110:
+ profile = "High 10 Profile";
+ break;
+ case 122:
+ profile = "High 4:2:2 Profile";
+ break;
+ case 144:
+ profile = "High 4:4:4 Profile";
+ break;
+ default:
+ profile = "Unknown Profile";
+ break;
+ }
+ break;
+
+ case 1:
+ constraintSet0Flag = (byte)
+ ((parse & 0x80) >> 7);
+ constraintSet1Flag = (byte)
+ ((parse & 0x40) >> 6);
+ constraintSet2Flag = (byte)
+ ((parse & 0x20) >> 5);
+ constraintSet3Flag = (byte)
+ ((parse & 0x10) >> 4);
+ break;
+
+ case 0:
+ byte b = (byte)(parse & 0xFF);
+ if (b == 11 && constraintSet3Flag == 1)
+ {
+ level = "1b";
+ }
+ else
+ {
+ level = string.Format(
+ "{0:D}.{1:D}",
+ b / 10, (b - ((b / 10) * 10)));
+ }
+ stream.EncodingProfile = string.Format(
+ "{0} {1}", profile, level);
+ stream.IsVBR = true;
+ stream.IsInitialized = true;
+ break;
+ }
+ }
+ }
+ return;
+ }
+ }
+}
diff --git a/BDInfo/TSCodecDTS.cs b/BDInfo/TSCodecDTS.cs
new file mode 100644
index 0000000000..58eb60fc59
--- /dev/null
+++ b/BDInfo/TSCodecDTS.cs
@@ -0,0 +1,159 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//=============================================================================
+
+
+namespace BDInfo
+{
+ public abstract class TSCodecDTS
+ {
+ private static int[] dca_sample_rates =
+ {
+ 0, 8000, 16000, 32000, 0, 0, 11025, 22050, 44100, 0, 0,
+ 12000, 24000, 48000, 96000, 192000
+ };
+
+ private static int[] dca_bit_rates =
+ {
+ 32000, 56000, 64000, 96000, 112000, 128000,
+ 192000, 224000, 256000, 320000, 384000,
+ 448000, 512000, 576000, 640000, 768000,
+ 896000, 1024000, 1152000, 1280000, 1344000,
+ 1408000, 1411200, 1472000, 1509000, 1920000,
+ 2048000, 3072000, 3840000, 1/*open*/, 2/*variable*/, 3/*lossless*/
+ };
+
+ private static int[] dca_channels =
+ {
+ 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8
+ };
+
+ private static int[] dca_bits_per_sample =
+ {
+ 16, 16, 20, 20, 0, 24, 24
+ };
+
+ public static void Scan(
+ TSAudioStream stream,
+ TSStreamBuffer buffer,
+ long bitrate,
+ ref string tag)
+ {
+ if (stream.IsInitialized) return;
+
+ bool syncFound = false;
+ uint sync = 0;
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ sync = (sync << 8) + buffer.ReadByte();
+ if (sync == 0x7FFE8001)
+ {
+ syncFound = true;
+ break;
+ }
+ }
+ if (!syncFound) return;
+
+ int frame_type = buffer.ReadBits(1);
+ int samples_deficit = buffer.ReadBits(5);
+ int crc_present = buffer.ReadBits(1);
+ int sample_blocks = buffer.ReadBits(7);
+ int frame_size = buffer.ReadBits(14);
+ if (frame_size < 95)
+ {
+ return;
+ }
+ int amode = buffer.ReadBits(6);
+ int sample_rate = buffer.ReadBits(4);
+ if (sample_rate < 0 || sample_rate >= dca_sample_rates.Length)
+ {
+ return;
+ }
+ int bit_rate = buffer.ReadBits(5);
+ if (bit_rate < 0 || bit_rate >= dca_bit_rates.Length)
+ {
+ return;
+ }
+ int downmix = buffer.ReadBits(1);
+ int dynrange = buffer.ReadBits(1);
+ int timestamp = buffer.ReadBits(1);
+ int aux_data = buffer.ReadBits(1);
+ int hdcd = buffer.ReadBits(1);
+ int ext_descr = buffer.ReadBits(3);
+ int ext_coding = buffer.ReadBits(1);
+ int aspf = buffer.ReadBits(1);
+ int lfe = buffer.ReadBits(2);
+ int predictor_history = buffer.ReadBits(1);
+ if (crc_present == 1)
+ {
+ int crc = buffer.ReadBits(16);
+ }
+ int multirate_inter = buffer.ReadBits(1);
+ int version = buffer.ReadBits(4);
+ int copy_history = buffer.ReadBits(2);
+ int source_pcm_res = buffer.ReadBits(3);
+ int front_sum = buffer.ReadBits(1);
+ int surround_sum = buffer.ReadBits(1);
+ int dialog_norm = buffer.ReadBits(4);
+ if (source_pcm_res < 0 || source_pcm_res >= dca_bits_per_sample.Length)
+ {
+ return;
+ }
+ int subframes = buffer.ReadBits(4);
+ int total_channels = buffer.ReadBits(3) + 1 + ext_coding;
+
+ stream.SampleRate = dca_sample_rates[sample_rate];
+ stream.ChannelCount = total_channels;
+ stream.LFE = (lfe > 0 ? 1 : 0);
+ stream.BitDepth = dca_bits_per_sample[source_pcm_res];
+ stream.DialNorm = -dialog_norm;
+ if ((source_pcm_res & 0x1) == 0x1)
+ {
+ stream.AudioMode = TSAudioMode.Extended;
+ }
+
+ stream.BitRate = (uint)dca_bit_rates[bit_rate];
+ switch (stream.BitRate)
+ {
+ case 1:
+ if (bitrate > 0)
+ {
+ stream.BitRate = bitrate;
+ stream.IsVBR = false;
+ stream.IsInitialized = true;
+ }
+ else
+ {
+ stream.BitRate = 0;
+ }
+ break;
+
+ case 2:
+ case 3:
+ stream.IsVBR = true;
+ stream.IsInitialized = true;
+ break;
+
+ default:
+ stream.IsVBR = false;
+ stream.IsInitialized = true;
+ break;
+ }
+ }
+ }
+}
diff --git a/BDInfo/TSCodecDTSHD.cs b/BDInfo/TSCodecDTSHD.cs
new file mode 100644
index 0000000000..169a8077f2
--- /dev/null
+++ b/BDInfo/TSCodecDTSHD.cs
@@ -0,0 +1,246 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//=============================================================================
+
+
+namespace BDInfo
+{
+ public abstract class TSCodecDTSHD
+ {
+ private static int[] SampleRates = new int[]
+ { 0x1F40, 0x3E80, 0x7D00, 0x0FA00, 0x1F400, 0x5622, 0x0AC44, 0x15888, 0x2B110, 0x56220, 0x2EE0, 0x5DC0, 0x0BB80, 0x17700, 0x2EE00, 0x5DC00 };
+
+ public static void Scan(
+ TSAudioStream stream,
+ TSStreamBuffer buffer,
+ long bitrate,
+ ref string tag)
+ {
+ if (stream.IsInitialized &&
+ (stream.StreamType == TSStreamType.DTS_HD_SECONDARY_AUDIO ||
+ (stream.CoreStream != null &&
+ stream.CoreStream.IsInitialized))) return;
+
+ bool syncFound = false;
+ uint sync = 0;
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ sync = (sync << 8) + buffer.ReadByte();
+ if (sync == 0x64582025)
+ {
+ syncFound = true;
+ break;
+ }
+ }
+
+ if (!syncFound)
+ {
+ tag = "CORE";
+ if (stream.CoreStream == null)
+ {
+ stream.CoreStream = new TSAudioStream();
+ stream.CoreStream.StreamType = TSStreamType.DTS_AUDIO;
+ }
+ if (!stream.CoreStream.IsInitialized)
+ {
+ buffer.BeginRead();
+ TSCodecDTS.Scan(stream.CoreStream, buffer, bitrate, ref tag);
+ }
+ return;
+ }
+
+ tag = "HD";
+ int temp1 = buffer.ReadBits(8);
+ int nuSubStreamIndex = buffer.ReadBits(2);
+ int nuExtSSHeaderSize = 0;
+ int nuExtSSFSize = 0;
+ int bBlownUpHeader = buffer.ReadBits(1);
+ if (1 == bBlownUpHeader)
+ {
+ nuExtSSHeaderSize = buffer.ReadBits(12) + 1;
+ nuExtSSFSize = buffer.ReadBits(20) + 1;
+ }
+ else
+ {
+ nuExtSSHeaderSize = buffer.ReadBits(8) + 1;
+ nuExtSSFSize = buffer.ReadBits(16) + 1;
+ }
+ int nuNumAudioPresent = 1;
+ int nuNumAssets = 1;
+ int bStaticFieldsPresent = buffer.ReadBits(1);
+ if (1 == bStaticFieldsPresent)
+ {
+ int nuRefClockCode = buffer.ReadBits(2);
+ int nuExSSFrameDurationCode = buffer.ReadBits(3) + 1;
+ long nuTimeStamp = 0;
+ if (1 == buffer.ReadBits(1))
+ {
+ nuTimeStamp = (buffer.ReadBits(18) << 18) + buffer.ReadBits(18);
+ }
+ nuNumAudioPresent = buffer.ReadBits(3) + 1;
+ nuNumAssets = buffer.ReadBits(3) + 1;
+ int[] nuActiveExSSMask = new int[nuNumAudioPresent];
+ for (int i = 0; i < nuNumAudioPresent; i++)
+ {
+ nuActiveExSSMask[i] = buffer.ReadBits(nuSubStreamIndex + 1); //?
+ }
+ for (int i = 0; i < nuNumAudioPresent; i++)
+ {
+ for (int j = 0; j < nuSubStreamIndex + 1; j++)
+ {
+ if (((j + 1) % 2) == 1)
+ {
+ int mask = buffer.ReadBits(8);
+ }
+ }
+ }
+ if (1 == buffer.ReadBits(1))
+ {
+ int nuMixMetadataAdjLevel = buffer.ReadBits(2);
+ int nuBits4MixOutMask = buffer.ReadBits(2) * 4 + 4;
+ int nuNumMixOutConfigs = buffer.ReadBits(2) + 1;
+ int[] nuMixOutChMask = new int[nuNumMixOutConfigs];
+ for (int i = 0; i < nuNumMixOutConfigs; i++)
+ {
+ nuMixOutChMask[i] = buffer.ReadBits(nuBits4MixOutMask);
+ }
+ }
+ }
+ int[] AssetSizes = new int[nuNumAssets];
+ for (int i = 0; i < nuNumAssets; i++)
+ {
+ if (1 == bBlownUpHeader)
+ {
+ AssetSizes[i] = buffer.ReadBits(20) + 1;
+ }
+ else
+ {
+ AssetSizes[i] = buffer.ReadBits(16) + 1;
+ }
+ }
+ for (int i = 0; i < nuNumAssets; i++)
+ {
+ long bufferPosition = buffer.Position;
+ int nuAssetDescriptorFSIZE = buffer.ReadBits(9) + 1;
+ int DescriptorDataForAssetIndex = buffer.ReadBits(3);
+ if (1 == bStaticFieldsPresent)
+ {
+ int AssetTypeDescrPresent = buffer.ReadBits(1);
+ if (1 == AssetTypeDescrPresent)
+ {
+ int AssetTypeDescriptor = buffer.ReadBits(4);
+ }
+ int LanguageDescrPresent = buffer.ReadBits(1);
+ if (1 == LanguageDescrPresent)
+ {
+ int LanguageDescriptor = buffer.ReadBits(24);
+ }
+ int bInfoTextPresent = buffer.ReadBits(1);
+ if (1 == bInfoTextPresent)
+ {
+ int nuInfoTextByteSize = buffer.ReadBits(10) + 1;
+ int[] InfoText = new int[nuInfoTextByteSize];
+ for (int j = 0; j < nuInfoTextByteSize; j++)
+ {
+ InfoText[j] = buffer.ReadBits(8);
+ }
+ }
+ int nuBitResolution = buffer.ReadBits(5) + 1;
+ int nuMaxSampleRate = buffer.ReadBits(4);
+ int nuTotalNumChs = buffer.ReadBits(8) + 1;
+ int bOne2OneMapChannels2Speakers = buffer.ReadBits(1);
+ int nuSpkrActivityMask = 0;
+ if (1 == bOne2OneMapChannels2Speakers)
+ {
+ int bEmbeddedStereoFlag = 0;
+ if (nuTotalNumChs > 2)
+ {
+ bEmbeddedStereoFlag = buffer.ReadBits(1);
+ }
+ int bEmbeddedSixChFlag = 0;
+ if (nuTotalNumChs > 6)
+ {
+ bEmbeddedSixChFlag = buffer.ReadBits(1);
+ }
+ int bSpkrMaskEnabled = buffer.ReadBits(1);
+ int nuNumBits4SAMask = 0;
+ if (1 == bSpkrMaskEnabled)
+ {
+ nuNumBits4SAMask = buffer.ReadBits(2);
+ nuNumBits4SAMask = nuNumBits4SAMask * 4 + 4;
+ nuSpkrActivityMask = buffer.ReadBits(nuNumBits4SAMask);
+ }
+ // TODO...
+ }
+ stream.SampleRate = SampleRates[nuMaxSampleRate];
+ stream.BitDepth = nuBitResolution;
+
+ stream.LFE = 0;
+ if ((nuSpkrActivityMask & 0x8) == 0x8)
+ {
+ ++stream.LFE;
+ }
+ if ((nuSpkrActivityMask & 0x1000) == 0x1000)
+ {
+ ++stream.LFE;
+ }
+ stream.ChannelCount = nuTotalNumChs - stream.LFE;
+ }
+ if (nuNumAssets > 1)
+ {
+ // TODO...
+ break;
+ }
+ }
+
+ // TODO
+ if (stream.CoreStream != null)
+ {
+ TSAudioStream coreStream = (TSAudioStream)stream.CoreStream;
+ if (coreStream.AudioMode == TSAudioMode.Extended &&
+ stream.ChannelCount == 5)
+ {
+ stream.AudioMode = TSAudioMode.Extended;
+ }
+ /*
+ if (coreStream.DialNorm != 0)
+ {
+ stream.DialNorm = coreStream.DialNorm;
+ }
+ */
+ }
+
+ if (stream.StreamType == TSStreamType.DTS_HD_MASTER_AUDIO)
+ {
+ stream.IsVBR = true;
+ stream.IsInitialized = true;
+ }
+ else if (bitrate > 0)
+ {
+ stream.IsVBR = false;
+ stream.BitRate = bitrate;
+ if (stream.CoreStream != null)
+ {
+ stream.BitRate += stream.CoreStream.BitRate;
+ stream.IsInitialized = true;
+ }
+ stream.IsInitialized = (stream.BitRate > 0 ? true : false);
+ }
+ }
+ }
+}
diff --git a/BDInfo/TSCodecLPCM.cs b/BDInfo/TSCodecLPCM.cs
new file mode 100644
index 0000000000..d12674f0e8
--- /dev/null
+++ b/BDInfo/TSCodecLPCM.cs
@@ -0,0 +1,123 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//=============================================================================
+
+
+namespace BDInfo
+{
+ public abstract class TSCodecLPCM
+ {
+ public static void Scan(
+ TSAudioStream stream,
+ TSStreamBuffer buffer,
+ ref string tag)
+ {
+ if (stream.IsInitialized) return;
+
+ byte[] header = buffer.ReadBytes(4);
+ int flags = (header[2] << 8) + header[3];
+
+ switch ((flags & 0xF000) >> 12)
+ {
+ case 1: // 1/0/0
+ stream.ChannelCount = 1;
+ stream.LFE = 0;
+ break;
+ case 3: // 2/0/0
+ stream.ChannelCount = 2;
+ stream.LFE = 0;
+ break;
+ case 4: // 3/0/0
+ stream.ChannelCount = 3;
+ stream.LFE = 0;
+ break;
+ case 5: // 2/1/0
+ stream.ChannelCount = 3;
+ stream.LFE = 0;
+ break;
+ case 6: // 3/1/0
+ stream.ChannelCount = 4;
+ stream.LFE = 0;
+ break;
+ case 7: // 2/2/0
+ stream.ChannelCount = 4;
+ stream.LFE = 0;
+ break;
+ case 8: // 3/2/0
+ stream.ChannelCount = 5;
+ stream.LFE = 0;
+ break;
+ case 9: // 3/2/1
+ stream.ChannelCount = 5;
+ stream.LFE = 1;
+ break;
+ case 10: // 3/4/0
+ stream.ChannelCount = 7;
+ stream.LFE = 0;
+ break;
+ case 11: // 3/4/1
+ stream.ChannelCount = 7;
+ stream.LFE = 1;
+ break;
+ default:
+ stream.ChannelCount = 0;
+ stream.LFE = 0;
+ break;
+ }
+
+ switch ((flags & 0xC0) >> 6)
+ {
+ case 1:
+ stream.BitDepth = 16;
+ break;
+ case 2:
+ stream.BitDepth = 20;
+ break;
+ case 3:
+ stream.BitDepth = 24;
+ break;
+ default:
+ stream.BitDepth = 0;
+ break;
+ }
+
+ switch ((flags & 0xF00) >> 8)
+ {
+ case 1:
+ stream.SampleRate = 48000;
+ break;
+ case 4:
+ stream.SampleRate = 96000;
+ break;
+ case 5:
+ stream.SampleRate = 192000;
+ break;
+ default:
+ stream.SampleRate = 0;
+ break;
+ }
+
+ stream.BitRate = (uint)
+ (stream.SampleRate * stream.BitDepth *
+ (stream.ChannelCount + stream.LFE));
+
+ stream.IsVBR = false;
+ stream.IsInitialized = true;
+ }
+ }
+}
diff --git a/BDInfo/TSCodecMPEG2.cs b/BDInfo/TSCodecMPEG2.cs
new file mode 100644
index 0000000000..3413a06e92
--- /dev/null
+++ b/BDInfo/TSCodecMPEG2.cs
@@ -0,0 +1,208 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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
+//=============================================================================
+
+#undef DEBUG
+
+
+namespace BDInfo
+{
+ public abstract class TSCodecMPEG2
+ {
+ public static void Scan(
+ TSVideoStream stream,
+ TSStreamBuffer buffer,
+ ref string tag)
+ {
+ int parse = 0;
+ int pictureParse = 0;
+ int sequenceHeaderParse = 0;
+ int extensionParse = 0;
+ int sequenceExtensionParse = 0;
+
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ parse = (parse << 8) + buffer.ReadByte();
+
+ if (parse == 0x00000100)
+ {
+ pictureParse = 2;
+ }
+ else if (parse == 0x000001B3)
+ {
+ sequenceHeaderParse = 7;
+ }
+ else if (sequenceHeaderParse > 0)
+ {
+ --sequenceHeaderParse;
+ switch (sequenceHeaderParse)
+ {
+#if DEBUG
+ case 6:
+ break;
+
+ case 5:
+ break;
+
+ case 4:
+ stream.Width =
+ (int)((parse & 0xFFF000) >> 12);
+ stream.Height =
+ (int)(parse & 0xFFF);
+ break;
+
+ case 3:
+ stream.AspectRatio =
+ (TSAspectRatio)((parse & 0xF0) >> 4);
+
+ switch ((parse & 0xF0) >> 4)
+ {
+ case 0: // Forbidden
+ break;
+ case 1: // Square
+ break;
+ case 2: // 4:3
+ break;
+ case 3: // 16:9
+ break;
+ case 4: // 2.21:1
+ break;
+ default: // Reserved
+ break;
+ }
+
+ switch (parse & 0xF)
+ {
+ case 0: // Forbidden
+ break;
+ case 1: // 23.976
+ stream.FrameRateEnumerator = 24000;
+ stream.FrameRateDenominator = 1001;
+ break;
+ case 2: // 24
+ stream.FrameRateEnumerator = 24000;
+ stream.FrameRateDenominator = 1000;
+ break;
+ case 3: // 25
+ stream.FrameRateEnumerator = 25000;
+ stream.FrameRateDenominator = 1000;
+ break;
+ case 4: // 29.97
+ stream.FrameRateEnumerator = 30000;
+ stream.FrameRateDenominator = 1001;
+ break;
+ case 5: // 30
+ stream.FrameRateEnumerator = 30000;
+ stream.FrameRateDenominator = 1000;
+ break;
+ case 6: // 50
+ stream.FrameRateEnumerator = 50000;
+ stream.FrameRateDenominator = 1000;
+ break;
+ case 7: // 59.94
+ stream.FrameRateEnumerator = 60000;
+ stream.FrameRateDenominator = 1001;
+ break;
+ case 8: // 60
+ stream.FrameRateEnumerator = 60000;
+ stream.FrameRateDenominator = 1000;
+ break;
+ default: // Reserved
+ stream.FrameRateEnumerator = 0;
+ stream.FrameRateDenominator = 0;
+ break;
+ }
+ break;
+
+ case 2:
+ break;
+
+ case 1:
+ break;
+#endif
+
+ case 0:
+#if DEBUG
+ stream.BitRate =
+ (((parse & 0xFFFFC0) >> 6) * 200);
+#endif
+ stream.IsVBR = true;
+ stream.IsInitialized = true;
+ break;
+ }
+ }
+ else if (pictureParse > 0)
+ {
+ --pictureParse;
+ if (pictureParse == 0)
+ {
+ switch ((parse & 0x38) >> 3)
+ {
+ case 1:
+ tag = "I";
+ break;
+ case 2:
+ tag = "P";
+ break;
+ case 3:
+ tag = "B";
+ break;
+ default:
+ break;
+ }
+ if (stream.IsInitialized) return;
+ }
+ }
+ else if (parse == 0x000001B5)
+ {
+ extensionParse = 1;
+ }
+ else if (extensionParse > 0)
+ {
+ --extensionParse;
+ if (extensionParse == 0)
+ {
+ if ((parse & 0xF0) == 0x10)
+ {
+ sequenceExtensionParse = 1;
+ }
+ }
+ }
+ else if (sequenceExtensionParse > 0)
+ {
+ --sequenceExtensionParse;
+#if DEBUG
+ if (sequenceExtensionParse == 0)
+ {
+ uint sequenceExtension =
+ ((parse & 0x8) >> 3);
+ if (sequenceExtension == 0)
+ {
+ stream.IsInterlaced = true;
+ }
+ else
+ {
+ stream.IsInterlaced = false;
+ }
+ }
+#endif
+ }
+ }
+ }
+ }
+}
diff --git a/BDInfo/TSCodecMVC.cs b/BDInfo/TSCodecMVC.cs
new file mode 100644
index 0000000000..80fed3886c
--- /dev/null
+++ b/BDInfo/TSCodecMVC.cs
@@ -0,0 +1,36 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//=============================================================================
+
+
+namespace BDInfo
+{
+ // TODO: Do something more interesting here...
+
+ public abstract class TSCodecMVC
+ {
+ public static void Scan(
+ TSVideoStream stream,
+ TSStreamBuffer buffer,
+ ref string tag)
+ {
+ stream.IsVBR = true;
+ stream.IsInitialized = true;
+ }
+ }
+}
diff --git a/BDInfo/TSCodecTrueHD.cs b/BDInfo/TSCodecTrueHD.cs
new file mode 100644
index 0000000000..baf4fa3dfe
--- /dev/null
+++ b/BDInfo/TSCodecTrueHD.cs
@@ -0,0 +1,186 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//=============================================================================
+
+
+namespace BDInfo
+{
+ public abstract class TSCodecTrueHD
+ {
+ public static void Scan(
+ TSAudioStream stream,
+ TSStreamBuffer buffer,
+ ref string tag)
+ {
+ if (stream.IsInitialized &&
+ stream.CoreStream != null &&
+ stream.CoreStream.IsInitialized) return;
+
+ bool syncFound = false;
+ uint sync = 0;
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ sync = (sync << 8) + buffer.ReadByte();
+ if (sync == 0xF8726FBA)
+ {
+ syncFound = true;
+ break;
+ }
+ }
+
+ if (!syncFound)
+ {
+ tag = "CORE";
+ if (stream.CoreStream == null)
+ {
+ stream.CoreStream = new TSAudioStream();
+ stream.CoreStream.StreamType = TSStreamType.AC3_AUDIO;
+ }
+ if (!stream.CoreStream.IsInitialized)
+ {
+ buffer.BeginRead();
+ TSCodecAC3.Scan(stream.CoreStream, buffer, ref tag);
+ }
+ return;
+ }
+
+ tag = "HD";
+ int ratebits = buffer.ReadBits(4);
+ if (ratebits != 0xF)
+ {
+ stream.SampleRate =
+ (((ratebits & 8) > 0 ? 44100 : 48000) << (ratebits & 7));
+ }
+ int temp1 = buffer.ReadBits(8);
+ int channels_thd_stream1 = buffer.ReadBits(5);
+ int temp2 = buffer.ReadBits(2);
+
+ stream.ChannelCount = 0;
+ stream.LFE = 0;
+ int c_LFE2 = buffer.ReadBits(1);
+ if (c_LFE2 == 1)
+ {
+ stream.LFE += 1;
+ }
+ int c_Cvh = buffer.ReadBits(1);
+ if (c_Cvh == 1)
+ {
+ stream.ChannelCount += 1;
+ }
+ int c_LRw = buffer.ReadBits(1);
+ if (c_LRw == 1)
+ {
+ stream.ChannelCount += 2;
+ }
+ int c_LRsd = buffer.ReadBits(1);
+ if (c_LRsd == 1)
+ {
+ stream.ChannelCount += 2;
+ }
+ int c_Ts = buffer.ReadBits(1);
+ if (c_Ts == 1)
+ {
+ stream.ChannelCount += 1;
+ }
+ int c_Cs = buffer.ReadBits(1);
+ if (c_Cs == 1)
+ {
+ stream.ChannelCount += 1;
+ }
+ int c_LRrs = buffer.ReadBits(1);
+ if (c_LRrs == 1)
+ {
+ stream.ChannelCount += 2;
+ }
+ int c_LRc = buffer.ReadBits(1);
+ if (c_LRc == 1)
+ {
+ stream.ChannelCount += 2;
+ }
+ int c_LRvh = buffer.ReadBits(1);
+ if (c_LRvh == 1)
+ {
+ stream.ChannelCount += 2;
+ }
+ int c_LRs = buffer.ReadBits(1);
+ if (c_LRs == 1)
+ {
+ stream.ChannelCount += 2;
+ }
+ int c_LFE = buffer.ReadBits(1);
+ if (c_LFE == 1)
+ {
+ stream.LFE += 1;
+ }
+ int c_C = buffer.ReadBits(1);
+ if (c_C == 1)
+ {
+ stream.ChannelCount += 1;
+ }
+ int c_LR = buffer.ReadBits(1);
+ if (c_LR == 1)
+ {
+ stream.ChannelCount += 2;
+ }
+
+ int access_unit_size = 40 << (ratebits & 7);
+ int access_unit_size_pow2 = 64 << (ratebits & 7);
+
+ int a1 = buffer.ReadBits(16);
+ int a2 = buffer.ReadBits(16);
+ int a3 = buffer.ReadBits(16);
+
+ int is_vbr = buffer.ReadBits(1);
+ int peak_bitrate = buffer.ReadBits(15);
+ peak_bitrate = (peak_bitrate * stream.SampleRate) >> 4;
+
+ double peak_bitdepth =
+ (double)peak_bitrate /
+ (stream.ChannelCount + stream.LFE) /
+ stream.SampleRate;
+ if (peak_bitdepth > 14)
+ {
+ stream.BitDepth = 24;
+ }
+ else
+ {
+ stream.BitDepth = 16;
+ }
+
+#if DEBUG
+ System.Diagnostics.Debug.WriteLine(string.Format(
+ "{0}\t{1}\t{2:F2}",
+ stream.PID, peak_bitrate, peak_bitdepth));
+#endif
+ /*
+ // TODO: Get THD dialnorm from metadata
+ if (stream.CoreStream != null)
+ {
+ TSAudioStream coreStream = (TSAudioStream)stream.CoreStream;
+ if (coreStream.DialNorm != 0)
+ {
+ stream.DialNorm = coreStream.DialNorm;
+ }
+ }
+ */
+
+ stream.IsVBR = true;
+ stream.IsInitialized = true;
+ }
+ }
+}
diff --git a/BDInfo/TSCodecVC1.cs b/BDInfo/TSCodecVC1.cs
new file mode 100644
index 0000000000..164ef8c47e
--- /dev/null
+++ b/BDInfo/TSCodecVC1.cs
@@ -0,0 +1,131 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//=============================================================================
+
+
+namespace BDInfo
+{
+ public abstract class TSCodecVC1
+ {
+ public static void Scan(
+ TSVideoStream stream,
+ TSStreamBuffer buffer,
+ ref string tag)
+ {
+ int parse = 0;
+ byte frameHeaderParse = 0;
+ byte sequenceHeaderParse = 0;
+ bool isInterlaced = false;
+
+ for (int i = 0; i < buffer.Length; i++)
+ {
+ parse = (parse << 8) + buffer.ReadByte();
+
+ if (parse == 0x0000010D)
+ {
+ frameHeaderParse = 4;
+ }
+ else if (frameHeaderParse > 0)
+ {
+ --frameHeaderParse;
+ if (frameHeaderParse == 0)
+ {
+ uint pictureType = 0;
+ if (isInterlaced)
+ {
+ if ((parse & 0x80000000) == 0)
+ {
+ pictureType =
+ (uint)((parse & 0x78000000) >> 13);
+ }
+ else
+ {
+ pictureType =
+ (uint)((parse & 0x3c000000) >> 12);
+ }
+ }
+ else
+ {
+ pictureType =
+ (uint)((parse & 0xf0000000) >> 14);
+ }
+
+ if ((pictureType & 0x20000) == 0)
+ {
+ tag = "P";
+ }
+ else if ((pictureType & 0x10000) == 0)
+ {
+ tag = "B";
+ }
+ else if ((pictureType & 0x8000) == 0)
+ {
+ tag = "I";
+ }
+ else if ((pictureType & 0x4000) == 0)
+ {
+ tag = "BI";
+ }
+ else
+ {
+ tag = null;
+ }
+ if (stream.IsInitialized) return;
+ }
+ }
+ else if (parse == 0x0000010F)
+ {
+ sequenceHeaderParse = 6;
+ }
+ else if (sequenceHeaderParse > 0)
+ {
+ --sequenceHeaderParse;
+ switch (sequenceHeaderParse)
+ {
+ case 5:
+ int profileLevel = ((parse & 0x38) >> 3);
+ if (((parse & 0xC0) >> 6) == 3)
+ {
+ stream.EncodingProfile = string.Format(
+ "Advanced Profile {0}", profileLevel);
+ }
+ else
+ {
+ stream.EncodingProfile = string.Format(
+ "Main Profile {0}", profileLevel);
+ }
+ break;
+
+ case 0:
+ if (((parse & 0x40) >> 6) > 0)
+ {
+ isInterlaced = true;
+ }
+ else
+ {
+ isInterlaced = false;
+ }
+ break;
+ }
+ stream.IsVBR = true;
+ stream.IsInitialized = true;
+ }
+ }
+ }
+ }
+}
diff --git a/BDInfo/TSInterleavedFile.cs b/BDInfo/TSInterleavedFile.cs
new file mode 100644
index 0000000000..44a16bbaa6
--- /dev/null
+++ b/BDInfo/TSInterleavedFile.cs
@@ -0,0 +1,38 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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.IO;
+using MediaBrowser.Model.IO;
+
+// TODO: Do more interesting things here...
+
+namespace BDInfo
+{
+ public class TSInterleavedFile
+ {
+ public FileSystemMetadata FileInfo = null;
+ public string Name = null;
+
+ public TSInterleavedFile(FileSystemMetadata fileInfo)
+ {
+ FileInfo = fileInfo;
+ Name = fileInfo.Name.ToUpper();
+ }
+ }
+}
diff --git a/BDInfo/TSPlaylistFile.cs b/BDInfo/TSPlaylistFile.cs
new file mode 100644
index 0000000000..46d66f513e
--- /dev/null
+++ b/BDInfo/TSPlaylistFile.cs
@@ -0,0 +1,1292 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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
+//=============================================================================
+
+#undef DEBUG
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Text;
+
+namespace BDInfo
+{
+ public class TSPlaylistFile
+ {
+ private readonly IFileSystem _fileSystem;
+ private readonly ITextEncoding _textEncoding;
+ private FileSystemMetadata FileInfo = null;
+ public string FileType = null;
+ public bool IsInitialized = false;
+ public string Name = null;
+ public BDROM BDROM = null;
+ public bool HasHiddenTracks = false;
+ public bool HasLoops = false;
+ public bool IsCustom = false;
+
+ public List Chapters = new List();
+
+ public Dictionary Streams =
+ new Dictionary();
+ public Dictionary PlaylistStreams =
+ new Dictionary();
+ public List StreamClips =
+ new List();
+ public List> AngleStreams =
+ new List>();
+ public List> AngleClips =
+ new List>();
+ public int AngleCount = 0;
+
+ public List SortedStreams =
+ new List();
+ public List VideoStreams =
+ new List();
+ public List AudioStreams =
+ new List();
+ public List TextStreams =
+ new List();
+ public List GraphicsStreams =
+ new List();
+
+ public TSPlaylistFile(
+ BDROM bdrom,
+ FileSystemMetadata fileInfo, IFileSystem fileSystem, ITextEncoding textEncoding)
+ {
+ BDROM = bdrom;
+ FileInfo = fileInfo;
+ _fileSystem = fileSystem;
+ _textEncoding = textEncoding;
+ Name = fileInfo.Name.ToUpper();
+ }
+
+ public TSPlaylistFile(
+ BDROM bdrom,
+ string name,
+ List clips, IFileSystem fileSystem, ITextEncoding textEncoding)
+ {
+ BDROM = bdrom;
+ Name = name;
+ _fileSystem = fileSystem;
+ _textEncoding = textEncoding;
+ IsCustom = true;
+ foreach (TSStreamClip clip in clips)
+ {
+ TSStreamClip newClip = new TSStreamClip(
+ clip.StreamFile, clip.StreamClipFile);
+
+ newClip.Name = clip.Name;
+ newClip.TimeIn = clip.TimeIn;
+ newClip.TimeOut = clip.TimeOut;
+ newClip.Length = newClip.TimeOut - newClip.TimeIn;
+ newClip.RelativeTimeIn = TotalLength;
+ newClip.RelativeTimeOut = newClip.RelativeTimeIn + newClip.Length;
+ newClip.AngleIndex = clip.AngleIndex;
+ newClip.Chapters.Add(clip.TimeIn);
+ StreamClips.Add(newClip);
+
+ if (newClip.AngleIndex > AngleCount)
+ {
+ AngleCount = newClip.AngleIndex;
+ }
+ if (newClip.AngleIndex == 0)
+ {
+ Chapters.Add(newClip.RelativeTimeIn);
+ }
+ }
+ LoadStreamClips();
+ IsInitialized = true;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ public ulong InterleavedFileSize
+ {
+ get
+ {
+ ulong size = 0;
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ size += clip.InterleavedFileSize;
+ }
+ return size;
+ }
+ }
+ public ulong FileSize
+ {
+ get
+ {
+ ulong size = 0;
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ size += clip.FileSize;
+ }
+ return size;
+ }
+ }
+ public double TotalLength
+ {
+ get
+ {
+ double length = 0;
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ if (clip.AngleIndex == 0)
+ {
+ length += clip.Length;
+ }
+ }
+ return length;
+ }
+ }
+
+ public double TotalAngleLength
+ {
+ get
+ {
+ double length = 0;
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ length += clip.Length;
+ }
+ return length;
+ }
+ }
+
+ public ulong TotalSize
+ {
+ get
+ {
+ ulong size = 0;
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ if (clip.AngleIndex == 0)
+ {
+ size += clip.PacketSize;
+ }
+ }
+ return size;
+ }
+ }
+
+ public ulong TotalAngleSize
+ {
+ get
+ {
+ ulong size = 0;
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ size += clip.PacketSize;
+ }
+ return size;
+ }
+ }
+
+ public ulong TotalBitRate
+ {
+ get
+ {
+ if (TotalLength > 0)
+ {
+ return (ulong)Math.Round(((TotalSize * 8.0) / TotalLength));
+ }
+ return 0;
+ }
+ }
+
+ public ulong TotalAngleBitRate
+ {
+ get
+ {
+ if (TotalAngleLength > 0)
+ {
+ return (ulong)Math.Round(((TotalAngleSize * 8.0) / TotalAngleLength));
+ }
+ return 0;
+ }
+ }
+
+ public void Scan(
+ Dictionary streamFiles,
+ Dictionary streamClipFiles)
+ {
+ Stream fileStream = null;
+ BinaryReader fileReader = null;
+
+ try
+ {
+ Streams.Clear();
+ StreamClips.Clear();
+
+ fileStream = _fileSystem.OpenRead(FileInfo.FullName);
+ fileReader = new BinaryReader(fileStream);
+
+ byte[] data = new byte[fileStream.Length];
+ int dataLength = fileReader.Read(data, 0, data.Length);
+
+ int pos = 0;
+
+ FileType = ReadString(data, 8, ref pos);
+ if (FileType != "MPLS0100" && FileType != "MPLS0200")
+ {
+ throw new Exception(string.Format(
+ "Playlist {0} has an unknown file type {1}.",
+ FileInfo.Name, FileType));
+ }
+
+ int playlistOffset = ReadInt32(data, ref pos);
+ int chaptersOffset = ReadInt32(data, ref pos);
+ int extensionsOffset = ReadInt32(data, ref pos);
+
+ pos = playlistOffset;
+
+ int playlistLength = ReadInt32(data, ref pos);
+ int playlistReserved = ReadInt16(data, ref pos);
+ int itemCount = ReadInt16(data, ref pos);
+ int subitemCount = ReadInt16(data, ref pos);
+
+ List chapterClips = new List();
+ for (int itemIndex = 0; itemIndex < itemCount; itemIndex++)
+ {
+ int itemStart = pos;
+ int itemLength = ReadInt16(data, ref pos);
+ string itemName = ReadString(data, 5, ref pos);
+ string itemType = ReadString(data, 4, ref pos);
+
+ TSStreamFile streamFile = null;
+ string streamFileName = string.Format(
+ "{0}.M2TS", itemName);
+ if (streamFiles.ContainsKey(streamFileName))
+ {
+ streamFile = streamFiles[streamFileName];
+ }
+ if (streamFile == null)
+ {
+ // Error condition
+ }
+
+ TSStreamClipFile streamClipFile = null;
+ string streamClipFileName = string.Format(
+ "{0}.CLPI", itemName);
+ if (streamClipFiles.ContainsKey(streamClipFileName))
+ {
+ streamClipFile = streamClipFiles[streamClipFileName];
+ }
+ if (streamClipFile == null)
+ {
+ throw new Exception(string.Format(
+ "Playlist {0} referenced missing file {1}.",
+ FileInfo.Name, streamFileName));
+ }
+
+ pos += 1;
+ int multiangle = (data[pos] >> 4) & 0x01;
+ int condition = data[pos] & 0x0F;
+ pos += 2;
+
+ int inTime = ReadInt32(data, ref pos);
+ if (inTime < 0) inTime &= 0x7FFFFFFF;
+ double timeIn = (double)inTime / 45000;
+
+ int outTime = ReadInt32(data, ref pos);
+ if (outTime < 0) outTime &= 0x7FFFFFFF;
+ double timeOut = (double)outTime / 45000;
+
+ TSStreamClip streamClip = new TSStreamClip(
+ streamFile, streamClipFile);
+
+ streamClip.Name = streamFileName; //TODO
+ streamClip.TimeIn = timeIn;
+ streamClip.TimeOut = timeOut;
+ streamClip.Length = streamClip.TimeOut - streamClip.TimeIn;
+ streamClip.RelativeTimeIn = TotalLength;
+ streamClip.RelativeTimeOut = streamClip.RelativeTimeIn + streamClip.Length;
+ StreamClips.Add(streamClip);
+ chapterClips.Add(streamClip);
+
+ pos += 12;
+ if (multiangle > 0)
+ {
+ int angles = data[pos];
+ pos += 2;
+ for (int angle = 0; angle < angles - 1; angle++)
+ {
+ string angleName = ReadString(data, 5, ref pos);
+ string angleType = ReadString(data, 4, ref pos);
+ pos += 1;
+
+ TSStreamFile angleFile = null;
+ string angleFileName = string.Format(
+ "{0}.M2TS", angleName);
+ if (streamFiles.ContainsKey(angleFileName))
+ {
+ angleFile = streamFiles[angleFileName];
+ }
+ if (angleFile == null)
+ {
+ throw new Exception(string.Format(
+ "Playlist {0} referenced missing angle file {1}.",
+ FileInfo.Name, angleFileName));
+ }
+
+ TSStreamClipFile angleClipFile = null;
+ string angleClipFileName = string.Format(
+ "{0}.CLPI", angleName);
+ if (streamClipFiles.ContainsKey(angleClipFileName))
+ {
+ angleClipFile = streamClipFiles[angleClipFileName];
+ }
+ if (angleClipFile == null)
+ {
+ throw new Exception(string.Format(
+ "Playlist {0} referenced missing angle file {1}.",
+ FileInfo.Name, angleClipFileName));
+ }
+
+ TSStreamClip angleClip =
+ new TSStreamClip(angleFile, angleClipFile);
+ angleClip.AngleIndex = angle + 1;
+ angleClip.TimeIn = streamClip.TimeIn;
+ angleClip.TimeOut = streamClip.TimeOut;
+ angleClip.RelativeTimeIn = streamClip.RelativeTimeIn;
+ angleClip.RelativeTimeOut = streamClip.RelativeTimeOut;
+ angleClip.Length = streamClip.Length;
+ StreamClips.Add(angleClip);
+ }
+ if (angles - 1 > AngleCount) AngleCount = angles - 1;
+ }
+
+ int streamInfoLength = ReadInt16(data, ref pos);
+ pos += 2;
+ int streamCountVideo = data[pos++];
+ int streamCountAudio = data[pos++];
+ int streamCountPG = data[pos++];
+ int streamCountIG = data[pos++];
+ int streamCountSecondaryAudio = data[pos++];
+ int streamCountSecondaryVideo = data[pos++];
+ int streamCountPIP = data[pos++];
+ pos += 5;
+
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "{0} : {1} -> V:{2} A:{3} PG:{4} IG:{5} 2A:{6} 2V:{7} PIP:{8}",
+ Name, streamFileName, streamCountVideo, streamCountAudio, streamCountPG, streamCountIG,
+ streamCountSecondaryAudio, streamCountSecondaryVideo, streamCountPIP));
+#endif
+
+ for (int i = 0; i < streamCountVideo; i++)
+ {
+ TSStream stream = CreatePlaylistStream(data, ref pos);
+ if (stream != null) PlaylistStreams[stream.PID] = stream;
+ }
+ for (int i = 0; i < streamCountAudio; i++)
+ {
+ TSStream stream = CreatePlaylistStream(data, ref pos);
+ if (stream != null) PlaylistStreams[stream.PID] = stream;
+ }
+ for (int i = 0; i < streamCountPG; i++)
+ {
+ TSStream stream = CreatePlaylistStream(data, ref pos);
+ if (stream != null) PlaylistStreams[stream.PID] = stream;
+ }
+ for (int i = 0; i < streamCountIG; i++)
+ {
+ TSStream stream = CreatePlaylistStream(data, ref pos);
+ if (stream != null) PlaylistStreams[stream.PID] = stream;
+ }
+ for (int i = 0; i < streamCountSecondaryAudio; i++)
+ {
+ TSStream stream = CreatePlaylistStream(data, ref pos);
+ if (stream != null) PlaylistStreams[stream.PID] = stream;
+ pos += 2;
+ }
+ for (int i = 0; i < streamCountSecondaryVideo; i++)
+ {
+ TSStream stream = CreatePlaylistStream(data, ref pos);
+ if (stream != null) PlaylistStreams[stream.PID] = stream;
+ pos += 6;
+ }
+ /*
+ * TODO
+ *
+ for (int i = 0; i < streamCountPIP; i++)
+ {
+ TSStream stream = CreatePlaylistStream(data, ref pos);
+ if (stream != null) PlaylistStreams[stream.PID] = stream;
+ }
+ */
+
+ pos += itemLength - (pos - itemStart) + 2;
+ }
+
+ pos = chaptersOffset + 4;
+
+ int chapterCount = ReadInt16(data, ref pos);
+
+ for (int chapterIndex = 0;
+ chapterIndex < chapterCount;
+ chapterIndex++)
+ {
+ int chapterType = data[pos+1];
+
+ if (chapterType == 1)
+ {
+ int streamFileIndex =
+ ((int)data[pos + 2] << 8) + data[pos + 3];
+
+ long chapterTime =
+ ((long)data[pos + 4] << 24) +
+ ((long)data[pos + 5] << 16) +
+ ((long)data[pos + 6] << 8) +
+ ((long)data[pos + 7]);
+
+ TSStreamClip streamClip = chapterClips[streamFileIndex];
+
+ double chapterSeconds = (double)chapterTime / 45000;
+
+ double relativeSeconds =
+ chapterSeconds -
+ streamClip.TimeIn +
+ streamClip.RelativeTimeIn;
+
+ // TODO: Ignore short last chapter?
+ if (TotalLength - relativeSeconds > 1.0)
+ {
+ streamClip.Chapters.Add(chapterSeconds);
+ this.Chapters.Add(relativeSeconds);
+ }
+ }
+ else
+ {
+ // TODO: Handle other chapter types?
+ }
+ pos += 14;
+ }
+ }
+ finally
+ {
+ if (fileReader != null)
+ {
+ fileReader.Dispose();
+ }
+ if (fileStream != null)
+ {
+ fileStream.Dispose();
+ }
+ }
+ }
+
+ public void Initialize()
+ {
+ LoadStreamClips();
+
+ Dictionary> clipTimes = new Dictionary>();
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ if (clip.AngleIndex == 0)
+ {
+ if (clipTimes.ContainsKey(clip.Name))
+ {
+ if (clipTimes[clip.Name].Contains(clip.TimeIn))
+ {
+ HasLoops = true;
+ break;
+ }
+ else
+ {
+ clipTimes[clip.Name].Add(clip.TimeIn);
+ }
+ }
+ else
+ {
+ clipTimes[clip.Name] = new List { clip.TimeIn };
+ }
+ }
+ }
+ ClearBitrates();
+ IsInitialized = true;
+ }
+
+ protected TSStream CreatePlaylistStream(byte[] data, ref int pos)
+ {
+ TSStream stream = null;
+
+ int start = pos;
+
+ int headerLength = data[pos++];
+ int headerPos = pos;
+ int headerType = data[pos++];
+
+ int pid = 0;
+ int subpathid = 0;
+ int subclipid = 0;
+
+ switch (headerType)
+ {
+ case 1:
+ pid = ReadInt16(data, ref pos);
+ break;
+ case 2:
+ subpathid = data[pos++];
+ subclipid = data[pos++];
+ pid = ReadInt16(data, ref pos);
+ break;
+ case 3:
+ subpathid = data[pos++];
+ pid = ReadInt16(data, ref pos);
+ break;
+ case 4:
+ subpathid = data[pos++];
+ subclipid = data[pos++];
+ pid = ReadInt16(data, ref pos);
+ break;
+ default:
+ break;
+ }
+
+ pos = headerPos + headerLength;
+
+ int streamLength = data[pos++];
+ int streamPos = pos;
+
+ TSStreamType streamType = (TSStreamType)data[pos++];
+ switch (streamType)
+ {
+ case TSStreamType.MVC_VIDEO:
+ // TODO
+ break;
+
+ case TSStreamType.AVC_VIDEO:
+ case TSStreamType.MPEG1_VIDEO:
+ case TSStreamType.MPEG2_VIDEO:
+ case TSStreamType.VC1_VIDEO:
+
+ TSVideoFormat videoFormat = (TSVideoFormat)
+ (data[pos] >> 4);
+ TSFrameRate frameRate = (TSFrameRate)
+ (data[pos] & 0xF);
+ TSAspectRatio aspectRatio = (TSAspectRatio)
+ (data[pos + 1] >> 4);
+
+ stream = new TSVideoStream();
+ ((TSVideoStream)stream).VideoFormat = videoFormat;
+ ((TSVideoStream)stream).AspectRatio = aspectRatio;
+ ((TSVideoStream)stream).FrameRate = frameRate;
+
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\t{0} {1} {2} {3} {4}",
+ pid,
+ streamType,
+ videoFormat,
+ frameRate,
+ aspectRatio));
+#endif
+
+ break;
+
+ case TSStreamType.AC3_AUDIO:
+ case TSStreamType.AC3_PLUS_AUDIO:
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ case TSStreamType.DTS_AUDIO:
+ case TSStreamType.DTS_HD_AUDIO:
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ case TSStreamType.LPCM_AUDIO:
+ case TSStreamType.MPEG1_AUDIO:
+ case TSStreamType.MPEG2_AUDIO:
+
+ int audioFormat = ReadByte(data, ref pos);
+
+ TSChannelLayout channelLayout = (TSChannelLayout)
+ (audioFormat >> 4);
+ TSSampleRate sampleRate = (TSSampleRate)
+ (audioFormat & 0xF);
+
+ string audioLanguage = ReadString(data, 3, ref pos);
+
+ stream = new TSAudioStream();
+ ((TSAudioStream)stream).ChannelLayout = channelLayout;
+ ((TSAudioStream)stream).SampleRate = TSAudioStream.ConvertSampleRate(sampleRate);
+ ((TSAudioStream)stream).LanguageCode = audioLanguage;
+
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\t{0} {1} {2} {3} {4}",
+ pid,
+ streamType,
+ audioLanguage,
+ channelLayout,
+ sampleRate));
+#endif
+
+ break;
+
+ case TSStreamType.INTERACTIVE_GRAPHICS:
+ case TSStreamType.PRESENTATION_GRAPHICS:
+
+ string graphicsLanguage = ReadString(data, 3, ref pos);
+
+ stream = new TSGraphicsStream();
+ ((TSGraphicsStream)stream).LanguageCode = graphicsLanguage;
+
+ if (data[pos] != 0)
+ {
+ }
+
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\t{0} {1} {2}",
+ pid,
+ streamType,
+ graphicsLanguage));
+#endif
+
+ break;
+
+ case TSStreamType.SUBTITLE:
+
+ int code = ReadByte(data, ref pos); // TODO
+ string textLanguage = ReadString(data, 3, ref pos);
+
+ stream = new TSTextStream();
+ ((TSTextStream)stream).LanguageCode = textLanguage;
+
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\t{0} {1} {2}",
+ pid,
+ streamType,
+ textLanguage));
+#endif
+
+ break;
+
+ default:
+ break;
+ }
+
+ pos = streamPos + streamLength;
+
+ if (stream != null)
+ {
+ stream.PID = (ushort)pid;
+ stream.StreamType = streamType;
+ }
+
+ return stream;
+ }
+
+ private void LoadStreamClips()
+ {
+ AngleClips.Clear();
+ if (AngleCount > 0)
+ {
+ for (int angleIndex = 0; angleIndex < AngleCount; angleIndex++)
+ {
+ AngleClips.Add(new Dictionary());
+ }
+ }
+
+ TSStreamClip referenceClip = null;
+ if (StreamClips.Count > 0)
+ {
+ referenceClip = StreamClips[0];
+ }
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ if (clip.StreamClipFile.Streams.Count > referenceClip.StreamClipFile.Streams.Count)
+ {
+ referenceClip = clip;
+ }
+ else if (clip.Length > referenceClip.Length)
+ {
+ referenceClip = clip;
+ }
+ if (AngleCount > 0)
+ {
+ if (clip.AngleIndex == 0)
+ {
+ for (int angleIndex = 0; angleIndex < AngleCount; angleIndex++)
+ {
+ AngleClips[angleIndex][clip.RelativeTimeIn] = clip;
+ }
+ }
+ else
+ {
+ AngleClips[clip.AngleIndex - 1][clip.RelativeTimeIn] = clip;
+ }
+ }
+ }
+
+ foreach (TSStream clipStream
+ in referenceClip.StreamClipFile.Streams.Values)
+ {
+ if (!Streams.ContainsKey(clipStream.PID))
+ {
+ TSStream stream = clipStream.Clone();
+ Streams[clipStream.PID] = stream;
+
+ if (!IsCustom && !PlaylistStreams.ContainsKey(stream.PID))
+ {
+ stream.IsHidden = true;
+ HasHiddenTracks = true;
+ }
+
+ if (stream.IsVideoStream)
+ {
+ VideoStreams.Add((TSVideoStream)stream);
+ }
+ else if (stream.IsAudioStream)
+ {
+ AudioStreams.Add((TSAudioStream)stream);
+ }
+ else if (stream.IsGraphicsStream)
+ {
+ GraphicsStreams.Add((TSGraphicsStream)stream);
+ }
+ else if (stream.IsTextStream)
+ {
+ TextStreams.Add((TSTextStream)stream);
+ }
+ }
+ }
+
+ if (referenceClip.StreamFile != null)
+ {
+ // TODO: Better way to add this in?
+ if (BDInfoSettings.EnableSSIF &&
+ referenceClip.StreamFile.InterleavedFile != null &&
+ referenceClip.StreamFile.Streams.ContainsKey(4114) &&
+ !Streams.ContainsKey(4114))
+ {
+ TSStream stream = referenceClip.StreamFile.Streams[4114].Clone();
+ Streams[4114] = stream;
+ if (stream.IsVideoStream)
+ {
+ VideoStreams.Add((TSVideoStream)stream);
+ }
+ }
+
+ foreach (TSStream clipStream
+ in referenceClip.StreamFile.Streams.Values)
+ {
+ if (Streams.ContainsKey(clipStream.PID))
+ {
+ TSStream stream = Streams[clipStream.PID];
+
+ if (stream.StreamType != clipStream.StreamType) continue;
+
+ if (clipStream.BitRate > stream.BitRate)
+ {
+ stream.BitRate = clipStream.BitRate;
+ }
+ stream.IsVBR = clipStream.IsVBR;
+
+ if (stream.IsVideoStream &&
+ clipStream.IsVideoStream)
+ {
+ ((TSVideoStream)stream).EncodingProfile =
+ ((TSVideoStream)clipStream).EncodingProfile;
+ }
+ else if (stream.IsAudioStream &&
+ clipStream.IsAudioStream)
+ {
+ TSAudioStream audioStream = (TSAudioStream)stream;
+ TSAudioStream clipAudioStream = (TSAudioStream)clipStream;
+
+ if (clipAudioStream.ChannelCount > audioStream.ChannelCount)
+ {
+ audioStream.ChannelCount = clipAudioStream.ChannelCount;
+ }
+ if (clipAudioStream.LFE > audioStream.LFE)
+ {
+ audioStream.LFE = clipAudioStream.LFE;
+ }
+ if (clipAudioStream.SampleRate > audioStream.SampleRate)
+ {
+ audioStream.SampleRate = clipAudioStream.SampleRate;
+ }
+ if (clipAudioStream.BitDepth > audioStream.BitDepth)
+ {
+ audioStream.BitDepth = clipAudioStream.BitDepth;
+ }
+ if (clipAudioStream.DialNorm < audioStream.DialNorm)
+ {
+ audioStream.DialNorm = clipAudioStream.DialNorm;
+ }
+ if (clipAudioStream.AudioMode != TSAudioMode.Unknown)
+ {
+ audioStream.AudioMode = clipAudioStream.AudioMode;
+ }
+ if (clipAudioStream.CoreStream != null &&
+ audioStream.CoreStream == null)
+ {
+ audioStream.CoreStream = (TSAudioStream)
+ clipAudioStream.CoreStream.Clone();
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < AngleCount; i++)
+ {
+ AngleStreams.Add(new Dictionary());
+ }
+
+ if (!BDInfoSettings.KeepStreamOrder)
+ {
+ VideoStreams.Sort(CompareVideoStreams);
+ }
+ foreach (TSStream stream in VideoStreams)
+ {
+ SortedStreams.Add(stream);
+ for (int i = 0; i < AngleCount; i++)
+ {
+ TSStream angleStream = stream.Clone();
+ angleStream.AngleIndex = i + 1;
+ AngleStreams[i][angleStream.PID] = angleStream;
+ SortedStreams.Add(angleStream);
+ }
+ }
+
+ if (!BDInfoSettings.KeepStreamOrder)
+ {
+ AudioStreams.Sort(CompareAudioStreams);
+ }
+ foreach (TSStream stream in AudioStreams)
+ {
+ SortedStreams.Add(stream);
+ }
+
+ if (!BDInfoSettings.KeepStreamOrder)
+ {
+ GraphicsStreams.Sort(CompareGraphicsStreams);
+ }
+ foreach (TSStream stream in GraphicsStreams)
+ {
+ SortedStreams.Add(stream);
+ }
+
+ if (!BDInfoSettings.KeepStreamOrder)
+ {
+ TextStreams.Sort(CompareTextStreams);
+ }
+ foreach (TSStream stream in TextStreams)
+ {
+ SortedStreams.Add(stream);
+ }
+ }
+
+ public void ClearBitrates()
+ {
+ foreach (TSStreamClip clip in StreamClips)
+ {
+ clip.PayloadBytes = 0;
+ clip.PacketCount = 0;
+ clip.PacketSeconds = 0;
+
+ if (clip.StreamFile != null)
+ {
+ foreach (TSStream stream in clip.StreamFile.Streams.Values)
+ {
+ stream.PayloadBytes = 0;
+ stream.PacketCount = 0;
+ stream.PacketSeconds = 0;
+ }
+
+ if (clip.StreamFile != null &&
+ clip.StreamFile.StreamDiagnostics != null)
+ {
+ clip.StreamFile.StreamDiagnostics.Clear();
+ }
+ }
+ }
+
+ foreach (TSStream stream in SortedStreams)
+ {
+ stream.PayloadBytes = 0;
+ stream.PacketCount = 0;
+ stream.PacketSeconds = 0;
+ }
+ }
+
+ public bool IsValid
+ {
+ get
+ {
+ if (!IsInitialized) return false;
+
+ if (BDInfoSettings.FilterShortPlaylists &&
+ TotalLength < BDInfoSettings.FilterShortPlaylistsValue)
+ {
+ return false;
+ }
+
+ if (HasLoops &&
+ BDInfoSettings.FilterLoopingPlaylists)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ public static int CompareVideoStreams(
+ TSVideoStream x,
+ TSVideoStream y)
+ {
+ if (x == null && y == null)
+ {
+ return 0;
+ }
+ else if (x == null && y != null)
+ {
+ return 1;
+ }
+ else if (x != null && y == null)
+ {
+ return -1;
+ }
+ else
+ {
+ if (x.Height > y.Height)
+ {
+ return -1;
+ }
+ else if (y.Height > x.Height)
+ {
+ return 1;
+ }
+ else if (x.PID > y.PID)
+ {
+ return 1;
+ }
+ else if (y.PID > x.PID)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+
+ public static int CompareAudioStreams(
+ TSAudioStream x,
+ TSAudioStream y)
+ {
+ if (x == y)
+ {
+ return 0;
+ }
+ else if (x == null && y == null)
+ {
+ return 0;
+ }
+ else if (x == null && y != null)
+ {
+ return -1;
+ }
+ else if (x != null && y == null)
+ {
+ return 1;
+ }
+ else
+ {
+ if (x.ChannelCount > y.ChannelCount)
+ {
+ return -1;
+ }
+ else if (y.ChannelCount > x.ChannelCount)
+ {
+ return 1;
+ }
+ else
+ {
+ int sortX = GetStreamTypeSortIndex(x.StreamType);
+ int sortY = GetStreamTypeSortIndex(y.StreamType);
+
+ if (sortX > sortY)
+ {
+ return -1;
+ }
+ else if (sortY > sortX)
+ {
+ return 1;
+ }
+ else
+ {
+ if (x.LanguageCode == "eng")
+ {
+ return -1;
+ }
+ else if (y.LanguageCode == "eng")
+ {
+ return 1;
+ }
+ else if (x.LanguageCode != y.LanguageCode)
+ {
+ return string.Compare(
+ x.LanguageName, y.LanguageName);
+ }
+ else if (x.PID < y.PID)
+ {
+ return -1;
+ }
+ else if (y.PID < x.PID)
+ {
+ return 1;
+ }
+ return 0;
+ }
+ }
+ }
+ }
+
+ public static int CompareTextStreams(
+ TSTextStream x,
+ TSTextStream y)
+ {
+ if (x == y)
+ {
+ return 0;
+ }
+ else if (x == null && y == null)
+ {
+ return 0;
+ }
+ else if (x == null && y != null)
+ {
+ return -1;
+ }
+ else if (x != null && y == null)
+ {
+ return 1;
+ }
+ else
+ {
+ if (x.LanguageCode == "eng")
+ {
+ return -1;
+ }
+ else if (y.LanguageCode == "eng")
+ {
+ return 1;
+ }
+ else
+ {
+ if (x.LanguageCode == y.LanguageCode)
+ {
+ if (x.PID > y.PID)
+ {
+ return 1;
+ }
+ else if (y.PID > x.PID)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ return string.Compare(
+ x.LanguageName, y.LanguageName);
+ }
+ }
+ }
+ }
+
+ private static int CompareGraphicsStreams(
+ TSGraphicsStream x,
+ TSGraphicsStream y)
+ {
+ if (x == y)
+ {
+ return 0;
+ }
+ else if (x == null && y == null)
+ {
+ return 0;
+ }
+ else if (x == null && y != null)
+ {
+ return -1;
+ }
+ else if (x != null && y == null)
+ {
+ return 1;
+ }
+ else
+ {
+ int sortX = GetStreamTypeSortIndex(x.StreamType);
+ int sortY = GetStreamTypeSortIndex(y.StreamType);
+
+ if (sortX > sortY)
+ {
+ return -1;
+ }
+ else if (sortY > sortX)
+ {
+ return 1;
+ }
+ else if (x.LanguageCode == "eng")
+ {
+ return -1;
+ }
+ else if (y.LanguageCode == "eng")
+ {
+ return 1;
+ }
+ else
+ {
+ if (x.LanguageCode == y.LanguageCode)
+ {
+ if (x.PID > y.PID)
+ {
+ return 1;
+ }
+ else if (y.PID > x.PID)
+ {
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ return string.Compare(x.LanguageName, y.LanguageName);
+ }
+ }
+ }
+ }
+
+ private static int GetStreamTypeSortIndex(TSStreamType streamType)
+ {
+ switch (streamType)
+ {
+ case TSStreamType.Unknown:
+ return 0;
+ case TSStreamType.MPEG1_VIDEO:
+ return 1;
+ case TSStreamType.MPEG2_VIDEO:
+ return 2;
+ case TSStreamType.AVC_VIDEO:
+ return 3;
+ case TSStreamType.VC1_VIDEO:
+ return 4;
+ case TSStreamType.MVC_VIDEO:
+ return 5;
+
+ case TSStreamType.MPEG1_AUDIO:
+ return 1;
+ case TSStreamType.MPEG2_AUDIO:
+ return 2;
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ return 3;
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ return 4;
+ case TSStreamType.AC3_AUDIO:
+ return 5;
+ case TSStreamType.DTS_AUDIO:
+ return 6;
+ case TSStreamType.AC3_PLUS_AUDIO:
+ return 7;
+ case TSStreamType.DTS_HD_AUDIO:
+ return 8;
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ return 9;
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ return 10;
+ case TSStreamType.LPCM_AUDIO:
+ return 11;
+
+ case TSStreamType.SUBTITLE:
+ return 1;
+ case TSStreamType.INTERACTIVE_GRAPHICS:
+ return 2;
+ case TSStreamType.PRESENTATION_GRAPHICS:
+ return 3;
+
+ default:
+ return 0;
+ }
+ }
+
+ protected string ReadString(
+ byte[] data,
+ int count,
+ ref int pos)
+ {
+ string val =
+ _textEncoding.GetASCIIEncoding().GetString(data, pos, count);
+
+ pos += count;
+
+ return val;
+ }
+
+ protected int ReadInt32(
+ byte[] data,
+ ref int pos)
+ {
+ int val =
+ ((int)data[pos] << 24) +
+ ((int)data[pos + 1] << 16) +
+ ((int)data[pos + 2] << 8) +
+ ((int)data[pos + 3]);
+
+ pos += 4;
+
+ return val;
+ }
+
+ protected int ReadInt16(
+ byte[] data,
+ ref int pos)
+ {
+ int val =
+ ((int)data[pos] << 8) +
+ ((int)data[pos + 1]);
+
+ pos += 2;
+
+ return val;
+ }
+
+ protected byte ReadByte(
+ byte[] data,
+ ref int pos)
+ {
+ return data[pos++];
+ }
+ }
+}
diff --git a/BDInfo/TSStream.cs b/BDInfo/TSStream.cs
new file mode 100644
index 0000000000..5afb81c5e4
--- /dev/null
+++ b/BDInfo/TSStream.cs
@@ -0,0 +1,801 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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 BDInfo
+{
+ public enum TSStreamType : byte
+ {
+ Unknown = 0,
+ MPEG1_VIDEO = 0x01,
+ MPEG2_VIDEO = 0x02,
+ AVC_VIDEO = 0x1b,
+ MVC_VIDEO = 0x20,
+ VC1_VIDEO = 0xea,
+ MPEG1_AUDIO = 0x03,
+ MPEG2_AUDIO = 0x04,
+ LPCM_AUDIO = 0x80,
+ AC3_AUDIO = 0x81,
+ AC3_PLUS_AUDIO = 0x84,
+ AC3_PLUS_SECONDARY_AUDIO = 0xA1,
+ AC3_TRUE_HD_AUDIO = 0x83,
+ DTS_AUDIO = 0x82,
+ DTS_HD_AUDIO = 0x85,
+ DTS_HD_SECONDARY_AUDIO = 0xA2,
+ DTS_HD_MASTER_AUDIO = 0x86,
+ PRESENTATION_GRAPHICS = 0x90,
+ INTERACTIVE_GRAPHICS = 0x91,
+ SUBTITLE = 0x92
+ }
+
+ public enum TSVideoFormat : byte
+ {
+ Unknown = 0,
+ VIDEOFORMAT_480i = 1,
+ VIDEOFORMAT_576i = 2,
+ VIDEOFORMAT_480p = 3,
+ VIDEOFORMAT_1080i = 4,
+ VIDEOFORMAT_720p = 5,
+ VIDEOFORMAT_1080p = 6,
+ VIDEOFORMAT_576p = 7,
+ }
+
+ public enum TSFrameRate : byte
+ {
+ Unknown = 0,
+ FRAMERATE_23_976 = 1,
+ FRAMERATE_24 = 2,
+ FRAMERATE_25 = 3,
+ FRAMERATE_29_97 = 4,
+ FRAMERATE_50 = 6,
+ FRAMERATE_59_94 = 7
+ }
+
+ public enum TSChannelLayout : byte
+ {
+ Unknown = 0,
+ CHANNELLAYOUT_MONO = 1,
+ CHANNELLAYOUT_STEREO = 3,
+ CHANNELLAYOUT_MULTI = 6,
+ CHANNELLAYOUT_COMBO = 12
+ }
+
+ public enum TSSampleRate : byte
+ {
+ Unknown = 0,
+ SAMPLERATE_48 = 1,
+ SAMPLERATE_96 = 4,
+ SAMPLERATE_192 = 5,
+ SAMPLERATE_48_192 = 12,
+ SAMPLERATE_48_96 = 14
+ }
+
+ public enum TSAspectRatio
+ {
+ Unknown = 0,
+ ASPECT_4_3 = 2,
+ ASPECT_16_9 = 3,
+ ASPECT_2_21 = 4
+ }
+
+ public class TSDescriptor
+ {
+ public byte Name;
+ public byte[] Value;
+
+ public TSDescriptor(byte name, byte length)
+ {
+ Name = name;
+ Value = new byte[length];
+ }
+
+ public TSDescriptor Clone()
+ {
+ TSDescriptor descriptor =
+ new TSDescriptor(Name, (byte)Value.Length);
+ Value.CopyTo(descriptor.Value, 0);
+ return descriptor;
+ }
+ }
+
+ public abstract class TSStream
+ {
+ public TSStream()
+ {
+ }
+
+ public override string ToString()
+ {
+ return string.Format("{0} ({1})", CodecShortName, PID);
+ }
+
+ public ushort PID;
+ public TSStreamType StreamType;
+ public List Descriptors = null;
+ public long BitRate = 0;
+ public long ActiveBitRate = 0;
+ public bool IsVBR = false;
+ public bool IsInitialized = false;
+ public string LanguageName;
+ public bool IsHidden = false;
+
+ public ulong PayloadBytes = 0;
+ public ulong PacketCount = 0;
+ public double PacketSeconds = 0;
+ public int AngleIndex = 0;
+
+ public ulong PacketSize
+ {
+ get
+ {
+ return PacketCount * 192;
+ }
+ }
+
+ private string _LanguageCode;
+ public string LanguageCode
+ {
+ get
+ {
+ return _LanguageCode;
+ }
+ set
+ {
+ _LanguageCode = value;
+ LanguageName = LanguageCodes.GetName(value);
+ }
+ }
+
+ public bool IsVideoStream
+ {
+ get
+ {
+ switch (StreamType)
+ {
+ case TSStreamType.MPEG1_VIDEO:
+ case TSStreamType.MPEG2_VIDEO:
+ case TSStreamType.AVC_VIDEO:
+ case TSStreamType.MVC_VIDEO:
+ case TSStreamType.VC1_VIDEO:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+
+ public bool IsAudioStream
+ {
+ get
+ {
+ switch (StreamType)
+ {
+ case TSStreamType.MPEG1_AUDIO:
+ case TSStreamType.MPEG2_AUDIO:
+ case TSStreamType.LPCM_AUDIO:
+ case TSStreamType.AC3_AUDIO:
+ case TSStreamType.AC3_PLUS_AUDIO:
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ case TSStreamType.DTS_AUDIO:
+ case TSStreamType.DTS_HD_AUDIO:
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+
+ public bool IsGraphicsStream
+ {
+ get
+ {
+ switch (StreamType)
+ {
+ case TSStreamType.PRESENTATION_GRAPHICS:
+ case TSStreamType.INTERACTIVE_GRAPHICS:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+
+ public bool IsTextStream
+ {
+ get
+ {
+ switch (StreamType)
+ {
+ case TSStreamType.SUBTITLE:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+
+ public string CodecName
+ {
+ get
+ {
+ switch (StreamType)
+ {
+ case TSStreamType.MPEG1_VIDEO:
+ return "MPEG-1 Video";
+ case TSStreamType.MPEG2_VIDEO:
+ return "MPEG-2 Video";
+ case TSStreamType.AVC_VIDEO:
+ return "MPEG-4 AVC Video";
+ case TSStreamType.MVC_VIDEO:
+ return "MPEG-4 MVC Video";
+ case TSStreamType.VC1_VIDEO:
+ return "VC-1 Video";
+ case TSStreamType.MPEG1_AUDIO:
+ return "MP1 Audio";
+ case TSStreamType.MPEG2_AUDIO:
+ return "MP2 Audio";
+ case TSStreamType.LPCM_AUDIO:
+ return "LPCM Audio";
+ case TSStreamType.AC3_AUDIO:
+ if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
+ return "Dolby Digital EX Audio";
+ else
+ return "Dolby Digital Audio";
+ case TSStreamType.AC3_PLUS_AUDIO:
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ return "Dolby Digital Plus Audio";
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ return "Dolby TrueHD Audio";
+ case TSStreamType.DTS_AUDIO:
+ if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
+ return "DTS-ES Audio";
+ else
+ return "DTS Audio";
+ case TSStreamType.DTS_HD_AUDIO:
+ return "DTS-HD High-Res Audio";
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ return "DTS Express";
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ return "DTS-HD Master Audio";
+ case TSStreamType.PRESENTATION_GRAPHICS:
+ return "Presentation Graphics";
+ case TSStreamType.INTERACTIVE_GRAPHICS:
+ return "Interactive Graphics";
+ case TSStreamType.SUBTITLE:
+ return "Subtitle";
+ default:
+ return "UNKNOWN";
+ }
+ }
+ }
+
+ public string CodecAltName
+ {
+ get
+ {
+ switch (StreamType)
+ {
+ case TSStreamType.MPEG1_VIDEO:
+ return "MPEG-1";
+ case TSStreamType.MPEG2_VIDEO:
+ return "MPEG-2";
+ case TSStreamType.AVC_VIDEO:
+ return "AVC";
+ case TSStreamType.MVC_VIDEO:
+ return "MVC";
+ case TSStreamType.VC1_VIDEO:
+ return "VC-1";
+ case TSStreamType.MPEG1_AUDIO:
+ return "MP1";
+ case TSStreamType.MPEG2_AUDIO:
+ return "MP2";
+ case TSStreamType.LPCM_AUDIO:
+ return "LPCM";
+ case TSStreamType.AC3_AUDIO:
+ return "DD AC3";
+ case TSStreamType.AC3_PLUS_AUDIO:
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ return "DD AC3+";
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ return "Dolby TrueHD";
+ case TSStreamType.DTS_AUDIO:
+ return "DTS";
+ case TSStreamType.DTS_HD_AUDIO:
+ return "DTS-HD Hi-Res";
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ return "DTS Express";
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ return "DTS-HD Master";
+ case TSStreamType.PRESENTATION_GRAPHICS:
+ return "PGS";
+ case TSStreamType.INTERACTIVE_GRAPHICS:
+ return "IGS";
+ case TSStreamType.SUBTITLE:
+ return "SUB";
+ default:
+ return "UNKNOWN";
+ }
+ }
+ }
+
+ public string CodecShortName
+ {
+ get
+ {
+ switch (StreamType)
+ {
+ case TSStreamType.MPEG1_VIDEO:
+ return "MPEG-1";
+ case TSStreamType.MPEG2_VIDEO:
+ return "MPEG-2";
+ case TSStreamType.AVC_VIDEO:
+ return "AVC";
+ case TSStreamType.MVC_VIDEO:
+ return "MVC";
+ case TSStreamType.VC1_VIDEO:
+ return "VC-1";
+ case TSStreamType.MPEG1_AUDIO:
+ return "MP1";
+ case TSStreamType.MPEG2_AUDIO:
+ return "MP2";
+ case TSStreamType.LPCM_AUDIO:
+ return "LPCM";
+ case TSStreamType.AC3_AUDIO:
+ if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
+ return "AC3-EX";
+ else
+ return "AC3";
+ case TSStreamType.AC3_PLUS_AUDIO:
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ return "AC3+";
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ return "TrueHD";
+ case TSStreamType.DTS_AUDIO:
+ if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
+ return "DTS-ES";
+ else
+ return "DTS";
+ case TSStreamType.DTS_HD_AUDIO:
+ return "DTS-HD HR";
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ return "DTS Express";
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ return "DTS-HD MA";
+ case TSStreamType.PRESENTATION_GRAPHICS:
+ return "PGS";
+ case TSStreamType.INTERACTIVE_GRAPHICS:
+ return "IGS";
+ case TSStreamType.SUBTITLE:
+ return "SUB";
+ default:
+ return "UNKNOWN";
+ }
+ }
+ }
+
+ public virtual string Description
+ {
+ get
+ {
+ return "";
+ }
+ }
+
+ public abstract TSStream Clone();
+
+ protected void CopyTo(TSStream stream)
+ {
+ stream.PID = PID;
+ stream.StreamType = StreamType;
+ stream.IsVBR = IsVBR;
+ stream.BitRate = BitRate;
+ stream.IsInitialized = IsInitialized;
+ stream.LanguageCode = _LanguageCode;
+ if (Descriptors != null)
+ {
+ stream.Descriptors = new List();
+ foreach (TSDescriptor descriptor in Descriptors)
+ {
+ stream.Descriptors.Add(descriptor.Clone());
+ }
+ }
+ }
+ }
+
+ public class TSVideoStream : TSStream
+ {
+ public TSVideoStream()
+ {
+ }
+
+ public int Width;
+ public int Height;
+ public bool IsInterlaced;
+ public int FrameRateEnumerator;
+ public int FrameRateDenominator;
+ public TSAspectRatio AspectRatio;
+ public string EncodingProfile;
+
+ private TSVideoFormat _VideoFormat;
+ public TSVideoFormat VideoFormat
+ {
+ get
+ {
+ return _VideoFormat;
+ }
+ set
+ {
+ _VideoFormat = value;
+ switch (value)
+ {
+ case TSVideoFormat.VIDEOFORMAT_480i:
+ Height = 480;
+ IsInterlaced = true;
+ break;
+ case TSVideoFormat.VIDEOFORMAT_480p:
+ Height = 480;
+ IsInterlaced = false;
+ break;
+ case TSVideoFormat.VIDEOFORMAT_576i:
+ Height = 576;
+ IsInterlaced = true;
+ break;
+ case TSVideoFormat.VIDEOFORMAT_576p:
+ Height = 576;
+ IsInterlaced = false;
+ break;
+ case TSVideoFormat.VIDEOFORMAT_720p:
+ Height = 720;
+ IsInterlaced = false;
+ break;
+ case TSVideoFormat.VIDEOFORMAT_1080i:
+ Height = 1080;
+ IsInterlaced = true;
+ break;
+ case TSVideoFormat.VIDEOFORMAT_1080p:
+ Height = 1080;
+ IsInterlaced = false;
+ break;
+ }
+ }
+ }
+
+ private TSFrameRate _FrameRate;
+ public TSFrameRate FrameRate
+ {
+ get
+ {
+ return _FrameRate;
+ }
+ set
+ {
+ _FrameRate = value;
+ switch (value)
+ {
+ case TSFrameRate.FRAMERATE_23_976:
+ FrameRateEnumerator = 24000;
+ FrameRateDenominator = 1001;
+ break;
+ case TSFrameRate.FRAMERATE_24:
+ FrameRateEnumerator = 24000;
+ FrameRateDenominator = 1000;
+ break;
+ case TSFrameRate.FRAMERATE_25:
+ FrameRateEnumerator = 25000;
+ FrameRateDenominator = 1000;
+ break;
+ case TSFrameRate.FRAMERATE_29_97:
+ FrameRateEnumerator = 30000;
+ FrameRateDenominator = 1001;
+ break;
+ case TSFrameRate.FRAMERATE_50:
+ FrameRateEnumerator = 50000;
+ FrameRateDenominator = 1000;
+ break;
+ case TSFrameRate.FRAMERATE_59_94:
+ FrameRateEnumerator = 60000;
+ FrameRateDenominator = 1001;
+ break;
+ }
+ }
+ }
+
+ public override string Description
+ {
+ get
+ {
+ string description = "";
+
+ if (Height > 0)
+ {
+ description += string.Format("{0:D}{1} / ",
+ Height,
+ IsInterlaced ? "i" : "p");
+ }
+ if (FrameRateEnumerator > 0 &&
+ FrameRateDenominator > 0)
+ {
+ if (FrameRateEnumerator % FrameRateDenominator == 0)
+ {
+ description += string.Format("{0:D} fps / ",
+ FrameRateEnumerator / FrameRateDenominator);
+ }
+ else
+ {
+ description += string.Format("{0:F3} fps / ",
+ (double)FrameRateEnumerator / FrameRateDenominator);
+ }
+
+ }
+ if (AspectRatio == TSAspectRatio.ASPECT_4_3)
+ {
+ description += "4:3 / ";
+ }
+ else if (AspectRatio == TSAspectRatio.ASPECT_16_9)
+ {
+ description += "16:9 / ";
+ }
+ if (EncodingProfile != null)
+ {
+ description += EncodingProfile + " / ";
+ }
+ if (description.EndsWith(" / "))
+ {
+ description = description.Substring(0, description.Length - 3);
+ }
+ return description;
+ }
+ }
+
+ public override TSStream Clone()
+ {
+ TSVideoStream stream = new TSVideoStream();
+ CopyTo(stream);
+
+ stream.VideoFormat = _VideoFormat;
+ stream.FrameRate = _FrameRate;
+ stream.Width = Width;
+ stream.Height = Height;
+ stream.IsInterlaced = IsInterlaced;
+ stream.FrameRateEnumerator = FrameRateEnumerator;
+ stream.FrameRateDenominator = FrameRateDenominator;
+ stream.AspectRatio = AspectRatio;
+ stream.EncodingProfile = EncodingProfile;
+
+ return stream;
+ }
+ }
+
+ public enum TSAudioMode
+ {
+ Unknown,
+ DualMono,
+ Stereo,
+ Surround,
+ Extended
+ }
+
+ public class TSAudioStream : TSStream
+ {
+ public TSAudioStream()
+ {
+ }
+
+ public int SampleRate;
+ public int ChannelCount;
+ public int BitDepth;
+ public int LFE;
+ public int DialNorm;
+ public TSAudioMode AudioMode;
+ public TSAudioStream CoreStream;
+ public TSChannelLayout ChannelLayout;
+
+ public static int ConvertSampleRate(
+ TSSampleRate sampleRate)
+ {
+ switch (sampleRate)
+ {
+ case TSSampleRate.SAMPLERATE_48:
+ return 48000;
+
+ case TSSampleRate.SAMPLERATE_96:
+ case TSSampleRate.SAMPLERATE_48_96:
+ return 96000;
+
+ case TSSampleRate.SAMPLERATE_192:
+ case TSSampleRate.SAMPLERATE_48_192:
+ return 192000;
+ }
+ return 0;
+ }
+
+ public string ChannelDescription
+ {
+ get
+ {
+ if (ChannelLayout == TSChannelLayout.CHANNELLAYOUT_MONO &&
+ ChannelCount == 2)
+ {
+ }
+
+ string description = "";
+ if (ChannelCount > 0)
+ {
+ description += string.Format(
+ "{0:D}.{1:D}",
+ ChannelCount, LFE);
+ }
+ else
+ {
+ switch (ChannelLayout)
+ {
+ case TSChannelLayout.CHANNELLAYOUT_MONO:
+ description += "1.0";
+ break;
+ case TSChannelLayout.CHANNELLAYOUT_STEREO:
+ description += "2.0";
+ break;
+ case TSChannelLayout.CHANNELLAYOUT_MULTI:
+ description += "5.1";
+ break;
+ }
+ }
+ if (AudioMode == TSAudioMode.Extended)
+ {
+ if (StreamType == TSStreamType.AC3_AUDIO)
+ {
+ description += "-EX";
+ }
+ if (StreamType == TSStreamType.DTS_AUDIO ||
+ StreamType == TSStreamType.DTS_HD_AUDIO ||
+ StreamType == TSStreamType.DTS_HD_MASTER_AUDIO)
+ {
+ description += "-ES";
+ }
+ }
+ return description;
+ }
+ }
+
+ public override string Description
+ {
+ get
+ {
+ string description = ChannelDescription;
+
+ if (SampleRate > 0)
+ {
+ description += string.Format(
+ " / {0:D} kHz", SampleRate / 1000);
+ }
+ if (BitRate > 0)
+ {
+ description += string.Format(
+ " / {0:D} kbps", (uint)Math.Round((double)BitRate / 1000));
+ }
+ if (BitDepth > 0)
+ {
+ description += string.Format(
+ " / {0:D}-bit", BitDepth);
+ }
+ if (DialNorm != 0)
+ {
+ description += string.Format(
+ " / DN {0}dB", DialNorm);
+ }
+ if (ChannelCount == 2)
+ {
+ switch (AudioMode)
+ {
+ case TSAudioMode.DualMono:
+ description += " / Dual Mono";
+ break;
+
+ case TSAudioMode.Surround:
+ description += " / Dolby Surround";
+ break;
+ }
+ }
+ if (description.EndsWith(" / "))
+ {
+ description = description.Substring(0, description.Length - 3);
+ }
+ if (CoreStream != null)
+ {
+ string codec = "";
+ switch (CoreStream.StreamType)
+ {
+ case TSStreamType.AC3_AUDIO:
+ codec = "AC3 Embedded";
+ break;
+ case TSStreamType.DTS_AUDIO:
+ codec = "DTS Core";
+ break;
+ }
+ description += string.Format(
+ " ({0}: {1})",
+ codec,
+ CoreStream.Description);
+ }
+ return description;
+ }
+ }
+
+ public override TSStream Clone()
+ {
+ TSAudioStream stream = new TSAudioStream();
+ CopyTo(stream);
+
+ stream.SampleRate = SampleRate;
+ stream.ChannelLayout = ChannelLayout;
+ stream.ChannelCount = ChannelCount;
+ stream.BitDepth = BitDepth;
+ stream.LFE = LFE;
+ stream.DialNorm = DialNorm;
+ stream.AudioMode = AudioMode;
+ if (CoreStream != null)
+ {
+ stream.CoreStream = (TSAudioStream)CoreStream.Clone();
+ }
+
+ return stream;
+ }
+ }
+
+ public class TSGraphicsStream : TSStream
+ {
+ public TSGraphicsStream()
+ {
+ IsVBR = true;
+ IsInitialized = true;
+ }
+
+ public override TSStream Clone()
+ {
+ TSGraphicsStream stream = new TSGraphicsStream();
+ CopyTo(stream);
+ return stream;
+ }
+ }
+
+ public class TSTextStream : TSStream
+ {
+ public TSTextStream()
+ {
+ IsVBR = true;
+ IsInitialized = true;
+ }
+
+ public override TSStream Clone()
+ {
+ TSTextStream stream = new TSTextStream();
+ CopyTo(stream);
+ return stream;
+ }
+ }
+}
diff --git a/BDInfo/TSStreamBuffer.cs b/BDInfo/TSStreamBuffer.cs
new file mode 100644
index 0000000000..2f9094876e
--- /dev/null
+++ b/BDInfo/TSStreamBuffer.cs
@@ -0,0 +1,142 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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.Specialized;
+using System.IO;
+
+namespace BDInfo
+{
+ public class TSStreamBuffer
+ {
+ private MemoryStream Stream = new MemoryStream();
+ private int SkipBits = 0;
+ private byte[] Buffer;
+ private int BufferLength = 0;
+ public int TransferLength = 0;
+
+ public TSStreamBuffer()
+ {
+ Buffer = new byte[4096];
+ Stream = new MemoryStream(Buffer);
+ }
+
+ public long Length
+ {
+ get
+ {
+ return (long)BufferLength;
+ }
+ }
+
+ public long Position
+ {
+ get
+ {
+ return Stream.Position;
+ }
+ }
+
+ public void Add(
+ byte[] buffer,
+ int offset,
+ int length)
+ {
+ TransferLength += length;
+
+ if (BufferLength + length >= Buffer.Length)
+ {
+ length = Buffer.Length - BufferLength;
+ }
+ if (length > 0)
+ {
+ Array.Copy(buffer, offset, Buffer, BufferLength, length);
+ BufferLength += length;
+ }
+ }
+
+ public void Seek(
+ long offset,
+ SeekOrigin loc)
+ {
+ Stream.Seek(offset, loc);
+ }
+
+ public void Reset()
+ {
+ BufferLength = 0;
+ TransferLength = 0;
+ }
+
+ public void BeginRead()
+ {
+ SkipBits = 0;
+ Stream.Seek(0, SeekOrigin.Begin);
+ }
+
+ public void EndRead()
+ {
+ }
+
+ public byte[] ReadBytes(int bytes)
+ {
+ if (Stream.Position + bytes >= BufferLength)
+ {
+ return null;
+ }
+
+ byte[] value = new byte[bytes];
+ Stream.Read(value, 0, bytes);
+ return value;
+ }
+
+ public byte ReadByte()
+ {
+ return (byte)Stream.ReadByte();
+ }
+
+ public int ReadBits(int bits)
+ {
+ long pos = Stream.Position;
+
+ int shift = 24;
+ int data = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ if (pos + i >= BufferLength) break;
+ data += (Stream.ReadByte() << shift);
+ shift -= 8;
+ }
+ BitVector32 vector = new BitVector32(data);
+
+ int value = 0;
+ for (int i = SkipBits; i < SkipBits + bits; i++)
+ {
+ value <<= 1;
+ value += (vector[1 << (32 - i - 1)] ? 1 : 0);
+ }
+
+ SkipBits += bits;
+ Stream.Seek(pos + (SkipBits >> 3), SeekOrigin.Begin);
+ SkipBits = SkipBits % 8;
+
+ return value;
+ }
+ }
+}
diff --git a/BDInfo/TSStreamClip.cs b/BDInfo/TSStreamClip.cs
new file mode 100644
index 0000000000..d7592a71aa
--- /dev/null
+++ b/BDInfo/TSStreamClip.cs
@@ -0,0 +1,113 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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 BDInfo
+{
+ public class TSStreamClip
+ {
+ public int AngleIndex = 0;
+ public string Name;
+ public double TimeIn;
+ public double TimeOut;
+ public double RelativeTimeIn;
+ public double RelativeTimeOut;
+ public double Length;
+
+ public ulong FileSize = 0;
+ public ulong InterleavedFileSize = 0;
+ public ulong PayloadBytes = 0;
+ public ulong PacketCount = 0;
+ public double PacketSeconds = 0;
+
+ public List Chapters = new List();
+
+ public TSStreamFile StreamFile = null;
+ public TSStreamClipFile StreamClipFile = null;
+
+ public TSStreamClip(
+ TSStreamFile streamFile,
+ TSStreamClipFile streamClipFile)
+ {
+ if (streamFile != null)
+ {
+ Name = streamFile.Name;
+ StreamFile = streamFile;
+ FileSize = (ulong)StreamFile.FileInfo.Length;
+ if (StreamFile.InterleavedFile != null)
+ {
+ InterleavedFileSize = (ulong)StreamFile.InterleavedFile.FileInfo.Length;
+ }
+ }
+ StreamClipFile = streamClipFile;
+ }
+
+ public string DisplayName
+ {
+ get
+ {
+ if (StreamFile != null &&
+ StreamFile.InterleavedFile != null &&
+ BDInfoSettings.EnableSSIF)
+ {
+ return StreamFile.InterleavedFile.Name;
+ }
+ return Name;
+ }
+ }
+
+ public ulong PacketSize
+ {
+ get
+ {
+ return PacketCount * 192;
+ }
+ }
+
+ public ulong PacketBitRate
+ {
+ get
+ {
+ if (PacketSeconds > 0)
+ {
+ return (ulong)Math.Round(((PacketSize * 8.0) / PacketSeconds));
+ }
+ return 0;
+ }
+ }
+
+ public bool IsCompatible(TSStreamClip clip)
+ {
+ foreach (TSStream stream1 in StreamFile.Streams.Values)
+ {
+ if (clip.StreamFile.Streams.ContainsKey(stream1.PID))
+ {
+ TSStream stream2 = clip.StreamFile.Streams[stream1.PID];
+ if (stream1.StreamType != stream2.StreamType)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/BDInfo/TSStreamClipFile.cs b/BDInfo/TSStreamClipFile.cs
new file mode 100644
index 0000000000..f2accb88d8
--- /dev/null
+++ b/BDInfo/TSStreamClipFile.cs
@@ -0,0 +1,253 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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
+//=============================================================================
+
+#undef DEBUG
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Text;
+
+namespace BDInfo
+{
+ public class TSStreamClipFile
+ {
+ private readonly IFileSystem _fileSystem;
+ private readonly ITextEncoding _textEncoding;
+ public FileSystemMetadata FileInfo = null;
+ public string FileType = null;
+ public bool IsValid = false;
+ public string Name = null;
+
+ public Dictionary Streams =
+ new Dictionary();
+
+ public TSStreamClipFile(
+ FileSystemMetadata fileInfo, IFileSystem fileSystem, ITextEncoding textEncoding)
+ {
+ FileInfo = fileInfo;
+ _fileSystem = fileSystem;
+ _textEncoding = textEncoding;
+ Name = fileInfo.Name.ToUpper();
+ }
+
+ public void Scan()
+ {
+ Stream fileStream = null;
+ BinaryReader fileReader = null;
+
+ try
+ {
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "Scanning {0}...", Name));
+#endif
+ Streams.Clear();
+
+ fileStream = _fileSystem.OpenRead(FileInfo.FullName);
+ fileReader = new BinaryReader(fileStream);
+
+ byte[] data = new byte[fileStream.Length];
+ fileReader.Read(data, 0, data.Length);
+
+ byte[] fileType = new byte[8];
+ Array.Copy(data, 0, fileType, 0, fileType.Length);
+
+ FileType = _textEncoding.GetASCIIEncoding().GetString(fileType, 0, fileType.Length);
+ if (FileType != "HDMV0100" &&
+ FileType != "HDMV0200")
+ {
+ throw new Exception(string.Format(
+ "Clip info file {0} has an unknown file type {1}.",
+ FileInfo.Name, FileType));
+ }
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\tFileType: {0}", FileType));
+#endif
+ int clipIndex =
+ ((int)data[12] << 24) +
+ ((int)data[13] << 16) +
+ ((int)data[14] << 8) +
+ ((int)data[15]);
+
+ int clipLength =
+ ((int)data[clipIndex] << 24) +
+ ((int)data[clipIndex + 1] << 16) +
+ ((int)data[clipIndex + 2] << 8) +
+ ((int)data[clipIndex + 3]);
+
+ byte[] clipData = new byte[clipLength];
+ Array.Copy(data, clipIndex + 4, clipData, 0, clipData.Length);
+
+ int streamCount = clipData[8];
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\tStreamCount: {0}", streamCount));
+#endif
+ int streamOffset = 10;
+ for (int streamIndex = 0;
+ streamIndex < streamCount;
+ streamIndex++)
+ {
+ TSStream stream = null;
+
+ ushort PID = (ushort)
+ ((clipData[streamOffset] << 8) +
+ clipData[streamOffset + 1]);
+
+ streamOffset += 2;
+
+ TSStreamType streamType = (TSStreamType)
+ clipData[streamOffset + 1];
+ switch (streamType)
+ {
+ case TSStreamType.MVC_VIDEO:
+ // TODO
+ break;
+
+ case TSStreamType.AVC_VIDEO:
+ case TSStreamType.MPEG1_VIDEO:
+ case TSStreamType.MPEG2_VIDEO:
+ case TSStreamType.VC1_VIDEO:
+ {
+ TSVideoFormat videoFormat = (TSVideoFormat)
+ (clipData[streamOffset + 2] >> 4);
+ TSFrameRate frameRate = (TSFrameRate)
+ (clipData[streamOffset + 2] & 0xF);
+ TSAspectRatio aspectRatio = (TSAspectRatio)
+ (clipData[streamOffset + 3] >> 4);
+
+ stream = new TSVideoStream();
+ ((TSVideoStream)stream).VideoFormat = videoFormat;
+ ((TSVideoStream)stream).AspectRatio = aspectRatio;
+ ((TSVideoStream)stream).FrameRate = frameRate;
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\t{0} {1} {2} {3} {4}",
+ PID,
+ streamType,
+ videoFormat,
+ frameRate,
+ aspectRatio));
+#endif
+ }
+ break;
+
+ case TSStreamType.AC3_AUDIO:
+ case TSStreamType.AC3_PLUS_AUDIO:
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ case TSStreamType.DTS_AUDIO:
+ case TSStreamType.DTS_HD_AUDIO:
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ case TSStreamType.LPCM_AUDIO:
+ case TSStreamType.MPEG1_AUDIO:
+ case TSStreamType.MPEG2_AUDIO:
+ {
+ byte[] languageBytes = new byte[3];
+ Array.Copy(clipData, streamOffset + 3,
+ languageBytes, 0, languageBytes.Length);
+ string languageCode =
+ _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length);
+
+ TSChannelLayout channelLayout = (TSChannelLayout)
+ (clipData[streamOffset + 2] >> 4);
+ TSSampleRate sampleRate = (TSSampleRate)
+ (clipData[streamOffset + 2] & 0xF);
+
+ stream = new TSAudioStream();
+ ((TSAudioStream)stream).LanguageCode = languageCode;
+ ((TSAudioStream)stream).ChannelLayout = channelLayout;
+ ((TSAudioStream)stream).SampleRate = TSAudioStream.ConvertSampleRate(sampleRate);
+ ((TSAudioStream)stream).LanguageCode = languageCode;
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\t{0} {1} {2} {3} {4}",
+ PID,
+ streamType,
+ languageCode,
+ channelLayout,
+ sampleRate));
+#endif
+ }
+ break;
+
+ case TSStreamType.INTERACTIVE_GRAPHICS:
+ case TSStreamType.PRESENTATION_GRAPHICS:
+ {
+ byte[] languageBytes = new byte[3];
+ Array.Copy(clipData, streamOffset + 2,
+ languageBytes, 0, languageBytes.Length);
+ string languageCode =
+ _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length);
+
+ stream = new TSGraphicsStream();
+ stream.LanguageCode = languageCode;
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\t{0} {1} {2}",
+ PID,
+ streamType,
+ languageCode));
+#endif
+ }
+ break;
+
+ case TSStreamType.SUBTITLE:
+ {
+ byte[] languageBytes = new byte[3];
+ Array.Copy(clipData, streamOffset + 3,
+ languageBytes, 0, languageBytes.Length);
+ string languageCode =
+ _textEncoding.GetASCIIEncoding().GetString(languageBytes, 0, languageBytes.Length);
+#if DEBUG
+ Debug.WriteLine(string.Format(
+ "\t{0} {1} {2}",
+ PID,
+ streamType,
+ languageCode));
+#endif
+ stream = new TSTextStream();
+ stream.LanguageCode = languageCode;
+ }
+ break;
+ }
+
+ if (stream != null)
+ {
+ stream.PID = PID;
+ stream.StreamType = streamType;
+ Streams.Add(PID, stream);
+ }
+
+ streamOffset += clipData[streamOffset] + 1;
+ }
+ IsValid = true;
+ }
+ finally
+ {
+ if (fileReader != null) fileReader.Dispose();
+ if (fileStream != null) fileStream.Dispose();
+ }
+ }
+ }
+}
diff --git a/BDInfo/TSStreamFile.cs b/BDInfo/TSStreamFile.cs
new file mode 100644
index 0000000000..31020cbf40
--- /dev/null
+++ b/BDInfo/TSStreamFile.cs
@@ -0,0 +1,1553 @@
+//============================================================================
+// BDInfo - Blu-ray Video and Audio Analysis Tool
+// Copyright © 2010 Cinema Squid
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// 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
+//=============================================================================
+
+#undef DEBUG
+using System;
+using System.Collections.Generic;
+using System.IO;
+using MediaBrowser.Model.IO;
+
+namespace BDInfo
+{
+ public class TSStreamState
+ {
+ public ulong TransferCount = 0;
+
+ public string StreamTag = null;
+
+ public ulong TotalPackets = 0;
+ public ulong WindowPackets = 0;
+
+ public ulong TotalBytes = 0;
+ public ulong WindowBytes = 0;
+
+ public long PeakTransferLength = 0;
+ public long PeakTransferRate = 0;
+
+ public double TransferMarker = 0;
+ public double TransferInterval = 0;
+
+ public TSStreamBuffer StreamBuffer = new TSStreamBuffer();
+
+ public uint Parse = 0;
+ public bool TransferState = false;
+ public int TransferLength = 0;
+ public int PacketLength = 0;
+ public byte PacketLengthParse = 0;
+ public byte PacketParse = 0;
+
+ public byte PTSParse = 0;
+ public ulong PTS = 0;
+ public ulong PTSTemp = 0;
+ public ulong PTSLast = 0;
+ public ulong PTSPrev = 0;
+ public ulong PTSDiff = 0;
+ public ulong PTSCount = 0;
+ public ulong PTSTransfer = 0;
+
+ public byte DTSParse = 0;
+ public ulong DTSTemp = 0;
+ public ulong DTSPrev = 0;
+
+ public byte PESHeaderLength = 0;
+ public byte PESHeaderFlags = 0;
+#if DEBUG
+ public byte PESHeaderIndex = 0;
+ public byte[] PESHeader = new byte[256 + 9];
+#endif
+ }
+
+ public class TSPacketParser
+ {
+ public bool SyncState = false;
+ public byte TimeCodeParse = 4;
+ public byte PacketLength = 0;
+ public byte HeaderParse = 0;
+
+ public uint TimeCode;
+ public byte TransportErrorIndicator;
+ public byte PayloadUnitStartIndicator;
+ public byte TransportPriority;
+ public ushort PID;
+ public byte TransportScramblingControl;
+ public byte AdaptionFieldControl;
+
+ public bool AdaptionFieldState = false;
+ public byte AdaptionFieldParse = 0;
+ public byte AdaptionFieldLength = 0;
+
+ public ushort PCRPID = 0xFFFF;
+ public byte PCRParse = 0;
+ public ulong PreviousPCR = 0;
+ public ulong PCR = 0;
+ public ulong PCRCount = 0;
+ public ulong PTSFirst = ulong.MaxValue;
+ public ulong PTSLast = ulong.MinValue;
+ public ulong PTSDiff = 0;
+
+ public byte[] PAT = new byte[1024];
+ public bool PATSectionStart = false;
+ public byte PATPointerField = 0;
+ public uint PATOffset = 0;
+ public byte PATSectionLengthParse = 0;
+ public ushort PATSectionLength = 0;
+ public uint PATSectionParse = 0;
+ public bool PATTransferState = false;
+ public byte PATSectionNumber = 0;
+ public byte PATLastSectionNumber = 0;
+
+ public ushort TransportStreamId = 0xFFFF;
+
+ public List PMTProgramDescriptors = new List();
+ public ushort PMTPID = 0xFFFF;
+ public Dictionary PMT = new Dictionary();
+ public bool PMTSectionStart = false;
+ public ushort PMTProgramInfoLength = 0;
+ public byte PMTProgramDescriptor = 0;
+ public byte PMTProgramDescriptorLengthParse = 0;
+ public byte PMTProgramDescriptorLength = 0;
+ public ushort PMTStreamInfoLength = 0;
+ public uint PMTStreamDescriptorLengthParse = 0;
+ public uint PMTStreamDescriptorLength = 0;
+ public byte PMTPointerField = 0;
+ public uint PMTOffset = 0;
+ public uint PMTSectionLengthParse = 0;
+ public ushort PMTSectionLength = 0;
+ public uint PMTSectionParse = 0;
+ public bool PMTTransferState = false;
+ public byte PMTSectionNumber = 0;
+ public byte PMTLastSectionNumber = 0;
+
+ public byte PMTTemp = 0;
+
+ public TSStream Stream = null;
+ public TSStreamState StreamState = null;
+
+ public ulong TotalPackets = 0;
+ }
+
+ public class TSStreamDiagnostics
+ {
+ public ulong Bytes = 0;
+ public ulong Packets = 0;
+ public double Marker = 0;
+ public double Interval = 0;
+ public string Tag = null;
+ }
+
+ public class TSStreamFile
+ {
+ public FileSystemMetadata FileInfo = null;
+ public string Name = null;
+ public long Size = 0;
+ public double Length = 0;
+
+ public TSInterleavedFile InterleavedFile = null;
+
+ private Dictionary StreamStates =
+ new Dictionary();
+
+ public Dictionary Streams =
+ new Dictionary();
+
+ public Dictionary> StreamDiagnostics =
+ new Dictionary>();
+
+ private List Playlists = null;
+
+ private readonly IFileSystem _fileSystem;
+
+ public TSStreamFile(FileSystemMetadata fileInfo, IFileSystem fileSystem)
+ {
+ FileInfo = fileInfo;
+ _fileSystem = fileSystem;
+ Name = fileInfo.Name.ToUpper();
+ }
+
+ public string DisplayName
+ {
+ get
+ {
+ if (BDInfoSettings.EnableSSIF &&
+ InterleavedFile != null)
+ {
+ return InterleavedFile.Name;
+ }
+ return Name;
+ }
+ }
+
+ private bool ScanStream(
+ TSStream stream,
+ TSStreamState streamState,
+ TSStreamBuffer buffer)
+ {
+ streamState.StreamTag = null;
+
+ long bitrate = 0;
+ if (stream.IsAudioStream &&
+ streamState.PTSTransfer > 0)
+ {
+ bitrate = (long)Math.Round(
+ (buffer.TransferLength * 8.0) /
+ ((double)streamState.PTSTransfer / 90000));
+
+ if (bitrate > streamState.PeakTransferRate)
+ {
+ streamState.PeakTransferRate = bitrate;
+ }
+ }
+ if (buffer.TransferLength > streamState.PeakTransferLength)
+ {
+ streamState.PeakTransferLength = buffer.TransferLength;
+ }
+
+ buffer.BeginRead();
+ switch (stream.StreamType)
+ {
+ case TSStreamType.MPEG2_VIDEO:
+ TSCodecMPEG2.Scan(
+ (TSVideoStream)stream, buffer, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.AVC_VIDEO:
+ TSCodecAVC.Scan(
+ (TSVideoStream)stream, buffer, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.MVC_VIDEO:
+ TSCodecMVC.Scan(
+ (TSVideoStream)stream, buffer, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.VC1_VIDEO:
+ TSCodecVC1.Scan(
+ (TSVideoStream)stream, buffer, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.AC3_AUDIO:
+ TSCodecAC3.Scan(
+ (TSAudioStream)stream, buffer, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.AC3_PLUS_AUDIO:
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ TSCodecAC3.Scan(
+ (TSAudioStream)stream, buffer, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ TSCodecTrueHD.Scan(
+ (TSAudioStream)stream, buffer, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.LPCM_AUDIO:
+ TSCodecLPCM.Scan(
+ (TSAudioStream)stream, buffer, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.DTS_AUDIO:
+ TSCodecDTS.Scan(
+ (TSAudioStream)stream, buffer, bitrate, ref streamState.StreamTag);
+ break;
+
+ case TSStreamType.DTS_HD_AUDIO:
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ TSCodecDTSHD.Scan(
+ (TSAudioStream)stream, buffer, bitrate, ref streamState.StreamTag);
+ break;
+
+ default:
+ stream.IsInitialized = true;
+ break;
+ }
+ buffer.EndRead();
+ streamState.StreamBuffer.Reset();
+
+ bool isAVC = false;
+ bool isMVC = false;
+ foreach (TSStream finishedStream in Streams.Values)
+ {
+ if (!finishedStream.IsInitialized)
+ {
+ return false;
+ }
+ if (finishedStream.StreamType == TSStreamType.AVC_VIDEO)
+ {
+ isAVC = true;
+ }
+ if (finishedStream.StreamType == TSStreamType.MVC_VIDEO)
+ {
+ isMVC = true;
+ }
+ }
+ if (isMVC && !isAVC)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ private void UpdateStreamBitrates(
+ ushort PTSPID,
+ ulong PTS,
+ ulong PTSDiff)
+ {
+ if (Playlists == null) return;
+
+ foreach (ushort PID in StreamStates.Keys)
+ {
+ if (Streams.ContainsKey(PID) &&
+ Streams[PID].IsVideoStream &&
+ PID != PTSPID)
+ {
+ continue;
+ }
+ if (StreamStates[PID].WindowPackets == 0)
+ {
+ continue;
+ }
+ UpdateStreamBitrate(PID, PTSPID, PTS, PTSDiff);
+ }
+
+ foreach (TSPlaylistFile playlist in Playlists)
+ {
+ double packetSeconds = 0;
+ foreach (TSStreamClip clip in playlist.StreamClips)
+ {
+ if (clip.AngleIndex == 0)
+ {
+ packetSeconds += clip.PacketSeconds;
+ }
+ }
+ if (packetSeconds > 0)
+ {
+ foreach (TSStream playlistStream in playlist.SortedStreams)
+ {
+ if (playlistStream.IsVBR)
+ {
+ playlistStream.BitRate = (long)Math.Round(
+ ((playlistStream.PayloadBytes * 8.0) / packetSeconds));
+
+ if (playlistStream.StreamType == TSStreamType.AC3_TRUE_HD_AUDIO &&
+ ((TSAudioStream)playlistStream).CoreStream != null)
+ {
+ playlistStream.BitRate -=
+ ((TSAudioStream)playlistStream).CoreStream.BitRate;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void UpdateStreamBitrate(
+ ushort PID,
+ ushort PTSPID,
+ ulong PTS,
+ ulong PTSDiff)
+ {
+ if (Playlists == null) return;
+
+ TSStreamState streamState = StreamStates[PID];
+ double streamTime = (double)PTS / 90000;
+ double streamInterval = (double)PTSDiff / 90000;
+ double streamOffset = streamTime + streamInterval;
+
+ foreach (TSPlaylistFile playlist in Playlists)
+ {
+ foreach (TSStreamClip clip in playlist.StreamClips)
+ {
+ if (clip.Name != this.Name) continue;
+
+ if (streamTime == 0 ||
+ (streamTime >= clip.TimeIn &&
+ streamTime <= clip.TimeOut))
+ {
+ clip.PayloadBytes += streamState.WindowBytes;
+ clip.PacketCount += streamState.WindowPackets;
+
+ if (streamOffset > clip.TimeIn &&
+ streamOffset - clip.TimeIn > clip.PacketSeconds)
+ {
+ clip.PacketSeconds = streamOffset - clip.TimeIn;
+ }
+
+ Dictionary playlistStreams = playlist.Streams;
+ if (clip.AngleIndex > 0 &&
+ clip.AngleIndex < playlist.AngleStreams.Count + 1)
+ {
+ playlistStreams = playlist.AngleStreams[clip.AngleIndex - 1];
+ }
+ if (playlistStreams.ContainsKey(PID))
+ {
+ TSStream stream = playlistStreams[PID];
+
+ stream.PayloadBytes += streamState.WindowBytes;
+ stream.PacketCount += streamState.WindowPackets;
+
+ if (stream.IsVideoStream)
+ {
+ stream.PacketSeconds += streamInterval;
+
+ stream.ActiveBitRate = (long)Math.Round(
+ ((stream.PayloadBytes * 8.0) /
+ stream.PacketSeconds));
+ }
+
+ if (stream.StreamType == TSStreamType.AC3_TRUE_HD_AUDIO &&
+ ((TSAudioStream)stream).CoreStream != null)
+ {
+ stream.ActiveBitRate -=
+ ((TSAudioStream)stream).CoreStream.BitRate;
+ }
+ }
+ }
+ }
+ }
+
+ if (Streams.ContainsKey(PID))
+ {
+ TSStream stream = Streams[PID];
+ stream.PayloadBytes += streamState.WindowBytes;
+ stream.PacketCount += streamState.WindowPackets;
+
+ if (stream.IsVideoStream)
+ {
+ TSStreamDiagnostics diag = new TSStreamDiagnostics();
+ diag.Marker = (double)PTS / 90000;
+ diag.Interval = (double)PTSDiff / 90000;
+ diag.Bytes = streamState.WindowBytes;
+ diag.Packets = streamState.WindowPackets;
+ diag.Tag = streamState.StreamTag;
+ StreamDiagnostics[PID].Add(diag);
+
+ stream.PacketSeconds += streamInterval;
+ }
+ }
+ streamState.WindowPackets = 0;
+ streamState.WindowBytes = 0;
+ }
+
+ public void Scan(List playlists, bool isFullScan)
+ {
+ if (playlists == null || playlists.Count == 0)
+ {
+ return;
+ }
+
+ Playlists = playlists;
+ int dataSize = 16384;
+ Stream fileStream = null;
+ try
+ {
+ string fileName;
+ if (BDInfoSettings.EnableSSIF &&
+ InterleavedFile != null)
+ {
+ fileName = InterleavedFile.FileInfo.FullName;
+ }
+ else
+ {
+ fileName = FileInfo.FullName;
+ }
+ fileStream = _fileSystem.GetFileStream(
+ fileName,
+ FileOpenMode.Open,
+ FileAccessMode.Read,
+ FileShareMode.Read,
+ false);
+
+ Size = 0;
+ Length = 0;
+
+ Streams.Clear();
+ StreamStates.Clear();
+ StreamDiagnostics.Clear();
+
+ TSPacketParser parser =
+ new TSPacketParser();
+
+ long fileLength = (uint)fileStream.Length;
+ byte[] buffer = new byte[dataSize];
+ int bufferLength = 0;
+ while ((bufferLength =
+ fileStream.Read(buffer, 0, buffer.Length)) > 0)
+ {
+ int offset = 0;
+ for (int i = 0; i < bufferLength; i++)
+ {
+ if (parser.SyncState == false)
+ {
+ if (parser.TimeCodeParse > 0)
+ {
+ parser.TimeCodeParse--;
+ switch (parser.TimeCodeParse)
+ {
+ case 3:
+ parser.TimeCode = 0;
+ parser.TimeCode |=
+ ((uint)buffer[i] & 0x3F) << 24;
+ break;
+ case 2:
+ parser.TimeCode |=
+ ((uint)buffer[i] & 0xFF) << 16;
+ break;
+ case 1:
+ parser.TimeCode |=
+ ((uint)buffer[i] & 0xFF) << 8;
+ break;
+ case 0:
+ parser.TimeCode |=
+ ((uint)buffer[i] & 0xFF);
+ break;
+ }
+ }
+ else if (buffer[i] == 0x47)
+ {
+ parser.SyncState = true;
+ parser.PacketLength = 187;
+ parser.TimeCodeParse = 4;
+ parser.HeaderParse = 3;
+ }
+ }
+ else if (parser.HeaderParse > 0)
+ {
+ parser.PacketLength--;
+ parser.HeaderParse--;
+
+ switch (parser.HeaderParse)
+ {
+ case 2:
+ {
+ parser.TransportErrorIndicator =
+ (byte)((buffer[i] >> 7) & 0x1);
+ parser.PayloadUnitStartIndicator =
+ (byte)((buffer[i] >> 6) & 0x1);
+ parser.TransportPriority =
+ (byte)((buffer[i] >> 5) & 0x1);
+ parser.PID =
+ (ushort)((buffer[i] & 0x1f) << 8);
+ }
+ break;
+
+ case 1:
+ {
+ parser.PID |= (ushort)buffer[i];
+ if (Streams.ContainsKey(parser.PID))
+ {
+ parser.Stream = Streams[parser.PID];
+ }
+ else
+ {
+ parser.Stream = null;
+ }
+ if (!StreamStates.ContainsKey(parser.PID))
+ {
+ StreamStates[parser.PID] = new TSStreamState();
+ }
+ parser.StreamState = StreamStates[parser.PID];
+ parser.StreamState.TotalPackets++;
+ parser.StreamState.WindowPackets++;
+ parser.TotalPackets++;
+ }
+ break;
+
+ case 0:
+ {
+ parser.TransportScramblingControl =
+ (byte)((buffer[i] >> 6) & 0x3);
+ parser.AdaptionFieldControl =
+ (byte)((buffer[i] >> 4) & 0x3);
+
+ if ((parser.AdaptionFieldControl & 0x2) == 0x2)
+ {
+ parser.AdaptionFieldState = true;
+ }
+ if (parser.PayloadUnitStartIndicator == 1)
+ {
+ if (parser.PID == 0)
+ {
+ parser.PATSectionStart = true;
+ }
+ else if (parser.PID == parser.PMTPID)
+ {
+ parser.PMTSectionStart = true;
+ }
+ else if (parser.StreamState != null &&
+ parser.StreamState.TransferState)
+ {
+ parser.StreamState.TransferState = false;
+ parser.StreamState.TransferCount++;
+
+ bool isFinished = ScanStream(
+ parser.Stream,
+ parser.StreamState,
+ parser.StreamState.StreamBuffer);
+
+ if (!isFullScan && isFinished)
+ {
+ return;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ else if (parser.AdaptionFieldState)
+ {
+ parser.PacketLength--;
+ parser.AdaptionFieldParse = buffer[i];
+ parser.AdaptionFieldLength = buffer[i];
+ parser.AdaptionFieldState = false;
+ }
+ else if (parser.AdaptionFieldParse > 0)
+ {
+ parser.PacketLength--;
+ parser.AdaptionFieldParse--;
+ if ((parser.AdaptionFieldLength - parser.AdaptionFieldParse) == 1)
+ {
+ if ((buffer[i] & 0x10) == 0x10)
+ {
+ parser.PCRParse = 6;
+ parser.PCR = 0;
+ }
+ }
+ else if (parser.PCRParse > 0)
+ {
+ parser.PCRParse--;
+ parser.PCR = (parser.PCR << 8) + (ulong)buffer[i];
+ if (parser.PCRParse == 0)
+ {
+ parser.PreviousPCR = parser.PCR;
+ parser.PCR = (parser.PCR & 0x1FF) +
+ ((parser.PCR >> 15) * 300);
+ }
+ parser.PCRCount++;
+ }
+ if (parser.PacketLength == 0)
+ {
+ parser.SyncState = false;
+ }
+ }
+ else if (parser.PID == 0)
+ {
+ if (parser.PATTransferState)
+ {
+ if ((bufferLength - i) > parser.PATSectionLength)
+ {
+ offset = parser.PATSectionLength;
+ }
+ else
+ {
+ offset = (bufferLength - i);
+ }
+ if (parser.PacketLength <= offset)
+ {
+ offset = parser.PacketLength;
+ }
+
+ for (int k = 0; k < offset; k++)
+ {
+ parser.PAT[parser.PATOffset++] = buffer[i++];
+ parser.PATSectionLength--;
+ parser.PacketLength--;
+ } --i;
+
+ if (parser.PATSectionLength == 0)
+ {
+ parser.PATTransferState = false;
+ if (parser.PATSectionNumber == parser.PATLastSectionNumber)
+ {
+ for (int k = 0; k < (parser.PATOffset - 4); k += 4)
+ {
+ uint programNumber = (uint)
+ ((parser.PAT[k] << 8) +
+ parser.PAT[k + 1]);
+
+ ushort programPID = (ushort)
+ (((parser.PAT[k + 2] & 0x1F) << 8) +
+ parser.PAT[k + 3]);
+
+ if (programNumber == 1)
+ {
+ parser.PMTPID = programPID;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ --parser.PacketLength;
+ if (parser.PATSectionStart)
+ {
+ parser.PATPointerField = buffer[i];
+ if (parser.PATPointerField == 0)
+ {
+ parser.PATSectionLengthParse = 3;
+ }
+ parser.PATSectionStart = false;
+ }
+ else if (parser.PATPointerField > 0)
+ {
+ --parser.PATPointerField;
+ if (parser.PATPointerField == 0)
+ {
+ parser.PATSectionLengthParse = 3;
+ }
+ }
+ else if (parser.PATSectionLengthParse > 0)
+ {
+ --parser.PATSectionLengthParse;
+ switch (parser.PATSectionLengthParse)
+ {
+ case 2:
+ break;
+ case 1:
+ parser.PATSectionLength = (ushort)
+ ((buffer[i] & 0xF) << 8);
+ break;
+ case 0:
+ parser.PATSectionLength |= buffer[i];
+ if (parser.PATSectionLength > 1021)
+ {
+ parser.PATSectionLength = 0;
+ }
+ else
+ {
+ parser.PATSectionParse = 5;
+ }
+ break;
+ }
+ }
+ else if (parser.PATSectionParse > 0)
+ {
+ --parser.PATSectionLength;
+ --parser.PATSectionParse;
+
+ switch (parser.PATSectionParse)
+ {
+ case 4:
+ parser.TransportStreamId = (ushort)
+ (buffer[i] << 8);
+ break;
+ case 3:
+ parser.TransportStreamId |= buffer[i];
+ break;
+ case 2:
+ break;
+ case 1:
+ parser.PATSectionNumber = buffer[i];
+ if (parser.PATSectionNumber == 0)
+ {
+ parser.PATOffset = 0;
+ }
+ break;
+ case 0:
+ parser.PATLastSectionNumber = buffer[i];
+ parser.PATTransferState = true;
+ break;
+ }
+ }
+ }
+ if (parser.PacketLength == 0)
+ {
+ parser.SyncState = false;
+ }
+ }
+ else if (parser.PID == parser.PMTPID)
+ {
+ if (parser.PMTTransferState)
+ {
+ if ((bufferLength - i) >= parser.PMTSectionLength)
+ {
+ offset = parser.PMTSectionLength;
+ }
+ else
+ {
+ offset = (bufferLength - i);
+ }
+ if (parser.PacketLength <= offset)
+ {
+ offset = parser.PacketLength;
+ }
+ if (!parser.PMT.ContainsKey(parser.PID))
+ {
+ parser.PMT[parser.PID] = new byte[1024];
+ }
+
+ byte[] PMT = parser.PMT[parser.PID];
+ for (int k = 0; k < offset; k++)
+ {
+ PMT[parser.PMTOffset++] = buffer[i++];
+ --parser.PMTSectionLength;
+ --parser.PacketLength;
+ } --i;
+
+ if (parser.PMTSectionLength == 0)
+ {
+ parser.PMTTransferState = false;
+ if (parser.PMTSectionNumber == parser.PMTLastSectionNumber)
+ {
+ //Console.WriteLine("PMT Start: " + parser.PMTTemp);
+ try
+ {
+ for (int k = 0; k < (parser.PMTOffset - 4); k += 5)
+ {
+ byte streamType = PMT[k];
+
+ ushort streamPID = (ushort)
+ (((PMT[k + 1] & 0x1F) << 8) +
+ PMT[k + 2]);
+
+ ushort streamInfoLength = (ushort)
+ (((PMT[k + 3] & 0xF) << 8) +
+ PMT[k + 4]);
+
+ /*
+ if (streamInfoLength == 2)
+ {
+ // TODO: Cleanup
+ //streamInfoLength = 0;
+ }
+
+ Console.WriteLine(string.Format(
+ "Type: {0} PID: {1} Length: {2}",
+ streamType, streamPID, streamInfoLength));
+ */
+
+ if (!Streams.ContainsKey(streamPID))
+ {
+ List streamDescriptors =
+ new List();
+
+ /*
+ * TODO: Getting bad streamInfoLength
+ if (streamInfoLength > 0)
+ {
+ for (int d = 0; d < streamInfoLength; d++)
+ {
+ byte name = PMT[k + d + 5];
+ byte length = PMT[k + d + 6];
+ TSDescriptor descriptor =
+ new TSDescriptor(name, length);
+ for (int v = 0; v < length; v++)
+ {
+ descriptor.Value[v] =
+ PMT[k + d + v + 7];
+ }
+ streamDescriptors.Add(descriptor);
+ d += (length + 1);
+ }
+ }
+ */
+ CreateStream(streamPID, streamType, streamDescriptors);
+ }
+ k += streamInfoLength;
+ }
+ }
+ catch (Exception ex)
+ {
+ // TODO
+ //Console.WriteLine(ex.Message);
+ }
+ }
+ }
+ }
+ else
+ {
+ --parser.PacketLength;
+ if (parser.PMTSectionStart)
+ {
+ parser.PMTPointerField = buffer[i];
+ if (parser.PMTPointerField == 0)
+ {
+ parser.PMTSectionLengthParse = 3;
+ }
+ parser.PMTSectionStart = false;
+ }
+ else if (parser.PMTPointerField > 0)
+ {
+ --parser.PMTPointerField;
+ if (parser.PMTPointerField == 0)
+ {
+ parser.PMTSectionLengthParse = 3;
+ }
+ }
+ else if (parser.PMTSectionLengthParse > 0)
+ {
+ --parser.PMTSectionLengthParse;
+ switch (parser.PMTSectionLengthParse)
+ {
+ case 2:
+ if (buffer[i] != 0x2)
+ {
+ parser.PMTSectionLengthParse = 0;
+ }
+ break;
+ case 1:
+ parser.PMTSectionLength = (ushort)
+ ((buffer[i] & 0xF) << 8);
+ break;
+ case 0:
+ parser.PMTSectionLength |= buffer[i];
+ if (parser.PMTSectionLength > 1021)
+ {
+ parser.PMTSectionLength = 0;
+ }
+ else
+ {
+ parser.PMTSectionParse = 9;
+ }
+ break;
+ }
+ }
+ else if (parser.PMTSectionParse > 0)
+ {
+ --parser.PMTSectionLength;
+ --parser.PMTSectionParse;
+
+ switch (parser.PMTSectionParse)
+ {
+ case 8:
+ case 7:
+ break;
+ case 6:
+ parser.PMTTemp = buffer[i];
+ break;
+ case 5:
+ parser.PMTSectionNumber = buffer[i];
+ if (parser.PMTSectionNumber == 0)
+ {
+ parser.PMTOffset = 0;
+ }
+ break;
+ case 4:
+ parser.PMTLastSectionNumber = buffer[i];
+ break;
+ case 3:
+ parser.PCRPID = (ushort)
+ ((buffer[i] & 0x1F) << 8);
+ break;
+ case 2:
+ parser.PCRPID |= buffer[i];
+ break;
+ case 1:
+ parser.PMTProgramInfoLength = (ushort)
+ ((buffer[i] & 0xF) << 8);
+ break;
+ case 0:
+ parser.PMTProgramInfoLength |= buffer[i];
+ if (parser.PMTProgramInfoLength == 0)
+ {
+ parser.PMTTransferState = true;
+ }
+ else
+ {
+ parser.PMTProgramDescriptorLengthParse = 2;
+ }
+ break;
+ }
+ }
+ else if (parser.PMTProgramInfoLength > 0)
+ {
+ --parser.PMTSectionLength;
+ --parser.PMTProgramInfoLength;
+
+ if (parser.PMTProgramDescriptorLengthParse > 0)
+ {
+ --parser.PMTProgramDescriptorLengthParse;
+ switch (parser.PMTProgramDescriptorLengthParse)
+ {
+ case 1:
+ parser.PMTProgramDescriptor = buffer[i];
+ break;
+ case 0:
+ parser.PMTProgramDescriptorLength = buffer[i];
+ parser.PMTProgramDescriptors.Add(
+ new TSDescriptor(
+ parser.PMTProgramDescriptor,
+ parser.PMTProgramDescriptorLength));
+ break;
+ }
+ }
+ else if (parser.PMTProgramDescriptorLength > 0)
+ {
+ --parser.PMTProgramDescriptorLength;
+
+ TSDescriptor descriptor = parser.PMTProgramDescriptors[
+ parser.PMTProgramDescriptors.Count - 1];
+
+ int valueIndex =
+ descriptor.Value.Length -
+ parser.PMTProgramDescriptorLength - 1;
+
+ descriptor.Value[valueIndex] = buffer[i];
+
+ if (parser.PMTProgramDescriptorLength == 0 &&
+ parser.PMTProgramInfoLength > 0)
+ {
+ parser.PMTProgramDescriptorLengthParse = 2;
+ }
+ }
+ if (parser.PMTProgramInfoLength == 0)
+ {
+ parser.PMTTransferState = true;
+ }
+ }
+ }
+ if (parser.PacketLength == 0)
+ {
+ parser.SyncState = false;
+ }
+ }
+ else if (parser.Stream != null &&
+ parser.StreamState != null &&
+ parser.TransportScramblingControl == 0)
+ {
+ TSStream stream = parser.Stream;
+ TSStreamState streamState = parser.StreamState;
+
+ streamState.Parse =
+ (streamState.Parse << 8) + buffer[i];
+
+ if (streamState.TransferState)
+ {
+ if ((bufferLength - i) >= streamState.PacketLength &&
+ streamState.PacketLength > 0)
+ {
+ offset = streamState.PacketLength;
+ }
+ else
+ {
+ offset = (bufferLength - i);
+ }
+ if (parser.PacketLength <= offset)
+ {
+ offset = parser.PacketLength;
+ }
+ streamState.TransferLength = offset;
+
+ if (!stream.IsInitialized ||
+ stream.IsVideoStream)
+ {
+ streamState.StreamBuffer.Add(
+ buffer, i, offset);
+ }
+ else
+ {
+ streamState.StreamBuffer.TransferLength += offset;
+ }
+
+ i += (int)(streamState.TransferLength - 1);
+ streamState.PacketLength -= streamState.TransferLength;
+ parser.PacketLength -= (byte)streamState.TransferLength;
+
+ streamState.TotalBytes += (ulong)streamState.TransferLength;
+ streamState.WindowBytes += (ulong)streamState.TransferLength;
+
+ if (streamState.PacketLength == 0)
+ {
+ streamState.TransferState = false;
+ streamState.TransferCount++;
+ bool isFinished = ScanStream(
+ stream,
+ streamState,
+ streamState.StreamBuffer);
+
+ if (!isFullScan && isFinished)
+ {
+ return;
+ }
+ }
+ }
+ else
+ {
+ --parser.PacketLength;
+
+ bool headerFound = false;
+ if (stream.IsVideoStream &&
+ streamState.Parse == 0x000001FD)
+ {
+ headerFound = true;
+ }
+ if (stream.IsVideoStream &&
+ streamState.Parse >= 0x000001E0 &&
+ streamState.Parse <= 0x000001EF)
+ {
+ headerFound = true;
+ }
+ if (stream.IsAudioStream &&
+ streamState.Parse == 0x000001BD)
+ {
+ headerFound = true;
+ }
+ if (stream.IsAudioStream &&
+ (streamState.Parse == 0x000001FA ||
+ streamState.Parse == 0x000001FD))
+ {
+ headerFound = true;
+ }
+
+ if (!stream.IsVideoStream &&
+ !stream.IsAudioStream &&
+ (streamState.Parse == 0x000001FA ||
+ streamState.Parse == 0x000001FD ||
+ streamState.Parse == 0x000001BD ||
+ (streamState.Parse >= 0x000001E0 &&
+ streamState.Parse <= 0x000001EF)))
+ {
+ headerFound = true;
+ }
+
+ if (headerFound)
+ {
+ streamState.PacketLengthParse = 2;
+#if DEBUG
+ streamState.PESHeaderIndex = 0;
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)((streamState.Parse >> 24) & 0xFF);
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)((streamState.Parse >> 16) & 0xFF);
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)((streamState.Parse >> 8) & 0xFF);
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ }
+ else if (streamState.PacketLengthParse > 0)
+ {
+ --streamState.PacketLengthParse;
+ switch (streamState.PacketLengthParse)
+ {
+ case 1:
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 0:
+ streamState.PacketLength =
+ (int)(streamState.Parse & 0xFFFF);
+ streamState.PacketParse = 3;
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+ }
+ }
+ else if (streamState.PacketParse > 0)
+ {
+ --streamState.PacketLength;
+ --streamState.PacketParse;
+
+ switch (streamState.PacketParse)
+ {
+ case 2:
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 1:
+ streamState.PESHeaderFlags =
+ (byte)(streamState.Parse & 0xFF);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 0:
+ streamState.PESHeaderLength =
+ (byte)(streamState.Parse & 0xFF);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ if ((streamState.PESHeaderFlags & 0xC0) == 0x80)
+ {
+ streamState.PTSParse = 5;
+ }
+ else if ((streamState.PESHeaderFlags & 0xC0) == 0xC0)
+ {
+ streamState.DTSParse = 10;
+ }
+ if (streamState.PESHeaderLength == 0)
+ {
+ streamState.TransferState = true;
+ }
+ break;
+ }
+ }
+ else if (streamState.PTSParse > 0)
+ {
+ --streamState.PacketLength;
+ --streamState.PESHeaderLength;
+ --streamState.PTSParse;
+
+ switch (streamState.PTSParse)
+ {
+ case 4:
+ streamState.PTSTemp =
+ ((streamState.Parse & 0xE) << 29);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xff);
+#endif
+ break;
+
+ case 3:
+ streamState.PTSTemp |=
+ ((streamState.Parse & 0xFF) << 22);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 2:
+ streamState.PTSTemp |=
+ ((streamState.Parse & 0xFE) << 14);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 1:
+ streamState.PTSTemp |=
+ ((streamState.Parse & 0xFF) << 7);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 0:
+ streamState.PTSTemp |=
+ ((streamState.Parse & 0xFE) >> 1);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xff);
+#endif
+ streamState.PTS = streamState.PTSTemp;
+
+ if (streamState.PTS > streamState.PTSLast)
+ {
+ if (streamState.PTSLast > 0)
+ {
+ streamState.PTSTransfer = (streamState.PTS - streamState.PTSLast);
+ }
+ streamState.PTSLast = streamState.PTS;
+ }
+
+ streamState.PTSDiff = streamState.PTS - streamState.DTSPrev;
+
+ if (streamState.PTSCount > 0 &&
+ stream.IsVideoStream)
+ {
+ UpdateStreamBitrates(stream.PID, streamState.PTS, streamState.PTSDiff);
+ if (streamState.DTSTemp < parser.PTSFirst)
+ {
+ parser.PTSFirst = streamState.DTSTemp;
+ }
+ if (streamState.DTSTemp > parser.PTSLast)
+ {
+ parser.PTSLast = streamState.DTSTemp;
+ }
+ Length = (double)(parser.PTSLast - parser.PTSFirst) / 90000;
+ }
+
+ streamState.DTSPrev = streamState.PTS;
+ streamState.PTSCount++;
+ if (streamState.PESHeaderLength == 0)
+ {
+ streamState.TransferState = true;
+ }
+ break;
+ }
+ }
+ else if (streamState.DTSParse > 0)
+ {
+ --streamState.PacketLength;
+ --streamState.PESHeaderLength;
+ --streamState.DTSParse;
+
+ switch (streamState.DTSParse)
+ {
+ case 9:
+ streamState.PTSTemp =
+ ((streamState.Parse & 0xE) << 29);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 8:
+ streamState.PTSTemp |=
+ ((streamState.Parse & 0xFF) << 22);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 7:
+ streamState.PTSTemp |=
+ ((streamState.Parse & 0xFE) << 14);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xff);
+#endif
+ break;
+
+ case 6:
+ streamState.PTSTemp |=
+ ((streamState.Parse & 0xFF) << 7);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 5:
+ streamState.PTSTemp |=
+ ((streamState.Parse & 0xFE) >> 1);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xff);
+#endif
+ streamState.PTS = streamState.PTSTemp;
+ if (streamState.PTS > streamState.PTSLast)
+ {
+ streamState.PTSLast = streamState.PTS;
+ }
+ break;
+
+ case 4:
+ streamState.DTSTemp =
+ ((streamState.Parse & 0xE) << 29);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xff);
+#endif
+ break;
+
+ case 3:
+ streamState.DTSTemp |=
+ ((streamState.Parse & 0xFF) << 22);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xff);
+#endif
+ break;
+
+ case 2:
+ streamState.DTSTemp |=
+ ((streamState.Parse & 0xFE) << 14);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xff);
+#endif
+ break;
+
+ case 1:
+ streamState.DTSTemp |=
+ ((streamState.Parse & 0xFF) << 7);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ break;
+
+ case 0:
+ streamState.DTSTemp |=
+ ((streamState.Parse & 0xFE) >> 1);
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xff);
+#endif
+ streamState.PTSDiff = streamState.DTSTemp - streamState.DTSPrev;
+
+ if (streamState.PTSCount > 0 &&
+ stream.IsVideoStream)
+ {
+ UpdateStreamBitrates(stream.PID, streamState.DTSTemp, streamState.PTSDiff);
+ if (streamState.DTSTemp < parser.PTSFirst)
+ {
+ parser.PTSFirst = streamState.DTSTemp;
+ }
+ if (streamState.DTSTemp > parser.PTSLast)
+ {
+ parser.PTSLast = streamState.DTSTemp;
+ }
+ Length = (double)(parser.PTSLast - parser.PTSFirst) / 90000;
+ }
+ streamState.DTSPrev = streamState.DTSTemp;
+ streamState.PTSCount++;
+ if (streamState.PESHeaderLength == 0)
+ {
+ streamState.TransferState = true;
+ }
+ break;
+ }
+ }
+ else if (streamState.PESHeaderLength > 0)
+ {
+ --streamState.PacketLength;
+ --streamState.PESHeaderLength;
+#if DEBUG
+ streamState.PESHeader[streamState.PESHeaderIndex++] =
+ (byte)(streamState.Parse & 0xFF);
+#endif
+ if (streamState.PESHeaderLength == 0)
+ {
+ streamState.TransferState = true;
+ }
+ }
+ }
+ if (parser.PacketLength == 0)
+ {
+ parser.SyncState = false;
+ }
+ }
+ else
+ {
+ parser.PacketLength--;
+ if ((bufferLength - i) >= parser.PacketLength)
+ {
+ i = i + parser.PacketLength;
+ parser.PacketLength = 0;
+ }
+ else
+ {
+ parser.PacketLength -= (byte)((bufferLength - i) + 1);
+ i = bufferLength;
+ }
+ if (parser.PacketLength == 0)
+ {
+ parser.SyncState = false;
+ }
+ }
+ }
+ Size += bufferLength;
+ }
+
+ ulong PTSLast = 0;
+ ulong PTSDiff = 0;
+ foreach (TSStream stream in Streams.Values)
+ {
+ if (!stream.IsVideoStream) continue;
+
+ if (StreamStates.ContainsKey(stream.PID) &&
+ StreamStates[stream.PID].PTSLast > PTSLast)
+ {
+ PTSLast = StreamStates[stream.PID].PTSLast;
+ PTSDiff = PTSLast - StreamStates[stream.PID].DTSPrev;
+ }
+ UpdateStreamBitrates(stream.PID, PTSLast, PTSDiff);
+ }
+ }
+ finally
+ {
+ if (fileStream != null)
+ {
+ fileStream.Dispose();
+ }
+ }
+ }
+
+ private TSStream CreateStream(
+ ushort streamPID,
+ byte streamType,
+ List streamDescriptors)
+ {
+ TSStream stream = null;
+
+ switch ((TSStreamType)streamType)
+ {
+ case TSStreamType.MVC_VIDEO:
+ case TSStreamType.AVC_VIDEO:
+ case TSStreamType.MPEG1_VIDEO:
+ case TSStreamType.MPEG2_VIDEO:
+ case TSStreamType.VC1_VIDEO:
+ {
+ stream = new TSVideoStream();
+ }
+ break;
+
+ case TSStreamType.AC3_AUDIO:
+ case TSStreamType.AC3_PLUS_AUDIO:
+ case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
+ case TSStreamType.AC3_TRUE_HD_AUDIO:
+ case TSStreamType.DTS_AUDIO:
+ case TSStreamType.DTS_HD_AUDIO:
+ case TSStreamType.DTS_HD_MASTER_AUDIO:
+ case TSStreamType.DTS_HD_SECONDARY_AUDIO:
+ case TSStreamType.LPCM_AUDIO:
+ case TSStreamType.MPEG1_AUDIO:
+ case TSStreamType.MPEG2_AUDIO:
+ {
+ stream = new TSAudioStream();
+ }
+ break;
+
+ case TSStreamType.INTERACTIVE_GRAPHICS:
+ case TSStreamType.PRESENTATION_GRAPHICS:
+ {
+ stream = new TSGraphicsStream();
+ }
+ break;
+
+ case TSStreamType.SUBTITLE:
+ {
+ stream = new TSTextStream();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (stream != null &&
+ !Streams.ContainsKey(streamPID))
+ {
+ stream.PID = streamPID;
+ stream.StreamType = (TSStreamType)streamType;
+ stream.Descriptors = streamDescriptors;
+ Streams[stream.PID] = stream;
+ }
+ if (!StreamDiagnostics.ContainsKey(streamPID))
+ {
+ StreamDiagnostics[streamPID] =
+ new List();
+ }
+
+ return stream;
+ }
+ }
+}
diff --git a/BDInfo/project.json b/BDInfo/project.json
new file mode 100644
index 0000000000..fbbe9eaf32
--- /dev/null
+++ b/BDInfo/project.json
@@ -0,0 +1,17 @@
+{
+ "frameworks":{
+ "netstandard1.6":{
+ "dependencies":{
+ "NETStandard.Library":"1.6.0",
+ }
+ },
+ ".NETPortable,Version=v4.5,Profile=Profile7":{
+ "buildOptions": {
+ "define": [ ]
+ },
+ "frameworkAssemblies":{
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/DvdLib/BigEndianBinaryReader.cs b/DvdLib/BigEndianBinaryReader.cs
new file mode 100644
index 0000000000..56d9db8255
--- /dev/null
+++ b/DvdLib/BigEndianBinaryReader.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace DvdLib
+{
+ public class BigEndianBinaryReader : BinaryReader
+ {
+ public BigEndianBinaryReader(Stream input)
+ : base(input)
+ {
+ }
+
+ public override ushort ReadUInt16()
+ {
+ return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
+ }
+
+ public override uint ReadUInt32()
+ {
+ return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
+ }
+
+ private byte[] ReadAndReverseBytes(int count)
+ {
+ byte[] val = base.ReadBytes(count);
+ Array.Reverse(val, 0, count);
+ return val;
+ }
+ }
+}
diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj
new file mode 100644
index 0000000000..ba63e77f0f
--- /dev/null
+++ b/DvdLib/DvdLib.csproj
@@ -0,0 +1,71 @@
+
+
+
+
+ 11.0
+ Debug
+ AnyCPU
+ {713F42B5-878E-499D-A878-E4C652B1D5E8}
+ Library
+ Properties
+ DvdLib
+ DvdLib
+ en-US
+ 512
+ {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Profile7
+ v4.5
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}
+ MediaBrowser.Model
+
+
+
+
+
\ No newline at end of file
diff --git a/DvdLib/DvdLib.nuget.targets b/DvdLib/DvdLib.nuget.targets
new file mode 100644
index 0000000000..e69ce0e64f
--- /dev/null
+++ b/DvdLib/DvdLib.nuget.targets
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DvdLib/Ifo/AudioAttributes.cs b/DvdLib/Ifo/AudioAttributes.cs
new file mode 100644
index 0000000000..5b3b9fd9a0
--- /dev/null
+++ b/DvdLib/Ifo/AudioAttributes.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DvdLib.Ifo
+{
+ public enum AudioCodec
+ {
+ AC3 = 0,
+ MPEG1 = 2,
+ MPEG2ext = 3,
+ LPCM = 4,
+ DTS = 6,
+ }
+
+ public enum ApplicationMode
+ {
+ Unspecified = 0,
+ Karaoke = 1,
+ Surround = 2,
+ }
+
+ public class AudioAttributes
+ {
+ public readonly AudioCodec Codec;
+ public readonly bool MultichannelExtensionPresent;
+ public readonly ApplicationMode Mode;
+ public readonly byte QuantDRC;
+ public readonly byte SampleRate;
+ public readonly byte Channels;
+ public readonly ushort LanguageCode;
+ public readonly byte LanguageExtension;
+ public readonly byte CodeExtension;
+ }
+
+ public class MultiChannelExtension
+ {
+
+ }
+}
diff --git a/DvdLib/Ifo/Cell.cs b/DvdLib/Ifo/Cell.cs
new file mode 100644
index 0000000000..d0f442e362
--- /dev/null
+++ b/DvdLib/Ifo/Cell.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace DvdLib.Ifo
+{
+ public class Cell
+ {
+ public CellPlaybackInfo PlaybackInfo { get; private set; }
+ public CellPositionInfo PositionInfo { get; private set; }
+
+ internal void ParsePlayback(BinaryReader br)
+ {
+ PlaybackInfo = new CellPlaybackInfo(br);
+ }
+
+ internal void ParsePosition(BinaryReader br)
+ {
+ PositionInfo = new CellPositionInfo(br);
+ }
+ }
+}
diff --git a/DvdLib/Ifo/CellPlaybackInfo.cs b/DvdLib/Ifo/CellPlaybackInfo.cs
new file mode 100644
index 0000000000..ae3883eaa0
--- /dev/null
+++ b/DvdLib/Ifo/CellPlaybackInfo.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace DvdLib.Ifo
+{
+ public enum BlockMode
+ {
+ NotInBlock = 0,
+ FirstCell = 1,
+ InBlock = 2,
+ LastCell = 3,
+ }
+
+ public enum BlockType
+ {
+ Normal = 0,
+ Angle = 1,
+ }
+
+ public enum PlaybackMode
+ {
+ Normal = 0,
+ StillAfterEachVOBU = 1,
+ }
+
+ public class CellPlaybackInfo
+ {
+ public readonly BlockMode Mode;
+ public readonly BlockType Type;
+ public readonly bool SeamlessPlay;
+ public readonly bool Interleaved;
+ public readonly bool STCDiscontinuity;
+ public readonly bool SeamlessAngle;
+ public readonly PlaybackMode PlaybackMode;
+ public readonly bool Restricted;
+ public readonly byte StillTime;
+ public readonly byte CommandNumber;
+ public readonly DvdTime PlaybackTime;
+ public readonly uint FirstSector;
+ public readonly uint FirstILVUEndSector;
+ public readonly uint LastVOBUStartSector;
+ public readonly uint LastSector;
+
+ internal CellPlaybackInfo(BinaryReader br)
+ {
+ br.BaseStream.Seek(0x4, SeekOrigin.Current);
+ PlaybackTime = new DvdTime(br.ReadBytes(4));
+ br.BaseStream.Seek(0x10, SeekOrigin.Current);
+ }
+ }
+}
diff --git a/DvdLib/Ifo/CellPositionInfo.cs b/DvdLib/Ifo/CellPositionInfo.cs
new file mode 100644
index 0000000000..2e07159402
--- /dev/null
+++ b/DvdLib/Ifo/CellPositionInfo.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace DvdLib.Ifo
+{
+ public class CellPositionInfo
+ {
+ public readonly ushort VOBId;
+ public readonly byte CellId;
+
+ internal CellPositionInfo(BinaryReader br)
+ {
+ VOBId = br.ReadUInt16();
+ br.ReadByte();
+ CellId = br.ReadByte();
+ }
+ }
+}
diff --git a/DvdLib/Ifo/Chapter.cs b/DvdLib/Ifo/Chapter.cs
new file mode 100644
index 0000000000..802c6ce62a
--- /dev/null
+++ b/DvdLib/Ifo/Chapter.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DvdLib.Ifo
+{
+ public class Chapter
+ {
+ public ushort ProgramChainNumber { get; private set; }
+ public ushort ProgramNumber { get; private set; }
+ public uint ChapterNumber { get; private set; }
+
+ public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
+ {
+ ProgramChainNumber = pgcNum;
+ ProgramNumber = programNum;
+ ChapterNumber = chapterNum;
+ }
+ }
+}
diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs
new file mode 100644
index 0000000000..508c23db45
--- /dev/null
+++ b/DvdLib/Ifo/Dvd.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Diagnostics;
+using MediaBrowser.Model.IO;
+
+namespace DvdLib.Ifo
+{
+ public class Dvd
+ {
+ private readonly ushort _titleSetCount;
+ public readonly List Titles;
+
+ private ushort _titleCount;
+ public readonly Dictionary VTSPaths = new Dictionary();
+ private readonly IFileSystem _fileSystem;
+
+ public Dvd(string path, IFileSystem fileSystem)
+ {
+ _fileSystem = fileSystem;
+ Titles = new List();
+ var allFiles = _fileSystem.GetFiles(path, true).ToList();
+
+ var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
+ allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
+
+ if (vmgPath == null)
+ {
+ var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase));
+
+ foreach (var ifo in allIfos)
+ {
+ var num = ifo.Name.Split('_').ElementAtOrDefault(1);
+ ushort ifoNumber;
+ var numbersRead = new List();
+
+ if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out ifoNumber) && !numbersRead.Contains(ifoNumber))
+ {
+ ReadVTS(ifoNumber, ifo.FullName);
+ numbersRead.Add(ifoNumber);
+ }
+ }
+ }
+ else
+ {
+ using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ {
+ using (BigEndianBinaryReader vmgRead = new BigEndianBinaryReader(vmgFs))
+ {
+ vmgFs.Seek(0x3E, SeekOrigin.Begin);
+ _titleSetCount = vmgRead.ReadUInt16();
+
+ // read address of TT_SRPT
+ vmgFs.Seek(0xC4, SeekOrigin.Begin);
+ uint ttSectorPtr = vmgRead.ReadUInt32();
+ vmgFs.Seek(ttSectorPtr * 2048, SeekOrigin.Begin);
+ ReadTT_SRPT(vmgRead);
+ }
+ }
+
+ for (ushort titleSetNum = 1; titleSetNum <= _titleSetCount; titleSetNum++)
+ {
+ ReadVTS(titleSetNum, allFiles);
+ }
+ }
+ }
+
+ private void ReadTT_SRPT(BinaryReader read)
+ {
+ _titleCount = read.ReadUInt16();
+ read.BaseStream.Seek(6, SeekOrigin.Current);
+ for (uint titleNum = 1; titleNum <= _titleCount; titleNum++)
+ {
+ Title t = new Title(titleNum);
+ t.ParseTT_SRPT(read);
+ Titles.Add(t);
+ }
+ }
+
+ private void ReadVTS(ushort vtsNum, List allFiles)
+ {
+ var filename = String.Format("VTS_{0:00}_0.IFO", vtsNum);
+
+ var vtsPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)) ??
+ allFiles.FirstOrDefault(i => string.Equals(i.Name, Path.ChangeExtension(filename, ".bup"), StringComparison.OrdinalIgnoreCase));
+
+ if (vtsPath == null)
+ {
+ throw new FileNotFoundException("Unable to find VTS IFO file");
+ }
+
+ ReadVTS(vtsNum, vtsPath.FullName);
+ }
+
+ private void ReadVTS(ushort vtsNum, string vtsPath)
+ {
+ VTSPaths[vtsNum] = vtsPath;
+
+ using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ {
+ using (BigEndianBinaryReader vtsRead = new BigEndianBinaryReader(vtsFs))
+ {
+ // Read VTS_PTT_SRPT
+ vtsFs.Seek(0xC8, SeekOrigin.Begin);
+ uint vtsPttSrptSecPtr = vtsRead.ReadUInt32();
+ uint baseAddr = (vtsPttSrptSecPtr * 2048);
+ vtsFs.Seek(baseAddr, SeekOrigin.Begin);
+
+ ushort numTitles = vtsRead.ReadUInt16();
+ vtsRead.ReadUInt16();
+ uint endaddr = vtsRead.ReadUInt32();
+ uint[] offsets = new uint[numTitles];
+ for (ushort titleNum = 0; titleNum < numTitles; titleNum++)
+ {
+ offsets[titleNum] = vtsRead.ReadUInt32();
+ }
+
+ for (uint titleNum = 0; titleNum < numTitles; titleNum++)
+ {
+ uint chapNum = 1;
+ vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
+ Title t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
+ if (t == null) continue;
+
+ do
+ {
+ t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
+ if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break;
+ chapNum++;
+ }
+ while (vtsFs.Position < (baseAddr + endaddr));
+ }
+
+ // Read VTS_PGCI
+ vtsFs.Seek(0xCC, SeekOrigin.Begin);
+ uint vtsPgciSecPtr = vtsRead.ReadUInt32();
+ vtsFs.Seek(vtsPgciSecPtr * 2048, SeekOrigin.Begin);
+
+ long startByte = vtsFs.Position;
+
+ ushort numPgcs = vtsRead.ReadUInt16();
+ vtsFs.Seek(6, SeekOrigin.Current);
+ for (ushort pgcNum = 1; pgcNum <= numPgcs; pgcNum++)
+ {
+ byte pgcCat = vtsRead.ReadByte();
+ bool entryPgc = (pgcCat & 0x80) != 0;
+ uint titleNum = (uint)(pgcCat & 0x7F);
+
+ vtsFs.Seek(3, SeekOrigin.Current);
+ uint vtsPgcOffset = vtsRead.ReadUInt32();
+
+ Title t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
+ if (t != null) t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/DvdLib/Ifo/DvdTime.cs b/DvdLib/Ifo/DvdTime.cs
new file mode 100644
index 0000000000..f565f5fdf7
--- /dev/null
+++ b/DvdLib/Ifo/DvdTime.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DvdLib.Ifo
+{
+ public class DvdTime
+ {
+ public readonly byte Hour, Minute, Second, Frames, FrameRate;
+
+ public DvdTime(byte[] data)
+ {
+ Hour = GetBCDValue(data[0]);
+ Minute = GetBCDValue(data[1]);
+ Second = GetBCDValue(data[2]);
+ Frames = GetBCDValue((byte)(data[3] & 0x3F));
+
+ if ((data[3] & 0x80) != 0) FrameRate = 30;
+ else if ((data[3] & 0x40) != 0) FrameRate = 25;
+ }
+
+ private byte GetBCDValue(byte data)
+ {
+ return (byte)((((data & 0xF0) >> 4) * 10) + (data & 0x0F));
+ }
+
+ public static explicit operator TimeSpan(DvdTime time)
+ {
+ int ms = (int)(((1.0 / (double)time.FrameRate) * time.Frames) * 1000.0);
+ return new TimeSpan(0, time.Hour, time.Minute, time.Second, ms);
+ }
+ }
+}
diff --git a/DvdLib/Ifo/PgcCommandTable.cs b/DvdLib/Ifo/PgcCommandTable.cs
new file mode 100644
index 0000000000..2ead78cef9
--- /dev/null
+++ b/DvdLib/Ifo/PgcCommandTable.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DvdLib.Ifo
+{
+ public class ProgramChainCommandTable
+ {
+ public readonly ushort LastByteAddress;
+ public readonly List PreCommands;
+ public readonly List PostCommands;
+ public readonly List CellCommands;
+ }
+
+ public class VirtualMachineCommand
+ {
+ public readonly byte[] Command;
+ }
+}
diff --git a/DvdLib/Ifo/Program.cs b/DvdLib/Ifo/Program.cs
new file mode 100644
index 0000000000..48870d9dd0
--- /dev/null
+++ b/DvdLib/Ifo/Program.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DvdLib.Ifo
+{
+ public class Program
+ {
+ public readonly List Cells;
+
+ public Program(List cells)
+ {
+ Cells = cells;
+ }
+ }
+}
diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs
new file mode 100644
index 0000000000..3179f73cd6
--- /dev/null
+++ b/DvdLib/Ifo/ProgramChain.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace DvdLib.Ifo
+{
+ public enum ProgramPlaybackMode
+ {
+ Sequential,
+ Random,
+ Shuffle
+ }
+
+ public class ProgramChain
+ {
+ private ushort _unknown1;
+
+ private byte _programCount;
+ public readonly List Programs;
+
+ private byte _cellCount;
+ public readonly List Cells;
+
+ public DvdTime PlaybackTime { get; private set; }
+ public UserOperation ProhibitedUserOperations { get; private set; }
+ public byte[] AudioStreamControl { get; private set; } // 8*2 entries
+ public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
+
+ private ushort _nextProgramNumber;
+ public readonly ProgramChain Next;
+
+ private ushort _prevProgramNumber;
+ public readonly ProgramChain Previous;
+
+ private ushort _goupProgramNumber;
+ public readonly ProgramChain Goup; // ?? maybe Group
+
+ private byte _playbackMode;
+ public ProgramPlaybackMode PlaybackMode { get; private set; }
+ public uint ProgramCount { get; private set; }
+
+ public byte StillTime { get; private set; }
+ public byte[] Palette { get; private set; } // 16*4 entries
+
+ private ushort _commandTableOffset;
+ public readonly ProgramChainCommandTable CommandTable;
+
+ private ushort _programMapOffset;
+ private ushort _cellPlaybackOffset;
+ private ushort _cellPositionOffset;
+
+ public readonly uint VideoTitleSetIndex;
+
+ internal ProgramChain(uint vtsPgcNum)
+ {
+ VideoTitleSetIndex = vtsPgcNum;
+ Cells = new List();
+ Programs = new List();
+ }
+
+ internal void ParseHeader(BinaryReader br)
+ {
+ long startPos = br.BaseStream.Position;
+
+ br.ReadUInt16();
+ _programCount = br.ReadByte();
+ _cellCount = br.ReadByte();
+ PlaybackTime = new DvdTime(br.ReadBytes(4));
+ ProhibitedUserOperations = (UserOperation)br.ReadUInt32();
+ AudioStreamControl = br.ReadBytes(16);
+ SubpictureStreamControl = br.ReadBytes(128);
+
+ _nextProgramNumber = br.ReadUInt16();
+ _prevProgramNumber = br.ReadUInt16();
+ _goupProgramNumber = br.ReadUInt16();
+
+ StillTime = br.ReadByte();
+ byte pbMode = br.ReadByte();
+ if (pbMode == 0) PlaybackMode = ProgramPlaybackMode.Sequential;
+ else PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
+ ProgramCount = (uint)(pbMode & 0x7F);
+
+ Palette = br.ReadBytes(64);
+ _commandTableOffset = br.ReadUInt16();
+ _programMapOffset = br.ReadUInt16();
+ _cellPlaybackOffset = br.ReadUInt16();
+ _cellPositionOffset = br.ReadUInt16();
+
+ // read position info
+ br.BaseStream.Seek(startPos + _cellPositionOffset, SeekOrigin.Begin);
+ for (int cellNum = 0; cellNum < _cellCount; cellNum++)
+ {
+ Cell c = new Cell();
+ c.ParsePosition(br);
+ Cells.Add(c);
+ }
+
+ br.BaseStream.Seek(startPos + _cellPlaybackOffset, SeekOrigin.Begin);
+ for (int cellNum = 0; cellNum < _cellCount; cellNum++)
+ {
+ Cells[cellNum].ParsePlayback(br);
+ }
+
+ br.BaseStream.Seek(startPos + _programMapOffset, SeekOrigin.Begin);
+ List cellNumbers = new List();
+ for (int progNum = 0; progNum < _programCount; progNum++) cellNumbers.Add(br.ReadByte() - 1);
+
+ for (int i = 0; i < cellNumbers.Count; i++)
+ {
+ int max = (i + 1 == cellNumbers.Count) ? _cellCount : cellNumbers[i+1];
+ Programs.Add(new Program(Cells.Where((c, idx) => idx >= cellNumbers[i] && idx < max).ToList()));
+ }
+ }
+ }
+}
diff --git a/DvdLib/Ifo/Title.cs b/DvdLib/Ifo/Title.cs
new file mode 100644
index 0000000000..70deb45bfa
--- /dev/null
+++ b/DvdLib/Ifo/Title.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace DvdLib.Ifo
+{
+ public class Title
+ {
+ public uint TitleNumber { get; private set; }
+ public uint AngleCount { get; private set; }
+ public ushort ChapterCount { get; private set; }
+ public byte VideoTitleSetNumber { get; private set; }
+
+ private ushort _parentalManagementMask;
+ private byte _titleNumberInVTS;
+ private uint _vtsStartSector; // relative to start of entire disk
+
+ public ProgramChain EntryProgramChain { get; private set; }
+ public readonly List ProgramChains;
+
+ public readonly List Chapters;
+
+ public Title(uint titleNum)
+ {
+ ProgramChains = new List();
+ Chapters = new List();
+ Chapters = new List();
+ TitleNumber = titleNum;
+ }
+
+ public bool IsVTSTitle(uint vtsNum, uint vtsTitleNum)
+ {
+ return (vtsNum == VideoTitleSetNumber && vtsTitleNum == _titleNumberInVTS);
+ }
+
+ internal void ParseTT_SRPT(BinaryReader br)
+ {
+ byte titleType = br.ReadByte();
+ // TODO parse Title Type
+
+ AngleCount = br.ReadByte();
+ ChapterCount = br.ReadUInt16();
+ _parentalManagementMask = br.ReadUInt16();
+ VideoTitleSetNumber = br.ReadByte();
+ _titleNumberInVTS = br.ReadByte();
+ _vtsStartSector = br.ReadUInt32();
+ }
+
+ internal void AddPgc(BinaryReader br, long startByte, bool entryPgc, uint pgcNum)
+ {
+ long curPos = br.BaseStream.Position;
+ br.BaseStream.Seek(startByte, SeekOrigin.Begin);
+
+ ProgramChain pgc = new ProgramChain(pgcNum);
+ pgc.ParseHeader(br);
+ ProgramChains.Add(pgc);
+ if (entryPgc) EntryProgramChain = pgc;
+
+ br.BaseStream.Seek(curPos, SeekOrigin.Begin);
+ }
+ }
+}
diff --git a/DvdLib/Ifo/UserOperation.cs b/DvdLib/Ifo/UserOperation.cs
new file mode 100644
index 0000000000..c3cffd4870
--- /dev/null
+++ b/DvdLib/Ifo/UserOperation.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DvdLib.Ifo
+{
+ [Flags]
+ public enum UserOperation
+ {
+ None = 0,
+ TitleOrTimePlay = 1,
+ ChapterSearchOrPlay = 2,
+ TitlePlay = 4,
+ Stop = 8,
+ GoUp = 16,
+ TimeOrChapterSearch = 32,
+ PrevOrTopProgramSearch = 64,
+ NextProgramSearch = 128,
+ ForwardScan = 256,
+ BackwardScan = 512,
+ TitleMenuCall = 1024,
+ RootMenuCall = 2048,
+ SubpictureMenuCall = 4096,
+ AudioMenuCall = 8192,
+ AngleMenuCall = 16384,
+ ChapterMenuCall = 32768,
+ Resume = 65536,
+ ButtonSelectOrActive = 131072,
+ StillOff = 262144,
+ PauseOn = 524288,
+ AudioStreamChange = 1048576,
+ SubpictureStreamChange = 2097152,
+ AngleChange = 4194304,
+ KaraokeAudioPresentationModeChange = 8388608,
+ VideoPresentationModeChange = 16777216,
+ }
+}
diff --git a/DvdLib/Ifo/VideoAttributes.cs b/DvdLib/Ifo/VideoAttributes.cs
new file mode 100644
index 0000000000..b2d3759426
--- /dev/null
+++ b/DvdLib/Ifo/VideoAttributes.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DvdLib.Ifo
+{
+ public enum VideoCodec
+ {
+ MPEG1 = 0,
+ MPEG2 = 1,
+ }
+
+ public enum VideoFormat
+ {
+ NTSC = 0,
+ PAL = 1,
+ }
+
+ public enum AspectRatio
+ {
+ ar4to3 = 0,
+ ar16to9 = 3
+ }
+
+ public enum FilmMode
+ {
+ None = -1,
+ Camera = 0,
+ Film = 1,
+ }
+
+ public class VideoAttributes
+ {
+ public readonly VideoCodec Codec;
+ public readonly VideoFormat Format;
+ public readonly AspectRatio Aspect;
+ public readonly bool AutomaticPanScan;
+ public readonly bool AutomaticLetterBox;
+ public readonly bool Line21CCField1;
+ public readonly bool Line21CCField2;
+ public readonly int Width;
+ public readonly int Height;
+ public readonly bool Letterboxed;
+ public readonly FilmMode FilmMode;
+
+ public VideoAttributes()
+ {
+ }
+ }
+}
diff --git a/DvdLib/Properties/AssemblyInfo.cs b/DvdLib/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..cca792684f
--- /dev/null
+++ b/DvdLib/Properties/AssemblyInfo.cs
@@ -0,0 +1,29 @@
+using System.Resources;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("DvdLib")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DvdLib")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: NeutralResourcesLanguage("en")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.1")]
\ No newline at end of file
diff --git a/DvdLib/project.json b/DvdLib/project.json
new file mode 100644
index 0000000000..fbbe9eaf32
--- /dev/null
+++ b/DvdLib/project.json
@@ -0,0 +1,17 @@
+{
+ "frameworks":{
+ "netstandard1.6":{
+ "dependencies":{
+ "NETStandard.Library":"1.6.0",
+ }
+ },
+ ".NETPortable,Version=v4.5,Profile=Profile7":{
+ "buildOptions": {
+ "define": [ ]
+ },
+ "frameworkAssemblies":{
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MediaBrowser.Common.Implementations/Archiving/ZipClient.cs b/Emby.Common.Implementations/Archiving/ZipClient.cs
similarity index 84%
rename from MediaBrowser.Common.Implementations/Archiving/ZipClient.cs
rename to Emby.Common.Implementations/Archiving/ZipClient.cs
index 1272e43b90..791c6678cd 100644
--- a/MediaBrowser.Common.Implementations/Archiving/ZipClient.cs
+++ b/Emby.Common.Implementations/Archiving/ZipClient.cs
@@ -1,21 +1,20 @@
-using MediaBrowser.Model.IO;
-using SharpCompress.Archive.Rar;
-using SharpCompress.Archive.SevenZip;
-using SharpCompress.Archive.Tar;
+using System.IO;
+using MediaBrowser.Model.IO;
+using SharpCompress.Archives.Rar;
+using SharpCompress.Archives.SevenZip;
+using SharpCompress.Archives.Tar;
using SharpCompress.Common;
-using SharpCompress.Reader;
-using SharpCompress.Reader.Zip;
-using System.IO;
-using CommonIO;
+using SharpCompress.Readers;
+using SharpCompress.Readers.Zip;
-namespace MediaBrowser.Common.Implementations.Archiving
+namespace Emby.Common.Implementations.Archiving
{
///
/// Class DotNetZipClient
///
public class ZipClient : IZipClient
{
- private IFileSystem _fileSystem;
+ private readonly IFileSystem _fileSystem;
public ZipClient(IFileSystem fileSystem)
{
@@ -46,11 +45,12 @@ namespace MediaBrowser.Common.Implementations.Archiving
{
using (var reader = ReaderFactory.Open(source))
{
- var options = ExtractOptions.ExtractFullPath;
+ var options = new ExtractionOptions();
+ options.ExtractFullPath = true;
if (overwriteExistingFiles)
{
- options = options | ExtractOptions.Overwrite;
+ options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
@@ -61,11 +61,12 @@ namespace MediaBrowser.Common.Implementations.Archiving
{
using (var reader = ZipReader.Open(source))
{
- var options = ExtractOptions.ExtractFullPath;
+ var options = new ExtractionOptions();
+ options.ExtractFullPath = true;
if (overwriteExistingFiles)
{
- options = options | ExtractOptions.Overwrite;
+ options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
@@ -98,11 +99,12 @@ namespace MediaBrowser.Common.Implementations.Archiving
{
using (var reader = archive.ExtractAllEntries())
{
- var options = ExtractOptions.ExtractFullPath;
+ var options = new ExtractionOptions();
+ options.ExtractFullPath = true;
if (overwriteExistingFiles)
{
- options = options | ExtractOptions.Overwrite;
+ options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
@@ -137,11 +139,12 @@ namespace MediaBrowser.Common.Implementations.Archiving
{
using (var reader = archive.ExtractAllEntries())
{
- var options = ExtractOptions.ExtractFullPath;
+ var options = new ExtractionOptions();
+ options.ExtractFullPath = true;
if (overwriteExistingFiles)
{
- options = options | ExtractOptions.Overwrite;
+ options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
@@ -175,11 +178,12 @@ namespace MediaBrowser.Common.Implementations.Archiving
{
using (var reader = archive.ExtractAllEntries())
{
- var options = ExtractOptions.ExtractFullPath;
+ var options = new ExtractionOptions();
+ options.ExtractFullPath = true;
if (overwriteExistingFiles)
{
- options = options | ExtractOptions.Overwrite;
+ options.Overwrite = true;
}
reader.WriteAllToDirectory(targetPath, options);
diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs
similarity index 77%
rename from MediaBrowser.Common.Implementations/BaseApplicationHost.cs
rename to Emby.Common.Implementations/BaseApplicationHost.cs
index e68fee829d..02d7cb31fd 100644
--- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
+++ b/Emby.Common.Implementations/BaseApplicationHost.cs
@@ -1,16 +1,12 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Implementations.Archiving;
-using MediaBrowser.Common.Implementations.Devices;
-using MediaBrowser.Common.Implementations.IO;
-using MediaBrowser.Common.Implementations.ScheduledTasks;
-using MediaBrowser.Common.Implementations.Security;
-using MediaBrowser.Common.Implementations.Serialization;
-using MediaBrowser.Common.Implementations.Updates;
+using Emby.Common.Implementations.Devices;
+using Emby.Common.Implementations.IO;
+using Emby.Common.Implementations.ScheduledTasks;
+using Emby.Common.Implementations.Serialization;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Progress;
-using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Common.Security;
using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Events;
@@ -18,27 +14,42 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
-using ServiceStack;
-using SimpleInjector;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using CommonIO;
+using MediaBrowser.Common.Extensions;
+using Emby.Common.Implementations.Cryptography;
+using Emby.Common.Implementations.Diagnostics;
+using Emby.Common.Implementations.Net;
+using Emby.Common.Implementations.EnvironmentInfo;
+using Emby.Common.Implementations.Threading;
+using MediaBrowser.Common;
using MediaBrowser.Common.IO;
+using MediaBrowser.Model.Cryptography;
+using MediaBrowser.Model.Diagnostics;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Threading;
-namespace MediaBrowser.Common.Implementations
+#if NETSTANDARD1_6
+using System.Runtime.Loader;
+#endif
+
+namespace Emby.Common.Implementations
{
///
/// Class BaseApplicationHost
///
/// The type of the T application paths type.
- public abstract class BaseApplicationHost : IApplicationHost, IDependencyContainer
+ public abstract class BaseApplicationHost : IApplicationHost
where TApplicationPathsType : class, IApplicationPaths
{
///
@@ -67,7 +78,7 @@ namespace MediaBrowser.Common.Implementations
/// Gets or sets the plugins.
///
/// The plugins.
- public IEnumerable Plugins { get; protected set; }
+ public IPlugin[] Plugins { get; protected set; }
///
/// Gets or sets the log manager.
@@ -81,11 +92,6 @@ namespace MediaBrowser.Common.Implementations
/// The application paths.
protected TApplicationPathsType ApplicationPaths { get; private set; }
- ///
- /// The container
- ///
- protected readonly Container Container = new Container();
-
///
/// The json serializer
///
@@ -125,11 +131,6 @@ namespace MediaBrowser.Common.Implementations
/// The kernel.
protected ITaskManager TaskManager { get; private set; }
///
- /// Gets the security manager.
- ///
- /// The security manager.
- protected ISecurityManager SecurityManager { get; private set; }
- ///
/// Gets the HTTP client.
///
/// The HTTP client.
@@ -146,22 +147,14 @@ namespace MediaBrowser.Common.Implementations
/// The configuration manager.
protected IConfigurationManager ConfigurationManager { get; private set; }
- ///
- /// Gets or sets the installation manager.
- ///
- /// The installation manager.
- protected IInstallationManager InstallationManager { get; private set; }
-
protected IFileSystem FileSystemManager { get; private set; }
- ///
- /// Gets or sets the zip client.
- ///
- /// The zip client.
- protected IZipClient ZipClient { get; private set; }
-
protected IIsoManager IsoManager { get; private set; }
+ protected IProcessFactory ProcessFactory { get; private set; }
+ protected ITimerFactory TimerFactory { get; private set; }
+ protected ISocketFactory SocketFactory { get; private set; }
+
///
/// Gets the name.
///
@@ -174,6 +167,10 @@ namespace MediaBrowser.Common.Implementations
/// true if this instance is running as service; otherwise, false.
public abstract bool IsRunningAsService { get; }
+ protected ICryptoProvider CryptographyProvider = new CryptographyProvider();
+
+ protected IEnvironmentInfo EnvironmentInfo { get; private set; }
+
private DeviceId _deviceId;
public string SystemId
{
@@ -183,26 +180,44 @@ namespace MediaBrowser.Common.Implementations
{
_deviceId = new DeviceId(ApplicationPaths, LogManager.GetLogger("SystemId"), FileSystemManager);
}
-
+
return _deviceId.Value;
}
}
public virtual string OperatingSystemDisplayName
{
- get { return Environment.OSVersion.VersionString; }
+ get { return EnvironmentInfo.OperatingSystemName; }
}
- public IMemoryStreamProvider MemoryStreamProvider { get; set; }
+ ///
+ /// The container
+ ///
+ protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
+
+ protected ISystemEvents SystemEvents { get; private set; }
+ protected IMemoryStreamFactory MemoryStreamFactory { get; private set; }
///
/// Initializes a new instance of the class.
///
- protected BaseApplicationHost(TApplicationPathsType applicationPaths,
- ILogManager logManager,
- IFileSystem fileSystem)
+ protected BaseApplicationHost(TApplicationPathsType applicationPaths,
+ ILogManager logManager,
+ IFileSystem fileSystem,
+ IEnvironmentInfo environmentInfo,
+ ISystemEvents systemEvents,
+ IMemoryStreamFactory memoryStreamFactory,
+ INetworkManager networkManager)
{
- XmlSerializer = new XmlSerializer (fileSystem, logManager.GetLogger("XmlSerializer"));
+ NetworkManager = networkManager;
+ EnvironmentInfo = environmentInfo;
+ SystemEvents = systemEvents;
+ MemoryStreamFactory = memoryStreamFactory;
+
+ // hack alert, until common can target .net core
+ BaseExtensions.CryptographyProvider = CryptographyProvider;
+
+ XmlSerializer = new MyXmlSerializer(fileSystem, logManager.GetLogger("XmlSerializer"));
FailedAssemblies = new List();
ApplicationPaths = applicationPaths;
@@ -221,28 +236,10 @@ namespace MediaBrowser.Common.Implementations
/// Task.
public virtual async Task Init(IProgress progress)
{
- try
- {
- // https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Web.config#L4
- Licensing.RegisterLicense("1001-e1JlZjoxMDAxLE5hbWU6VGVzdCBCdXNpbmVzcyxUeXBlOkJ1c2luZXNzLEhhc2g6UHVNTVRPclhvT2ZIbjQ5MG5LZE1mUTd5RUMzQnBucTFEbTE3TDczVEF4QUNMT1FhNXJMOWkzVjFGL2ZkVTE3Q2pDNENqTkQyUktRWmhvUVBhYTBiekJGUUZ3ZE5aZHFDYm9hL3lydGlwUHI5K1JsaTBYbzNsUC85cjVJNHE5QVhldDN6QkE4aTlvdldrdTgyTk1relY2eis2dFFqTThYN2lmc0JveHgycFdjPSxFeHBpcnk6MjAxMy0wMS0wMX0=");
- }
- catch
- {
- // Failing under mono
- }
progress.Report(1);
JsonSerializer = CreateJsonSerializer();
- if (Environment.OSVersion.Platform == PlatformID.Win32NT)
- {
- MemoryStreamProvider = new RecyclableMemoryStreamProvider();
- }
- else
- {
- MemoryStreamProvider = new MemoryStreamProvider();
- }
-
OnLoggerLoaded(true);
LogManager.LoggerLoaded += (s, e) => OnLoggerLoaded(false);
@@ -310,11 +307,10 @@ namespace MediaBrowser.Common.Implementations
builder.AppendLine(string.Format("Command line: {0}", string.Join(" ", Environment.GetCommandLineArgs())));
+#if NET46
builder.AppendLine(string.Format("Operating system: {0}", Environment.OSVersion));
- builder.AppendLine(string.Format("Processor count: {0}", Environment.ProcessorCount));
builder.AppendLine(string.Format("64-Bit OS: {0}", Environment.Is64BitOperatingSystem));
builder.AppendLine(string.Format("64-Bit Process: {0}", Environment.Is64BitProcess));
- builder.AppendLine(string.Format("Program data path: {0}", appPaths.ProgramDataPath));
Type type = Type.GetType("Mono.Runtime");
if (type != null)
@@ -325,23 +321,25 @@ namespace MediaBrowser.Common.Implementations
builder.AppendLine("Mono: " + displayName.Invoke(null, null));
}
}
+#endif
- builder.AppendLine(string.Format("Application Path: {0}", appPaths.ApplicationPath));
+ builder.AppendLine(string.Format("Processor count: {0}", Environment.ProcessorCount));
+ builder.AppendLine(string.Format("Program data path: {0}", appPaths.ProgramDataPath));
+ builder.AppendLine(string.Format("Application directory: {0}", appPaths.ProgramSystemPath));
return builder;
}
- protected virtual IJsonSerializer CreateJsonSerializer()
- {
- return new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer"));
- }
+ protected abstract IJsonSerializer CreateJsonSerializer();
private void SetHttpLimit()
{
try
{
// Increase the max http request limit
+#if NET46
ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
+#endif
}
catch (Exception ex)
{
@@ -386,13 +384,13 @@ namespace MediaBrowser.Common.Implementations
/// Task.
public virtual Task RunStartupTasks()
{
- Resolve().AddTasks(GetExports(false));
+ Resolve().AddTasks(GetExports(false));
- ConfigureAutorun ();
+ ConfigureAutorun();
- ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
+ ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
- return Task.FromResult (true);
+ return Task.FromResult(true);
}
///
@@ -427,10 +425,56 @@ namespace MediaBrowser.Common.Implementations
///
protected virtual void FindParts()
{
- RegisterModules();
-
ConfigurationManager.AddParts(GetExports());
- Plugins = GetExports();
+ Plugins = GetExports().Select(LoadPlugin).Where(i => i != null).ToArray();
+ }
+
+ private IPlugin LoadPlugin(IPlugin plugin)
+ {
+ try
+ {
+ var assemblyPlugin = plugin as IPluginAssembly;
+
+ if (assemblyPlugin != null)
+ {
+#if NET46
+ var assembly = plugin.GetType().Assembly;
+ var assemblyName = assembly.GetName();
+
+ var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0];
+ var assemblyId = new Guid(attribute.Value);
+
+ var assemblyFileName = assemblyName.Name + ".dll";
+ var assemblyFilePath = Path.Combine(ApplicationPaths.PluginsPath, assemblyFileName);
+
+ assemblyPlugin.SetAttributes(assemblyFilePath, assemblyFileName, assemblyName.Version, assemblyId);
+#elif NETSTANDARD1_6
+ var typeInfo = plugin.GetType().GetTypeInfo();
+ var assembly = typeInfo.Assembly;
+ var assemblyName = assembly.GetName();
+
+ var attribute = (GuidAttribute)assembly.GetCustomAttribute(typeof(GuidAttribute));
+ var assemblyId = new Guid(attribute.Value);
+
+ var assemblyFileName = assemblyName.Name + ".dll";
+ var assemblyFilePath = Path.Combine(ApplicationPaths.PluginsPath, assemblyFileName);
+
+ assemblyPlugin.SetAttributes(assemblyFilePath, assemblyFileName, assemblyName.Version, assemblyId);
+#else
+return null;
+#endif
+ }
+
+ var isFirstRun = !File.Exists(plugin.ConfigurationFilePath);
+ plugin.SetStartupInfo(isFirstRun, File.GetLastWriteTimeUtc, s => Directory.CreateDirectory(s));
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error loading plugin {0}", ex, plugin.GetType().FullName);
+ return null;
+ }
+
+ return plugin;
}
///
@@ -449,7 +493,17 @@ namespace MediaBrowser.Common.Implementations
AllConcreteTypes = assemblies
.SelectMany(GetTypes)
- .Where(t => t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType)
+ .Where(t =>
+ {
+#if NET46
+ return t.IsClass && !t.IsAbstract && !t.IsInterface && !t.IsGenericType;
+#endif
+#if NETSTANDARD1_6
+ var typeInfo = t.GetTypeInfo();
+ return typeInfo.IsClass && !typeInfo.IsAbstract && !typeInfo.IsInterface && !typeInfo.IsGenericType;
+#endif
+ return false;
+ })
.ToArray();
}
@@ -459,62 +513,46 @@ namespace MediaBrowser.Common.Implementations
/// Task.
protected virtual Task RegisterResources(IProgress progress)
{
- RegisterSingleInstance(ConfigurationManager);
- RegisterSingleInstance(this);
+ RegisterSingleInstance(ConfigurationManager);
+ RegisterSingleInstance(this);
- RegisterSingleInstance(ApplicationPaths);
+ RegisterSingleInstance(ApplicationPaths);
- TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LogManager.GetLogger("TaskManager"), FileSystemManager);
+ TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LogManager.GetLogger("TaskManager"), FileSystemManager, SystemEvents);
- RegisterSingleInstance(JsonSerializer);
- RegisterSingleInstance(XmlSerializer);
- RegisterSingleInstance(MemoryStreamProvider);
+ RegisterSingleInstance(JsonSerializer);
+ RegisterSingleInstance(XmlSerializer);
+ RegisterSingleInstance(MemoryStreamFactory);
+ RegisterSingleInstance(SystemEvents);
- RegisterSingleInstance(LogManager);
- RegisterSingleInstance(Logger);
+ RegisterSingleInstance(LogManager);
+ RegisterSingleInstance(Logger);
- RegisterSingleInstance(TaskManager);
+ RegisterSingleInstance(TaskManager);
+ RegisterSingleInstance(EnvironmentInfo);
- RegisterSingleInstance(FileSystemManager);
+ RegisterSingleInstance(FileSystemManager);
- HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamProvider);
- RegisterSingleInstance(HttpClient);
+ HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory);
+ RegisterSingleInstance(HttpClient);
- NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));
- RegisterSingleInstance(NetworkManager);
+ RegisterSingleInstance(NetworkManager);
- SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager);
- RegisterSingleInstance(SecurityManager);
+ IsoManager = new IsoManager();
+ RegisterSingleInstance(IsoManager);
- InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager);
- RegisterSingleInstance(InstallationManager);
+ ProcessFactory = new ProcessFactory();
+ RegisterSingleInstance(ProcessFactory);
- ZipClient = new ZipClient(FileSystemManager);
- RegisterSingleInstance(ZipClient);
+ TimerFactory = new TimerFactory();
+ RegisterSingleInstance(TimerFactory);
- IsoManager = new IsoManager();
- RegisterSingleInstance(IsoManager);
+ SocketFactory = new SocketFactory(LogManager.GetLogger("SocketFactory"));
+ RegisterSingleInstance(SocketFactory);
- return Task.FromResult (true);
- }
+ RegisterSingleInstance(CryptographyProvider);
- private void RegisterModules()
- {
- var moduleTypes = GetExportTypes();
-
- foreach (var type in moduleTypes)
- {
- try
- {
- var instance = Activator.CreateInstance(type) as IDependencyModule;
- if (instance != null)
- instance.BindDependencies(this);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error setting up dependency bindings for " + type.Name, ex);
- }
- }
+ return Task.FromResult(true);
}
///
@@ -544,14 +582,12 @@ namespace MediaBrowser.Common.Implementations
Logger.Error("LoaderException: " + loaderException.Message);
}
}
-
+
// If it fails we can still get a list of the Types it was able to resolve
return ex.Types.Where(t => t != null);
}
}
- protected abstract INetworkManager CreateNetworkManager(ILogger logger);
-
///
/// Creates an instance of type and resolves all constructor dependancies
///
@@ -565,7 +601,7 @@ namespace MediaBrowser.Common.Implementations
}
catch (Exception ex)
{
- Logger.ErrorException("Error creating {0}", ex, type.Name);
+ Logger.ErrorException("Error creating {0}", ex, type.FullName);
throw;
}
@@ -584,17 +620,12 @@ namespace MediaBrowser.Common.Implementations
}
catch (Exception ex)
{
- Logger.ErrorException("Error creating {0}", ex, type.Name);
+ Logger.ErrorException("Error creating {0}", ex, type.FullName);
// Don't blow up in release mode
return null;
}
}
- void IDependencyContainer.RegisterSingleInstance(T obj, bool manageLifetime)
- {
- RegisterSingleInstance(obj, manageLifetime);
- }
-
///
/// Registers the specified obj.
///
@@ -617,11 +648,6 @@ namespace MediaBrowser.Common.Implementations
}
}
- void IDependencyContainer.RegisterSingleInstance(Func func)
- {
- RegisterSingleInstance(func);
- }
-
///
/// Registers the single instance.
///
@@ -633,11 +659,6 @@ namespace MediaBrowser.Common.Implementations
Container.RegisterSingleton(func);
}
- void IDependencyContainer.Register(Type typeInterface, Type typeImplementation)
- {
- Container.Register(typeInterface, typeImplementation);
- }
-
///
/// Resolves this instance.
///
@@ -673,7 +694,13 @@ namespace MediaBrowser.Common.Implementations
{
try
{
+#if NET46
return Assembly.Load(File.ReadAllBytes(file));
+#elif NETSTANDARD1_6
+
+ return AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file)));
+#endif
+ return null;
}
catch (Exception ex)
{
@@ -692,7 +719,14 @@ namespace MediaBrowser.Common.Implementations
{
var currentType = typeof(T);
- return AllConcreteTypes.AsParallel().Where(currentType.IsAssignableFrom);
+#if NET46
+ return AllConcreteTypes.Where(currentType.IsAssignableFrom);
+#elif NETSTANDARD1_6
+ var currentTypeInfo = currentType.GetTypeInfo();
+
+ return AllConcreteTypes.Where(currentTypeInfo.IsAssignableFrom);
+#endif
+ return new List();
}
///
@@ -747,7 +781,7 @@ namespace MediaBrowser.Common.Implementations
{
var list = Plugins.ToList();
list.Remove(plugin);
- Plugins = list;
+ Plugins = list.ToArray();
}
///
diff --git a/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs b/Emby.Common.Implementations/BaseApplicationPaths.cs
similarity index 92%
rename from MediaBrowser.Common.Implementations/BaseApplicationPaths.cs
rename to Emby.Common.Implementations/BaseApplicationPaths.cs
index 9ba2effd3a..8792778ba6 100644
--- a/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs
+++ b/Emby.Common.Implementations/BaseApplicationPaths.cs
@@ -1,7 +1,7 @@
-using MediaBrowser.Common.Configuration;
-using System.IO;
+using System.IO;
+using MediaBrowser.Common.Configuration;
-namespace MediaBrowser.Common.Implementations
+namespace Emby.Common.Implementations
{
///
/// Provides a base class to hold common application paths used by both the Ui and Server.
@@ -12,22 +12,18 @@ namespace MediaBrowser.Common.Implementations
///
/// Initializes a new instance of the class.
///
- protected BaseApplicationPaths(string programDataPath, string applicationPath)
+ protected BaseApplicationPaths(string programDataPath, string appFolderPath)
{
ProgramDataPath = programDataPath;
- ApplicationPath = applicationPath;
+ ProgramSystemPath = appFolderPath;
}
- public string ApplicationPath { get; private set; }
public string ProgramDataPath { get; private set; }
///
/// Gets the path to the system folder
///
- public string ProgramSystemPath
- {
- get { return Path.GetDirectoryName(ApplicationPath); }
- }
+ public string ProgramSystemPath { get; private set; }
///
/// The _data directory
diff --git a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs b/Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs
similarity index 95%
rename from MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
rename to Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs
index fa15023ca5..27c9fe6157 100644
--- a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
+++ b/Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs
@@ -1,18 +1,19 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Events;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using System;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
-using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
+using Emby.Common.Implementations;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
-namespace MediaBrowser.Common.Implementations.Configuration
+namespace Emby.Common.Implementations.Configuration
{
///
/// Class BaseConfigurationManager
@@ -79,7 +80,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
get
{
// Lazy load
- LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
+ LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
return _configuration;
}
protected set
@@ -126,7 +127,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
Logger.Info("Saving system configuration");
var path = CommonApplicationPaths.SystemConfigurationFilePath;
- Directory.CreateDirectory(Path.GetDirectoryName(path));
+ FileSystem.CreateDirectory(Path.GetDirectoryName(path));
lock (_configurationSyncLock)
{
@@ -196,9 +197,9 @@ namespace MediaBrowser.Common.Implementations.Configuration
&& !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
{
// Validate
- if (!Directory.Exists(newPath))
+ if (!FileSystem.DirectoryExists(newPath))
{
- throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
+ throw new FileNotFoundException(string.Format("{0} does not exist.", newPath));
}
EnsureWriteAccess(newPath);
@@ -253,7 +254,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
{
return Activator.CreateInstance(configurationType);
}
- catch (DirectoryNotFoundException)
+ catch (IOException)
{
return Activator.CreateInstance(configurationType);
}
@@ -293,7 +294,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
var path = GetConfigurationFile(key);
- Directory.CreateDirectory(Path.GetDirectoryName(path));
+ FileSystem.CreateDirectory(Path.GetDirectoryName(path));
lock (_configurationSyncLock)
{
diff --git a/MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs b/Emby.Common.Implementations/Configuration/ConfigurationHelper.cs
similarity index 80%
rename from MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs
rename to Emby.Common.Implementations/Configuration/ConfigurationHelper.cs
index 419b85fa73..0d43a651ea 100644
--- a/MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs
+++ b/Emby.Common.Implementations/Configuration/ConfigurationHelper.cs
@@ -1,9 +1,10 @@
-using MediaBrowser.Model.Serialization;
-using System;
+using System;
using System.IO;
using System.Linq;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Serialization;
-namespace MediaBrowser.Common.Implementations.Configuration
+namespace Emby.Common.Implementations.Configuration
{
///
/// Class ConfigurationHelper
@@ -18,7 +19,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
/// The path.
/// The XML serializer.
/// System.Object.
- public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer)
+ public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
{
object configuration;
@@ -27,7 +28,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
// Use try/catch to avoid the extra file system lookup using File.Exists
try
{
- buffer = File.ReadAllBytes(path);
+ buffer = fileSystem.ReadAllBytes(path);
configuration = xmlSerializer.DeserializeFromBytes(type, buffer);
}
@@ -46,10 +47,10 @@ namespace MediaBrowser.Common.Implementations.Configuration
// If the file didn't exist before, or if something has changed, re-save
if (buffer == null || !buffer.SequenceEqual(newBytes))
{
- Directory.CreateDirectory(Path.GetDirectoryName(path));
+ fileSystem.CreateDirectory(Path.GetDirectoryName(path));
// Save it after load in case we got new items
- File.WriteAllBytes(path, newBytes);
+ fileSystem.WriteAllBytes(path, newBytes);
}
return configuration;
diff --git a/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs
new file mode 100644
index 0000000000..01a31bcc03
--- /dev/null
+++ b/Emby.Common.Implementations/Cryptography/CryptographyProvider.cs
@@ -0,0 +1,40 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using MediaBrowser.Model.Cryptography;
+
+namespace Emby.Common.Implementations.Cryptography
+{
+ public class CryptographyProvider : ICryptoProvider
+ {
+ public Guid GetMD5(string str)
+ {
+ return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
+ }
+
+ public byte[] ComputeSHA1(byte[] bytes)
+ {
+ using (var provider = SHA1.Create())
+ {
+ return provider.ComputeHash(bytes);
+ }
+ }
+
+ public byte[] ComputeMD5(Stream str)
+ {
+ using (var provider = MD5.Create())
+ {
+ return provider.ComputeHash(str);
+ }
+ }
+
+ public byte[] ComputeMD5(byte[] bytes)
+ {
+ using (var provider = MD5.Create())
+ {
+ return provider.ComputeHash(bytes);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common.Implementations/Devices/DeviceId.cs b/Emby.Common.Implementations/Devices/DeviceId.cs
similarity index 94%
rename from MediaBrowser.Common.Implementations/Devices/DeviceId.cs
rename to Emby.Common.Implementations/Devices/DeviceId.cs
index f1581704b8..3d23ab872b 100644
--- a/MediaBrowser.Common.Implementations/Devices/DeviceId.cs
+++ b/Emby.Common.Implementations/Devices/DeviceId.cs
@@ -1,11 +1,11 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Logging;
-using System;
+using System;
using System.IO;
using System.Text;
-using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
-namespace MediaBrowser.Common.Implementations.Devices
+namespace Emby.Common.Implementations.Devices
{
public class DeviceId
{
diff --git a/Emby.Common.Implementations/Diagnostics/CommonProcess.cs b/Emby.Common.Implementations/Diagnostics/CommonProcess.cs
new file mode 100644
index 0000000000..462345ced5
--- /dev/null
+++ b/Emby.Common.Implementations/Diagnostics/CommonProcess.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Diagnostics;
+
+namespace Emby.Common.Implementations.Diagnostics
+{
+ public class CommonProcess : IProcess
+ {
+ public event EventHandler Exited;
+
+ private readonly ProcessOptions _options;
+ private readonly Process _process;
+
+ public CommonProcess(ProcessOptions options)
+ {
+ _options = options;
+
+ var startInfo = new ProcessStartInfo
+ {
+ Arguments = options.Arguments,
+ FileName = options.FileName,
+ WorkingDirectory = options.WorkingDirectory,
+ UseShellExecute = options.UseShellExecute,
+ CreateNoWindow = options.CreateNoWindow,
+ RedirectStandardError = options.RedirectStandardError,
+ RedirectStandardInput = options.RedirectStandardInput,
+ RedirectStandardOutput = options.RedirectStandardOutput
+ };
+
+#if NET46
+ startInfo.ErrorDialog = options.ErrorDialog;
+
+ if (options.IsHidden)
+ {
+ startInfo.WindowStyle = ProcessWindowStyle.Hidden;
+ }
+#endif
+
+ _process = new Process
+ {
+ StartInfo = startInfo
+ };
+
+ if (options.EnableRaisingEvents)
+ {
+ _process.EnableRaisingEvents = true;
+ _process.Exited += _process_Exited;
+ }
+ }
+
+ private void _process_Exited(object sender, EventArgs e)
+ {
+ if (Exited != null)
+ {
+ Exited(this, e);
+ }
+ }
+
+ public ProcessOptions StartInfo
+ {
+ get { return _options; }
+ }
+
+ public StreamWriter StandardInput
+ {
+ get { return _process.StandardInput; }
+ }
+
+ public StreamReader StandardError
+ {
+ get { return _process.StandardError; }
+ }
+
+ public StreamReader StandardOutput
+ {
+ get { return _process.StandardOutput; }
+ }
+
+ public int ExitCode
+ {
+ get { return _process.ExitCode; }
+ }
+
+ public void Start()
+ {
+ _process.Start();
+ }
+
+ public void Kill()
+ {
+ _process.Kill();
+ }
+
+ public bool WaitForExit(int timeMs)
+ {
+ return _process.WaitForExit(timeMs);
+ }
+
+ public void Dispose()
+ {
+ _process.Dispose();
+ }
+ }
+}
diff --git a/Emby.Common.Implementations/Diagnostics/ProcessFactory.cs b/Emby.Common.Implementations/Diagnostics/ProcessFactory.cs
new file mode 100644
index 0000000000..292da023c7
--- /dev/null
+++ b/Emby.Common.Implementations/Diagnostics/ProcessFactory.cs
@@ -0,0 +1,12 @@
+using MediaBrowser.Model.Diagnostics;
+
+namespace Emby.Common.Implementations.Diagnostics
+{
+ public class ProcessFactory : IProcessFactory
+ {
+ public IProcess Create(ProcessOptions options)
+ {
+ return new CommonProcess(options);
+ }
+ }
+}
diff --git a/Emby.Common.Implementations/Emby.Common.Implementations.xproj b/Emby.Common.Implementations/Emby.Common.Implementations.xproj
new file mode 100644
index 0000000000..5bb6e4e589
--- /dev/null
+++ b/Emby.Common.Implementations/Emby.Common.Implementations.xproj
@@ -0,0 +1,23 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 5a27010a-09c6-4e86-93ea-437484c10917
+ Emby.Common.Implementations
+ .\obj
+ .\bin\
+ v4.5.2
+
+
+ 2.0
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs
new file mode 100644
index 0000000000..6cc4626eaf
--- /dev/null
+++ b/Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using MediaBrowser.Model.System;
+
+namespace Emby.Common.Implementations.EnvironmentInfo
+{
+ public class EnvironmentInfo : IEnvironmentInfo
+ {
+ public MediaBrowser.Model.System.Architecture? CustomArchitecture { get; set; }
+
+ public MediaBrowser.Model.System.OperatingSystem OperatingSystem
+ {
+ get
+ {
+#if NET46
+ switch (Environment.OSVersion.Platform)
+ {
+ case PlatformID.MacOSX:
+ return MediaBrowser.Model.System.OperatingSystem.OSX;
+ case PlatformID.Win32NT:
+ return MediaBrowser.Model.System.OperatingSystem.Windows;
+ case PlatformID.Unix:
+ return MediaBrowser.Model.System.OperatingSystem.Linux;
+ }
+#elif NETSTANDARD1_6
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ return OperatingSystem.OSX;
+ }
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return OperatingSystem.Windows;
+ }
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ return OperatingSystem.Linux;
+ }
+#endif
+ return MediaBrowser.Model.System.OperatingSystem.Windows;
+ }
+ }
+
+ public string OperatingSystemName
+ {
+ get
+ {
+#if NET46
+ return Environment.OSVersion.Platform.ToString();
+#elif NETSTANDARD1_6
+ return System.Runtime.InteropServices.RuntimeInformation.OSDescription;
+#endif
+ return "Operating System";
+ }
+ }
+
+ public string OperatingSystemVersion
+ {
+ get
+ {
+#if NET46
+ return Environment.OSVersion.Version.ToString() + " " + Environment.OSVersion.ServicePack.ToString();
+#elif NETSTANDARD1_6
+ return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
+#endif
+ return "1.0";
+ }
+ }
+
+ public MediaBrowser.Model.System.Architecture SystemArchitecture
+ {
+ get
+ {
+ if (CustomArchitecture.HasValue)
+ {
+ return CustomArchitecture.Value;
+ }
+#if NET46
+ return Environment.Is64BitOperatingSystem ? MediaBrowser.Model.System.Architecture.X64 : MediaBrowser.Model.System.Architecture.X86;
+#elif NETSTANDARD1_6
+ switch(System.Runtime.InteropServices.RuntimeInformation.OSArchitecture)
+ {
+ case System.Runtime.InteropServices.Architecture.Arm:
+ return MediaBrowser.Model.System.Architecture.Arm;
+ case System.Runtime.InteropServices.Architecture.Arm64:
+ return MediaBrowser.Model.System.Architecture.Arm64;
+ case System.Runtime.InteropServices.Architecture.X64:
+ return MediaBrowser.Model.System.Architecture.X64;
+ case System.Runtime.InteropServices.Architecture.X86:
+ return MediaBrowser.Model.System.Architecture.X86;
+ }
+#endif
+ return MediaBrowser.Model.System.Architecture.X64;
+ }
+ }
+
+ public string GetEnvironmentVariable(string name)
+ {
+ return Environment.GetEnvironmentVariable(name);
+ }
+
+ public virtual string GetUserId()
+ {
+ return null;
+ }
+
+ public string StackTrace
+ {
+ get { return Environment.StackTrace; }
+ }
+
+ public void SetProcessEnvironmentVariable(string name, string value)
+ {
+ Environment.SetEnvironmentVariable(name, value);
+ }
+ }
+}
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientInfo.cs b/Emby.Common.Implementations/HttpClientManager/HttpClientInfo.cs
similarity index 83%
rename from MediaBrowser.Common.Implementations/HttpClientManager/HttpClientInfo.cs
rename to Emby.Common.Implementations/HttpClientManager/HttpClientInfo.cs
index 8af6ef6c6e..ca481b33e7 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientInfo.cs
+++ b/Emby.Common.Implementations/HttpClientManager/HttpClientInfo.cs
@@ -1,6 +1,6 @@
using System;
-namespace MediaBrowser.Common.Implementations.HttpClientManager
+namespace Emby.Common.Implementations.HttpClientManager
{
///
/// Class HttpClientInfo
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs
similarity index 91%
rename from MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
rename to Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs
index eec18e9857..06af5af536 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -13,13 +13,13 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
-using System.Net.Cache;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using CommonIO;
+using Emby.Common.Implementations.HttpClientManager;
+using MediaBrowser.Model.IO;
-namespace MediaBrowser.Common.Implementations.HttpClientManager
+namespace Emby.Common.Implementations.HttpClientManager
{
///
/// Class HttpClientManager
@@ -42,7 +42,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
- private readonly IMemoryStreamProvider _memoryStreamProvider;
+ private readonly IMemoryStreamFactory _memoryStreamProvider;
///
/// Initializes a new instance of the class.
@@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// appPaths
/// or
/// logger
- public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamProvider memoryStreamProvider)
+ public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider)
{
if (appPaths == null)
{
@@ -69,11 +69,13 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
_memoryStreamProvider = memoryStreamProvider;
_appPaths = appPaths;
+#if NET46
// http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
ServicePointManager.Expect100Continue = false;
// Trakt requests sometimes fail without this
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
+#endif
}
///
@@ -130,6 +132,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private void AddIpv4Option(HttpWebRequest request, HttpRequestOptions options)
{
+#if NET46
request.ServicePoint.BindIPEndPointDelegate = (servicePount, remoteEndPoint, retryCount) =>
{
if (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork)
@@ -138,6 +141,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
throw new InvalidOperationException("no IPv4 address");
};
+#endif
}
private WebRequest GetRequest(HttpRequestOptions options, string method)
@@ -164,34 +168,66 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
AddRequestHeaders(httpWebRequest, options);
- httpWebRequest.AutomaticDecompression = options.EnableHttpCompression ?
- (options.DecompressionMethod ?? DecompressionMethods.Deflate) :
- DecompressionMethods.None;
+#if NET46
+ if (options.EnableHttpCompression)
+ {
+ if (options.DecompressionMethod.HasValue)
+ {
+ httpWebRequest.AutomaticDecompression = options.DecompressionMethod.Value == CompressionMethod.Gzip
+ ? DecompressionMethods.GZip
+ : DecompressionMethods.Deflate;
+ }
+ else
+ {
+ httpWebRequest.AutomaticDecompression = DecompressionMethods.Deflate;
+ }
+ }
+ else
+ {
+ httpWebRequest.AutomaticDecompression = DecompressionMethods.None;
+ }
+#endif
}
- request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
+
+
+#if NET46
+ request.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.BypassCache);
+#endif
if (httpWebRequest != null)
{
if (options.EnableKeepAlive)
{
+#if NET46
httpWebRequest.KeepAlive = true;
+#endif
}
}
request.Method = method;
+#if NET46
request.Timeout = options.TimeoutMs;
-
+#endif
+
if (httpWebRequest != null)
{
if (!string.IsNullOrEmpty(options.Host))
{
+#if NET46
httpWebRequest.Host = options.Host;
+#elif NETSTANDARD1_6
+ httpWebRequest.Headers["Host"] = options.Host;
+#endif
}
if (!string.IsNullOrEmpty(options.Referer))
{
+#if NET46
httpWebRequest.Referer = options.Referer;
+#elif NETSTANDARD1_6
+ httpWebRequest.Headers["Referer"] = options.Referer;
+#endif
}
}
@@ -201,7 +237,10 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
if (parts.Length == 2)
{
request.Credentials = GetCredential(url, parts[0], parts[1]);
+ // TODO: .net core ??
+#if NET46
request.PreAuthenticate = true;
+#endif
}
}
@@ -226,11 +265,19 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase))
{
+#if NET46
request.UserAgent = header.Value;
+#elif NETSTANDARD1_6
+ request.Headers["User-Agent"] = header.Value;
+#endif
}
else
{
+#if NET46
request.Headers.Set(header.Key, header.Value);
+#elif NETSTANDARD1_6
+ request.Headers[header.Key] = header.Value;
+#endif
}
}
}
@@ -330,7 +377,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
if (_fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
{
- using (var stream = _fileSystem.GetFileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
+ using (var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
{
var memoryStream = _memoryStreamProvider.CreateNew();
@@ -342,7 +389,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
ResponseUrl = url,
Content = memoryStream,
StatusCode = HttpStatusCode.OK,
- Headers = new NameValueCollection(),
ContentLength = memoryStream.Length
};
}
@@ -370,7 +416,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
await responseStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
- using (var fileStream = _fileSystem.GetFileStream(responseCachePath, FileMode.Create, FileAccess.Write, FileShare.None, true))
+ using (var fileStream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.None, true))
{
await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
@@ -407,8 +453,10 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
httpWebRequest.ContentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
+#if NET46
httpWebRequest.ContentLength = bytes.Length;
- httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
+#endif
+ (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
}
if (options.ResourcePool != null)
@@ -487,7 +535,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, Stream content, long? contentLength, IDisposable disposable)
{
- return new HttpResponseInfo(disposable)
+ var responseInfo = new HttpResponseInfo(disposable)
{
Content = content,
@@ -495,17 +543,22 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
ContentType = httpResponse.ContentType,
- Headers = new NameValueCollection(httpResponse.Headers),
-
ContentLength = contentLength,
ResponseUrl = httpResponse.ResponseUri.ToString()
};
+
+ if (httpResponse.Headers != null)
+ {
+ SetHeaders(httpResponse.Headers, responseInfo);
+ }
+
+ return responseInfo;
}
private HttpResponseInfo GetResponseInfo(HttpWebResponse httpResponse, string tempFile, long? contentLength)
{
- return new HttpResponseInfo
+ var responseInfo = new HttpResponseInfo
{
TempFilePath = tempFile,
@@ -513,10 +566,23 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
ContentType = httpResponse.ContentType,
- Headers = httpResponse.Headers,
-
ContentLength = contentLength
};
+
+ if (httpResponse.Headers != null)
+ {
+ SetHeaders(httpResponse.Headers, responseInfo);
+ }
+
+ return responseInfo;
+ }
+
+ private void SetHeaders(WebHeaderCollection headers, HttpResponseInfo responseInfo)
+ {
+ foreach (var key in headers.AllKeys)
+ {
+ responseInfo.Headers[key] = headers[key];
+ }
}
public Task Post(HttpRequestOptions options)
@@ -621,7 +687,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
// We're not able to track progress
using (var stream = httpResponse.GetResponseStream())
{
- using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true))
+ using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
}
@@ -631,7 +697,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
using (var stream = ProgressStream.CreateReadProgressStream(httpResponse.GetResponseStream(), options.Progress.Report, contentLength.Value))
{
- using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true))
+ using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
}
@@ -867,6 +933,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private Task GetResponseAsync(WebRequest request, TimeSpan timeout)
{
+#if NET46
var taskCompletion = new TaskCompletionSource();
Task asyncTask = Task.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null);
@@ -879,6 +946,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
asyncTask.ContinueWith(callback.OnError, TaskContinuationOptions.OnlyOnFaulted);
return taskCompletion.Task;
+#endif
+
+ return request.GetResponseAsync();
}
private static void TimeoutCallback(object state, bool timedOut)
diff --git a/MediaBrowser.Common.Implementations/IO/IsoManager.cs b/Emby.Common.Implementations/IO/IsoManager.cs
similarity index 96%
rename from MediaBrowser.Common.Implementations/IO/IsoManager.cs
rename to Emby.Common.Implementations/IO/IsoManager.cs
index de88ddadab..14614acaf8 100644
--- a/MediaBrowser.Common.Implementations/IO/IsoManager.cs
+++ b/Emby.Common.Implementations/IO/IsoManager.cs
@@ -1,11 +1,11 @@
-using MediaBrowser.Model.IO;
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
-namespace MediaBrowser.Common.Implementations.IO
+namespace Emby.Common.Implementations.IO
{
///
/// Class IsoManager
diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
new file mode 100644
index 0000000000..62d285072c
--- /dev/null
+++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
@@ -0,0 +1,794 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+
+namespace Emby.Common.Implementations.IO
+{
+ ///
+ /// Class ManagedFileSystem
+ ///
+ public class ManagedFileSystem : IFileSystem
+ {
+ protected ILogger Logger;
+
+ private readonly bool _supportsAsyncFileStreams;
+ private char[] _invalidFileNameChars;
+ private readonly List _shortcutHandlers = new List();
+ private bool EnableFileSystemRequestConcat = true;
+
+ public ManagedFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, bool enableFileSystemRequestConcat)
+ {
+ Logger = logger;
+ _supportsAsyncFileStreams = supportsAsyncFileStreams;
+ EnableFileSystemRequestConcat = enableFileSystemRequestConcat;
+ SetInvalidFileNameChars(enableManagedInvalidFileNameChars);
+ }
+
+ public void AddShortcutHandler(IShortcutHandler handler)
+ {
+ _shortcutHandlers.Add(handler);
+ }
+
+ protected void SetInvalidFileNameChars(bool enableManagedInvalidFileNameChars)
+ {
+ if (enableManagedInvalidFileNameChars)
+ {
+ _invalidFileNameChars = Path.GetInvalidFileNameChars();
+ }
+ else
+ {
+ // GetInvalidFileNameChars is less restrictive in Linux/Mac than Windows, this mimic Windows behavior for mono under Linux/Mac.
+ _invalidFileNameChars = new char[41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
+ '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
+ '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
+ '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
+ }
+ }
+
+ public char DirectorySeparatorChar
+ {
+ get
+ {
+ return Path.DirectorySeparatorChar;
+ }
+ }
+
+ public char PathSeparator
+ {
+ get
+ {
+ return Path.PathSeparator;
+ }
+ }
+
+ public string GetFullPath(string path)
+ {
+ return Path.GetFullPath(path);
+ }
+
+ ///
+ /// Determines whether the specified filename is shortcut.
+ ///
+ /// The filename.
+ /// true if the specified filename is shortcut; otherwise, false.
+ /// filename
+ public virtual bool IsShortcut(string filename)
+ {
+ if (string.IsNullOrEmpty(filename))
+ {
+ throw new ArgumentNullException("filename");
+ }
+
+ var extension = Path.GetExtension(filename);
+ return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
+ }
+
+ ///
+ /// Resolves the shortcut.
+ ///
+ /// The filename.
+ /// System.String.
+ /// filename
+ public virtual string ResolveShortcut(string filename)
+ {
+ if (string.IsNullOrEmpty(filename))
+ {
+ throw new ArgumentNullException("filename");
+ }
+
+ var extension = Path.GetExtension(filename);
+ var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
+
+ if (handler != null)
+ {
+ return handler.Resolve(filename);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Creates the shortcut.
+ ///
+ /// The shortcut path.
+ /// The target.
+ ///
+ /// shortcutPath
+ /// or
+ /// target
+ ///
+ public void CreateShortcut(string shortcutPath, string target)
+ {
+ if (string.IsNullOrEmpty(shortcutPath))
+ {
+ throw new ArgumentNullException("shortcutPath");
+ }
+
+ if (string.IsNullOrEmpty(target))
+ {
+ throw new ArgumentNullException("target");
+ }
+
+ var extension = Path.GetExtension(shortcutPath);
+ var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
+
+ if (handler != null)
+ {
+ handler.Create(shortcutPath, target);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ ///
+ /// Returns a object for the specified file or directory path.
+ ///
+ /// A path to a file or directory.
+ /// A object.
+ /// If the specified path points to a directory, the returned object's
+ /// property will be set to true and all other properties will reflect the properties of the directory.
+ public FileSystemMetadata GetFileSystemInfo(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
+ if (Path.HasExtension(path))
+ {
+ var fileInfo = new FileInfo(path);
+
+ if (fileInfo.Exists)
+ {
+ return GetFileSystemMetadata(fileInfo);
+ }
+
+ return GetFileSystemMetadata(new DirectoryInfo(path));
+ }
+ else
+ {
+ var fileInfo = new DirectoryInfo(path);
+
+ if (fileInfo.Exists)
+ {
+ return GetFileSystemMetadata(fileInfo);
+ }
+
+ return GetFileSystemMetadata(new FileInfo(path));
+ }
+ }
+
+ ///
+ /// Returns a object for the specified file path.
+ ///
+ /// A path to a file.
+ /// A object.
+ /// If the specified path points to a directory, the returned object's
+ /// property and the property will both be set to false.
+ /// For automatic handling of files and directories, use .
+ public FileSystemMetadata GetFileInfo(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ var fileInfo = new FileInfo(path);
+
+ return GetFileSystemMetadata(fileInfo);
+ }
+
+ ///
+ /// Returns a object for the specified directory path.
+ ///
+ /// A path to a directory.
+ /// A object.
+ /// If the specified path points to a file, the returned object's
+ /// property will be set to true and the property will be set to false.
+ /// For automatic handling of files and directories, use .
+ public FileSystemMetadata GetDirectoryInfo(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ var fileInfo = new DirectoryInfo(path);
+
+ return GetFileSystemMetadata(fileInfo);
+ }
+
+ private FileSystemMetadata GetFileSystemMetadata(FileSystemInfo info)
+ {
+ var result = new FileSystemMetadata();
+
+ result.Exists = info.Exists;
+ result.FullName = info.FullName;
+ result.Extension = info.Extension;
+ result.Name = info.Name;
+
+ if (result.Exists)
+ {
+ var attributes = info.Attributes;
+ result.IsDirectory = info is DirectoryInfo || (attributes & FileAttributes.Directory) == FileAttributes.Directory;
+ result.IsHidden = (attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
+ result.IsReadOnly = (attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
+
+ var fileInfo = info as FileInfo;
+ if (fileInfo != null)
+ {
+ result.Length = fileInfo.Length;
+ result.DirectoryName = fileInfo.DirectoryName;
+ }
+
+ result.CreationTimeUtc = GetCreationTimeUtc(info);
+ result.LastWriteTimeUtc = GetLastWriteTimeUtc(info);
+ }
+ else
+ {
+ result.IsDirectory = info is DirectoryInfo;
+ }
+
+ return result;
+ }
+
+ ///
+ /// The space char
+ ///
+ private const char SpaceChar = ' ';
+
+ ///
+ /// Takes a filename and removes invalid characters
+ ///
+ /// The filename.
+ /// System.String.
+ /// filename
+ public string GetValidFilename(string filename)
+ {
+ if (string.IsNullOrEmpty(filename))
+ {
+ throw new ArgumentNullException("filename");
+ }
+
+ var builder = new StringBuilder(filename);
+
+ foreach (var c in _invalidFileNameChars)
+ {
+ builder = builder.Replace(c, SpaceChar);
+ }
+
+ return builder.ToString();
+ }
+
+ ///
+ /// Gets the creation time UTC.
+ ///
+ /// The info.
+ /// DateTime.
+ public DateTime GetCreationTimeUtc(FileSystemInfo info)
+ {
+ // This could throw an error on some file systems that have dates out of range
+ try
+ {
+ return info.CreationTimeUtc;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error determining CreationTimeUtc for {0}", ex, info.FullName);
+ return DateTime.MinValue;
+ }
+ }
+
+ ///
+ /// Gets the creation time UTC.
+ ///
+ /// The path.
+ /// DateTime.
+ public DateTime GetCreationTimeUtc(string path)
+ {
+ return GetCreationTimeUtc(GetFileSystemInfo(path));
+ }
+
+ public DateTime GetCreationTimeUtc(FileSystemMetadata info)
+ {
+ return info.CreationTimeUtc;
+ }
+
+ public DateTime GetLastWriteTimeUtc(FileSystemMetadata info)
+ {
+ return info.LastWriteTimeUtc;
+ }
+
+ ///
+ /// Gets the creation time UTC.
+ ///
+ /// The info.
+ /// DateTime.
+ public DateTime GetLastWriteTimeUtc(FileSystemInfo info)
+ {
+ // This could throw an error on some file systems that have dates out of range
+ try
+ {
+ return info.LastWriteTimeUtc;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error determining LastAccessTimeUtc for {0}", ex, info.FullName);
+ return DateTime.MinValue;
+ }
+ }
+
+ ///
+ /// Gets the last write time UTC.
+ ///
+ /// The path.
+ /// DateTime.
+ public DateTime GetLastWriteTimeUtc(string path)
+ {
+ return GetLastWriteTimeUtc(GetFileSystemInfo(path));
+ }
+
+ ///
+ /// Gets the file stream.
+ ///
+ /// The path.
+ /// The mode.
+ /// The access.
+ /// The share.
+ /// if set to true [is asynchronous].
+ /// FileStream.
+ public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
+ {
+ if (_supportsAsyncFileStreams && isAsync)
+ {
+ return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 262144, true);
+ }
+
+ return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 262144);
+ }
+
+ private FileMode GetFileMode(FileOpenMode mode)
+ {
+ switch (mode)
+ {
+ case FileOpenMode.Append:
+ return FileMode.Append;
+ case FileOpenMode.Create:
+ return FileMode.Create;
+ case FileOpenMode.CreateNew:
+ return FileMode.CreateNew;
+ case FileOpenMode.Open:
+ return FileMode.Open;
+ case FileOpenMode.OpenOrCreate:
+ return FileMode.OpenOrCreate;
+ case FileOpenMode.Truncate:
+ return FileMode.Truncate;
+ default:
+ throw new Exception("Unrecognized FileOpenMode");
+ }
+ }
+
+ private FileAccess GetFileAccess(FileAccessMode mode)
+ {
+ switch (mode)
+ {
+ case FileAccessMode.ReadWrite:
+ return FileAccess.ReadWrite;
+ case FileAccessMode.Write:
+ return FileAccess.Write;
+ case FileAccessMode.Read:
+ return FileAccess.Read;
+ default:
+ throw new Exception("Unrecognized FileAccessMode");
+ }
+ }
+
+ private FileShare GetFileShare(FileShareMode mode)
+ {
+ switch (mode)
+ {
+ case FileShareMode.ReadWrite:
+ return FileShare.ReadWrite;
+ case FileShareMode.Write:
+ return FileShare.Write;
+ case FileShareMode.Read:
+ return FileShare.Read;
+ case FileShareMode.None:
+ return FileShare.None;
+ default:
+ throw new Exception("Unrecognized FileShareMode");
+ }
+ }
+
+ public void SetHidden(string path, bool isHidden)
+ {
+ var info = GetFileInfo(path);
+
+ if (info.Exists && info.IsHidden != isHidden)
+ {
+ if (isHidden)
+ {
+ File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.Hidden);
+ }
+ else
+ {
+ FileAttributes attributes = File.GetAttributes(path);
+ attributes = RemoveAttribute(attributes, FileAttributes.Hidden);
+ File.SetAttributes(path, attributes);
+ }
+ }
+ }
+
+ public void SetReadOnly(string path, bool isReadOnly)
+ {
+ var info = GetFileInfo(path);
+
+ if (info.Exists && info.IsReadOnly != isReadOnly)
+ {
+ if (isReadOnly)
+ {
+ File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.ReadOnly);
+ }
+ else
+ {
+ FileAttributes attributes = File.GetAttributes(path);
+ attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
+ File.SetAttributes(path, attributes);
+ }
+ }
+ }
+
+ private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
+ {
+ return attributes & ~attributesToRemove;
+ }
+
+ ///
+ /// Swaps the files.
+ ///
+ /// The file1.
+ /// The file2.
+ public void SwapFiles(string file1, string file2)
+ {
+ if (string.IsNullOrEmpty(file1))
+ {
+ throw new ArgumentNullException("file1");
+ }
+
+ if (string.IsNullOrEmpty(file2))
+ {
+ throw new ArgumentNullException("file2");
+ }
+
+ var temp1 = Path.GetTempFileName();
+
+ // Copying over will fail against hidden files
+ RemoveHiddenAttribute(file1);
+ RemoveHiddenAttribute(file2);
+
+ CopyFile(file1, temp1, true);
+
+ CopyFile(file2, file1, true);
+ CopyFile(temp1, file2, true);
+
+ DeleteFile(temp1);
+ }
+
+ ///
+ /// Removes the hidden attribute.
+ ///
+ /// The path.
+ private void RemoveHiddenAttribute(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ var currentFile = new FileInfo(path);
+
+ // This will fail if the file is hidden
+ if (currentFile.Exists)
+ {
+ if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+ {
+ currentFile.Attributes &= ~FileAttributes.Hidden;
+ }
+ }
+ }
+
+ public bool ContainsSubPath(string parentPath, string path)
+ {
+ if (string.IsNullOrEmpty(parentPath))
+ {
+ throw new ArgumentNullException("parentPath");
+ }
+
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ return path.IndexOf(parentPath.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
+ public bool IsRootPath(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ var parent = Path.GetDirectoryName(path);
+
+ if (!string.IsNullOrEmpty(parent))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public string NormalizePath(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ if (path.EndsWith(":\\", StringComparison.OrdinalIgnoreCase))
+ {
+ return path;
+ }
+
+ return path.TrimEnd(Path.DirectorySeparatorChar);
+ }
+
+ public string GetFileNameWithoutExtension(FileSystemMetadata info)
+ {
+ if (info.IsDirectory)
+ {
+ return info.Name;
+ }
+
+ return Path.GetFileNameWithoutExtension(info.FullName);
+ }
+
+ public string GetFileNameWithoutExtension(string path)
+ {
+ return Path.GetFileNameWithoutExtension(path);
+ }
+
+ public bool IsPathFile(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ // Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
+
+ if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
+ !path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ return true;
+
+ //return Path.IsPathRooted(path);
+ }
+
+ public void DeleteFile(string path)
+ {
+ var fileInfo = GetFileInfo(path);
+
+ if (fileInfo.Exists)
+ {
+ if (fileInfo.IsHidden)
+ {
+ SetHidden(path, false);
+ }
+ if (fileInfo.IsReadOnly)
+ {
+ SetReadOnly(path, false);
+ }
+ }
+
+ File.Delete(path);
+ }
+
+ public void DeleteDirectory(string path, bool recursive)
+ {
+ Directory.Delete(path, recursive);
+ }
+
+ public void CreateDirectory(string path)
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ public List GetDrives()
+ {
+ // Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
+ return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemMetadata
+ {
+ Name = GetName(d),
+ FullName = d.RootDirectory.FullName,
+ IsDirectory = true
+
+ }).ToList();
+ }
+
+ private string GetName(DriveInfo drive)
+ {
+ return drive.Name;
+ }
+
+ public IEnumerable GetDirectories(string path, bool recursive = false)
+ {
+ var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+
+ return ToMetadata(path, new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
+ }
+
+ public IEnumerable GetFiles(string path, bool recursive = false)
+ {
+ var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+
+ return ToMetadata(path, new DirectoryInfo(path).EnumerateFiles("*", searchOption));
+ }
+
+ public IEnumerable GetFileSystemEntries(string path, bool recursive = false)
+ {
+ var directoryInfo = new DirectoryInfo(path);
+ var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+
+ if (EnableFileSystemRequestConcat)
+ {
+ return ToMetadata(path, directoryInfo.EnumerateDirectories("*", searchOption))
+ .Concat(ToMetadata(path, directoryInfo.EnumerateFiles("*", searchOption)));
+ }
+
+ return ToMetadata(path, directoryInfo.EnumerateFileSystemInfos("*", searchOption));
+ }
+
+ private IEnumerable ToMetadata(string parentPath, IEnumerable infos)
+ {
+ return infos.Select(i =>
+ {
+ try
+ {
+ return GetFileSystemMetadata(i);
+ }
+ catch (PathTooLongException)
+ {
+ // Can't log using the FullName because it will throw the PathTooLongExceptiona again
+ //Logger.Warn("Path too long: {0}", i.FullName);
+ Logger.Warn("File or directory path too long. Parent folder: {0}", parentPath);
+ return null;
+ }
+
+ }).Where(i => i != null);
+ }
+
+ public string[] ReadAllLines(string path)
+ {
+ return File.ReadAllLines(path);
+ }
+
+ public void WriteAllLines(string path, IEnumerable lines)
+ {
+ File.WriteAllLines(path, lines);
+ }
+
+ public Stream OpenRead(string path)
+ {
+ return File.OpenRead(path);
+ }
+
+ public void CopyFile(string source, string target, bool overwrite)
+ {
+ File.Copy(source, target, overwrite);
+ }
+
+ public void MoveFile(string source, string target)
+ {
+ File.Move(source, target);
+ }
+
+ public void MoveDirectory(string source, string target)
+ {
+ Directory.Move(source, target);
+ }
+
+ public bool DirectoryExists(string path)
+ {
+ return Directory.Exists(path);
+ }
+
+ public bool FileExists(string path)
+ {
+ return File.Exists(path);
+ }
+
+ public string ReadAllText(string path)
+ {
+ return File.ReadAllText(path);
+ }
+
+ public byte[] ReadAllBytes(string path)
+ {
+ return File.ReadAllBytes(path);
+ }
+
+ public void WriteAllText(string path, string text, Encoding encoding)
+ {
+ File.WriteAllText(path, text, encoding);
+ }
+
+ public void WriteAllText(string path, string text)
+ {
+ File.WriteAllText(path, text);
+ }
+
+ public void WriteAllBytes(string path, byte[] bytes)
+ {
+ File.WriteAllBytes(path, bytes);
+ }
+
+ public string ReadAllText(string path, Encoding encoding)
+ {
+ return File.ReadAllText(path, encoding);
+ }
+
+ public IEnumerable GetDirectoryPaths(string path, bool recursive = false)
+ {
+ var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+ return Directory.EnumerateDirectories(path, "*", searchOption);
+ }
+
+ public IEnumerable GetFilePaths(string path, bool recursive = false)
+ {
+ var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+ return Directory.EnumerateFiles(path, "*", searchOption);
+ }
+
+ public IEnumerable GetFileSystemEntryPaths(string path, bool recursive = false)
+ {
+ var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+ return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
+ }
+
+ public virtual void SetExecutable(string path)
+ {
+
+ }
+ }
+}
diff --git a/MediaBrowser.Common.Implementations/Logging/NLogger.cs b/Emby.Common.Implementations/Logging/NLogger.cs
similarity index 99%
rename from MediaBrowser.Common.Implementations/Logging/NLogger.cs
rename to Emby.Common.Implementations/Logging/NLogger.cs
index 11f41261a2..8abd3d0d92 100644
--- a/MediaBrowser.Common.Implementations/Logging/NLogger.cs
+++ b/Emby.Common.Implementations/Logging/NLogger.cs
@@ -2,7 +2,7 @@
using System;
using System.Text;
-namespace MediaBrowser.Common.Implementations.Logging
+namespace Emby.Common.Implementations.Logging
{
///
/// Class NLogger
diff --git a/Emby.Common.Implementations/Logging/NlogManager.cs b/Emby.Common.Implementations/Logging/NlogManager.cs
new file mode 100644
index 0000000000..f7b723e8bc
--- /dev/null
+++ b/Emby.Common.Implementations/Logging/NlogManager.cs
@@ -0,0 +1,544 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using NLog;
+using NLog.Config;
+using NLog.Filters;
+using NLog.Targets;
+using NLog.Targets.Wrappers;
+using MediaBrowser.Model.Logging;
+
+namespace Emby.Common.Implementations.Logging
+{
+ ///
+ /// Class NlogManager
+ ///
+ public class NlogManager : ILogManager
+ {
+ #region Private Fields
+
+ private LogSeverity _severity = LogSeverity.Debug;
+
+ ///
+ /// Gets or sets the log directory.
+ ///
+ /// The log directory.
+ private readonly string LogDirectory;
+
+ ///
+ /// Gets or sets the log file prefix.
+ ///
+ /// The log file prefix.
+ private readonly string LogFilePrefix;
+
+ #endregion
+
+ #region Event Declarations
+
+ ///
+ /// Occurs when [logger loaded].
+ ///
+ public event EventHandler LoggerLoaded;
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the log file path.
+ ///
+ /// The log file path.
+ public string LogFilePath { get; private set; }
+
+ ///
+ /// Gets or sets the exception message prefix.
+ ///
+ /// The exception message prefix.
+ public string ExceptionMessagePrefix { get; set; }
+
+ public string NLogConfigurationFilePath { get; set; }
+
+ public LogSeverity LogSeverity
+ {
+
+ get
+ {
+ return _severity;
+ }
+
+ set
+ {
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "SET LogSeverity, _severity = [{0}], value = [{1}]",
+ _severity.ToString(),
+ value.ToString()
+ ));
+
+ var changed = _severity != value;
+
+ _severity = value;
+
+ if (changed)
+ {
+ UpdateLogLevel(value);
+ }
+
+ }
+ }
+
+ #endregion
+
+ #region Constructor(s)
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The log directory.
+ /// The log file name prefix.
+ public NlogManager(string logDirectory, string logFileNamePrefix)
+ {
+ DebugFileWriter(
+ logDirectory, String.Format(
+ "NlogManager constructor called, logDirectory is [{0}], logFileNamePrefix is [{1}], _severity is [{2}].",
+ logDirectory,
+ logFileNamePrefix,
+ _severity.ToString()
+ ));
+
+ LogDirectory = logDirectory;
+ LogFilePrefix = logFileNamePrefix;
+
+ LogManager.Configuration = new LoggingConfiguration();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The log directory.
+ /// The log file name prefix.
+ public NlogManager(string logDirectory, string logFileNamePrefix, LogSeverity initialSeverity) : this(logDirectory, logFileNamePrefix)
+ {
+ _severity = initialSeverity;
+
+ DebugFileWriter(
+ logDirectory, String.Format(
+ "NlogManager constructor called, logDirectory is [{0}], logFileNamePrefix is [{1}], _severity is [{2}].",
+ logDirectory,
+ logFileNamePrefix,
+ _severity.ToString()
+ ));
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Adds the file target.
+ ///
+ /// The path.
+ /// The level.
+ private void AddFileTarget(string path, LogSeverity level)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "AddFileTarget called, path = [{0}], level = [{1}].",
+ path,
+ level.ToString()
+ ));
+
+ RemoveTarget("ApplicationLogFileWrapper");
+
+ var wrapper = new AsyncTargetWrapper();
+ wrapper.Name = "ApplicationLogFileWrapper";
+
+ var logFile = new FileTarget
+ {
+ FileName = path,
+ Layout = "${longdate} ${level} ${logger}: ${message}"
+ };
+
+ logFile.Name = "ApplicationLogFile";
+
+ wrapper.WrappedTarget = logFile;
+
+ AddLogTarget(wrapper, level);
+
+ }
+
+ ///
+ /// Gets the log level.
+ ///
+ /// The severity.
+ /// LogLevel.
+ /// Unrecognized LogSeverity
+ private LogLevel GetLogLevel(LogSeverity severity)
+ {
+ switch (severity)
+ {
+ case LogSeverity.Debug:
+ return LogLevel.Debug;
+ case LogSeverity.Error:
+ return LogLevel.Error;
+ case LogSeverity.Fatal:
+ return LogLevel.Fatal;
+ case LogSeverity.Info:
+ return LogLevel.Info;
+ case LogSeverity.Warn:
+ return LogLevel.Warn;
+ default:
+ throw new ArgumentException("Unrecognized LogSeverity");
+ }
+ }
+
+ private void UpdateLogLevel(LogSeverity newLevel)
+ {
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "UpdateLogLevel called, newLevel = [{0}].",
+ newLevel.ToString()
+ ));
+
+ var level = GetLogLevel(newLevel);
+
+ var rules = LogManager.Configuration.LoggingRules;
+
+ foreach (var rule in rules)
+ {
+ if (!rule.IsLoggingEnabledForLevel(level))
+ {
+ rule.EnableLoggingForLevel(level);
+ }
+ foreach (var lev in rule.Levels.ToArray())
+ {
+ if (lev < level)
+ {
+ rule.DisableLoggingForLevel(lev);
+ }
+ }
+ }
+ }
+
+ private void AddCustomFilters(string defaultLoggerNamePattern, LoggingRule defaultRule)
+ {
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "AddCustomFilters called, defaultLoggerNamePattern = [{0}], defaultRule.LoggerNamePattern = [{1}].",
+ defaultLoggerNamePattern,
+ defaultRule.LoggerNamePattern
+ ));
+
+ try
+ {
+ var customConfig = new NLog.Config.XmlLoggingConfiguration(NLogConfigurationFilePath);
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Custom Configuration Loaded, Rule Count = [{0}].",
+ customConfig.LoggingRules.Count.ToString()
+ ));
+
+ foreach (var customRule in customConfig.LoggingRules)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Read Custom Rule, LoggerNamePattern = [{0}], Targets = [{1}].",
+ customRule.LoggerNamePattern,
+ string.Join(",", customRule.Targets.Select(x => x.Name).ToList())
+ ));
+
+ if (customRule.LoggerNamePattern.Equals(defaultLoggerNamePattern))
+ {
+
+ if (customRule.Targets.Any((arg) => arg.Name.Equals(defaultRule.Targets.First().Name)))
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Custom rule filters can be applied to this target, Filter Count = [{0}].",
+ customRule.Filters.Count.ToString()
+ ));
+
+ foreach (ConditionBasedFilter customFilter in customRule.Filters)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Read Custom Filter, Filter = [{0}], Action = [{1}], Type = [{2}].",
+ customFilter.Condition.ToString(),
+ customFilter.Action.ToString(),
+ customFilter.GetType().ToString()
+ ));
+
+ defaultRule.Filters.Add(customFilter);
+
+ }
+ }
+ else
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Ignoring custom rule as [Target] does not match."
+ ));
+
+ }
+
+ }
+ else
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Ignoring custom rule as [LoggerNamePattern] does not match."
+ ));
+
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // Intentionally do nothing, prevent issues affecting normal execution.
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Exception in AddCustomFilters, ex.Message = [{0}].",
+ ex.Message
+ )
+ );
+
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Gets the logger.
+ ///
+ /// The name.
+ /// ILogger.
+ public MediaBrowser.Model.Logging.ILogger GetLogger(string name)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "GetLogger called, name = [{0}].",
+ name
+ ));
+
+ return new NLogger(name, this);
+
+ }
+
+ ///
+ /// Adds the log target.
+ ///
+ /// The target.
+ /// The level.
+ public void AddLogTarget(Target target, LogSeverity level)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "AddLogTarget called, target.Name = [{0}], level = [{1}].",
+ target.Name,
+ level.ToString()
+ ));
+
+ string loggerNamePattern = "*";
+ var config = LogManager.Configuration;
+ var rule = new LoggingRule(loggerNamePattern, GetLogLevel(level), target);
+
+ config.AddTarget(target.Name, target);
+
+ AddCustomFilters(loggerNamePattern, rule);
+
+ config.LoggingRules.Add(rule);
+
+ LogManager.Configuration = config;
+
+ }
+
+ ///
+ /// Removes the target.
+ ///
+ /// The name.
+ public void RemoveTarget(string name)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "RemoveTarget called, name = [{0}].",
+ name
+ ));
+
+ var config = LogManager.Configuration;
+
+ var target = config.FindTargetByName(name);
+
+ if (target != null)
+ {
+ foreach (var rule in config.LoggingRules.ToList())
+ {
+ var contains = rule.Targets.Contains(target);
+
+ rule.Targets.Remove(target);
+
+ if (contains)
+ {
+ config.LoggingRules.Remove(rule);
+ }
+ }
+
+ config.RemoveTarget(name);
+ LogManager.Configuration = config;
+ }
+ }
+
+ public void AddConsoleOutput()
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "AddConsoleOutput called."
+ ));
+
+ RemoveTarget("ConsoleTargetWrapper");
+
+ var wrapper = new AsyncTargetWrapper();
+ wrapper.Name = "ConsoleTargetWrapper";
+
+ var target = new ConsoleTarget()
+ {
+ Layout = "${level}, ${logger}, ${message}",
+ Error = false
+ };
+
+ target.Name = "ConsoleTarget";
+
+ wrapper.WrappedTarget = target;
+
+ AddLogTarget(wrapper, LogSeverity);
+
+ }
+
+ public void RemoveConsoleOutput()
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "RemoveConsoleOutput called."
+ ));
+
+ RemoveTarget("ConsoleTargetWrapper");
+
+ }
+
+ ///
+ /// Reloads the logger, maintaining the current log level.
+ ///
+ public void ReloadLogger()
+ {
+ ReloadLogger(LogSeverity);
+ }
+
+ ///
+ /// Reloads the logger, using the specified logging level.
+ ///
+ /// The level.
+ public void ReloadLogger(LogSeverity level)
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "ReloadLogger called, level = [{0}], LogFilePath (existing) = [{1}].",
+ level.ToString(),
+ LogFilePath
+ ));
+
+ LogFilePath = Path.Combine(LogDirectory, LogFilePrefix + "-" + decimal.Floor(DateTime.Now.Ticks / 10000000) + ".txt");
+
+ Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
+
+ AddFileTarget(LogFilePath, level);
+
+ LogSeverity = level;
+
+ if (LoggerLoaded != null)
+ {
+ try
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "ReloadLogger called, raised event LoggerLoaded."
+ ));
+
+ LoggerLoaded(this, EventArgs.Empty);
+
+ }
+ catch (Exception ex)
+ {
+ GetLogger("Logger").ErrorException("Error in LoggerLoaded event", ex);
+ }
+ }
+ }
+
+ ///
+ /// Flushes this instance.
+ ///
+ public void Flush()
+ {
+
+ DebugFileWriter(
+ LogDirectory, String.Format(
+ "Flush called."
+ ));
+
+ LogManager.Flush();
+
+ }
+
+ #endregion
+
+ #region Conditional Debug Methods
+
+ ///
+ /// DEBUG: Standalone method to write out debug to assist with logger development/troubleshooting.
+ ///
+ /// - The output file will be written to the server's log directory.
+ /// - Calls to the method are safe and will never throw any exceptions.
+ /// - Method calls will be omitted unless the library is compiled with DEBUG defined.
+ ///
+ ///
+ private static void DebugFileWriter(string logDirectory, string message)
+ {
+#if DEBUG
+ try
+ {
+
+ System.IO.File.AppendAllText(
+ Path.Combine(logDirectory, "NlogManager.txt"),
+ String.Format(
+ "{0} : {1}{2}",
+ System.DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
+ message,
+ System.Environment.NewLine
+ )
+ );
+
+ }
+ catch (Exception ex)
+ {
+ // Intentionally do nothing, prevent issues affecting normal execution.
+ }
+#endif
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs b/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs
new file mode 100644
index 0000000000..8476cea326
--- /dev/null
+++ b/Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Emby.Common.Implementations.Net
+{
+ ///
+ /// Correclty implements the interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an property.
+ ///
+ public abstract class DisposableManagedObjectBase : IDisposable
+ {
+
+ #region Public Methods
+
+ ///
+ /// Override this method and dispose any objects you own the lifetime of if disposing is true;
+ ///
+ /// True if managed objects should be disposed, if false, only unmanaged resources should be released.
+ protected abstract void Dispose(bool disposing);
+
+ ///
+ /// Throws and if the property is true.
+ ///
+ ///
+ /// Thrown if the property is true.
+ ///
+ protected virtual void ThrowIfDisposed()
+ {
+ if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName);
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Sets or returns a boolean indicating whether or not this instance has been disposed.
+ ///
+ ///
+ public bool IsDisposed
+ {
+ get;
+ private set;
+ }
+
+ #endregion
+
+ #region IDisposable Members
+
+ ///
+ /// Disposes this object instance and all internally managed resources.
+ ///
+ ///
+ /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes.
+ ///
+ ///
+ public void Dispose()
+ {
+ try
+ {
+ IsDisposed = true;
+
+ Dispose(true);
+ }
+ finally
+ {
+ GC.SuppressFinalize(this);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Emby.Common.Implementations/Net/NetSocket.cs b/Emby.Common.Implementations/Net/NetSocket.cs
new file mode 100644
index 0000000000..bc012dfe2d
--- /dev/null
+++ b/Emby.Common.Implementations/Net/NetSocket.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using Emby.Common.Implementations.Networking;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Logging;
+
+namespace Emby.Common.Implementations.Net
+{
+ public class NetSocket : ISocket
+ {
+ public Socket Socket { get; private set; }
+ private readonly ILogger _logger;
+
+ public bool DualMode { get; private set; }
+
+ public NetSocket(Socket socket, ILogger logger, bool isDualMode)
+ {
+ if (socket == null)
+ {
+ throw new ArgumentNullException("socket");
+ }
+ if (logger == null)
+ {
+ throw new ArgumentNullException("logger");
+ }
+
+ Socket = socket;
+ _logger = logger;
+ DualMode = isDualMode;
+ }
+
+ public IpEndPointInfo LocalEndPoint
+ {
+ get
+ {
+ return NetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.LocalEndPoint);
+ }
+ }
+
+ public IpEndPointInfo RemoteEndPoint
+ {
+ get
+ {
+ return NetworkManager.ToIpEndPointInfo((IPEndPoint)Socket.RemoteEndPoint);
+ }
+ }
+
+ public void Close()
+ {
+#if NET46
+ Socket.Close();
+#else
+ Socket.Dispose();
+#endif
+ }
+
+ public void Shutdown(bool both)
+ {
+ if (both)
+ {
+ Socket.Shutdown(SocketShutdown.Both);
+ }
+ else
+ {
+ // Change interface if ever needed
+ throw new NotImplementedException();
+ }
+ }
+
+ public void Listen(int backlog)
+ {
+ Socket.Listen(backlog);
+ }
+
+ public void Bind(IpEndPointInfo endpoint)
+ {
+ var nativeEndpoint = NetworkManager.ToIPEndPoint(endpoint);
+
+ Socket.Bind(nativeEndpoint);
+ }
+
+ private SocketAcceptor _acceptor;
+ public void StartAccept(Action onAccept, Func isClosed)
+ {
+ _acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed, DualMode);
+
+ _acceptor.StartAccept();
+ }
+
+ public void Dispose()
+ {
+ Socket.Dispose();
+ }
+ }
+}
diff --git a/Emby.Common.Implementations/Net/SocketAcceptor.cs b/Emby.Common.Implementations/Net/SocketAcceptor.cs
new file mode 100644
index 0000000000..d4c6d33e52
--- /dev/null
+++ b/Emby.Common.Implementations/Net/SocketAcceptor.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Net.Sockets;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+
+namespace Emby.Common.Implementations.Net
+{
+ public class SocketAcceptor
+ {
+ private readonly ILogger _logger;
+ private readonly Socket _originalSocket;
+ private readonly Func _isClosed;
+ private readonly Action _onAccept;
+ private readonly bool _isDualMode;
+
+ public SocketAcceptor(ILogger logger, Socket originalSocket, Action onAccept, Func isClosed, bool isDualMode)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException("logger");
+ }
+ if (originalSocket == null)
+ {
+ throw new ArgumentNullException("originalSocket");
+ }
+ if (onAccept == null)
+ {
+ throw new ArgumentNullException("onAccept");
+ }
+ if (isClosed == null)
+ {
+ throw new ArgumentNullException("isClosed");
+ }
+
+ _logger = logger;
+ _originalSocket = originalSocket;
+ _isClosed = isClosed;
+ _isDualMode = isDualMode;
+ _onAccept = onAccept;
+ }
+
+ public void StartAccept()
+ {
+ Socket dummy = null;
+ StartAccept(null, ref dummy);
+ }
+
+ public void StartAccept(SocketAsyncEventArgs acceptEventArg, ref Socket accepted)
+ {
+ if (acceptEventArg == null)
+ {
+ acceptEventArg = new SocketAsyncEventArgs();
+ acceptEventArg.Completed += new EventHandler(AcceptEventArg_Completed);
+ }
+ else
+ {
+ // socket must be cleared since the context object is being reused
+ acceptEventArg.AcceptSocket = null;
+ }
+
+ try
+ {
+ bool willRaiseEvent = _originalSocket.AcceptAsync(acceptEventArg);
+
+ if (!willRaiseEvent)
+ {
+ ProcessAccept(acceptEventArg);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (accepted != null)
+ {
+ try
+ {
+#if NET46
+ accepted.Close();
+#else
+ accepted.Dispose();
+#endif
+ }
+ catch
+ {
+ }
+ accepted = null;
+ }
+ }
+ }
+
+ // This method is the callback method associated with Socket.AcceptAsync
+ // operations and is invoked when an accept operation is complete
+ //
+ void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
+ {
+ ProcessAccept(e);
+ }
+
+ private void ProcessAccept(SocketAsyncEventArgs e)
+ {
+ if (_isClosed())
+ {
+ return;
+ }
+
+ // http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.acceptasync%28v=vs.110%29.aspx
+ // Under certain conditions ConnectionReset can occur
+ // Need to attept to re-accept
+ if (e.SocketError == SocketError.ConnectionReset)
+ {
+ _logger.Error("SocketError.ConnectionReset reported. Attempting to re-accept.");
+ Socket dummy = null;
+ StartAccept(e, ref dummy);
+ return;
+ }
+
+ var acceptSocket = e.AcceptSocket;
+ if (acceptSocket != null)
+ {
+ //ProcessAccept(acceptSocket);
+ _onAccept(new NetSocket(acceptSocket, _logger, _isDualMode));
+ }
+
+ // Accept the next connection request
+ StartAccept(e, ref acceptSocket);
+ }
+ }
+}
diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs
new file mode 100644
index 0000000000..70c7ba8458
--- /dev/null
+++ b/Emby.Common.Implementations/Net/SocketFactory.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Emby.Common.Implementations.Networking;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+
+namespace Emby.Common.Implementations.Net
+{
+ public class SocketFactory : ISocketFactory
+ {
+ // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS
+ // Be careful to check any changes compile and work for all platform projects it is shared in.
+
+ // Not entirely happy with this. Would have liked to have done something more generic/reusable,
+ // but that wasn't really the point so kept to YAGNI principal for now, even if the
+ // interfaces are a bit ugly, specific and make assumptions.
+
+ private readonly ILogger _logger;
+
+ public SocketFactory(ILogger logger)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException("logger");
+ }
+
+ _logger = logger;
+ }
+
+ public ISocket CreateSocket(IpAddressFamily family, MediaBrowser.Model.Net.SocketType socketType, MediaBrowser.Model.Net.ProtocolType protocolType, bool dualMode)
+ {
+ try
+ {
+ var addressFamily = family == IpAddressFamily.InterNetwork
+ ? AddressFamily.InterNetwork
+ : AddressFamily.InterNetworkV6;
+
+ var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
+
+ if (dualMode)
+ {
+ socket.DualMode = true;
+ }
+
+ return new NetSocket(socket, _logger, dualMode);
+ }
+ catch (SocketException ex)
+ {
+ throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex);
+ }
+ }
+
+ #region ISocketFactory Members
+
+ ///
+ /// Creates a new UDP socket and binds it to the specified local port.
+ ///
+ /// An integer specifying the local port to bind the socket to.
+ public IUdpSocket CreateUdpSocket(int localPort)
+ {
+ if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
+
+ var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+ try
+ {
+ retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ return new UdpSocket(retVal, localPort, IPAddress.Any);
+ }
+ catch
+ {
+ if (retVal != null)
+ retVal.Dispose();
+
+ throw;
+ }
+ }
+
+ ///
+ /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
+ ///
+ /// An implementation of the interface used by RSSDP components to perform socket operations.
+ public IUdpSocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
+ {
+ if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
+
+ var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+ try
+ {
+ retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
+
+ var localIp = NetworkManager.ToIPAddress(localIpAddress);
+
+ retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp));
+ return new UdpSocket(retVal, localPort, localIp);
+ }
+ catch
+ {
+ if (retVal != null)
+ retVal.Dispose();
+
+ throw;
+ }
+ }
+
+ ///
+ /// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port.
+ ///
+ /// The multicast IP address to make the socket a member of.
+ /// The multicast time to live value for the socket.
+ /// The number of the local port to bind to.
+ ///
+ public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort)
+ {
+ if (ipAddress == null) throw new ArgumentNullException("ipAddress");
+ if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", "ipAddress");
+ if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", "multicastTimeToLive");
+ if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
+
+ var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+
+ try
+ {
+#if NET46
+ retVal.ExclusiveAddressUse = false;
+#else
+ // The ExclusiveAddressUse socket option is a Windows-specific option that, when set to "true," tells Windows not to allow another socket to use the same local address as this socket
+ // See https://github.com/dotnet/corefx/pull/11509 for more details
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
+ {
+ retVal.ExclusiveAddressUse = false;
+ }
+#endif
+ //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
+ retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
+
+ var localIp = IPAddress.Any;
+
+ retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse(ipAddress), localIp));
+ retVal.MulticastLoopback = true;
+
+ return new UdpSocket(retVal, localPort, localIp);
+ }
+ catch
+ {
+ if (retVal != null)
+ retVal.Dispose();
+
+ throw;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs
new file mode 100644
index 0000000000..b2af9d162e
--- /dev/null
+++ b/Emby.Common.Implementations/Net/UdpSocket.cs
@@ -0,0 +1,242 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Security;
+using System.Threading.Tasks;
+using Emby.Common.Implementations.Networking;
+using MediaBrowser.Model.Net;
+
+namespace Emby.Common.Implementations.Net
+{
+ // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS
+ // Be careful to check any changes compile and work for all platform projects it is shared in.
+
+ internal sealed class UdpSocket : DisposableManagedObjectBase, IUdpSocket
+ {
+
+ #region Fields
+
+ private Socket _Socket;
+ private int _LocalPort;
+ #endregion
+
+ #region Constructors
+
+ public UdpSocket(Socket socket, int localPort, IPAddress ip)
+ {
+ if (socket == null) throw new ArgumentNullException("socket");
+
+ _Socket = socket;
+ _LocalPort = localPort;
+ LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
+
+ _Socket.Bind(new IPEndPoint(ip, _LocalPort));
+ }
+
+ #endregion
+
+ public IpAddressInfo LocalIPAddress
+ {
+ get;
+ private set;
+ }
+
+ #region IUdpSocket Members
+
+ public Task ReceiveAsync()
+ {
+ ThrowIfDisposed();
+
+ var tcs = new TaskCompletionSource();
+
+ EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0);
+ var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
+ state.TaskCompletionSource = tcs;
+
+#if NETSTANDARD1_6
+ _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.RemoteEndPoint)
+ .ContinueWith((task, asyncState) =>
+ {
+ if (task.Status != TaskStatus.Faulted)
+ {
+ var receiveState = asyncState as AsyncReceiveState;
+ receiveState.RemoteEndPoint = task.Result.RemoteEndPoint;
+ ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress);
+ }
+ }, state);
+#else
+ _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state);
+#endif
+
+ return tcs.Task;
+ }
+
+ public Task SendAsync(byte[] buffer, int size, IpEndPointInfo endPoint)
+ {
+ ThrowIfDisposed();
+
+ if (buffer == null) throw new ArgumentNullException("messageData");
+ if (endPoint == null) throw new ArgumentNullException("endPoint");
+
+ var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint);
+
+#if NETSTANDARD1_6
+
+ if (size != buffer.Length)
+ {
+ byte[] copy = new byte[size];
+ Buffer.BlockCopy(buffer, 0, copy, 0, size);
+ buffer = copy;
+ }
+
+ _Socket.SendTo(buffer, ipEndPoint);
+ return Task.FromResult(true);
+#else
+ var taskSource = new TaskCompletionSource();
+
+ try
+ {
+ _Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result =>
+ {
+ try
+ {
+ _Socket.EndSend(result);
+ taskSource.TrySetResult(true);
+ }
+ catch (Exception ex)
+ {
+ taskSource.TrySetException(ex);
+ }
+
+ }, null);
+ }
+ catch (Exception ex)
+ {
+ taskSource.TrySetException(ex);
+ }
+
+ //_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port));
+
+ return taskSource.Task;
+#endif
+ }
+
+ #endregion
+
+ #region Overrides
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ var socket = _Socket;
+ if (socket != null)
+ socket.Dispose();
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private static void ProcessResponse(AsyncReceiveState state, Func receiveData, IpAddressInfo localIpAddress)
+ {
+ try
+ {
+ var bytesRead = receiveData();
+
+ var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
+ state.TaskCompletionSource.SetResult(
+ new SocketReceiveResult
+ {
+ Buffer = state.Buffer,
+ ReceivedBytes = bytesRead,
+ RemoteEndPoint = ToIpEndPointInfo(ipEndPoint),
+ LocalIPAddress = localIpAddress
+ }
+ );
+ }
+ catch (ObjectDisposedException)
+ {
+ state.TaskCompletionSource.SetCanceled();
+ }
+ catch (SocketException se)
+ {
+ if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown)
+ state.TaskCompletionSource.SetException(se);
+ else
+ state.TaskCompletionSource.SetCanceled();
+ }
+ catch (Exception ex)
+ {
+ state.TaskCompletionSource.SetException(ex);
+ }
+ }
+
+ private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
+ {
+ if (endpoint == null)
+ {
+ return null;
+ }
+
+ return NetworkManager.ToIpEndPointInfo(endpoint);
+ }
+
+ private void ProcessResponse(IAsyncResult asyncResult)
+ {
+#if NET46
+ var state = asyncResult.AsyncState as AsyncReceiveState;
+ try
+ {
+ var bytesRead = state.Socket.EndReceiveFrom(asyncResult, ref state.RemoteEndPoint);
+
+ var ipEndPoint = state.RemoteEndPoint as IPEndPoint;
+ state.TaskCompletionSource.SetResult(
+ new SocketReceiveResult
+ {
+ Buffer = state.Buffer,
+ ReceivedBytes = bytesRead,
+ RemoteEndPoint = ToIpEndPointInfo(ipEndPoint),
+ LocalIPAddress = LocalIPAddress
+ }
+ );
+ }
+ catch (ObjectDisposedException)
+ {
+ state.TaskCompletionSource.SetCanceled();
+ }
+ catch (Exception ex)
+ {
+ state.TaskCompletionSource.SetException(ex);
+ }
+#endif
+ }
+
+ #endregion
+
+ #region Private Classes
+
+ private class AsyncReceiveState
+ {
+ public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint)
+ {
+ this.Socket = socket;
+ this.RemoteEndPoint = remoteEndPoint;
+ }
+
+ public EndPoint RemoteEndPoint;
+ public byte[] Buffer = new byte[8192];
+
+ public Socket Socket { get; private set; }
+
+ public TaskCompletionSource TaskCompletionSource { get; set; }
+
+ }
+
+ #endregion
+
+ }
+}
diff --git a/MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs b/Emby.Common.Implementations/Networking/NetworkManager.cs
similarity index 58%
rename from MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs
rename to Emby.Common.Implementations/Networking/NetworkManager.cs
index 1b5e260d71..4485e8b14e 100644
--- a/MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs
+++ b/Emby.Common.Implementations/Networking/NetworkManager.cs
@@ -6,28 +6,28 @@ using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
-using MoreLinq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Common.Net;
-namespace MediaBrowser.Common.Implementations.Networking
+namespace Emby.Common.Implementations.Networking
{
- public abstract class BaseNetworkManager
+ public class NetworkManager : INetworkManager
{
protected ILogger Logger { get; private set; }
private DateTime _lastRefresh;
- protected BaseNetworkManager(ILogger logger)
+ public NetworkManager(ILogger logger)
{
Logger = logger;
}
- private List _localIpAddresses;
+ private List _localIpAddresses;
private readonly object _localIpAddressSyncLock = new object();
- ///
- /// Gets the machine's local ip address
- ///
- /// IPAddress.
- public IEnumerable GetLocalIpAddresses()
+ public List GetLocalIpAddresses()
{
const int cacheMinutes = 5;
@@ -37,7 +37,7 @@ namespace MediaBrowser.Common.Implementations.Networking
if (_localIpAddresses == null || forceRefresh)
{
- var addresses = GetLocalIpAddressesInternal().ToList();
+ var addresses = GetLocalIpAddressesInternal().Select(ToIpAddressInfo).ToList();
_localIpAddresses = addresses;
_lastRefresh = DateTime.UtcNow;
@@ -49,24 +49,24 @@ namespace MediaBrowser.Common.Implementations.Networking
return _localIpAddresses;
}
- private IEnumerable GetLocalIpAddressesInternal()
+ private IEnumerable GetLocalIpAddressesInternal()
{
var list = GetIPsDefault()
.ToList();
if (list.Count == 0)
{
- list.AddRange(GetLocalIpAddressesFallback());
+ list.AddRange(GetLocalIpAddressesFallback().Result);
}
- return list.Where(FilterIpAddress).DistinctBy(i => i.ToString());
+ return list.Where(FilterIpAddress).DistinctBy(i => i.ToString());
}
- private bool FilterIpAddress(IPAddress address)
+ private bool FilterIpAddress(IPAddress address)
{
- var addressString = address.ToString ();
+ var addressString = address.ToString();
- if (addressString.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
+ if (addressString.StartsWith("169.", StringComparison.OrdinalIgnoreCase))
{
return false;
}
@@ -154,12 +154,12 @@ namespace MediaBrowser.Common.Implementations.Networking
{
var prefix = addressString.Substring(0, lengthMatch);
- if (GetLocalIpAddresses().Any(i => i.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
+ if (GetLocalIpAddresses().Any(i => i.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
}
- }
+ }
else if (resolveHost)
{
Uri uri;
@@ -170,7 +170,7 @@ namespace MediaBrowser.Common.Implementations.Networking
var host = uri.DnsSafeHost;
Logger.Debug("Resolving host {0}", host);
- address = GetIpAddresses(host).FirstOrDefault();
+ address = GetIpAddresses(host).Result.FirstOrDefault();
if (address != null)
{
@@ -193,52 +193,76 @@ namespace MediaBrowser.Common.Implementations.Networking
return false;
}
- public IEnumerable GetIpAddresses(string hostName)
+ private Task GetIpAddresses(string hostName)
{
- return Dns.GetHostAddresses(hostName);
+ return Dns.GetHostAddressesAsync(hostName);
}
- private List GetIPsDefault()
- {
- NetworkInterface[] interfaces;
+ private readonly List _validNetworkInterfaceTypes = new List
+ {
+ NetworkInterfaceType.Ethernet,
+ NetworkInterfaceType.Wireless80211
+ };
- try
- {
- interfaces = NetworkInterface.GetAllNetworkInterfaces();
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error in GetAllNetworkInterfaces", ex);
- return new List();
- }
+ private List GetIPsDefault()
+ {
+ NetworkInterface[] interfaces;
- return interfaces.SelectMany(network => {
+ try
+ {
+ var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
- try
- {
+ interfaces = NetworkInterface.GetAllNetworkInterfaces()
+ .Where(i => validStatuses.Contains(i.OperationalStatus))
+ .ToArray();
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error in GetAllNetworkInterfaces", ex);
+ return new List();
+ }
+
+ return interfaces.SelectMany(network =>
+ {
+
+ try
+ {
Logger.Debug("Querying interface: {0}. Type: {1}. Status: {2}", network.Name, network.NetworkInterfaceType, network.OperationalStatus);
- var properties = network.GetIPProperties();
+ var ipProperties = network.GetIPProperties();
- return properties.UnicastAddresses
- .Where(i => i.IsDnsEligible)
+ // Try to exclude virtual adapters
+ // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
+ var addr = ipProperties.GatewayAddresses.FirstOrDefault();
+ if (addr == null|| string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
+ {
+ return new List();
+ }
+
+ //if (!_validNetworkInterfaceTypes.Contains(network.NetworkInterfaceType))
+ //{
+ // return new List();
+ //}
+
+ return ipProperties.UnicastAddresses
+ //.Where(i => i.IsDnsEligible)
.Select(i => i.Address)
.Where(i => i.AddressFamily == AddressFamily.InterNetwork)
- .ToList();
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error querying network interface", ex);
- return new List();
- }
+ .ToList();
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error querying network interface", ex);
+ return new List();
+ }
- }).DistinctBy(i => i.ToString())
- .ToList();
- }
+ }).DistinctBy(i => i.ToString())
+ .ToList();
+ }
- private IEnumerable GetLocalIpAddressesFallback()
+ private async Task> GetLocalIpAddressesFallback()
{
- var host = Dns.GetHostEntry(Dns.GetHostName());
+ var host = await Dns.GetHostEntryAsync(Dns.GetHostName()).ConfigureAwait(false);
// Reverse them because the last one is usually the correct one
// It's not fool-proof so ultimately the consumer will have to examine them and decide
@@ -279,7 +303,7 @@ namespace MediaBrowser.Common.Implementations.Networking
/// IPEndPoint.
public IPEndPoint Parse(string endpointstring)
{
- return Parse(endpointstring, -1);
+ return Parse(endpointstring, -1).Result;
}
///
@@ -290,7 +314,7 @@ namespace MediaBrowser.Common.Implementations.Networking
/// IPEndPoint.
/// Endpoint descriptor may not be empty.
///
- private static IPEndPoint Parse(string endpointstring, int defaultport)
+ private static async Task Parse(string endpointstring, int defaultport)
{
if (String.IsNullOrEmpty(endpointstring)
|| endpointstring.Trim().Length == 0)
@@ -316,7 +340,7 @@ namespace MediaBrowser.Common.Implementations.Networking
//try to use the address as IPv4, otherwise get hostname
if (!IPAddress.TryParse(values[0], out ipaddy))
- ipaddy = GetIPfromHost(values[0]);
+ ipaddy = await GetIPfromHost(values[0]).ConfigureAwait(false);
}
else if (values.Length > 2) //ipv6
{
@@ -372,14 +396,130 @@ namespace MediaBrowser.Common.Implementations.Networking
/// The p.
/// IPAddress.
///
- private static IPAddress GetIPfromHost(string p)
+ private static async Task GetIPfromHost(string p)
{
- var hosts = Dns.GetHostAddresses(p);
+ var hosts = await Dns.GetHostAddressesAsync(p).ConfigureAwait(false);
if (hosts == null || hosts.Length == 0)
throw new ArgumentException(String.Format("Host not found: {0}", p));
return hosts[0];
}
+
+ public IpAddressInfo ParseIpAddress(string ipAddress)
+ {
+ IpAddressInfo info;
+ if (TryParseIpAddress(ipAddress, out info))
+ {
+ return info;
+ }
+
+ throw new ArgumentException("Invalid ip address: " + ipAddress);
+ }
+
+ public bool TryParseIpAddress(string ipAddress, out IpAddressInfo ipAddressInfo)
+ {
+ IPAddress address;
+ if (IPAddress.TryParse(ipAddress, out address))
+ {
+ ipAddressInfo = ToIpAddressInfo(address);
+ return true;
+ }
+
+ ipAddressInfo = null;
+ return false;
+ }
+
+ public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
+ {
+ if (endpoint == null)
+ {
+ return null;
+ }
+
+ return new IpEndPointInfo(ToIpAddressInfo(endpoint.Address), endpoint.Port);
+ }
+
+ public static IPEndPoint ToIPEndPoint(IpEndPointInfo endpoint)
+ {
+ if (endpoint == null)
+ {
+ return null;
+ }
+
+ return new IPEndPoint(ToIPAddress(endpoint.IpAddress), endpoint.Port);
+ }
+
+ public static IPAddress ToIPAddress(IpAddressInfo address)
+ {
+ if (address.Equals(IpAddressInfo.Any))
+ {
+ return IPAddress.Any;
+ }
+ if (address.Equals(IpAddressInfo.IPv6Any))
+ {
+ return IPAddress.IPv6Any;
+ }
+ if (address.Equals(IpAddressInfo.Loopback))
+ {
+ return IPAddress.Loopback;
+ }
+ if (address.Equals(IpAddressInfo.IPv6Loopback))
+ {
+ return IPAddress.IPv6Loopback;
+ }
+
+ return IPAddress.Parse(address.Address);
+ }
+
+ public static IpAddressInfo ToIpAddressInfo(IPAddress address)
+ {
+ if (address.Equals(IPAddress.Any))
+ {
+ return IpAddressInfo.Any;
+ }
+ if (address.Equals(IPAddress.IPv6Any))
+ {
+ return IpAddressInfo.IPv6Any;
+ }
+ if (address.Equals(IPAddress.Loopback))
+ {
+ return IpAddressInfo.Loopback;
+ }
+ if (address.Equals(IPAddress.IPv6Loopback))
+ {
+ return IpAddressInfo.IPv6Loopback;
+ }
+ return new IpAddressInfo
+ {
+ Address = address.ToString(),
+ AddressFamily = address.AddressFamily == AddressFamily.InterNetworkV6 ? IpAddressFamily.InterNetworkV6 : IpAddressFamily.InterNetwork
+ };
+ }
+
+ public async Task GetHostAddressesAsync(string host)
+ {
+ var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false);
+ return addresses.Select(ToIpAddressInfo).ToArray();
+ }
+
+ ///
+ /// Gets the network shares.
+ ///
+ /// The path.
+ /// IEnumerable{NetworkShare}.
+ public virtual IEnumerable GetNetworkShares(string path)
+ {
+ return new List();
+ }
+
+ ///
+ /// Gets available devices within the domain
+ ///
+ /// PC's in the Domain
+ public virtual IEnumerable GetNetworkDevices()
+ {
+ return new List();
+ }
}
}
diff --git a/Emby.Common.Implementations/Properties/AssemblyInfo.cs b/Emby.Common.Implementations/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..1a5abcb274
--- /dev/null
+++ b/Emby.Common.Implementations/Properties/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Emby.Common.Implementations")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("5a27010a-09c6-4e86-93ea-437484c10917")]
diff --git a/Emby.Common.Implementations/Reflection/AssemblyInfo.cs b/Emby.Common.Implementations/Reflection/AssemblyInfo.cs
new file mode 100644
index 0000000000..7a92f02d6b
--- /dev/null
+++ b/Emby.Common.Implementations/Reflection/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+using MediaBrowser.Model.Reflection;
+using System.Reflection;
+
+namespace Emby.Common.Implementations.Reflection
+{
+ public class AssemblyInfo : IAssemblyInfo
+ {
+ public Stream GetManifestResourceStream(Type type, string resource)
+ {
+#if NET46
+ return type.Assembly.GetManifestResourceStream(resource);
+#endif
+ return type.GetTypeInfo().Assembly.GetManifestResourceStream(resource);
+ }
+
+ public string[] GetManifestResourceNames(Type type)
+ {
+#if NET46
+ return type.Assembly.GetManifestResourceNames();
+#endif
+ return type.GetTypeInfo().Assembly.GetManifestResourceNames();
+ }
+
+ public Assembly[] GetCurrentAssemblies()
+ {
+ return AppDomain.CurrentDomain.GetAssemblies();
+ }
+ }
+}
diff --git a/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs b/Emby.Common.Implementations/ScheduledTasks/DailyTrigger.cs
similarity index 96%
rename from MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs
rename to Emby.Common.Implementations/ScheduledTasks/DailyTrigger.cs
index 3d33e958de..5735f80260 100644
--- a/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/DailyTrigger.cs
@@ -1,11 +1,11 @@
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Tasks;
-using System;
+using System;
using System.Globalization;
using System.Threading;
+using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.ScheduledTasks
+namespace Emby.Common.Implementations.ScheduledTasks
{
///
/// Represents a task trigger that fires everyday
diff --git a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs b/Emby.Common.Implementations/ScheduledTasks/IntervalTrigger.cs
similarity index 96%
rename from MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs
rename to Emby.Common.Implementations/ScheduledTasks/IntervalTrigger.cs
index 8038d5551d..4d2769d8fb 100644
--- a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/IntervalTrigger.cs
@@ -1,11 +1,11 @@
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Tasks;
-using System;
+using System;
using System.Linq;
using System.Threading;
+using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.ScheduledTasks
+namespace Emby.Common.Implementations.ScheduledTasks
{
///
/// Represents a task trigger that runs repeatedly on an interval
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
similarity index 80%
rename from MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
rename to Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index ced85780f8..cbc7c7c2d8 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -1,20 +1,20 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Events;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.ScheduledTasks;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Model.Tasks;
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.Implementations.ScheduledTasks
+namespace Emby.Common.Implementations.ScheduledTasks
{
///
/// Class ScheduledTaskWorker
@@ -53,6 +53,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// The task manager.
private ITaskManager TaskManager { get; set; }
private readonly IFileSystem _fileSystem;
+ private readonly ISystemEvents _systemEvents;
///
/// Initializes a new instance of the class.
@@ -73,7 +74,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// or
/// logger
///
- public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem)
+ public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem, ISystemEvents systemEvents)
{
if (scheduledTask == null)
{
@@ -102,6 +103,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
JsonSerializer = jsonSerializer;
Logger = logger;
_fileSystem = fileSystem;
+ _systemEvents = systemEvents;
InitTriggerEvents();
}
@@ -232,13 +234,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
///
/// The _triggers
///
- private List _triggers;
+ private Tuple[] _triggers;
///
/// Gets the triggers that define when the task will run
///
/// The triggers.
- /// value
- public IEnumerable Triggers
+ private Tuple[] InternalTriggers
{
get
{
@@ -257,11 +258,33 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
DisposeTriggers();
}
- _triggers = value.ToList();
+ _triggers = value.ToArray();
ReloadTriggerEvents(false);
+ }
+ }
- SaveTriggers(_triggers);
+ ///
+ /// Gets the triggers that define when the task will run
+ ///
+ /// The triggers.
+ /// value
+ public TaskTriggerInfo[] Triggers
+ {
+ get
+ {
+ return InternalTriggers.Select(i => i.Item1).ToArray();
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+
+ SaveTriggers(value);
+
+ InternalTriggers = value.Select(i => new Tuple(i, GetTrigger(i))).ToArray();
}
}
@@ -304,8 +327,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// if set to true [is application startup].
private void ReloadTriggerEvents(bool isApplicationStartup)
{
- foreach (var trigger in Triggers)
+ foreach (var triggerInfo in InternalTriggers)
{
+ var trigger = triggerInfo.Item2;
+
trigger.Stop();
trigger.Triggered -= trigger_Triggered;
@@ -362,6 +387,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
finally
{
_currentTask = null;
+ GC.Collect();
}
}
@@ -507,23 +533,29 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// Loads the triggers.
///
/// IEnumerable{BaseTaskTrigger}.
- private List LoadTriggers()
+ private Tuple[] LoadTriggers()
+ {
+ var settings = LoadTriggerSettings();
+
+ return settings.Select(i => new Tuple(i, GetTrigger(i))).ToArray();
+ }
+
+ private TaskTriggerInfo[] LoadTriggerSettings()
{
try
{
return JsonSerializer.DeserializeFromFile>(GetConfigurationFilePath())
- .Select(ScheduledTaskHelpers.GetTrigger)
- .ToList();
+ .ToArray();
}
catch (FileNotFoundException)
{
// File doesn't exist. No biggie. Return defaults.
- return ScheduledTask.GetDefaultTriggers().ToList();
+ return ScheduledTask.GetDefaultTriggers().ToArray();
}
catch (DirectoryNotFoundException)
{
// File doesn't exist. No biggie. Return defaults.
- return ScheduledTask.GetDefaultTriggers().ToList();
+ return ScheduledTask.GetDefaultTriggers().ToArray();
}
}
@@ -531,13 +563,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// Saves the triggers.
///
/// The triggers.
- private void SaveTriggers(IEnumerable triggers)
+ private void SaveTriggers(TaskTriggerInfo[] triggers)
{
var path = GetConfigurationFilePath();
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
- JsonSerializer.SerializeToFile(triggers.Select(ScheduledTaskHelpers.GetTriggerInfo), path);
+ JsonSerializer.SerializeToFile(triggers, path);
}
///
@@ -561,11 +593,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
Id = Id
};
- var hasKey = ScheduledTask as IHasKey;
- if (hasKey != null)
- {
- result.Key = hasKey.Key;
- }
+ result.Key = ScheduledTask.Key;
if (ex != null)
{
@@ -655,13 +683,98 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
}
}
+ ///
+ /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger
+ ///
+ /// The info.
+ /// BaseTaskTrigger.
+ ///
+ /// Invalid trigger type: + info.Type
+ private ITaskTrigger GetTrigger(TaskTriggerInfo info)
+ {
+ var options = new TaskExecutionOptions
+ {
+ MaxRuntimeMs = info.MaxRuntimeMs
+ };
+
+ if (info.Type.Equals(typeof(DailyTrigger).Name, StringComparison.OrdinalIgnoreCase))
+ {
+ if (!info.TimeOfDayTicks.HasValue)
+ {
+ throw new ArgumentNullException();
+ }
+
+ return new DailyTrigger
+ {
+ TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value),
+ TaskOptions = options
+ };
+ }
+
+ if (info.Type.Equals(typeof(WeeklyTrigger).Name, StringComparison.OrdinalIgnoreCase))
+ {
+ if (!info.TimeOfDayTicks.HasValue)
+ {
+ throw new ArgumentNullException();
+ }
+
+ if (!info.DayOfWeek.HasValue)
+ {
+ throw new ArgumentNullException();
+ }
+
+ return new WeeklyTrigger
+ {
+ TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value),
+ DayOfWeek = info.DayOfWeek.Value,
+ TaskOptions = options
+ };
+ }
+
+ if (info.Type.Equals(typeof(IntervalTrigger).Name, StringComparison.OrdinalIgnoreCase))
+ {
+ if (!info.IntervalTicks.HasValue)
+ {
+ throw new ArgumentNullException();
+ }
+
+ return new IntervalTrigger
+ {
+ Interval = TimeSpan.FromTicks(info.IntervalTicks.Value),
+ TaskOptions = options
+ };
+ }
+
+ if (info.Type.Equals(typeof(SystemEventTrigger).Name, StringComparison.OrdinalIgnoreCase))
+ {
+ if (!info.SystemEvent.HasValue)
+ {
+ throw new ArgumentNullException();
+ }
+
+ return new SystemEventTrigger(_systemEvents)
+ {
+ SystemEvent = info.SystemEvent.Value,
+ TaskOptions = options
+ };
+ }
+
+ if (info.Type.Equals(typeof(StartupTrigger).Name, StringComparison.OrdinalIgnoreCase))
+ {
+ return new StartupTrigger();
+ }
+
+ throw new ArgumentException("Unrecognized trigger type: " + info.Type);
+ }
+
///
/// Disposes each trigger
///
private void DisposeTriggers()
{
- foreach (var trigger in Triggers)
+ foreach (var triggerInfo in InternalTriggers)
{
+ var trigger = triggerInfo.Item2;
trigger.Triggered -= trigger_Triggered;
trigger.Stop();
}
diff --git a/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs b/Emby.Common.Implementations/ScheduledTasks/StartupTrigger.cs
similarity index 94%
rename from MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs
rename to Emby.Common.Implementations/ScheduledTasks/StartupTrigger.cs
index 41f58a7ad5..8aae644bc9 100644
--- a/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/StartupTrigger.cs
@@ -1,10 +1,10 @@
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Tasks;
-using System;
+using System;
using System.Threading.Tasks;
+using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.ScheduledTasks
+namespace Emby.Common.Implementations.ScheduledTasks
{
///
/// Class StartupTaskTrigger
diff --git a/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs b/Emby.Common.Implementations/ScheduledTasks/SystemEventTrigger.cs
similarity index 73%
rename from MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs
rename to Emby.Common.Implementations/ScheduledTasks/SystemEventTrigger.cs
index 9972dc8044..a136a975ae 100644
--- a/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/SystemEventTrigger.cs
@@ -1,11 +1,11 @@
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Tasks;
-using Microsoft.Win32;
-using System;
+using System;
using System.Threading.Tasks;
+using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.ScheduledTasks
+namespace Emby.Common.Implementations.ScheduledTasks
{
///
/// Class SystemEventTrigger
@@ -26,6 +26,13 @@ namespace MediaBrowser.Common.ScheduledTasks
///
public TaskExecutionOptions TaskOptions { get; set; }
+ private readonly ISystemEvents _systemEvents;
+
+ public SystemEventTrigger(ISystemEvents systemEvents)
+ {
+ _systemEvents = systemEvents;
+ }
+
///
/// Stars waiting for the trigger action
///
@@ -36,33 +43,28 @@ namespace MediaBrowser.Common.ScheduledTasks
switch (SystemEvent)
{
case SystemEvent.WakeFromSleep:
- SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
+ _systemEvents.Resume += _systemEvents_Resume;
break;
}
}
+ private async void _systemEvents_Resume(object sender, EventArgs e)
+ {
+ if (SystemEvent == SystemEvent.WakeFromSleep)
+ {
+ // This value is a bit arbitrary, but add a delay to help ensure network connections have been restored before running the task
+ await Task.Delay(10000).ConfigureAwait(false);
+
+ OnTriggered();
+ }
+ }
+
///
/// Stops waiting for the trigger action
///
public void Stop()
{
- SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
- }
-
- ///
- /// Handles the PowerModeChanged event of the SystemEvents control.
- ///
- /// The source of the event.
- /// The instance containing the event data.
- async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
- {
- if (e.Mode == PowerModes.Resume && SystemEvent == SystemEvent.WakeFromSleep)
- {
- // This value is a bit arbitrary, but add a delay to help ensure network connections have been restored before running the task
- await Task.Delay(10000).ConfigureAwait(false);
-
- OnTriggered();
- }
+ _systemEvents.Resume -= _systemEvents_Resume;
}
///
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Common.Implementations/ScheduledTasks/TaskManager.cs
similarity index 91%
rename from MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
rename to Emby.Common.Implementations/ScheduledTasks/TaskManager.cs
index b3a00b35f5..b0153c5882 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/TaskManager.cs
@@ -1,6 +1,5 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
-using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
@@ -10,10 +9,10 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using CommonIO;
-using Microsoft.Win32;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.System;
-namespace MediaBrowser.Common.Implementations.ScheduledTasks
+namespace Emby.Common.Implementations.ScheduledTasks
{
///
/// Class TaskManager
@@ -47,6 +46,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// The application paths.
private IApplicationPaths ApplicationPaths { get; set; }
+ private readonly ISystemEvents _systemEvents;
+
///
/// Gets the logger.
///
@@ -54,25 +55,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
private ILogger Logger { get; set; }
private readonly IFileSystem _fileSystem;
- private bool _suspendTriggers;
-
- public bool SuspendTriggers
- {
- get { return _suspendTriggers; }
- set
- {
- Logger.Info("Setting SuspendTriggers to {0}", value);
- var executeQueued = _suspendTriggers && !value;
-
- _suspendTriggers = value;
-
- if (executeQueued)
- {
- ExecuteQueuedTasks();
- }
- }
- }
-
///
/// Initializes a new instance of the class.
///
@@ -80,29 +62,23 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// The json serializer.
/// The logger.
/// kernel
- public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem)
+ public TaskManager(IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem, ISystemEvents systemEvents)
{
ApplicationPaths = applicationPaths;
JsonSerializer = jsonSerializer;
Logger = logger;
_fileSystem = fileSystem;
+ _systemEvents = systemEvents;
ScheduledTasks = new IScheduledTaskWorker[] { };
}
private void BindToSystemEvent()
{
- try
- {
- SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
- }
- catch
- {
-
- }
+ _systemEvents.Resume += _systemEvents_Resume;
}
- void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
+ private void _systemEvents_Resume(object sender, EventArgs e)
{
foreach (var task in ScheduledTasks)
{
@@ -235,7 +211,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
lock (_taskQueue)
{
- if (task.State == TaskState.Idle && !SuspendTriggers)
+ if (task.State == TaskState.Idle)
{
Execute(task, options);
return;
@@ -254,7 +230,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
var myTasks = ScheduledTasks.ToList();
var list = tasks.ToList();
- myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)));
+ myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem, _systemEvents)));
ScheduledTasks = myTasks.ToArray();
@@ -327,11 +303,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
///
private void ExecuteQueuedTasks()
{
- if (SuspendTriggers)
- {
- return;
- }
-
Logger.Info("ExecuteQueuedTasks");
// Execute queued tasks
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
similarity index 90%
rename from MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
rename to Emby.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index 0a2b9222a0..1cad2e9b83 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -1,15 +1,15 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.ScheduledTasks;
-using MediaBrowser.Model.Logging;
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
+namespace Emby.Common.Implementations.ScheduledTasks.Tasks
{
///
/// Deletes old cache files
@@ -40,13 +40,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// Creates the triggers that define when the task will run
///
/// IEnumerable{BaseTaskTrigger}.
- public IEnumerable GetDefaultTriggers()
+ public IEnumerable GetDefaultTriggers()
{
- // Until we can vary these default triggers per server and MBT, we need something that makes sense for both
- return new ITaskTrigger[] {
+ return new[] {
// Every so often
- new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
+ new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
};
}
@@ -95,7 +94,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// The progress.
private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress progress)
{
- var filesToDelete = _fileSystem.GetFiles(directory, true)
+ var filesToDelete = _fileSystem.GetFiles(directory, true)
.Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
.ToList();
@@ -168,6 +167,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
get { return "Cache file cleanup"; }
}
+ public string Key
+ {
+ get { return "DeleteCacheFiles"; }
+ }
+
///
/// Gets the description.
///
@@ -202,5 +206,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
get { return true; }
}
+
+ public bool IsLogged
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
similarity index 86%
rename from MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
rename to Emby.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
index b18ea03b1e..3f43fa8894 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
@@ -1,13 +1,13 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.ScheduledTasks;
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
+namespace Emby.Common.Implementations.ScheduledTasks.Tasks
{
///
/// Deletes old log files
@@ -36,13 +36,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// Creates the triggers that define when the task will run
///
/// IEnumerable{BaseTaskTrigger}.
- public IEnumerable GetDefaultTriggers()
+ public IEnumerable GetDefaultTriggers()
{
- // Until we can vary these default triggers per server and MBT, we need something that makes sense for both
- return new ITaskTrigger[] {
+ return new[] {
// Every so often
- new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
+ new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
};
}
@@ -82,6 +81,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
return Task.FromResult(true);
}
+ public string Key
+ {
+ get { return "CleanLogFiles"; }
+ }
+
///
/// Gets the name of the task
///
@@ -125,5 +129,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
get { return true; }
}
+
+ public bool IsLogged
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs b/Emby.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs
similarity index 86%
rename from MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs
rename to Emby.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs
index 78f60632fa..80411de055 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs
@@ -1,12 +1,12 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.ScheduledTasks;
-using MediaBrowser.Model.Logging;
-using System;
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
+namespace Emby.Common.Implementations.ScheduledTasks.Tasks
{
///
/// Class ReloadLoggerFileTask
@@ -39,9 +39,9 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// Gets the default triggers.
///
/// IEnumerable{BaseTaskTrigger}.
- public IEnumerable GetDefaultTriggers()
+ public IEnumerable GetDefaultTriggers()
{
- var trigger = new DailyTrigger { TimeOfDay = TimeSpan.FromHours(0) }; //12am
+ var trigger = new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, TimeOfDayTicks = TimeSpan.FromHours(0).Ticks }; //12am
return new[] { trigger };
}
@@ -74,6 +74,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
get { return "Start new log file"; }
}
+ public string Key { get; }
+
///
/// Gets the description.
///
@@ -101,5 +103,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
get { return true; }
}
+
+ public bool IsLogged
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs b/Emby.Common.Implementations/ScheduledTasks/WeeklyTrigger.cs
similarity index 98%
rename from MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs
rename to Emby.Common.Implementations/ScheduledTasks/WeeklyTrigger.cs
index 318802e07d..91540ba164 100644
--- a/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs
+++ b/Emby.Common.Implementations/ScheduledTasks/WeeklyTrigger.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
-namespace MediaBrowser.Common.ScheduledTasks
+namespace Emby.Common.Implementations.ScheduledTasks
{
///
/// Represents a task trigger that fires on a weekly basis
diff --git a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs b/Emby.Common.Implementations/Serialization/JsonSerializer.cs
similarity index 96%
rename from MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs
rename to Emby.Common.Implementations/Serialization/JsonSerializer.cs
index 5dbbe53731..c9db336890 100644
--- a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs
+++ b/Emby.Common.Implementations/Serialization/JsonSerializer.cs
@@ -1,10 +1,10 @@
-using MediaBrowser.Model.Serialization;
-using System;
+using System;
using System.IO;
-using CommonIO;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
-namespace MediaBrowser.Common.Implementations.Serialization
+namespace Emby.Common.Implementations.Serialization
{
///
/// Provides a wrapper around third party json serialization.
@@ -60,7 +60,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
throw new ArgumentNullException("file");
}
- using (Stream stream = _fileSystem.GetFileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
+ using (Stream stream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
{
SerializeToStream(obj, stream);
}
diff --git a/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs b/Emby.Common.Implementations/Serialization/XmlSerializer.cs
similarity index 78%
rename from MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
rename to Emby.Common.Implementations/Serialization/XmlSerializer.cs
index 77f65b0c7a..3583f998e5 100644
--- a/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
+++ b/Emby.Common.Implementations/Serialization/XmlSerializer.cs
@@ -4,20 +4,22 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Xml;
-using CommonIO;
+using System.Xml.Serialization;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
-namespace MediaBrowser.Common.Implementations.Serialization
+namespace Emby.Common.Implementations.Serialization
{
///
/// Provides a wrapper around third party xml serialization.
///
- public class XmlSerializer : IXmlSerializer
+ public class MyXmlSerializer : IXmlSerializer
{
- private readonly IFileSystem _fileSystem;
+ private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
- public XmlSerializer(IFileSystem fileSystem, ILogger logger)
+ public MyXmlSerializer(IFileSystem fileSystem, ILogger logger)
{
_fileSystem = fileSystem;
_logger = logger;
@@ -25,18 +27,18 @@ namespace MediaBrowser.Common.Implementations.Serialization
// Need to cache these
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
- private readonly Dictionary _serializers =
- new Dictionary();
+ private readonly Dictionary _serializers =
+ new Dictionary();
- private System.Xml.Serialization.XmlSerializer GetSerializer(Type type)
+ private XmlSerializer GetSerializer(Type type)
{
var key = type.FullName;
lock (_serializers)
{
- System.Xml.Serialization.XmlSerializer serializer;
+ XmlSerializer serializer;
if (!_serializers.TryGetValue(key, out serializer))
{
- serializer = new System.Xml.Serialization.XmlSerializer(type);
+ serializer = new XmlSerializer(type);
_serializers[key] = serializer;
}
return serializer;
@@ -48,9 +50,8 @@ namespace MediaBrowser.Common.Implementations.Serialization
///
/// The obj.
/// The writer.
- private void SerializeToWriter(object obj, XmlTextWriter writer)
+ private void SerializeToWriter(object obj, XmlWriter writer)
{
- writer.Formatting = Formatting.Indented;
var netSerializer = GetSerializer(obj.GetType());
netSerializer.Serialize(writer, obj);
}
@@ -63,7 +64,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// System.Object.
public object DeserializeFromStream(Type type, Stream stream)
{
- using (var reader = new XmlTextReader(stream))
+ using (var reader = XmlReader.Create(stream))
{
var netSerializer = GetSerializer(type);
return netSerializer.Deserialize(reader);
@@ -77,10 +78,18 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// The stream.
public void SerializeToStream(object obj, Stream stream)
{
- using (var writer = new XmlTextWriter(stream, null))
+#if NET46
+ using (var writer = new XmlTextWriter(stream, null))
+ {
+ writer.Formatting = Formatting.Indented;
+ SerializeToWriter(obj, writer);
+ }
+#else
+ using (var writer = XmlWriter.Create(stream))
{
SerializeToWriter(obj, writer);
}
+#endif
}
///
diff --git a/Emby.Common.Implementations/TextEncoding/TextEncoding.cs b/Emby.Common.Implementations/TextEncoding/TextEncoding.cs
new file mode 100644
index 0000000000..254d352224
--- /dev/null
+++ b/Emby.Common.Implementations/TextEncoding/TextEncoding.cs
@@ -0,0 +1,43 @@
+using System.Text;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Text;
+
+namespace Emby.Common.Implementations.TextEncoding
+{
+ public class TextEncoding : ITextEncoding
+ {
+ private readonly IFileSystem _fileSystem;
+
+ public TextEncoding(IFileSystem fileSystem)
+ {
+ _fileSystem = fileSystem;
+ }
+
+ public Encoding GetASCIIEncoding()
+ {
+ return Encoding.ASCII;
+ }
+
+ public Encoding GetFileEncoding(string srcFile)
+ {
+ // *** Detect byte order mark if any - otherwise assume default
+ var buffer = new byte[5];
+
+ using (var file = _fileSystem.OpenRead(srcFile))
+ {
+ file.Read(buffer, 0, 5);
+ }
+
+ if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
+ return Encoding.UTF8;
+ if (buffer[0] == 0xfe && buffer[1] == 0xff)
+ return Encoding.Unicode;
+ if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
+ return Encoding.UTF32;
+ if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
+ return Encoding.UTF7;
+
+ return null;
+ }
+ }
+}
diff --git a/Emby.Common.Implementations/Threading/CommonTimer.cs b/Emby.Common.Implementations/Threading/CommonTimer.cs
new file mode 100644
index 0000000000..8895f6798a
--- /dev/null
+++ b/Emby.Common.Implementations/Threading/CommonTimer.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Threading;
+
+namespace Emby.Common.Implementations.Threading
+{
+ public class CommonTimer : ITimer
+ {
+ private readonly Timer _timer;
+
+ public CommonTimer(Action | | | |