瀏覽代碼

Merge pull request #2350 from MediaBrowser/beta

Beta
Luke 8 年之前
父節點
當前提交
e7cebb91a7
共有 100 個文件被更改,包括 11451 次插入477 次删除
  1. 2 0
      .gitignore
  2. 77 0
      BDInfo/BDInfo.csproj
  3. 6 0
      BDInfo/BDInfo.nuget.targets
  4. 105 0
      BDInfo/BDInfoSettings.cs
  5. 437 0
      BDInfo/BDROM.cs
  6. 308 0
      BDInfo/BitVector32.cs
  7. 493 0
      BDInfo/LanguageCodes.cs
  8. 10 4
      BDInfo/Properties/AssemblyInfo.cs
  9. 5 0
      BDInfo/ReadMe.txt
  10. 309 0
      BDInfo/TSCodecAC3.cs
  11. 148 0
      BDInfo/TSCodecAVC.cs
  12. 159 0
      BDInfo/TSCodecDTS.cs
  13. 246 0
      BDInfo/TSCodecDTSHD.cs
  14. 123 0
      BDInfo/TSCodecLPCM.cs
  15. 208 0
      BDInfo/TSCodecMPEG2.cs
  16. 36 0
      BDInfo/TSCodecMVC.cs
  17. 186 0
      BDInfo/TSCodecTrueHD.cs
  18. 131 0
      BDInfo/TSCodecVC1.cs
  19. 38 0
      BDInfo/TSInterleavedFile.cs
  20. 1292 0
      BDInfo/TSPlaylistFile.cs
  21. 801 0
      BDInfo/TSStream.cs
  22. 142 0
      BDInfo/TSStreamBuffer.cs
  23. 113 0
      BDInfo/TSStreamClip.cs
  24. 253 0
      BDInfo/TSStreamClipFile.cs
  25. 1553 0
      BDInfo/TSStreamFile.cs
  26. 17 0
      BDInfo/project.json
  27. 0 0
      CONTRIBUTING.md
  28. 33 0
      DvdLib/BigEndianBinaryReader.cs
  29. 71 0
      DvdLib/DvdLib.csproj
  30. 6 0
      DvdLib/DvdLib.nuget.targets
  31. 41 0
      DvdLib/Ifo/AudioAttributes.cs
  32. 24 0
      DvdLib/Ifo/Cell.cs
  33. 54 0
      DvdLib/Ifo/CellPlaybackInfo.cs
  34. 21 0
      DvdLib/Ifo/CellPositionInfo.cs
  35. 21 0
      DvdLib/Ifo/Chapter.cs
  36. 161 0
      DvdLib/Ifo/Dvd.cs
  37. 34 0
      DvdLib/Ifo/DvdTime.cs
  38. 20 0
      DvdLib/Ifo/PgcCommandTable.cs
  39. 17 0
      DvdLib/Ifo/Program.cs
  40. 117 0
      DvdLib/Ifo/ProgramChain.cs
  41. 64 0
      DvdLib/Ifo/Title.cs
  42. 38 0
      DvdLib/Ifo/UserOperation.cs
  43. 51 0
      DvdLib/Ifo/VideoAttributes.cs
  44. 29 0
      DvdLib/Properties/AssemblyInfo.cs
  45. 17 0
      DvdLib/project.json
  46. 24 20
      Emby.Common.Implementations/Archiving/ZipClient.cs
  47. 176 142
      Emby.Common.Implementations/BaseApplicationHost.cs
  48. 6 10
      Emby.Common.Implementations/BaseApplicationPaths.cs
  49. 15 14
      Emby.Common.Implementations/Configuration/BaseConfigurationManager.cs
  50. 8 7
      Emby.Common.Implementations/Configuration/ConfigurationHelper.cs
  51. 40 0
      Emby.Common.Implementations/Cryptography/CryptographyProvider.cs
  52. 5 5
      Emby.Common.Implementations/Devices/DeviceId.cs
  53. 108 0
      Emby.Common.Implementations/Diagnostics/CommonProcess.cs
  54. 12 0
      Emby.Common.Implementations/Diagnostics/ProcessFactory.cs
  55. 23 0
      Emby.Common.Implementations/Emby.Common.Implementations.xproj
  56. 119 0
      Emby.Common.Implementations/EnvironmentInfo/EnvironmentInfo.cs
  57. 1 1
      Emby.Common.Implementations/HttpClientManager/HttpClientInfo.cs
  58. 92 22
      Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs
  59. 3 3
      Emby.Common.Implementations/IO/IsoManager.cs
  60. 794 0
      Emby.Common.Implementations/IO/ManagedFileSystem.cs
  61. 1 1
      Emby.Common.Implementations/Logging/NLogger.cs
  62. 544 0
      Emby.Common.Implementations/Logging/NlogManager.cs
  63. 74 0
      Emby.Common.Implementations/Net/DisposableManagedObjectBase.cs
  64. 97 0
      Emby.Common.Implementations/Net/NetSocket.cs
  65. 127 0
      Emby.Common.Implementations/Net/SocketAcceptor.cs
  66. 160 0
      Emby.Common.Implementations/Net/SocketFactory.cs
  67. 242 0
      Emby.Common.Implementations/Net/UdpSocket.cs
  68. 199 59
      Emby.Common.Implementations/Networking/NetworkManager.cs
  69. 19 0
      Emby.Common.Implementations/Properties/AssemblyInfo.cs
  70. 31 0
      Emby.Common.Implementations/Reflection/AssemblyInfo.cs
  71. 4 4
      Emby.Common.Implementations/ScheduledTasks/DailyTrigger.cs
  72. 4 4
      Emby.Common.Implementations/ScheduledTasks/IntervalTrigger.cs
  73. 143 30
      Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  74. 4 4
      Emby.Common.Implementations/ScheduledTasks/StartupTrigger.cs
  75. 23 21
      Emby.Common.Implementations/ScheduledTasks/SystemEventTrigger.cs
  76. 11 40
      Emby.Common.Implementations/ScheduledTasks/TaskManager.cs
  77. 20 11
      Emby.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  78. 18 9
      Emby.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
  79. 14 7
      Emby.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs
  80. 1 1
      Emby.Common.Implementations/ScheduledTasks/WeeklyTrigger.cs
  81. 5 5
      Emby.Common.Implementations/Serialization/JsonSerializer.cs
  82. 23 14
      Emby.Common.Implementations/Serialization/XmlSerializer.cs
  83. 43 0
      Emby.Common.Implementations/TextEncoding/TextEncoding.cs
  84. 39 0
      Emby.Common.Implementations/Threading/CommonTimer.cs
  85. 21 0
      Emby.Common.Implementations/Threading/TimerFactory.cs
  86. 22 0
      Emby.Common.Implementations/Xml/XmlReaderSettingsFactory.cs
  87. 71 0
      Emby.Common.Implementations/project.json
  88. 1 1
      Emby.Dlna/Common/Argument.cs
  89. 1 1
      Emby.Dlna/Common/DeviceIcon.cs
  90. 1 1
      Emby.Dlna/Common/DeviceService.cs
  91. 1 1
      Emby.Dlna/Common/ServiceAction.cs
  92. 1 1
      Emby.Dlna/Common/StateVariable.cs
  93. 1 1
      Emby.Dlna/ConfigurationExtension.cs
  94. 7 4
      Emby.Dlna/ConnectionManager/ConnectionManager.cs
  95. 3 3
      Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
  96. 11 11
      Emby.Dlna/ConnectionManager/ControlHandler.cs
  97. 2 2
      Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
  98. 11 6
      Emby.Dlna/ContentDirectory/ContentDirectory.cs
  99. 5 4
      Emby.Dlna/ContentDirectory/ContentDirectoryBrowser.cs
  100. 3 3
      Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs

+ 2 - 0
.gitignore

@@ -13,6 +13,8 @@ tmp/
 *.bak
 *.swp
 *~.nib
+project.fragment.lock.json
+project.lock.json
 local.properties
 .classpath
 .settings/

+ 77 - 0
BDInfo/BDInfo.csproj

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{88AE38DF-19D7-406F-A6A9-09527719A21E}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>BDInfo</RootNamespace>
+    <AssemblyName>BDInfo</AssemblyName>
+    <DefaultLanguage>en-US</DefaultLanguage>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <None Include="project.json" />
+    <!-- A reference to the entire .NET Framework is automatically included -->
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="BDInfoSettings.cs" />
+    <Compile Include="BDROM.cs" />
+    <Compile Include="BitVector32.cs" />
+    <Compile Include="LanguageCodes.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TSCodecAC3.cs" />
+    <Compile Include="TSCodecAVC.cs" />
+    <Compile Include="TSCodecDTS.cs" />
+    <Compile Include="TSCodecDTSHD.cs" />
+    <Compile Include="TSCodecLPCM.cs" />
+    <Compile Include="TSCodecMPEG2.cs" />
+    <Compile Include="TSCodecMVC.cs" />
+    <Compile Include="TSCodecTrueHD.cs" />
+    <Compile Include="TSCodecVC1.cs" />
+    <Compile Include="TSInterleavedFile.cs" />
+    <Compile Include="TSPlaylistFile.cs" />
+    <Compile Include="TSStream.cs" />
+    <Compile Include="TSStreamBuffer.cs" />
+    <Compile Include="TSStreamClip.cs" />
+    <Compile Include="TSStreamClipFile.cs" />
+    <Compile Include="TSStreamFile.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Name>MediaBrowser.Model</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 6 - 0
BDInfo/BDInfo.nuget.targets

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Target Name="EmitMSBuildWarning" BeforeTargets="Build">
+    <Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." />
+  </Target>
+</Project>

+ 105 - 0
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;
+            }
+        }
+
+        /// <summary>
+        /// Setting this to false throws an IComparer error on some discs.
+        /// </summary>
+        public static bool KeepStreamOrder
+        {
+            get
+            {
+                return true;
+            }
+        }
+
+        public static bool GenerateTextSummary
+        {
+            get
+            {
+                return false;
+            }
+        }
+
+        public static string LastPath
+        {
+            get
+            {
+                return string.Empty;
+            }
+        }
+    }
+}

+ 437 - 0
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<string, TSPlaylistFile> PlaylistFiles =
+            new Dictionary<string, TSPlaylistFile>();
+        public Dictionary<string, TSStreamClipFile> StreamClipFiles =
+            new Dictionary<string, TSStreamClipFile>();
+        public Dictionary<string, TSStreamFile> StreamFiles =
+            new Dictionary<string, TSStreamFile>();
+        public Dictionary<string, TSInterleavedFile> InterleavedFiles =
+            new Dictionary<string, TSInterleavedFile>();
+
+        private static List<string> ExcludeDirs = new List<string> { "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<FileSystemMetadata> GetFiles(string path, string extension)
+        {
+            return _fileSystem.GetFiles(path).Where(i => string.Equals(i.Extension, extension, StringComparison.OrdinalIgnoreCase));
+        }
+
+        public void Scan()
+        {
+            List<TSStreamClipFile> errorStreamClipFiles = new List<TSStreamClipFile>();
+            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<TSPlaylistFile> errorPlaylistFiles = new List<TSPlaylistFile>();
+            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<TSStreamFile> errorStreamFiles = new List<TSStreamFile>();
+            foreach (TSStreamFile streamFile in streamFiles)
+            {
+                try
+                {
+                    List<TSPlaylistFile> playlists = new List<TSPlaylistFile>();
+                    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;
+                }
+            }
+        }
+
+    }
+}

+ 308 - 0
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;
+
+    /// <devdoc>
+    ///    <para>Provides a simple light bit vector with easy integer or Boolean access to
+    ///       a 32 bit storage.</para>
+    /// </devdoc>
+    public struct BitVector32
+    {
+        private uint data;
+
+        /// <devdoc>
+        /// <para>Initializes a new instance of the BitVector32 structure with the specified internal data.</para>
+        /// </devdoc>
+        public BitVector32(int data)
+        {
+            this.data = (uint)data;
+        }
+
+        /// <devdoc>
+        /// <para>Initializes a new instance of the BitVector32 structure with the information in the specified 
+        ///    value.</para>
+        /// </devdoc>
+        public BitVector32(BitVector32 value)
+        {
+            this.data = value.data;
+        }
+
+        /// <devdoc>
+        ///    <para>Gets or sets a value indicating whether all the specified bits are set.</para>
+        /// </devdoc>
+        public bool this[int bit]
+        {
+            get
+            {
+                return (data & bit) == (uint)bit;
+            }
+            set
+            {
+                if (value)
+                {
+                    data |= (uint)bit;
+                }
+                else
+                {
+                    data &= ~(uint)bit;
+                }
+            }
+        }
+
+        /// <devdoc>
+        ///    <para>Gets or sets the value for the specified section.</para>
+        /// </devdoc>
+        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);
+            }
+        }
+
+        /// <devdoc>
+        ///    returns the raw data stored in this bit vector...
+        /// </devdoc>
+        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;
+        }
+
+        /// <devdoc>
+        ///    <para> Creates the first mask in a series.</para>
+        /// </devdoc>
+        public static int CreateMask()
+        {
+            return CreateMask(0);
+        }
+
+        /// <devdoc>
+        ///     Creates the next mask in a series.
+        /// </devdoc>
+        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;
+        }
+
+        /// <devdoc>
+        ///     Given a highValue, creates the mask
+        /// </devdoc>
+        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);
+        }
+
+        /// <devdoc>
+        ///    <para>Creates the first section in a series, with the specified maximum value.</para>
+        /// </devdoc>
+        public static Section CreateSection(short maxValue)
+        {
+            return CreateSectionHelper(maxValue, 0, 0);
+        }
+
+        /// <devdoc>
+        ///    <para>Creates the next section in a series, with the specified maximum value.</para>
+        /// </devdoc>
+        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();
+        }
+
+        /// <devdoc>
+        /// </devdoc>
+        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();
+        }
+
+        /// <devdoc>
+        /// </devdoc>
+        public override string ToString()
+        {
+            return BitVector32.ToString(this);
+        }
+
+        /// <devdoc>
+        ///    <para> 
+        ///       Represents an section of the vector that can contain a integer number.</para>
+        /// </devdoc>
+        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();
+            }
+
+            /// <devdoc>
+            /// </devdoc>
+            public static string ToString(Section value)
+            {
+                return "Section{0x" + Convert.ToString(value.Mask, 16) + ", 0x" + Convert.ToString(value.Offset, 16) + "}";
+            }
+
+            /// <devdoc>
+            /// </devdoc>
+            public override string ToString()
+            {
+                return Section.ToString(this);
+            }
+
+        }
+    }
+}

+ 493 - 0
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;
+            }
+        }
+    }
+}

+ 10 - 4
MediaBrowser.Model.Portable/Properties/AssemblyInfo.cs → 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
-//
+//
+// 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")]

+ 5 - 0
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.

+ 309 - 0
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;
+        }
+    }
+}

+ 148 - 0
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;
+        }
+    }
+}

+ 159 - 0
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;
+            }
+        }
+    }
+}

+ 246 - 0
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);
+            }            
+        }
+    }
+}

+ 123 - 0
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;
+        }
+    }
+}

+ 208 - 0
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
+                }
+            }
+        }
+    }
+}

+ 36 - 0
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;
+        }
+    }
+}

+ 186 - 0
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;
+        }
+    }
+}

+ 131 - 0
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;
+                }
+            }
+        }
+    }
+}

+ 38 - 0
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();
+        }
+    }
+}

+ 1292 - 0
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<double> Chapters = new List<double>();
+
+        public Dictionary<ushort, TSStream> Streams = 
+            new Dictionary<ushort, TSStream>();
+        public Dictionary<ushort, TSStream> PlaylistStreams =
+            new Dictionary<ushort, TSStream>();
+        public List<TSStreamClip> StreamClips =
+            new List<TSStreamClip>();
+        public List<Dictionary<ushort, TSStream>> AngleStreams =
+            new List<Dictionary<ushort, TSStream>>();
+        public List<Dictionary<double, TSStreamClip>> AngleClips = 
+            new List<Dictionary<double, TSStreamClip>>();
+        public int AngleCount = 0;
+
+        public List<TSStream> SortedStreams = 
+            new List<TSStream>();
+        public List<TSVideoStream> VideoStreams = 
+            new List<TSVideoStream>();
+        public List<TSAudioStream> AudioStreams = 
+            new List<TSAudioStream>();
+        public List<TSTextStream> TextStreams = 
+            new List<TSTextStream>();
+        public List<TSGraphicsStream> GraphicsStreams = 
+            new List<TSGraphicsStream>();
+
+        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<TSStreamClip> 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<string, TSStreamFile> streamFiles,
+            Dictionary<string, TSStreamClipFile> 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<TSStreamClip> chapterClips = new List<TSStreamClip>();
+                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<string, List<double>> clipTimes = new Dictionary<string, List<double>>();
+            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<double> { 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<double, TSStreamClip>());
+                }
+            }
+
+            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<ushort, TSStream>());
+            }
+
+            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++];
+        }
+    }
+}

+ 801 - 0
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<TSDescriptor> 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<TSDescriptor>();
+                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;
+        }
+    }
+}

+ 142 - 0
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;
+        }
+    }
+}

+ 113 - 0
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<double> Chapters = new List<double>();
+
+        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;
+        }
+    }
+}

+ 253 - 0
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<ushort, TSStream> Streams =
+            new Dictionary<ushort,TSStream>();
+
+        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();
+            }
+        }
+    }
+}

+ 1553 - 0
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<TSDescriptor> PMTProgramDescriptors = new List<TSDescriptor>();
+        public ushort PMTPID = 0xFFFF;
+        public Dictionary<ushort, byte[]> PMT = new Dictionary<ushort, byte[]>();
+        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<ushort, TSStreamState> StreamStates =
+            new Dictionary<ushort, TSStreamState>();
+
+        public Dictionary<ushort, TSStream> Streams =
+            new Dictionary<ushort, TSStream>();
+
+        public Dictionary<ushort, List<TSStreamDiagnostics>> StreamDiagnostics =
+            new Dictionary<ushort, List<TSStreamDiagnostics>>();
+
+        private List<TSPlaylistFile> 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<ushort, TSStream> 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<TSPlaylistFile> 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<TSDescriptor> streamDescriptors =
+                                                        new List<TSDescriptor>();
+
+                                                    /*
+                                                     * 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<TSDescriptor> 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<TSStreamDiagnostics>();
+            }
+
+            return stream;
+        } 
+    }
+}

+ 17 - 0
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":{
+                
+            }
+        }
+    }
+}

+ 0 - 0
CONTRIBUTING.md


+ 33 - 0
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;
+        }
+    }
+}

+ 71 - 0
DvdLib/DvdLib.csproj

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>DvdLib</RootNamespace>
+    <AssemblyName>DvdLib</AssemblyName>
+    <DefaultLanguage>en-US</DefaultLanguage>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <None Include="project.json" />
+    <!-- A reference to the entire .NET Framework is automatically included -->
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="BigEndianBinaryReader.cs" />
+    <Compile Include="Ifo\AudioAttributes.cs" />
+    <Compile Include="Ifo\Cell.cs" />
+    <Compile Include="Ifo\CellPlaybackInfo.cs" />
+    <Compile Include="Ifo\CellPositionInfo.cs" />
+    <Compile Include="Ifo\Chapter.cs" />
+    <Compile Include="Ifo\Dvd.cs" />
+    <Compile Include="Ifo\DvdTime.cs" />
+    <Compile Include="Ifo\PgcCommandTable.cs" />
+    <Compile Include="Ifo\Program.cs" />
+    <Compile Include="Ifo\ProgramChain.cs" />
+    <Compile Include="Ifo\Title.cs" />
+    <Compile Include="Ifo\UserOperation.cs" />
+    <Compile Include="Ifo\VideoAttributes.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Name>MediaBrowser.Model</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 6 - 0
DvdLib/DvdLib.nuget.targets

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Target Name="EmitMSBuildWarning" BeforeTargets="Build">
+    <Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." />
+  </Target>
+</Project>

+ 41 - 0
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
+    {
+
+    }
+}

+ 24 - 0
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);
+        }
+    }
+}

+ 54 - 0
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);
+        }
+    }
+}

+ 21 - 0
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();
+        }
+    }
+}

+ 21 - 0
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;
+        }
+    }
+}

+ 161 - 0
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<Title> Titles;
+
+        private ushort _titleCount;
+        public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
+        private readonly IFileSystem _fileSystem;
+
+        public Dvd(string path, IFileSystem fileSystem)
+        {
+            _fileSystem = fileSystem;
+            Titles = new List<Title>();
+            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<ushort>();
+
+                    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<FileSystemMetadata> 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);
+                    }
+                }
+            }
+        }
+    }
+}

+ 34 - 0
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);
+        }
+    }
+}

+ 20 - 0
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<VirtualMachineCommand> PreCommands;
+        public readonly List<VirtualMachineCommand> PostCommands;
+        public readonly List<VirtualMachineCommand> CellCommands;
+    }
+
+    public class VirtualMachineCommand
+    {
+        public readonly byte[] Command;
+    }
+}

+ 17 - 0
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<Cell> Cells;
+
+        public Program(List<Cell> cells)
+        {
+            Cells = cells;
+        }
+    }
+}

+ 117 - 0
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<Program> Programs;
+
+        private byte _cellCount;
+        public readonly List<Cell> 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<Cell>();
+            Programs = new List<Program>();
+        }
+
+        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<int> cellNumbers = new List<int>();
+            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()));
+            }
+        }
+    }
+}

+ 64 - 0
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<ProgramChain> ProgramChains;
+
+        public readonly List<Chapter> Chapters;        
+
+        public Title(uint titleNum)
+        {
+            ProgramChains = new List<ProgramChain>();
+            Chapters = new List<Chapter>();
+            Chapters = new List<Chapter>();
+            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);
+        }
+    }
+}

+ 38 - 0
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,
+    }
+}

+ 51 - 0
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()
+        {
+        }
+    }
+}

+ 29 - 0
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")]

+ 17 - 0
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":{
+                
+            }
+        }
+    }
+}

+ 24 - 20
MediaBrowser.Common.Implementations/Archiving/ZipClient.cs → 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
 {
     /// <summary>
     /// Class DotNetZipClient
     /// </summary>
     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);

+ 176 - 142
MediaBrowser.Common.Implementations/BaseApplicationHost.cs → 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;
-
-namespace MediaBrowser.Common.Implementations
+using MediaBrowser.Model.Cryptography;
+using MediaBrowser.Model.Diagnostics;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Threading;
+
+#if NETSTANDARD1_6
+using System.Runtime.Loader;
+#endif
+
+namespace Emby.Common.Implementations
 {
     /// <summary>
     /// Class BaseApplicationHost
     /// </summary>
     /// <typeparam name="TApplicationPathsType">The type of the T application paths type.</typeparam>
-    public abstract class BaseApplicationHost<TApplicationPathsType> : IApplicationHost, IDependencyContainer
+    public abstract class BaseApplicationHost<TApplicationPathsType> : IApplicationHost
         where TApplicationPathsType : class, IApplicationPaths
     {
         /// <summary>
@@ -67,7 +78,7 @@ namespace MediaBrowser.Common.Implementations
         /// Gets or sets the plugins.
         /// </summary>
         /// <value>The plugins.</value>
-        public IEnumerable<IPlugin> Plugins { get; protected set; }
+        public IPlugin[] Plugins { get; protected set; }
 
         /// <summary>
         /// Gets or sets the log manager.
@@ -81,11 +92,6 @@ namespace MediaBrowser.Common.Implementations
         /// <value>The application paths.</value>
         protected TApplicationPathsType ApplicationPaths { get; private set; }
 
-        /// <summary>
-        /// The container
-        /// </summary>
-        protected readonly Container Container = new Container();
-
         /// <summary>
         /// The json serializer
         /// </summary>
@@ -125,11 +131,6 @@ namespace MediaBrowser.Common.Implementations
         /// <value>The kernel.</value>
         protected ITaskManager TaskManager { get; private set; }
         /// <summary>
-        /// Gets the security manager.
-        /// </summary>
-        /// <value>The security manager.</value>
-        protected ISecurityManager SecurityManager { get; private set; }
-        /// <summary>
         /// Gets the HTTP client.
         /// </summary>
         /// <value>The HTTP client.</value>
@@ -146,22 +147,14 @@ namespace MediaBrowser.Common.Implementations
         /// <value>The configuration manager.</value>
         protected IConfigurationManager ConfigurationManager { get; private set; }
 
-        /// <summary>
-        /// Gets or sets the installation manager.
-        /// </summary>
-        /// <value>The installation manager.</value>
-        protected IInstallationManager InstallationManager { get; private set; }
-
         protected IFileSystem FileSystemManager { get; private set; }
 
-        /// <summary>
-        /// Gets or sets the zip client.
-        /// </summary>
-        /// <value>The zip client.</value>
-        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; }
+
         /// <summary>
         /// Gets the name.
         /// </summary>
@@ -174,6 +167,10 @@ namespace MediaBrowser.Common.Implementations
         /// <value><c>true</c> if this instance is running as service; otherwise, <c>false</c>.</value>
         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; }
+        /// <summary>
+        /// The container
+        /// </summary>
+        protected readonly SimpleInjector.Container Container = new SimpleInjector.Container();
+
+        protected ISystemEvents SystemEvents { get; private set; }
+        protected IMemoryStreamFactory MemoryStreamFactory { get; private set; }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class.
         /// </summary>
-        protected BaseApplicationHost(TApplicationPathsType applicationPaths, 
-            ILogManager logManager, 
-            IFileSystem fileSystem)
-        {
-			XmlSerializer = new XmlSerializer (fileSystem, logManager.GetLogger("XmlSerializer"));
+        protected BaseApplicationHost(TApplicationPathsType applicationPaths,
+            ILogManager logManager,
+            IFileSystem fileSystem,
+            IEnvironmentInfo environmentInfo,
+            ISystemEvents systemEvents,
+            IMemoryStreamFactory memoryStreamFactory,
+            INetworkManager networkManager)
+        {
+            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<string>();
 
             ApplicationPaths = applicationPaths;
@@ -221,28 +236,10 @@ namespace MediaBrowser.Common.Implementations
         /// <returns>Task.</returns>
         public virtual async Task Init(IProgress<double> 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
         /// <returns>Task.</returns>
         public virtual Task RunStartupTasks()
         {
-			Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
+            Resolve<ITaskManager>().AddTasks(GetExports<IScheduledTask>(false));
 
-			ConfigureAutorun ();
+            ConfigureAutorun();
 
-			ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
+            ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
 
-			return Task.FromResult (true);
+            return Task.FromResult(true);
         }
 
         /// <summary>
@@ -427,10 +425,56 @@ namespace MediaBrowser.Common.Implementations
         /// </summary>
         protected virtual void FindParts()
         {
-            RegisterModules();
-            
             ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
-            Plugins = GetExports<IPlugin>();
+            Plugins = GetExports<IPlugin>().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;
         }
 
         /// <summary>
@@ -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
         /// <returns>Task.</returns>
         protected virtual Task RegisterResources(IProgress<double> progress)
         {
-			RegisterSingleInstance(ConfigurationManager);
-			RegisterSingleInstance<IApplicationHost>(this);
+            RegisterSingleInstance(ConfigurationManager);
+            RegisterSingleInstance<IApplicationHost>(this);
 
-			RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
+            RegisterSingleInstance<IApplicationPaths>(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<IDependencyModule>();
-
-            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);
         }
 
         /// <summary>
@@ -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);
-
         /// <summary>
         /// Creates an instance of type and resolves all constructor dependancies
         /// </summary>
@@ -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>(T obj, bool manageLifetime)
-        {
-            RegisterSingleInstance(obj, manageLifetime);
-        }
-
         /// <summary>
         /// Registers the specified obj.
         /// </summary>
@@ -617,11 +648,6 @@ namespace MediaBrowser.Common.Implementations
             }
         }
 
-        void IDependencyContainer.RegisterSingleInstance<T>(Func<T> func)
-        {
-            RegisterSingleInstance(func);
-        }
-
         /// <summary>
         /// Registers the single instance.
         /// </summary>
@@ -633,11 +659,6 @@ namespace MediaBrowser.Common.Implementations
             Container.RegisterSingleton(func);
         }
 
-        void IDependencyContainer.Register(Type typeInterface, Type typeImplementation)
-        {
-            Container.Register(typeInterface, typeImplementation);
-        }
-
         /// <summary>
         /// Resolves this instance.
         /// </summary>
@@ -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<Type>();
         }
 
         /// <summary>
@@ -747,7 +781,7 @@ namespace MediaBrowser.Common.Implementations
         {
             var list = Plugins.ToList();
             list.Remove(plugin);
-            Plugins = list;
+            Plugins = list.ToArray();
         }
 
         /// <summary>

+ 6 - 10
MediaBrowser.Common.Implementations/BaseApplicationPaths.cs → 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
 {
     /// <summary>
     /// Provides a base class to hold common application paths used by both the Ui and Server.
@@ -12,22 +12,18 @@ namespace MediaBrowser.Common.Implementations
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
         /// </summary>
-        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; }
 
         /// <summary>
         /// Gets the path to the system folder
         /// </summary>
-        public string ProgramSystemPath
-        {
-            get { return Path.GetDirectoryName(ApplicationPath); }
-        }
+        public string ProgramSystemPath { get; private set; }
 
         /// <summary>
         /// The _data directory

+ 15 - 14
MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs → 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
 {
     /// <summary>
     /// 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)
             {

+ 8 - 7
MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs → 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
 {
     /// <summary>
     /// Class ConfigurationHelper
@@ -18,7 +19,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
         /// <param name="path">The path.</param>
         /// <param name="xmlSerializer">The XML serializer.</param>
         /// <returns>System.Object.</returns>
-        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;

+ 40 - 0
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);
+            }
+        }
+    }
+}

+ 5 - 5
MediaBrowser.Common.Implementations/Devices/DeviceId.cs → 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
     {

+ 108 - 0
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();
+        }
+    }
+}

+ 12 - 0
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);
+        }
+    }
+}

+ 23 - 0
Emby.Common.Implementations/Emby.Common.Implementations.xproj

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
+    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
+  </PropertyGroup>
+  <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>5a27010a-09c6-4e86-93ea-437484c10917</ProjectGuid>
+    <RootNamespace>Emby.Common.Implementations</RootNamespace>
+    <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
+    <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
+    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SchemaVersion>2.0</SchemaVersion>
+  </PropertyGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
+    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
+  </ItemGroup>
+  <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
+</Project>

+ 119 - 0
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);
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientInfo.cs → Emby.Common.Implementations/HttpClientManager/HttpClientInfo.cs

@@ -1,6 +1,6 @@
 using System;
 
-namespace MediaBrowser.Common.Implementations.HttpClientManager
+namespace Emby.Common.Implementations.HttpClientManager
 {
     /// <summary>
     /// Class HttpClientInfo

+ 92 - 22
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs → 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
 {
     /// <summary>
     /// 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;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="HttpClientManager" /> class.
@@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
         /// <exception cref="System.ArgumentNullException">appPaths
         /// or
         /// logger</exception>
-        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    
         }
 
         /// <summary>
@@ -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<HttpResponseInfo> 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<WebResponse> GetResponseAsync(WebRequest request, TimeSpan timeout)
         {
+#if NET46
             var taskCompletion = new TaskCompletionSource<WebResponse>();
 
             Task<WebResponse> asyncTask = Task.Factory.FromAsync<WebResponse>(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)

+ 3 - 3
MediaBrowser.Common.Implementations/IO/IsoManager.cs → 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
 {
     /// <summary>
     /// Class IsoManager

+ 794 - 0
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
+{
+    /// <summary>
+    /// Class ManagedFileSystem
+    /// </summary>
+    public class ManagedFileSystem : IFileSystem
+    {
+        protected ILogger Logger;
+
+        private readonly bool _supportsAsyncFileStreams;
+        private char[] _invalidFileNameChars;
+        private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
+        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);
+        }
+
+        /// <summary>
+        /// Determines whether the specified filename is shortcut.
+        /// </summary>
+        /// <param name="filename">The filename.</param>
+        /// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
+        /// <exception cref="System.ArgumentNullException">filename</exception>
+        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));
+        }
+
+        /// <summary>
+        /// Resolves the shortcut.
+        /// </summary>
+        /// <param name="filename">The filename.</param>
+        /// <returns>System.String.</returns>
+        /// <exception cref="System.ArgumentNullException">filename</exception>
+        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;
+        }
+
+        /// <summary>
+        /// Creates the shortcut.
+        /// </summary>
+        /// <param name="shortcutPath">The shortcut path.</param>
+        /// <param name="target">The target.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// shortcutPath
+        /// or
+        /// target
+        /// </exception>
+        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();
+            }
+        }
+
+        /// <summary>
+        /// Returns a <see cref="FileSystemMetadata"/> object for the specified file or directory path.
+        /// </summary>
+        /// <param name="path">A path to a file or directory.</param>
+        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
+        /// <remarks>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
+        /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and all other properties will reflect the properties of the directory.</remarks>
+        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));
+            }
+        }
+
+        /// <summary>
+        /// Returns a <see cref="FileSystemMetadata"/> object for the specified file path.
+        /// </summary>
+        /// <param name="path">A path to a file.</param>
+        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
+        /// <remarks><para>If the specified path points to a directory, the returned <see cref="FileSystemMetadata"/> object's
+        /// <see cref="FileSystemMetadata.IsDirectory"/> property and the <see cref="FileSystemMetadata.Exists"/> property will both be set to false.</para>
+        /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
+        public FileSystemMetadata GetFileInfo(string path)
+        {
+            if (string.IsNullOrEmpty(path))
+            {
+                throw new ArgumentNullException("path");
+            }
+
+            var fileInfo = new FileInfo(path);
+
+            return GetFileSystemMetadata(fileInfo);
+        }
+
+        /// <summary>
+        /// Returns a <see cref="FileSystemMetadata"/> object for the specified directory path.
+        /// </summary>
+        /// <param name="path">A path to a directory.</param>
+        /// <returns>A <see cref="FileSystemMetadata"/> object.</returns>
+        /// <remarks><para>If the specified path points to a file, the returned <see cref="FileSystemMetadata"/> object's
+        /// <see cref="FileSystemMetadata.IsDirectory"/> property will be set to true and the <see cref="FileSystemMetadata.Exists"/> property will be set to false.</para>
+        /// <para>For automatic handling of files <b>and</b> directories, use <see cref="GetFileSystemInfo"/>.</para></remarks>
+        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;
+        }
+
+        /// <summary>
+        /// The space char
+        /// </summary>
+        private const char SpaceChar = ' ';
+
+        /// <summary>
+        /// Takes a filename and removes invalid characters
+        /// </summary>
+        /// <param name="filename">The filename.</param>
+        /// <returns>System.String.</returns>
+        /// <exception cref="System.ArgumentNullException">filename</exception>
+        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();
+        }
+
+        /// <summary>
+        /// Gets the creation time UTC.
+        /// </summary>
+        /// <param name="info">The info.</param>
+        /// <returns>DateTime.</returns>
+        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;
+            }
+        }
+
+        /// <summary>
+        /// Gets the creation time UTC.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>DateTime.</returns>
+        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;
+        }
+
+        /// <summary>
+        /// Gets the creation time UTC.
+        /// </summary>
+        /// <param name="info">The info.</param>
+        /// <returns>DateTime.</returns>
+        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;
+            }
+        }
+
+        /// <summary>
+        /// Gets the last write time UTC.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>DateTime.</returns>
+        public DateTime GetLastWriteTimeUtc(string path)
+        {
+            return GetLastWriteTimeUtc(GetFileSystemInfo(path));
+        }
+
+        /// <summary>
+        /// Gets the file stream.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="mode">The mode.</param>
+        /// <param name="access">The access.</param>
+        /// <param name="share">The share.</param>
+        /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
+        /// <returns>FileStream.</returns>
+        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;
+        }
+
+        /// <summary>
+        /// Swaps the files.
+        /// </summary>
+        /// <param name="file1">The file1.</param>
+        /// <param name="file2">The file2.</param>
+        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);
+        }
+
+        /// <summary>
+        /// Removes the hidden attribute.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        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<FileSystemMetadata> 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<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
+        {
+            var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+
+            return ToMetadata(path, new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
+        }
+
+        public IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
+        {
+            var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+
+            return ToMetadata(path, new DirectoryInfo(path).EnumerateFiles("*", searchOption));
+        }
+
+        public IEnumerable<FileSystemMetadata> 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<FileSystemMetadata> ToMetadata(string parentPath, IEnumerable<FileSystemInfo> 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<string> 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<string> GetDirectoryPaths(string path, bool recursive = false)
+        {
+            var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+            return Directory.EnumerateDirectories(path, "*", searchOption);
+        }
+
+        public IEnumerable<string> GetFilePaths(string path, bool recursive = false)
+        {
+            var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+            return Directory.EnumerateFiles(path, "*", searchOption);
+        }
+
+        public IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
+        {
+            var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+            return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
+        }
+
+        public virtual void SetExecutable(string path)
+        {
+            
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Common.Implementations/Logging/NLogger.cs → 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
 {
     /// <summary>
     /// Class NLogger

+ 544 - 0
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
+{
+    /// <summary>
+    /// Class NlogManager
+    /// </summary>
+    public class NlogManager : ILogManager
+    {
+        #region Private Fields
+
+        private LogSeverity _severity = LogSeverity.Debug;
+
+        /// <summary>
+        /// Gets or sets the log directory.
+        /// </summary>
+        /// <value>The log directory.</value>
+        private readonly string LogDirectory;
+
+        /// <summary>
+        /// Gets or sets the log file prefix.
+        /// </summary>
+        /// <value>The log file prefix.</value>
+        private readonly string LogFilePrefix;
+
+        #endregion
+
+        #region Event Declarations
+
+        /// <summary>
+        /// Occurs when [logger loaded].
+        /// </summary>
+        public event EventHandler LoggerLoaded;
+
+        #endregion
+
+        #region Public Properties
+
+        /// <summary>
+        /// Gets the log file path.
+        /// </summary>
+        /// <value>The log file path.</value>
+        public string LogFilePath { get; private set; }
+
+        /// <summary>
+        /// Gets or sets the exception message prefix.
+        /// </summary>
+        /// <value>The exception message prefix.</value>
+        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)
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="NlogManager" /> class.
+        /// </summary>
+        /// <param name="logDirectory">The log directory.</param>
+        /// <param name="logFileNamePrefix">The log file name prefix.</param>
+        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();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="NlogManager" /> class.
+        /// </summary>
+        /// <param name="logDirectory">The log directory.</param>
+        /// <param name="logFileNamePrefix">The log file name prefix.</param>
+        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
+
+        /// <summary>
+        /// Adds the file target.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="level">The level.</param>
+        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);
+
+        }
+
+        /// <summary>
+        /// Gets the log level.
+        /// </summary>
+        /// <param name="severity">The severity.</param>
+        /// <returns>LogLevel.</returns>
+        /// <exception cref="System.ArgumentException">Unrecognized LogSeverity</exception>
+        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
+
+        /// <summary>
+        /// Gets the logger.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        /// <returns>ILogger.</returns>
+        public MediaBrowser.Model.Logging.ILogger GetLogger(string name)
+        {
+
+            DebugFileWriter(
+                LogDirectory, String.Format(
+                "GetLogger called, name = [{0}].",
+                name
+            ));
+
+            return new NLogger(name, this);
+
+        }
+
+        /// <summary>
+        /// Adds the log target.
+        /// </summary>
+        /// <param name="target">The target.</param>
+        /// <param name="level">The level.</param>
+        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;
+
+        }
+
+        /// <summary>
+        /// Removes the target.
+        /// </summary>
+        /// <param name="name">The name.</param>
+        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");
+
+        }
+
+        /// <summary>
+        /// Reloads the logger, maintaining the current log level.
+        /// </summary>
+        public void ReloadLogger()
+        {
+            ReloadLogger(LogSeverity);
+        }
+
+        /// <summary>
+        /// Reloads the logger, using the specified logging level.
+        /// </summary>
+        /// <param name="level">The level.</param>
+        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);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Flushes this instance.
+        /// </summary>
+        public void Flush()
+        {
+
+            DebugFileWriter(
+                LogDirectory, String.Format(
+                "Flush called."
+            ));
+
+            LogManager.Flush();
+
+        }
+
+        #endregion
+
+        #region Conditional Debug Methods
+
+        /// <summary>
+        /// DEBUG: Standalone method to write out debug to assist with logger development/troubleshooting.
+        /// <list type="bullet">
+        /// <item><description>The output file will be written to the server's log directory.</description></item>
+        /// <item><description>Calls to the method are safe and will never throw any exceptions.</description></item>
+        /// <item><description>Method calls will be omitted unless the library is compiled with DEBUG defined.</description></item>
+        /// </list>
+        /// </summary>
+        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
+    }
+}

+ 74 - 0
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
+{
+    /// <summary>
+    /// Correclty implements the <see cref="IDisposable"/> interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an <see cref="IsDisposed"/> property.
+    /// </summary>
+    public abstract class DisposableManagedObjectBase : IDisposable
+    {
+
+        #region Public Methods
+
+        /// <summary>
+        /// Override this method and dispose any objects you own the lifetime of if disposing is true;
+        /// </summary>
+        /// <param name="disposing">True if managed objects should be disposed, if false, only unmanaged resources should be released.</param>
+        protected abstract void Dispose(bool disposing);
+
+        /// <summary>
+        /// Throws and <see cref="System.ObjectDisposedException"/> if the <see cref="IsDisposed"/> property is true.
+        /// </summary>
+        /// <seealso cref="IsDisposed"/>
+        /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="IsDisposed"/> property is true.</exception>
+        /// <seealso cref="Dispose()"/>
+        protected virtual void ThrowIfDisposed()
+        {
+            if (this.IsDisposed) throw new ObjectDisposedException(this.GetType().FullName);
+        }
+
+        #endregion
+
+        #region Public Properties
+
+        /// <summary>
+        /// Sets or returns a boolean indicating whether or not this instance has been disposed.
+        /// </summary>
+        /// <seealso cref="Dispose()"/>
+        public bool IsDisposed
+        {
+            get;
+            private set;
+        }
+
+        #endregion
+
+        #region IDisposable Members
+
+        /// <summary>
+        /// Disposes this object instance and all internally managed resources.
+        /// </summary>
+        /// <remarks>
+        /// <para>Sets the <see cref="IsDisposed"/> property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes.</para>
+        /// </remarks>
+        /// <seealso cref="IsDisposed"/>
+        public void Dispose()
+        {
+            try
+            {
+                IsDisposed = true;
+
+                Dispose(true);
+            }
+            finally
+            {
+                GC.SuppressFinalize(this);
+            }
+        }
+
+        #endregion
+    }
+}

+ 97 - 0
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<ISocket> onAccept, Func<bool> isClosed)
+        {
+            _acceptor = new SocketAcceptor(_logger, Socket, onAccept, isClosed, DualMode);
+
+            _acceptor.StartAccept();
+        }
+
+        public void Dispose()
+        {
+            Socket.Dispose();
+        }
+    }
+}

+ 127 - 0
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<bool> _isClosed;
+        private readonly Action<ISocket> _onAccept;
+        private readonly bool _isDualMode;
+
+        public SocketAcceptor(ILogger logger, Socket originalSocket, Action<ISocket> onAccept, Func<bool> 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<SocketAsyncEventArgs>(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);
+        }
+    }
+}

+ 160 - 0
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
+
+        /// <summary>
+        /// Creates a new UDP socket and binds it to the specified local port.
+        /// </summary>
+        /// <param name="localPort">An integer specifying the local port to bind the socket to.</param>
+        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;
+            }
+        }
+
+        /// <summary>
+        /// Creates a new UDP socket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
+        /// </summary>
+        /// <returns>An implementation of the <see cref="IUdpSocket"/> interface used by RSSDP components to perform socket operations.</returns>
+        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;
+            }
+        }
+
+        /// <summary>
+        /// Creates a new UDP socket that is a member of the specified multicast IP address, and binds it to the specified local port.
+        /// </summary>
+        /// <param name="ipAddress">The multicast IP address to make the socket a member of.</param>
+        /// <param name="multicastTimeToLive">The multicast time to live value for the socket.</param>
+        /// <param name="localPort">The number of the local port to bind to.</param>
+        /// <returns></returns>
+        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
+    }
+}

+ 242 - 0
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<SocketReceiveResult> ReceiveAsync()
+        {
+            ThrowIfDisposed();
+
+            var tcs = new TaskCompletionSource<SocketReceiveResult>();
+
+            EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0);
+            var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
+            state.TaskCompletionSource = tcs;
+
+#if NETSTANDARD1_6
+            _Socket.ReceiveFromAsync(new ArraySegment<Byte>(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<bool>();
+
+            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<int> 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<SocketReceiveResult> TaskCompletionSource { get; set; }
+
+        }
+
+        #endregion
+
+    }
+}

+ 199 - 59
MediaBrowser.Common.Implementations/Networking/BaseNetworkManager.cs → 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<IPAddress> _localIpAddresses;
+        private List<IpAddressInfo> _localIpAddresses;
         private readonly object _localIpAddressSyncLock = new object();
 
-        /// <summary>
-        /// Gets the machine's local ip address
-        /// </summary>
-        /// <returns>IPAddress.</returns>
-		public IEnumerable<IPAddress> GetLocalIpAddresses()
+        public List<IpAddressInfo> 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<IPAddress> GetLocalIpAddressesInternal()
+        private IEnumerable<IPAddress> 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<IPAddress> GetIpAddresses(string hostName)
+        private Task<IPAddress[]> GetIpAddresses(string hostName)
         {
-            return Dns.GetHostAddresses(hostName);
+            return Dns.GetHostAddressesAsync(hostName);
         }
 
-		private List<IPAddress> GetIPsDefault()
-		{
-			NetworkInterface[] interfaces;
+        private readonly List<NetworkInterfaceType> _validNetworkInterfaceTypes = new List<NetworkInterfaceType>
+        {
+            NetworkInterfaceType.Ethernet,
+            NetworkInterfaceType.Wireless80211
+        };
+
+        private List<IPAddress> GetIPsDefault()
+        {
+            NetworkInterface[] interfaces;
 
-			try
-			{
-				interfaces = NetworkInterface.GetAllNetworkInterfaces();
-			}
-			catch (Exception ex)
-			{
-				Logger.ErrorException("Error in GetAllNetworkInterfaces", ex);
-				return new List<IPAddress>();
-			}
+            try
+            {
+                var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
 
-			return interfaces.SelectMany(network => {
+                interfaces = NetworkInterface.GetAllNetworkInterfaces()
+                    .Where(i => validStatuses.Contains(i.OperationalStatus))
+                    .ToArray();
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error in GetAllNetworkInterfaces", ex);
+                return new List<IPAddress>();
+            }
 
-				try
-				{
+            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();
+
+                    // 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<IPAddress>();
+                    }
+
+                    //if (!_validNetworkInterfaceTypes.Contains(network.NetworkInterfaceType))
+                    //{
+                    //    return new List<IPAddress>();
+                    //}
 
-					return properties.UnicastAddresses
-                        .Where(i => i.IsDnsEligible)
+                    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<IPAddress>();
-				}
-
-			}).DistinctBy(i => i.ToString())
-				.ToList();
-		}
-
-		private IEnumerable<IPAddress> GetLocalIpAddressesFallback()
+                        .ToList();
+                }
+                catch (Exception ex)
+                {
+                    Logger.ErrorException("Error querying network interface", ex);
+                    return new List<IPAddress>();
+                }
+
+            }).DistinctBy(i => i.ToString())
+                .ToList();
+        }
+
+        private async Task<IEnumerable<IPAddress>> 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
         /// <returns>IPEndPoint.</returns>
         public IPEndPoint Parse(string endpointstring)
         {
-            return Parse(endpointstring, -1);
+            return Parse(endpointstring, -1).Result;
         }
 
         /// <summary>
@@ -290,7 +314,7 @@ namespace MediaBrowser.Common.Implementations.Networking
         /// <returns>IPEndPoint.</returns>
         /// <exception cref="System.ArgumentException">Endpoint descriptor may not be empty.</exception>
         /// <exception cref="System.FormatException"></exception>
-        private static IPEndPoint Parse(string endpointstring, int defaultport)
+        private static async Task<IPEndPoint> 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
         /// <param name="p">The p.</param>
         /// <returns>IPAddress.</returns>
         /// <exception cref="System.ArgumentException"></exception>
-        private static IPAddress GetIPfromHost(string p)
+        private static async Task<IPAddress> 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<IpAddressInfo[]> GetHostAddressesAsync(string host)
+        {
+            var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false);
+            return addresses.Select(ToIpAddressInfo).ToArray();
+        }
+
+        /// <summary>
+        /// Gets the network shares.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>IEnumerable{NetworkShare}.</returns>
+        public virtual IEnumerable<NetworkShare> GetNetworkShares(string path)
+        {
+            return new List<NetworkShare>();
+        }
+
+        /// <summary>
+        /// Gets available devices within the domain
+        /// </summary>
+        /// <returns>PC's in the Domain</returns>
+        public virtual IEnumerable<FileSystemEntryInfo> GetNetworkDevices()
+        {
+            return new List<FileSystemEntryInfo>();
+        }
     }
 }

+ 19 - 0
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")]

+ 31 - 0
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();
+        }
+    }
+}

+ 4 - 4
MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs → 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
 {
     /// <summary>
     /// Represents a task trigger that fires everyday

+ 4 - 4
MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs → 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
 {
     /// <summary>
     /// Represents a task trigger that runs repeatedly on an interval

+ 143 - 30
MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs → Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -1,20 +1,20 @@
-using MediaBrowser.Common.Configuration;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Model.Events;
+using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.System;
 using MediaBrowser.Model.Tasks;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using CommonIO;
 
-namespace MediaBrowser.Common.Implementations.ScheduledTasks
+namespace Emby.Common.Implementations.ScheduledTasks
 {
     /// <summary>
     /// Class ScheduledTaskWorker
@@ -53,6 +53,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <value>The task manager.</value>
         private ITaskManager TaskManager { get; set; }
         private readonly IFileSystem _fileSystem;
+        private readonly ISystemEvents _systemEvents;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class.
@@ -73,7 +74,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// or
         /// logger
         /// </exception>
-        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
         /// <summary>
         /// The _triggers
         /// </summary>
-        private List<ITaskTrigger> _triggers;
+        private Tuple<TaskTriggerInfo,ITaskTrigger>[] _triggers;
         /// <summary>
         /// Gets the triggers that define when the task will run
         /// </summary>
         /// <value>The triggers.</value>
-        /// <exception cref="System.ArgumentNullException">value</exception>
-        public IEnumerable<ITaskTrigger> Triggers
+        private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
         {
             get
             {
@@ -257,11 +258,33 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
                     DisposeTriggers();
                 }
 
-                _triggers = value.ToList();
+                _triggers = value.ToArray();
 
                 ReloadTriggerEvents(false);
+            }
+        }
+
+        /// <summary>
+        /// Gets the triggers that define when the task will run
+        /// </summary>
+        /// <value>The triggers.</value>
+        /// <exception cref="System.ArgumentNullException">value</exception>
+        public TaskTriggerInfo[] Triggers
+        {
+            get
+            {
+                return InternalTriggers.Select(i => i.Item1).ToArray();
+            }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException("value");
+                }
+
+                SaveTriggers(value);
 
-                SaveTriggers(_triggers);
+                InternalTriggers = value.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray();
             }
         }
 
@@ -304,8 +327,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
         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.
         /// </summary>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
-        private List<ITaskTrigger> LoadTriggers()
+        private Tuple<TaskTriggerInfo, ITaskTrigger>[] LoadTriggers()
+        {
+            var settings = LoadTriggerSettings();
+
+            return settings.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray();
+        }
+
+        private TaskTriggerInfo[] LoadTriggerSettings()
         {
             try
             {
                 return JsonSerializer.DeserializeFromFile<IEnumerable<TaskTriggerInfo>>(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.
         /// </summary>
         /// <param name="triggers">The triggers.</param>
-        private void SaveTriggers(IEnumerable<ITaskTrigger> 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);
         }
 
         /// <summary>
@@ -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
             }
         }
 
+        /// <summary>
+        /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger
+        /// </summary>
+        /// <param name="info">The info.</param>
+        /// <returns>BaseTaskTrigger.</returns>
+        /// <exception cref="System.ArgumentNullException"></exception>
+        /// <exception cref="System.ArgumentException">Invalid trigger type:  + info.Type</exception>
+        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);
+        }
+
         /// <summary>
         /// Disposes each trigger
         /// </summary>
         private void DisposeTriggers()
         {
-            foreach (var trigger in Triggers)
+            foreach (var triggerInfo in InternalTriggers)
             {
+                var trigger = triggerInfo.Item2;
                 trigger.Triggered -= trigger_Triggered;
                 trigger.Stop();
             }

+ 4 - 4
MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs → 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
 {
     /// <summary>
     /// Class StartupTaskTrigger

+ 23 - 21
MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs → 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
 {
     /// <summary>
     /// Class SystemEventTrigger
@@ -26,6 +26,13 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// </value>
         public TaskExecutionOptions TaskOptions { get; set; }
 
+        private readonly ISystemEvents _systemEvents;
+
+        public SystemEventTrigger(ISystemEvents systemEvents)
+        {
+            _systemEvents = systemEvents;
+        }
+
         /// <summary>
         /// Stars waiting for the trigger action
         /// </summary>
@@ -36,27 +43,14 @@ namespace MediaBrowser.Common.ScheduledTasks
             switch (SystemEvent)
             {
                 case SystemEvent.WakeFromSleep:
-                    SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
+                    _systemEvents.Resume += _systemEvents_Resume;
                     break;
             }
         }
 
-        /// <summary>
-        /// Stops waiting for the trigger action
-        /// </summary>
-        public void Stop()
-        {
-            SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
-        }
-
-        /// <summary>
-        /// Handles the PowerModeChanged event of the SystemEvents control.
-        /// </summary>
-        /// <param name="sender">The source of the event.</param>
-        /// <param name="e">The <see cref="PowerModeChangedEventArgs" /> instance containing the event data.</param>
-        async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
+        private async void _systemEvents_Resume(object sender, EventArgs e)
         {
-            if (e.Mode == PowerModes.Resume && SystemEvent == SystemEvent.WakeFromSleep)
+            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);
@@ -65,6 +59,14 @@ namespace MediaBrowser.Common.ScheduledTasks
             }
         }
 
+        /// <summary>
+        /// Stops waiting for the trigger action
+        /// </summary>
+        public void Stop()
+        {
+            _systemEvents.Resume -= _systemEvents_Resume;
+        }
+
         /// <summary>
         /// Occurs when [triggered].
         /// </summary>

+ 11 - 40
MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs → 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
 {
     /// <summary>
     /// Class TaskManager
@@ -47,6 +46,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <value>The application paths.</value>
         private IApplicationPaths ApplicationPaths { get; set; }
 
+        private readonly ISystemEvents _systemEvents;
+
         /// <summary>
         /// Gets the logger.
         /// </summary>
@@ -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();
-                }
-            }
-        }
-
         /// <summary>
         /// Initializes a new instance of the <see cref="TaskManager" /> class.
         /// </summary>
@@ -80,29 +62,23 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="logger">The logger.</param>
         /// <exception cref="System.ArgumentException">kernel</exception>
-        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
         /// </summary>
         private void ExecuteQueuedTasks()
         {
-            if (SuspendTriggers)
-            {
-                return;
-            }
-
             Logger.Info("ExecuteQueuedTasks");
 
             // Execute queued tasks

+ 20 - 11
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs → 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
 {
     /// <summary>
     /// Deletes old cache files
@@ -40,13 +40,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         /// Creates the triggers that define when the task will run
         /// </summary>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
-        public IEnumerable<ITaskTrigger> GetDefaultTriggers()
+        public IEnumerable<TaskTriggerInfo> 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
         /// <param name="progress">The progress.</param>
         private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> 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"; }
+        }
+
         /// <summary>
         /// Gets the description.
         /// </summary>
@@ -202,5 +206,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         {
             get { return true; }
         }
+
+        public bool IsLogged
+        {
+            get { return true; }
+        }
     }
 }

+ 18 - 9
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs → 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
 {
     /// <summary>
     /// Deletes old log files
@@ -36,13 +36,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         /// Creates the triggers that define when the task will run
         /// </summary>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
-        public IEnumerable<ITaskTrigger> GetDefaultTriggers()
+        public IEnumerable<TaskTriggerInfo> 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"; }
+        }
+
         /// <summary>
         /// Gets the name of the task
         /// </summary>
@@ -125,5 +129,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         {
             get { return true; }
         }
+
+        public bool IsLogged
+        {
+            get { return true; }
+        }
     }
 }

+ 14 - 7
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerFileTask.cs → 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
 {
     /// <summary>
     /// Class ReloadLoggerFileTask
@@ -39,9 +39,9 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         /// Gets the default triggers.
         /// </summary>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
-        public IEnumerable<ITaskTrigger> GetDefaultTriggers()
+        public IEnumerable<TaskTriggerInfo> 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; }
+
         /// <summary>
         /// Gets the description.
         /// </summary>
@@ -101,5 +103,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         {
             get { return true; }
         }
+
+        public bool IsLogged
+        {
+            get { return true; }
+        }
     }
 }

+ 1 - 1
MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs → 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
 {
     /// <summary>
     /// Represents a task trigger that fires on a weekly basis

+ 5 - 5
MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs → 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
 {
     /// <summary>
     /// 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);
             }

+ 23 - 14
MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs → 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
 {
     /// <summary>
     /// Provides a wrapper around third party xml serialization.
     /// </summary>
-    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<string, System.Xml.Serialization.XmlSerializer> _serializers =
-            new Dictionary<string, System.Xml.Serialization.XmlSerializer>();
+        private readonly Dictionary<string, XmlSerializer> _serializers =
+            new Dictionary<string, XmlSerializer>();
 
-        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
         /// </summary>
         /// <param name="obj">The obj.</param>
         /// <param name="writer">The writer.</param>
-        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
         /// <returns>System.Object.</returns>
         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
         /// <param name="stream">The stream.</param>
         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
         }
 
         /// <summary>

+ 43 - 0
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;
+        }
+    }
+}

+ 39 - 0
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<object> callback, object state, TimeSpan dueTime, TimeSpan period)
+        {
+            _timer = new Timer(new TimerCallback(callback), state, dueTime, period);
+        }
+
+        public CommonTimer(Action<object> callback, object state, int dueTimeMs, int periodMs)
+        {
+            _timer = new Timer(new TimerCallback(callback), state, dueTimeMs, periodMs);
+        }
+
+        public void Change(TimeSpan dueTime, TimeSpan period)
+        {
+            _timer.Change(dueTime, period);
+        }
+
+        public void Change(int dueTimeMs, int periodMs)
+        {
+            _timer.Change(dueTimeMs, periodMs);
+        }
+
+        public void Dispose()
+        {
+            _timer.Dispose();
+        }
+    }
+}

+ 21 - 0
Emby.Common.Implementations/Threading/TimerFactory.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Threading;
+
+namespace Emby.Common.Implementations.Threading
+{
+    public class TimerFactory : ITimerFactory
+    {
+        public ITimer Create(Action<object> callback, object state, TimeSpan dueTime, TimeSpan period)
+        {
+            return new CommonTimer(callback, state, dueTime, period);
+        }
+
+        public ITimer Create(Action<object> callback, object state, int dueTimeMs, int periodMs)
+        {
+            return new CommonTimer(callback, state, dueTimeMs, periodMs);
+        }
+    }
+}

+ 22 - 0
Emby.Common.Implementations/Xml/XmlReaderSettingsFactory.cs

@@ -0,0 +1,22 @@
+using System.Xml;
+using MediaBrowser.Model.Xml;
+
+namespace Emby.Common.Implementations.Xml
+{
+    public class XmlReaderSettingsFactory : IXmlReaderSettingsFactory
+    {
+        public XmlReaderSettings Create(bool enableValidation)
+        {
+            var settings = new XmlReaderSettings();
+
+            if (!enableValidation)
+            {
+#if NET46
+                settings.ValidationType = ValidationType.None;
+#endif
+            }
+
+            return settings;
+        }
+    }
+}

+ 71 - 0
Emby.Common.Implementations/project.json

@@ -0,0 +1,71 @@
+{
+  "version": "1.0.0-*",
+
+  "dependencies": {
+
+  },
+
+  "frameworks": {
+    "net46": {
+      "frameworkAssemblies": {
+        "System.Collections": "4.0.0.0",
+        "System.IO": "4.0.0.0",
+        "System.Net": "4.0.0.0",
+        "System.Net.Http": "4.0.0.0",
+        "System.Net.Primitives": "4.0.0.0",
+        "System.Net.Http.WebRequest": "4.0.0.0",
+        "System.Reflection": "4.0.0.0",
+        "System.Runtime": "4.0.0.0",
+        "System.Runtime.Extensions": "4.0.0.0",
+        "System.Text.Encoding": "4.0.0.0",
+        "System.Threading": "4.0.0.0",
+        "System.Threading.Tasks": "4.0.0.0",
+        "System.Xml.ReaderWriter": "4.0.0"
+      },
+      "dependencies": {
+        "SimpleInjector": "3.2.4",
+        "ServiceStack.Text": "4.5.4",
+        "NLog": "4.4.0-betaV15",
+        "sharpcompress": "0.14.0",
+        "MediaBrowser.Model": {
+          "target": "project"
+        },
+        "MediaBrowser.Common": {
+          "target": "project"
+        }
+      }
+    },
+    "netstandard1.6": {
+      "imports": "dnxcore50",
+      "dependencies": {
+        "NETStandard.Library": "1.6.1",
+        "System.IO.FileSystem.DriveInfo": "4.3.0",
+        "System.Diagnostics.Process": "4.3.0",
+        "System.Threading.Timer": "4.3.0",
+        "System.Net.Requests": "4.3.0",
+        "System.Xml.ReaderWriter": "4.3.0",
+        "System.Xml.XmlSerializer": "4.3.0",
+        "System.Net.Http": "4.3.0",
+        "System.Net.Primitives": "4.3.0",
+        "System.Net.Sockets": "4.3.0",
+        "System.Net.NetworkInformation": "4.3.0",
+        "System.Net.NameResolution": "4.3.0",
+        "System.Runtime.InteropServices.RuntimeInformation": "4.3.0",
+        "System.Reflection": "4.3.0",
+        "System.Reflection.Primitives": "4.3.0",
+        "System.Runtime.Loader": "4.3.0",
+        "SimpleInjector": "3.2.4",
+        "ServiceStack.Text.Core": "1.0.27",
+        "NLog": "4.4.0-betaV15",
+        "sharpcompress": "0.14.0",
+		"System.AppDomain": "2.0.11",
+        "MediaBrowser.Model": {
+          "target": "project"
+        },
+        "MediaBrowser.Common": {
+          "target": "project"
+        }
+      }
+    }
+  }
+}

+ 1 - 1
MediaBrowser.Dlna/Common/Argument.cs → Emby.Dlna/Common/Argument.cs

@@ -1,5 +1,5 @@
 
-namespace MediaBrowser.Dlna.Common
+namespace Emby.Dlna.Common
 {  
     public class Argument
     {

+ 1 - 1
MediaBrowser.Dlna/Common/DeviceIcon.cs → Emby.Dlna/Common/DeviceIcon.cs

@@ -1,5 +1,5 @@
 
-namespace MediaBrowser.Dlna.Common
+namespace Emby.Dlna.Common
 {
     public class DeviceIcon
     {

+ 1 - 1
MediaBrowser.Dlna/Common/DeviceService.cs → Emby.Dlna/Common/DeviceService.cs

@@ -1,5 +1,5 @@
 
-namespace MediaBrowser.Dlna.Common
+namespace Emby.Dlna.Common
 {
     public class DeviceService
     {

+ 1 - 1
MediaBrowser.Dlna/Common/ServiceAction.cs → Emby.Dlna/Common/ServiceAction.cs

@@ -1,6 +1,6 @@
 using System.Collections.Generic;
 
-namespace MediaBrowser.Dlna.Common
+namespace Emby.Dlna.Common
 {
     public class ServiceAction
     {

+ 1 - 1
MediaBrowser.Dlna/Common/StateVariable.cs → Emby.Dlna/Common/StateVariable.cs

@@ -1,6 +1,6 @@
 using System.Collections.Generic;
 
-namespace MediaBrowser.Dlna.Common
+namespace Emby.Dlna.Common
 {
     public class StateVariable
     {

+ 1 - 1
MediaBrowser.Dlna/ConfigurationExtension.cs → Emby.Dlna/ConfigurationExtension.cs

@@ -2,7 +2,7 @@
 using MediaBrowser.Model.Configuration;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Dlna
+namespace Emby.Dlna
 {
     public static class ConfigurationExtension
     {

+ 7 - 4
MediaBrowser.Dlna/ConnectionManager/ConnectionManager.cs → Emby.Dlna/ConnectionManager/ConnectionManager.cs

@@ -1,24 +1,27 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Dlna.Service;
+using Emby.Dlna.Service;
 using MediaBrowser.Model.Logging;
 using System.Collections.Generic;
+using MediaBrowser.Model.Xml;
 
-namespace MediaBrowser.Dlna.ConnectionManager
+namespace Emby.Dlna.ConnectionManager
 {
     public class ConnectionManager : BaseService, IConnectionManager
     {
         private readonly IDlnaManager _dlna;
         private readonly ILogger _logger;
         private readonly IServerConfigurationManager _config;
+        protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
 
-        public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
+        public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
             : base(logger, httpClient)
         {
             _dlna = dlna;
             _config = config;
             _logger = logger;
+            XmlReaderSettingsFactory = xmlReaderSettingsFactory;
         }
 
         public string GetServiceXml(IDictionary<string, string> headers)
@@ -31,7 +34,7 @@ namespace MediaBrowser.Dlna.ConnectionManager
             var profile = _dlna.GetProfile(request.Headers) ??
                          _dlna.GetDefaultProfile();
 
-            return new ControlHandler(_logger, profile, _config).ProcessControlRequest(request);
+            return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request);
         }
     }
 }

+ 3 - 3
MediaBrowser.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs → Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs

@@ -1,8 +1,8 @@
-using MediaBrowser.Dlna.Common;
-using MediaBrowser.Dlna.Service;
+using Emby.Dlna.Common;
+using Emby.Dlna.Service;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Dlna.ConnectionManager
+namespace Emby.Dlna.ConnectionManager
 {
     public class ConnectionManagerXmlBuilder
     {

+ 11 - 11
MediaBrowser.Dlna/ConnectionManager/ControlHandler.cs → Emby.Dlna/ConnectionManager/ControlHandler.cs

@@ -1,25 +1,20 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Dlna.Server;
-using MediaBrowser.Dlna.Service;
+using Emby.Dlna.Server;
+using Emby.Dlna.Service;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Generic;
+using MediaBrowser.Model.Xml;
 
-namespace MediaBrowser.Dlna.ConnectionManager
+namespace Emby.Dlna.ConnectionManager
 {
     public class ControlHandler : BaseControlHandler
     {
         private readonly DeviceProfile _profile;
 
-        public ControlHandler(ILogger logger, DeviceProfile profile, IServerConfigurationManager config)
-            : base(config, logger)
-        {
-            _profile = profile;
-        }
-
-        protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
+        protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
         {
             if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
             {
@@ -31,11 +26,16 @@ namespace MediaBrowser.Dlna.ConnectionManager
 
         private IEnumerable<KeyValuePair<string, string>> HandleGetProtocolInfo()
         {
-            return new Headers(true)
+            return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
             {
                 { "Source", _profile.ProtocolInfo },
                 { "Sink", "" }
             };
         }
+
+        public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory)
+        {
+            _profile = profile;
+        }
     }
 }

+ 2 - 2
MediaBrowser.Dlna/ConnectionManager/ServiceActionListBuilder.cs → Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs

@@ -1,7 +1,7 @@
-using MediaBrowser.Dlna.Common;
+using Emby.Dlna.Common;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Dlna.ConnectionManager
+namespace Emby.Dlna.ConnectionManager
 {
     public class ServiceActionListBuilder
     {

+ 11 - 6
MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs → Emby.Dlna/ContentDirectory/ContentDirectory.cs

@@ -5,16 +5,17 @@ using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Localization;
-using MediaBrowser.Dlna.Service;
+using Emby.Dlna.Service;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Xml;
 
-namespace MediaBrowser.Dlna.ContentDirectory
+namespace Emby.Dlna.ContentDirectory
 {
     public class ContentDirectory : BaseService, IContentDirectory, IDisposable
     {
@@ -29,6 +30,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IUserViewManager _userViewManager;
         private readonly Func<IMediaEncoder> _mediaEncoder;
+        protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
 
         public ContentDirectory(IDlnaManager dlna,
             IUserDataManager userDataManager,
@@ -37,7 +39,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             IServerConfigurationManager config,
             IUserManager userManager,
             ILogger logger,
-            IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func<IMediaEncoder> mediaEncoder)
+            IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func<IMediaEncoder> mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
             : base(logger, httpClient)
         {
             _dlna = dlna;
@@ -51,6 +53,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             _mediaSourceManager = mediaSourceManager;
             _userViewManager = userViewManager;
             _mediaEncoder = mediaEncoder;
+            XmlReaderSettingsFactory = xmlReaderSettingsFactory;
         }
 
         private int SystemUpdateId
@@ -93,7 +96,8 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 _channelManager,
                 _mediaSourceManager,
                 _userViewManager,
-                _mediaEncoder())
+                _mediaEncoder(),
+                XmlReaderSettingsFactory)
                 .ProcessControlRequest(request);
         }
 
@@ -122,7 +126,8 @@ namespace MediaBrowser.Dlna.ContentDirectory
             }
 
             // No configuration so it's going to be pretty arbitrary
-            return _userManager.Users.First();
+            return _userManager.Users.FirstOrDefault(i => i.Policy.IsAdministrator) ??
+                _userManager.Users.First();
         }
 
         public void Dispose()

+ 5 - 4
MediaBrowser.Dlna/ContentDirectory/ContentDirectoryBrowser.cs → Emby.Dlna/ContentDirectory/ContentDirectoryBrowser.cs

@@ -10,8 +10,9 @@ using System.Security;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using Emby.Dlna.Server;
 
-namespace MediaBrowser.Dlna.ContentDirectory
+namespace Emby.Dlna.ContentDirectory
 {
     public class ContentDirectoryBrowser
     {
@@ -90,7 +91,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 request.ParentId = "1";
             }
 
-            builder.AppendFormat("<ObjectID>{0}</ObjectID>", SecurityElement.Escape(request.ParentId));
+            builder.AppendFormat("<ObjectID>{0}</ObjectID>", DescriptionXmlBuilder.Escape(request.ParentId));
             builder.Append("<BrowseFlag>BrowseDirectChildren</BrowseFlag>");
 
             //builder.Append("<BrowseFlag>BrowseMetadata</BrowseFlag>");
@@ -98,12 +99,12 @@ namespace MediaBrowser.Dlna.ContentDirectory
             builder.Append("<Filter>*</Filter>");
 
             request.StartIndex = request.StartIndex ?? 0;
-            builder.AppendFormat("<StartingIndex>{0}</StartingIndex>", SecurityElement.Escape(request.StartIndex.Value.ToString(CultureInfo.InvariantCulture)));
+            builder.AppendFormat("<StartingIndex>{0}</StartingIndex>", DescriptionXmlBuilder.Escape(request.StartIndex.Value.ToString(CultureInfo.InvariantCulture)));
 
             request.Limit = request.Limit ?? 20;
             if (request.Limit.HasValue)
             {
-                builder.AppendFormat("<RequestedCount>{0}</RequestedCount>", SecurityElement.Escape(request.Limit.Value.ToString(CultureInfo.InvariantCulture)));
+                builder.AppendFormat("<RequestedCount>{0}</RequestedCount>", DescriptionXmlBuilder.Escape(request.Limit.Value.ToString(CultureInfo.InvariantCulture)));
             }
 
             builder.Append("<SortCriteria></SortCriteria>");

+ 3 - 3
MediaBrowser.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs → Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs

@@ -1,8 +1,8 @@
-using MediaBrowser.Dlna.Common;
-using MediaBrowser.Dlna.Service;
+using Emby.Dlna.Common;
+using Emby.Dlna.Service;
 using System.Collections.Generic;
 
-namespace MediaBrowser.Dlna.ContentDirectory
+namespace Emby.Dlna.ContentDirectory
 {
     public class ContentDirectoryXmlBuilder
     {

部分文件因文件數量過多而無法顯示