diff options
277 files changed, 1054 insertions, 14212 deletions
diff --git a/Emby.Common.Implementations/Emby.Common.Implementations.csproj b/Emby.Common.Implementations/Emby.Common.Implementations.csproj index 00c90d16e..879e8e82f 100644 --- a/Emby.Common.Implementations/Emby.Common.Implementations.csproj +++ b/Emby.Common.Implementations/Emby.Common.Implementations.csproj @@ -32,12 +32,10 @@ </PropertyGroup> <ItemGroup> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> - <HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Text, Version=4.5.8.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\ServiceStack.Text.4.5.8\lib\net45\ServiceStack.Text.dll</HintPath> - <Private>True</Private> + <Reference Include="ServiceStack.Text, Version=4.5.12.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\ServiceStack.Text.4.5.12\lib\net45\ServiceStack.Text.dll</HintPath> </Reference> <Reference Include="SharpCompress, Version=0.14.0.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll</HintPath> diff --git a/Emby.Common.Implementations/Logging/NlogManager.cs b/Emby.Common.Implementations/Logging/NlogManager.cs index f7b723e8b..4446e2cdb 100644 --- a/Emby.Common.Implementations/Logging/NlogManager.cs +++ b/Emby.Common.Implementations/Logging/NlogManager.cs @@ -152,13 +152,23 @@ namespace Emby.Common.Implementations.Logging RemoveTarget("ApplicationLogFileWrapper"); - var wrapper = new AsyncTargetWrapper(); + // https://github.com/NLog/NLog/wiki/Performance + var wrapper = new AsyncTargetWrapper + { + OverflowAction = AsyncTargetWrapperOverflowAction.Block, + QueueLimit = 10000, + BatchSize = 500, + TimeToSleepBetweenBatches = 50 + }; + wrapper.Name = "ApplicationLogFileWrapper"; var logFile = new FileTarget { FileName = path, - Layout = "${longdate} ${level} ${logger}: ${message}" + Layout = "${longdate} ${level} ${logger}: ${message}", + KeepFileOpen = true, + ConcurrentWrites = false }; logFile.Name = "ApplicationLogFile"; diff --git a/Emby.Common.Implementations/Networking/NetworkManager.cs b/Emby.Common.Implementations/Networking/NetworkManager.cs index 2f218656c..354107bb7 100644 --- a/Emby.Common.Implementations/Networking/NetworkManager.cs +++ b/Emby.Common.Implementations/Networking/NetworkManager.cs @@ -506,7 +506,7 @@ namespace Emby.Common.Implementations.Networking public async Task<IpAddressInfo[]> GetHostAddressesAsync(string host) { var addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false); - return addresses.Select(ToIpAddressInfo).ToArray(); + return addresses.Select(ToIpAddressInfo).ToArray(addresses.Length); } /// <summary> diff --git a/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index c373ffddb..dd840677a 100644 --- a/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Extensions; namespace Emby.Common.Implementations.ScheduledTasks { @@ -274,7 +275,8 @@ namespace Emby.Common.Implementations.ScheduledTasks { get { - return InternalTriggers.Select(i => i.Item1).ToArray(); + var triggers = InternalTriggers; + return triggers.Select(i => i.Item1).ToArray(triggers.Length); } set { @@ -288,7 +290,7 @@ namespace Emby.Common.Implementations.ScheduledTasks SaveTriggers(triggerList); - InternalTriggers = triggerList.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray(); + InternalTriggers = triggerList.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray(triggerList.Length); } } diff --git a/Emby.Common.Implementations/packages.config b/Emby.Common.Implementations/packages.config index a255465cc..a9fc75af6 100644 --- a/Emby.Common.Implementations/packages.config +++ b/Emby.Common.Implementations/packages.config @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="NLog" version="4.4.11" targetFramework="net46" /> - <package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" /> + <package id="NLog" version="4.4.12" targetFramework="net46" /> + <package id="ServiceStack.Text" version="4.5.12" targetFramework="net46" /> <package id="SharpCompress" version="0.14.0" targetFramework="net462" /> <package id="SimpleInjector" version="4.0.8" targetFramework="net46" /> </packages>
\ No newline at end of file diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 4d82b6b25..4be2dc945 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -30,6 +30,7 @@ using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Xml; +using MediaBrowser.Model.Extensions; namespace Emby.Dlna.ContentDirectory { @@ -457,14 +458,14 @@ namespace Emby.Dlna.ContentDirectory { Limit = limit, StartIndex = startIndex, - SortBy = sortOrders.ToArray(), + SortBy = sortOrders.ToArray(sortOrders.Count), SortOrder = sort.SortOrder, User = user, Recursive = true, IsMissing = false, ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name }, IsFolder = isFolder, - MediaTypes = mediaTypes.ToArray(), + MediaTypes = mediaTypes.ToArray(mediaTypes.Count), DtoOptions = GetDtoOptions() }); } @@ -508,12 +509,12 @@ namespace Emby.Dlna.ContentDirectory { ItemId = item.Id - }).ToArray(); + }); var result = new QueryResult<ServerItem> { - Items = items.Select(i => new ServerItem(i)).ToArray(), - TotalRecordCount = items.Length + Items = items.Select(i => new ServerItem(i)).ToArray(items.Count), + TotalRecordCount = items.Count }; return ApplyPaging(result, startIndex, limit); @@ -662,7 +663,7 @@ namespace Emby.Dlna.ContentDirectory return new QueryResult<ServerItem> { - Items = list.ToArray(), + Items = list.ToArray(list.Count), TotalRecordCount = list.Count }; } @@ -740,7 +741,7 @@ namespace Emby.Dlna.ContentDirectory return new QueryResult<ServerItem> { - Items = list.ToArray(), + Items = list.ToArray(list.Count), TotalRecordCount = list.Count }; } @@ -828,7 +829,7 @@ namespace Emby.Dlna.ContentDirectory return new QueryResult<ServerItem> { - Items = list.ToArray(), + Items = list.ToArray(list.Count), TotalRecordCount = list.Count }; } @@ -995,7 +996,7 @@ namespace Emby.Dlna.ContentDirectory var result = new QueryResult<BaseItem> { TotalRecordCount = genresResult.TotalRecordCount, - Items = genresResult.Items.Select(i => i.Item1).ToArray() + Items = genresResult.Items.Select(i => i.Item1).ToArray(genresResult.Items.Length) }; return ToResult(result); @@ -1013,7 +1014,7 @@ namespace Emby.Dlna.ContentDirectory var result = new QueryResult<BaseItem> { TotalRecordCount = genresResult.TotalRecordCount, - Items = genresResult.Items.Select(i => i.Item1).ToArray() + Items = genresResult.Items.Select(i => i.Item1).ToArray(genresResult.Items.Length) }; return ToResult(result); @@ -1031,7 +1032,7 @@ namespace Emby.Dlna.ContentDirectory var result = new QueryResult<BaseItem> { TotalRecordCount = artists.TotalRecordCount, - Items = artists.Items.Select(i => i.Item1).ToArray() + Items = artists.Items.Select(i => i.Item1).ToArray(artists.Items.Length) }; return ToResult(result); @@ -1049,7 +1050,7 @@ namespace Emby.Dlna.ContentDirectory var result = new QueryResult<BaseItem> { TotalRecordCount = artists.TotalRecordCount, - Items = artists.Items.Select(i => i.Item1).ToArray() + Items = artists.Items.Select(i => i.Item1).ToArray(artists.Items.Length) }; return ToResult(result); @@ -1068,7 +1069,7 @@ namespace Emby.Dlna.ContentDirectory var result = new QueryResult<BaseItem> { TotalRecordCount = artists.TotalRecordCount, - Items = artists.Items.Select(i => i.Item1).ToArray() + Items = artists.Items.Select(i => i.Item1).ToArray(artists.Items.Length) }; return ToResult(result); @@ -1196,7 +1197,7 @@ namespace Emby.Dlna.ContentDirectory { var serverItems = result .Select(i => new ServerItem(i)) - .ToArray(); + .ToArray(result.Count); return new QueryResult<ServerItem> { @@ -1210,7 +1211,7 @@ namespace Emby.Dlna.ContentDirectory var serverItems = result .Items .Select(i => new ServerItem(i)) - .ToArray(); + .ToArray(result.Items.Length); return new QueryResult<ServerItem> { @@ -1227,7 +1228,7 @@ namespace Emby.Dlna.ContentDirectory sortOrders.Add(ItemSortBy.SortName); } - query.SortBy = sortOrders.ToArray(); + query.SortBy = sortOrders.ToArray(sortOrders.Count); query.SortOrder = sort.SortOrder; } @@ -1243,8 +1244,7 @@ namespace Emby.Dlna.ContentDirectory DtoOptions = GetDtoOptions() }); - var serverItems = itemsResult.Items.Select(i => new ServerItem(i)) - .ToArray(); + var serverItems = itemsResult.Items.Select(i => new ServerItem(i)).ToArray(itemsResult.Items.Length); return new QueryResult<ServerItem> { diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 82975ce22..847f63619 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -18,6 +18,7 @@ using System.Text; using System.Text.RegularExpressions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Reflection; +using MediaBrowser.Model.Extensions; namespace Emby.Dlna { @@ -106,7 +107,6 @@ namespace Emby.Dlna } else { - _logger.Debug("No matching device profile found. The default will need to be used."); LogUnmatchedProfile(deviceInfo); } @@ -220,12 +220,8 @@ namespace Emby.Dlna } else { - var msg = new StringBuilder(); - foreach (var header in headers) - { - msg.AppendLine(header.Key + ": " + header.Value); - } - _logger.LogMultiline("No matching device profile found. The default will need to be used.", LogSeverity.Info, msg); + var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray(headers.Count)); + _logger.Debug("No matching device profile found. {0}", headerString); } return profile; diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index e22298010..ff493e365 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -15,6 +15,7 @@ using System.Threading.Tasks; using System.Xml.Linq; using Emby.Dlna.Server; using MediaBrowser.Model.Threading; +using MediaBrowser.Model.Extensions; namespace Emby.Dlna.PlayTo { @@ -890,7 +891,7 @@ namespace Emby.Dlna.PlayTo if (room != null && !string.IsNullOrWhiteSpace(room.Value)) friendlyNames.Add(room.Value); - deviceProperties.Name = string.Join(" ", friendlyNames.ToArray()); + deviceProperties.Name = string.Join(" ", friendlyNames.ToArray(friendlyNames.Count)); var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault(); if (model != null) diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs index 06ce93640..ff025152a 100644 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ b/Emby.Dlna/Profiles/DefaultProfile.cs @@ -1,6 +1,7 @@ using MediaBrowser.Model.Dlna; using System.Linq; using System.Xml.Serialization; +using MediaBrowser.Model.Extensions; namespace Emby.Dlna.Profiles { @@ -164,7 +165,7 @@ namespace Emby.Dlna.Profiles public void AddXmlRootAttribute(string name, string value) { var atts = XmlRootAttributes ?? new XmlAttribute[] { }; - var list = atts.ToList(); + var list = atts.ToList(atts.Length); list.Add(new XmlAttribute { @@ -172,7 +173,7 @@ namespace Emby.Dlna.Profiles Value = value }); - XmlRootAttributes = list.ToArray(); + XmlRootAttributes = list.ToArray(list.Count); } } } diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 2a4a5792f..bba4adc5f 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -226,7 +226,7 @@ namespace Emby.Dlna.Server } } - var characters = characterList.ToArray(); + var characters = characterList.ToArray(characterList.Count); var serverName = new string(characters); diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 3092589c1..7cd10bd01 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -11,6 +11,7 @@ using System.Xml; using Emby.Dlna.Didl; using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Xml; +using MediaBrowser.Model.Extensions; namespace Emby.Dlna.Service { @@ -235,26 +236,29 @@ namespace Emby.Dlna.Service private void LogRequest(ControlRequest request) { - var builder = new StringBuilder(); + if (!Config.GetDlnaConfiguration().EnableDebugLog) + { + return; + } - var headers = string.Join(", ", request.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray()); - builder.AppendFormat("Headers: {0}", headers); - builder.AppendLine(); - //builder.Append(request.InputXml); + var originalHeaders = request.Headers; + var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray(originalHeaders.Count)); - Logger.LogMultiline("Control request", LogSeverity.Debug, builder); + Logger.Debug("Control request. Headers: {0}", headers); } private void LogResponse(ControlResponse response) { - var builder = new StringBuilder(); + if (!Config.GetDlnaConfiguration().EnableDebugLog) + { + return; + } - var headers = string.Join(", ", response.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray()); - builder.AppendFormat("Headers: {0}", headers); - builder.AppendLine(); - builder.Append(response.Xml); + var originalHeaders = response.Headers; + var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray(originalHeaders.Count)); + //builder.Append(response.Xml); - Logger.LogMultiline("Control response", LogSeverity.Debug, builder); + Logger.Debug("Control response. Headers: {0}", headers); } } } diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index cc7b77de6..bd23eba7a 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -17,12 +17,10 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using Emby.Drawing.Common; - -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Net; using MediaBrowser.Model.Threading; -using TagLib; +using MediaBrowser.Model.Extensions; namespace Emby.Drawing { @@ -662,7 +660,7 @@ namespace Emby.Drawing var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList(); cacheKeys.Add(originalImagePath + dateModified.Ticks); - return string.Join("|", cacheKeys.ToArray()).GetMD5().ToString("N"); + return string.Join("|", cacheKeys.ToArray(cacheKeys.Count)).GetMD5().ToString("N"); } /// <summary> diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index 57047cf81..c3c30ab6d 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -111,7 +111,7 @@ namespace Emby.Photos } item.Genres = image.ImageTag.Genres.ToList(); - item.Tags = image.ImageTag.Keywords.ToList(); + item.Tags = image.ImageTag.Keywords; item.Software = image.ImageTag.Software; if (image.ImageTag.Orientation == TagLib.Image.ImageOrientation.None) diff --git a/Emby.Server.Core/Emby.Server.Core.csproj b/Emby.Server.Core/Emby.Server.Core.csproj deleted file mode 100644 index 063ef6eb9..000000000 --- a/Emby.Server.Core/Emby.Server.Core.csproj +++ /dev/null @@ -1,176 +0,0 @@ -<?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> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>Emby.Server.Core</RootNamespace> - <AssemblyName>Emby.Server.Core</AssemblyName> - <TargetFrameworkVersion>v4.6</TargetFrameworkVersion> - <FileAlignment>512</FileAlignment> - <TargetFrameworkProfile /> - </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> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - </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> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - </PropertyGroup> - <ItemGroup> - <Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> - <HintPath>..\packages\Microsoft.IO.RecyclableMemoryStream.1.2.2\lib\net45\Microsoft.IO.RecyclableMemoryStream.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="ServiceStack.Text, Version=4.5.8.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\ServiceStack.Text.4.5.8\lib\net45\ServiceStack.Text.dll</HintPath> - <Private>True</Private> - </Reference> - <Reference Include="SimpleInjector, Version=4.0.8.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> - <HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath> - </Reference> - <Reference Include="System" /> - <Reference Include="System.Configuration" /> - <Reference Include="System.Core" /> - <Reference Include="System.Xml.Linq" /> - <Reference Include="System.Data.DataSetExtensions" /> - <Reference Include="Microsoft.CSharp" /> - <Reference Include="System.Data" /> - <Reference Include="System.Net.Http" /> - <Reference Include="System.Xml" /> - </ItemGroup> - <ItemGroup> - <Compile Include="..\SharedVersion.cs"> - <Link>Properties\SharedVersion.cs</Link> - </Compile> - <Compile Include="ApplicationHost.cs" /> - <Compile Include="ApplicationPathHelper.cs" /> - <Compile Include="Cryptography\ASN1.cs" /> - <Compile Include="Cryptography\ASN1Convert.cs" /> - <Compile Include="Cryptography\BitConverterLE.cs" /> - <Compile Include="Cryptography\CertificateGenerator.cs" /> - <Compile Include="Cryptography\CryptoConvert.cs" /> - <Compile Include="Cryptography\PfxGenerator.cs" /> - <Compile Include="Cryptography\PKCS1.cs" /> - <Compile Include="Cryptography\PKCS12.cs" /> - <Compile Include="Cryptography\PKCS7.cs" /> - <Compile Include="Cryptography\PKCS8.cs" /> - <Compile Include="Cryptography\X501Name.cs" /> - <Compile Include="Cryptography\X509Builder.cs" /> - <Compile Include="Cryptography\X509Certificate.cs" /> - <Compile Include="Cryptography\X509CertificateBuilder.cs" /> - <Compile Include="Cryptography\X509CertificateCollection.cs" /> - <Compile Include="Cryptography\X509Extension.cs" /> - <Compile Include="Cryptography\X509Extensions.cs" /> - <Compile Include="Cryptography\X520Attributes.cs" /> - <Compile Include="EntryPoints\ExternalPortForwarding.cs" /> - <Compile Include="HttpServerFactory.cs" /> - <Compile Include="IO\LibraryMonitor.cs" /> - <Compile Include="IO\MemoryStreamProvider.cs" /> - <Compile Include="Localization\TextLocalizer.cs" /> - <Compile Include="Logging\ConsoleLogger.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="SystemEvents.cs" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\Emby.Common.Implementations\Emby.Common.Implementations.csproj"> - <Project>{1e37a338-9f57-4b70-bd6d-bb9c591e319b}</Project> - <Name>Emby.Common.Implementations</Name> - </ProjectReference> - <ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj"> - <Project>{805844ab-e92f-45e6-9d99-4f6d48d129a5}</Project> - <Name>Emby.Dlna</Name> - </ProjectReference> - <ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj"> - <Project>{08fff49b-f175-4807-a2b5-73b0ebd9f716}</Project> - <Name>Emby.Drawing</Name> - </ProjectReference> - <ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj"> - <Project>{89ab4548-770d-41fd-a891-8daff44f452c}</Project> - <Name>Emby.Photos</Name> - </ProjectReference> - <ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj"> - <Project>{e383961b-9356-4d5d-8233-9a1079d03055}</Project> - <Name>Emby.Server.Implementations</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj"> - <Project>{4fd51ac5-2c16-4308-a993-c3a84f3b4582}</Project> - <Name>MediaBrowser.Api</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> - <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project> - <Name>MediaBrowser.Common</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> - <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> - <Name>MediaBrowser.Controller</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj"> - <Project>{7ef9f3e0-697d-42f3-a08f-19deb5f84392}</Project> - <Name>MediaBrowser.LocalMetadata</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj"> - <Project>{0bd82fa6-eb8a-4452-8af5-74f9c3849451}</Project> - <Name>MediaBrowser.MediaEncoding</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> - <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> - <Name>MediaBrowser.Model</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj"> - <Project>{442b5058-dcaf-4263-bb6a-f21e31120a1b}</Project> - <Name>MediaBrowser.Providers</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj"> - <Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project> - <Name>MediaBrowser.Server.Implementations</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj"> - <Project>{5624b7b5-b5a7-41d8-9f10-cc5611109619}</Project> - <Name>MediaBrowser.WebDashboard</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj"> - <Project>{23499896-b135-4527-8574-c26e926ea99e}</Project> - <Name>MediaBrowser.XbmcMetadata</Name> - </ProjectReference> - <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj"> - <Project>{cb7f2326-6497-4a3d-ba03-48513b17a7be}</Project> - <Name>Mono.Nat</Name> - </ProjectReference> - <ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj"> - <Project>{4a4402d4-e910-443b-b8fc-2c18286a2ca0}</Project> - <Name>OpenSubtitlesHandler</Name> - </ProjectReference> - <ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj"> - <Project>{1d74413b-e7cf-455b-b021-f52bdf881542}</Project> - <Name>SocketHttpListener</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> - <None Include="app.config" /> - <None Include="packages.config" /> - </ItemGroup> - <Import Project="$(MSBuildToolsPath)\Microsoft.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>
\ No newline at end of file diff --git a/Emby.Server.Core/Properties/AssemblyInfo.cs b/Emby.Server.Core/Properties/AssemblyInfo.cs deleted file mode 100644 index ead042981..000000000 --- a/Emby.Server.Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -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("Emby.Server.Core")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Emby.Server.Core")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// 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("776b9f0c-5195-45e3-9a36-1cc1f0d8e0b0")] - -// 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.*")]
\ No newline at end of file diff --git a/Emby.Server.Core/app.config b/Emby.Server.Core/app.config deleted file mode 100644 index 57ff62392..000000000 --- a/Emby.Server.Core/app.config +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<configuration> - <runtime> - <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> - <dependentAssembly> - <assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral"/> - <bindingRedirect oldVersion="0.0.0.0-4.0.7.0" newVersion="4.0.7.0"/> - </dependentAssembly> - </assemblyBinding> - </runtime> -<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration> diff --git a/Emby.Server.Core/packages.config b/Emby.Server.Core/packages.config deleted file mode 100644 index 6311b55eb..000000000 --- a/Emby.Server.Core/packages.config +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> - <package id="Microsoft.IO.RecyclableMemoryStream" version="1.2.2" targetFramework="net462" /> - <package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" /> - <package id="SimpleInjector" version="4.0.8" targetFramework="net46" /> -</packages>
\ No newline at end of file diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 567f139fd..702917832 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Activity { @@ -436,7 +437,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), Type = "ScheduledTaskFailed", - Overview = string.Join(Environment.NewLine, vals.ToArray()), + Overview = string.Join(Environment.NewLine, vals.ToArray(vals.Count)), ShortOverview = runningTime, Severity = LogSeverity.Error }); diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index e9b6f7a40..7720f8f2f 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -10,6 +10,7 @@ using MediaBrowser.Model.Activity; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using SQLitePCL.pretty; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Activity { @@ -94,13 +95,13 @@ namespace Emby.Server.Implementations.Activity var whereTextWithoutPaging = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); if (startIndex.HasValue && startIndex.Value > 0) { var pagingWhereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})", pagingWhereText, @@ -109,7 +110,7 @@ namespace Emby.Server.Implementations.Activity var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); commandText += whereText; @@ -154,7 +155,7 @@ namespace Emby.Server.Implementations.Activity result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); } - result.Items = list.ToArray(); + result.Items = list.ToArray(list.Count); return result; }, ReadTransactionMode); diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 68cb2a4e3..f1f24660c 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1,11 +1,52 @@ -using MediaBrowser.Api; +using Emby.Common.Implementations; +using Emby.Common.Implementations.Archiving; +using Emby.Common.Implementations.IO; +using Emby.Common.Implementations.Reflection; +using Emby.Common.Implementations.ScheduledTasks; +using Emby.Common.Implementations.Serialization; +using Emby.Common.Implementations.TextEncoding; +using Emby.Common.Implementations.Xml; +using Emby.Dlna; +using Emby.Dlna.ConnectionManager; +using Emby.Dlna.ContentDirectory; +using Emby.Dlna.Main; +using Emby.Dlna.MediaReceiverRegistrar; +using Emby.Dlna.Ssdp; +using Emby.Drawing; +using Emby.Photos; +using Emby.Server.Implementations.Activity; +using Emby.Server.Implementations.Channels; +using Emby.Server.Implementations.Collections; +using Emby.Server.Implementations.Configuration; +using Emby.Server.Implementations.Data; +using Emby.Server.Implementations.Devices; +using Emby.Server.Implementations.Dto; +using Emby.Server.Implementations.FFMpeg; +using Emby.Server.Implementations.HttpServer; +using Emby.Server.Implementations.HttpServer.Security; +using Emby.Server.Implementations.IO; +using Emby.Server.Implementations.Library; +using Emby.Server.Implementations.LiveTv; +using Emby.Server.Implementations.Localization; +using Emby.Server.Implementations.MediaEncoder; +using Emby.Server.Implementations.Migrations; +using Emby.Server.Implementations.Notifications; +using Emby.Server.Implementations.Playlists; +using Emby.Server.Implementations.Security; +using Emby.Server.Implementations.Session; +using Emby.Server.Implementations.Social; +using Emby.Server.Implementations.TV; +using Emby.Server.Implementations.Updates; +using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; -using Emby.Common.Implementations.ScheduledTasks; using MediaBrowser.Common.Net; +using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Progress; +using MediaBrowser.Common.Security; +using MediaBrowser.Common.Updates; using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; @@ -17,6 +58,9 @@ using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -34,102 +78,47 @@ using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Sync; using MediaBrowser.Controller.TV; using MediaBrowser.LocalMetadata.Savers; -using MediaBrowser.MediaEncoding.BdInfo; -using MediaBrowser.MediaEncoding.Encoder; -using MediaBrowser.MediaEncoding.Subtitles; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Diagnostics; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.News; +using MediaBrowser.Model.Reflection; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.Social; using MediaBrowser.Model.System; +using MediaBrowser.Model.Text; using MediaBrowser.Model.Updates; +using MediaBrowser.Model.Xml; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Subtitles; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; +using OpenSubtitlesHandler; +using ServiceStack; +using SocketHttpListener.Primitives; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Net; -using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; -using Emby.Common.Implementations; -using Emby.Common.Implementations.Archiving; -using Emby.Common.Implementations.IO; -using Emby.Common.Implementations.Reflection; -using Emby.Common.Implementations.Serialization; -using Emby.Common.Implementations.TextEncoding; -using Emby.Common.Implementations.Xml; -using Emby.Photos; -using MediaBrowser.Model.IO; -using MediaBrowser.Api.Playback; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Security; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using Emby.Dlna; -using Emby.Dlna.ConnectionManager; -using Emby.Dlna.ContentDirectory; -using Emby.Dlna.Main; -using Emby.Dlna.MediaReceiverRegistrar; -using Emby.Dlna.Ssdp; -using Emby.Server.Core; -using Emby.Server.Implementations.Activity; -using Emby.Server.Implementations.Devices; -using Emby.Server.Implementations.FFMpeg; -using Emby.Server.Core.IO; -using Emby.Server.Core.Localization; -using Emby.Server.Implementations.Migrations; -using Emby.Server.Implementations.Security; -using Emby.Server.Implementations.Social; -using Emby.Server.Implementations.Channels; -using Emby.Server.Implementations.Collections; -using Emby.Server.Implementations.Dto; -using Emby.Server.Implementations.IO; -using Emby.Server.Implementations.HttpServer; -using Emby.Server.Implementations.HttpServer.Security; -using Emby.Server.Implementations.Library; -using Emby.Server.Implementations.LiveTv; -using Emby.Server.Implementations.Localization; -using Emby.Server.Implementations.MediaEncoder; -using Emby.Server.Implementations.Notifications; -using Emby.Server.Implementations.Data; -using Emby.Server.Implementations.Playlists; -using Emby.Server.Implementations; -using Emby.Server.Implementations.ServerManager; -using Emby.Server.Implementations.Session; -using Emby.Server.Implementations.TV; -using Emby.Server.Implementations.Updates; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.News; -using MediaBrowser.Model.Reflection; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Social; -using MediaBrowser.Model.Text; -using MediaBrowser.Model.Xml; -using OpenSubtitlesHandler; -using ServiceStack; -using SocketHttpListener.Primitives; +using Emby.Server.MediaEncoding.Subtitles; +using MediaBrowser.MediaEncoding.BdInfo; using StringExtensions = MediaBrowser.Controller.Extensions.StringExtensions; -using Emby.Drawing; -using Emby.Server.Implementations.Migrations; -using MediaBrowser.Model.Diagnostics; -using Emby.Common.Implementations.Diagnostics; -using Emby.Server.Implementations.Configuration; -namespace Emby.Server.Core +namespace Emby.Server.Implementations { /// <summary> /// Class CompositionRoot @@ -357,13 +346,6 @@ namespace Emby.Server.Core { var builder = GetBaseExceptionMessage(ApplicationPaths); - // Skip if plugins haven't been loaded yet - //if (Plugins != null) - //{ - // var pluginString = string.Join("|", Plugins.Select(i => i.Name + "-" + i.Version.ToString()).ToArray()); - // builder.Insert(0, string.Format("Plugins: {0}{1}", pluginString, Environment.NewLine)); - //} - builder.Insert(0, string.Format("Version: {0}{1}", ApplicationVersion, Environment.NewLine)); builder.Insert(0, "*** Error Report ***" + Environment.NewLine); @@ -608,7 +590,7 @@ namespace Emby.Server.Core RegisterSingleInstance(HttpServer, false); progress.Report(10); - ServerManager = new ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager, MemoryStreamFactory, textEncoding); + ServerManager = new ServerManager.ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager, MemoryStreamFactory, textEncoding); RegisterSingleInstance(ServerManager); var innerProgress = new ActionableProgress<double>(); @@ -884,7 +866,7 @@ namespace Emby.Server.Core probePath = info.ProbePath; var hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase); - var mediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), + var mediaEncoder = new MediaEncoding.Encoder.MediaEncoder(LogManager.GetLogger("MediaEncoder"), JsonSerializer, encoderPath, probePath, @@ -980,8 +962,6 @@ namespace Emby.Server.Core BaseItem.CollectionManager = CollectionManager; BaseItem.MediaSourceManager = MediaSourceManager; CollectionFolder.XmlSerializer = XmlSerializer; - BaseStreamingService.AppHost = this; - BaseStreamingService.HttpClient = HttpClient; Utilities.CryptographyProvider = CryptographyProvider; AuthenticatedAttribute.AuthService = AuthService; } @@ -1254,7 +1234,7 @@ namespace Emby.Server.Core list.Add(GetAssembly(typeof(InstallationManager))); // MediaEncoding - list.Add(GetAssembly(typeof(MediaEncoder))); + list.Add(GetAssembly(typeof(MediaEncoding.Encoder.MediaEncoder))); // Dlna list.Add(GetAssembly(typeof(DlnaEntryPoint))); @@ -1267,10 +1247,7 @@ namespace Emby.Server.Core list.AddRange(GetAssembliesWithPartsInternal()); - // Include composable parts in the running assembly - list.Add(GetAssembly(typeof(ApplicationHost))); - - return list; + return list.ToList(); } protected abstract List<Assembly> GetAssembliesWithPartsInternal(); diff --git a/Emby.Server.Core/ApplicationPathHelper.cs b/Emby.Server.Implementations/ApplicationPathHelper.cs index e83d5444a..262cc526e 100644 --- a/Emby.Server.Core/ApplicationPathHelper.cs +++ b/Emby.Server.Implementations/ApplicationPathHelper.cs @@ -2,7 +2,7 @@ using System.Configuration; using System.IO; -namespace Emby.Server.Core +namespace Emby.Server.Implementations { public static class ApplicationPathHelper { diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 2adf6a37c..5e97cd5f5 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -159,7 +159,7 @@ namespace Emby.Server.Implementations.Channels all = all.Take(query.Limit.Value).ToList(); } - var returnItems = all.ToArray(); + var returnItems = all.ToArray(all.Count); var result = new QueryResult<Channel> { @@ -182,8 +182,10 @@ namespace Emby.Server.Implementations.Channels { }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false)) - .ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) + .ConfigureAwait(false)); + var returnItems = returnList + .ToArray(returnList.Count); var result = new QueryResult<BaseItemDto> { @@ -567,8 +569,9 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields.ToList() }; - var returnItems = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false)) - .ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false)); + var returnItems = returnList + .ToArray(returnList.Count); var result = new QueryResult<BaseItemDto> { @@ -676,12 +679,10 @@ namespace Emby.Server.Implementations.Channels internalItems = internalItems.Take(query.Limit.Value).ToArray(); } - var returnItemArray = internalItems.ToArray(); - return new QueryResult<BaseItem> { TotalRecordCount = totalCount, - Items = returnItemArray + Items = internalItems }; } @@ -813,12 +814,10 @@ namespace Emby.Server.Implementations.Channels var internalItems = await Task.WhenAll(itemTasks).ConfigureAwait(false); - var returnItemArray = internalItems.ToArray(); - return new QueryResult<BaseItem> { TotalRecordCount = totalCount, - Items = returnItemArray + Items = internalItems }; } @@ -837,8 +836,10 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields.ToList() }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false)) - .ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) + .ConfigureAwait(false)); + var returnItems = returnList + .ToArray(returnList.Count); var result = new QueryResult<BaseItemDto> { @@ -989,8 +990,10 @@ namespace Emby.Server.Implementations.Channels Fields = query.Fields.ToList() }; - var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false)) - .ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user) + .ConfigureAwait(false)); + var returnItems = returnList + .ToArray(returnList.Count); var result = new QueryResult<BaseItemDto> { @@ -1191,7 +1194,7 @@ namespace Emby.Server.Implementations.Channels } } - var returnItemArray = all.ToArray(); + var returnItemArray = all.ToArray(all.Count); RefreshIfNeeded(returnItemArray); return new QueryResult<BaseItem> @@ -1309,7 +1312,7 @@ namespace Emby.Server.Implementations.Channels { item.Name = info.Name; item.Genres = info.Genres; - item.Studios = info.Studios; + item.Studios = info.Studios.ToArray(info.Studios.Count); item.CommunityRating = info.CommunityRating; item.Overview = info.Overview; item.IndexNumber = info.IndexNumber; @@ -1319,7 +1322,7 @@ namespace Emby.Server.Implementations.Channels item.ProviderIds = info.ProviderIds; item.OfficialRating = info.OfficialRating; item.DateCreated = info.DateCreated ?? DateTime.UtcNow; - item.Tags = info.Tags; + item.Tags = info.Tags.ToArray(info.Tags.Count); item.HomePageUrl = info.HomePageUrl; } else if (info.Type == ChannelItemType.Folder && info.FolderType == ChannelFolderType.Container) diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 2241e9377..4d9bf0624 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -16,6 +16,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Configuration { @@ -216,7 +217,7 @@ namespace Emby.Server.Implementations.Configuration list.Add(service); - options.DisabledMetadataSavers = list.ToArray(); + options.DisabledMetadataSavers = list.ToArray(list.Count); } } @@ -236,7 +237,7 @@ namespace Emby.Server.Implementations.Configuration list.Add(options); - config.MetadataOptions = list.ToArray(); + config.MetadataOptions = list.ToArray(list.Count); } return options; diff --git a/Emby.Server.Core/Cryptography/ASN1.cs b/Emby.Server.Implementations/Cryptography/ASN1.cs index f5c826436..f5c826436 100644 --- a/Emby.Server.Core/Cryptography/ASN1.cs +++ b/Emby.Server.Implementations/Cryptography/ASN1.cs diff --git a/Emby.Server.Core/Cryptography/ASN1Convert.cs b/Emby.Server.Implementations/Cryptography/ASN1Convert.cs index 851d36dc7..851d36dc7 100644 --- a/Emby.Server.Core/Cryptography/ASN1Convert.cs +++ b/Emby.Server.Implementations/Cryptography/ASN1Convert.cs diff --git a/Emby.Server.Core/Cryptography/BitConverterLE.cs b/Emby.Server.Implementations/Cryptography/BitConverterLE.cs index 34e6bf6dc..34e6bf6dc 100644 --- a/Emby.Server.Core/Cryptography/BitConverterLE.cs +++ b/Emby.Server.Implementations/Cryptography/BitConverterLE.cs diff --git a/Emby.Server.Core/Cryptography/CertificateGenerator.cs b/Emby.Server.Implementations/Cryptography/CertificateGenerator.cs index 2600d7470..2600d7470 100644 --- a/Emby.Server.Core/Cryptography/CertificateGenerator.cs +++ b/Emby.Server.Implementations/Cryptography/CertificateGenerator.cs diff --git a/Emby.Server.Core/Cryptography/CryptoConvert.cs b/Emby.Server.Implementations/Cryptography/CryptoConvert.cs index 70a91bfff..70a91bfff 100644 --- a/Emby.Server.Core/Cryptography/CryptoConvert.cs +++ b/Emby.Server.Implementations/Cryptography/CryptoConvert.cs diff --git a/Emby.Server.Core/Cryptography/PKCS1.cs b/Emby.Server.Implementations/Cryptography/PKCS1.cs index 24c0708c5..24c0708c5 100644 --- a/Emby.Server.Core/Cryptography/PKCS1.cs +++ b/Emby.Server.Implementations/Cryptography/PKCS1.cs diff --git a/Emby.Server.Core/Cryptography/PKCS12.cs b/Emby.Server.Implementations/Cryptography/PKCS12.cs index 50f3776d9..50f3776d9 100644 --- a/Emby.Server.Core/Cryptography/PKCS12.cs +++ b/Emby.Server.Implementations/Cryptography/PKCS12.cs diff --git a/Emby.Server.Core/Cryptography/PKCS7.cs b/Emby.Server.Implementations/Cryptography/PKCS7.cs index 475854500..475854500 100644 --- a/Emby.Server.Core/Cryptography/PKCS7.cs +++ b/Emby.Server.Implementations/Cryptography/PKCS7.cs diff --git a/Emby.Server.Core/Cryptography/PKCS8.cs b/Emby.Server.Implementations/Cryptography/PKCS8.cs index 7e9a27298..7e9a27298 100644 --- a/Emby.Server.Core/Cryptography/PKCS8.cs +++ b/Emby.Server.Implementations/Cryptography/PKCS8.cs diff --git a/Emby.Server.Core/Cryptography/PfxGenerator.cs b/Emby.Server.Implementations/Cryptography/PfxGenerator.cs index 2d1dd649e..2d1dd649e 100644 --- a/Emby.Server.Core/Cryptography/PfxGenerator.cs +++ b/Emby.Server.Implementations/Cryptography/PfxGenerator.cs diff --git a/Emby.Server.Core/Cryptography/X501Name.cs b/Emby.Server.Implementations/Cryptography/X501Name.cs index 3318f95e2..3318f95e2 100644 --- a/Emby.Server.Core/Cryptography/X501Name.cs +++ b/Emby.Server.Implementations/Cryptography/X501Name.cs diff --git a/Emby.Server.Core/Cryptography/X509Builder.cs b/Emby.Server.Implementations/Cryptography/X509Builder.cs index a2e292350..a2e292350 100644 --- a/Emby.Server.Core/Cryptography/X509Builder.cs +++ b/Emby.Server.Implementations/Cryptography/X509Builder.cs diff --git a/Emby.Server.Core/Cryptography/X509Certificate.cs b/Emby.Server.Implementations/Cryptography/X509Certificate.cs index 3de58cfee..3de58cfee 100644 --- a/Emby.Server.Core/Cryptography/X509Certificate.cs +++ b/Emby.Server.Implementations/Cryptography/X509Certificate.cs diff --git a/Emby.Server.Core/Cryptography/X509CertificateBuilder.cs b/Emby.Server.Implementations/Cryptography/X509CertificateBuilder.cs index cecff6578..cecff6578 100644 --- a/Emby.Server.Core/Cryptography/X509CertificateBuilder.cs +++ b/Emby.Server.Implementations/Cryptography/X509CertificateBuilder.cs diff --git a/Emby.Server.Core/Cryptography/X509CertificateCollection.cs b/Emby.Server.Implementations/Cryptography/X509CertificateCollection.cs index a129bfc1a..a129bfc1a 100644 --- a/Emby.Server.Core/Cryptography/X509CertificateCollection.cs +++ b/Emby.Server.Implementations/Cryptography/X509CertificateCollection.cs diff --git a/Emby.Server.Core/Cryptography/X509Extension.cs b/Emby.Server.Implementations/Cryptography/X509Extension.cs index 36b17deba..36b17deba 100644 --- a/Emby.Server.Core/Cryptography/X509Extension.cs +++ b/Emby.Server.Implementations/Cryptography/X509Extension.cs diff --git a/Emby.Server.Core/Cryptography/X509Extensions.cs b/Emby.Server.Implementations/Cryptography/X509Extensions.cs index 018a04d79..018a04d79 100644 --- a/Emby.Server.Core/Cryptography/X509Extensions.cs +++ b/Emby.Server.Implementations/Cryptography/X509Extensions.cs diff --git a/Emby.Server.Core/Cryptography/X520Attributes.cs b/Emby.Server.Implementations/Cryptography/X520Attributes.cs index da7fd2b82..da7fd2b82 100644 --- a/Emby.Server.Core/Cryptography/X520Attributes.cs +++ b/Emby.Server.Implementations/Cryptography/X520Attributes.cs diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 72c069073..a34c90cb4 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -136,24 +136,6 @@ namespace Emby.Server.Implementations.Data queries.Add("PRAGMA temp_store = file"); } - ////foreach (var query in queries) - ////{ - //// db.Execute(query); - ////} - - //Logger.Info("synchronous: " + db.Query("PRAGMA synchronous").SelectScalarString().First()); - //Logger.Info("temp_store: " + db.Query("PRAGMA temp_store").SelectScalarString().First()); - - /*if (!string.Equals(_defaultWal, "wal", StringComparison.OrdinalIgnoreCase)) - { - queries.Add("PRAGMA journal_mode=WAL"); - - using (WriteLock.Write()) - { - db.ExecuteAll(string.Join(";", queries.ToArray())); - } - } - else*/ foreach (var query in queries) { db.Execute(query); @@ -212,6 +194,13 @@ namespace Emby.Server.Implementations.Data "pragma temp_store = memory" }); } + else + { + queries.AddRange(new List<string> + { + "pragma temp_store = file" + }); + } db.ExecuteAll(string.Join(";", queries.ToArray())); Logger.Info("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First()); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 44b2cd10b..23d46e821 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -31,6 +31,7 @@ using MediaBrowser.Model.Reflection; using SQLitePCL.pretty; using MediaBrowser.Model.System; using MediaBrowser.Model.Threading; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Data { @@ -836,7 +837,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@IsInMixedFolder", item.IsInMixedFolder); - if (item.LockedFields.Count > 0) + if (item.LockedFields.Length > 0) { saveItemStatement.TryBind("@LockedFields", string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray())); } @@ -845,7 +846,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@LockedFields"); } - if (item.Studios.Count > 0) + if (item.Studios.Length > 0) { saveItemStatement.TryBind("@Studios", string.Join("|", item.Studios.ToArray())); } @@ -865,9 +866,9 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ExternalServiceId", item.ServiceName); - if (item.Tags.Count > 0) + if (item.Tags.Length > 0) { - saveItemStatement.TryBind("@Tags", string.Join("|", item.Tags.ToArray())); + saveItemStatement.TryBind("@Tags", string.Join("|", item.Tags)); } else { @@ -984,16 +985,16 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item)); saveItemStatement.TryBind("@Images", SerializeImages(item)); - if (item.ProductionLocations.Count > 0) + if (item.ProductionLocations.Length > 0) { - saveItemStatement.TryBind("@ProductionLocations", string.Join("|", item.ProductionLocations.ToArray())); + saveItemStatement.TryBind("@ProductionLocations", string.Join("|", item.ProductionLocations)); } else { saveItemStatement.TryBindNull("@ProductionLocations"); } - if (item.ThemeSongIds.Count > 0) + if (item.ThemeSongIds.Length > 0) { saveItemStatement.TryBind("@ThemeSongIds", string.Join("|", item.ThemeSongIds.ToArray())); } @@ -1002,7 +1003,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@ThemeSongIds"); } - if (item.ThemeVideoIds.Count > 0) + if (item.ThemeVideoIds.Length > 0) { saveItemStatement.TryBind("@ThemeVideoIds", string.Join("|", item.ThemeVideoIds.ToArray())); } @@ -1089,9 +1090,9 @@ namespace Emby.Server.Implementations.Data private string SerializeImages(BaseItem item) { - var images = item.ImageInfos.ToList(); + var images = item.ImageInfos; - if (images.Count == 0) + if (images.Length == 0) { return null; } @@ -1108,22 +1109,24 @@ namespace Emby.Server.Implementations.Data return; } - if (item.ImageInfos.Count > 0) + if (item.ImageInfos.Length > 0) { return; } var parts = value.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - + var list = new List<ItemImageInfo>(); foreach (var part in parts) { var image = ItemImageInfoFromValueString(part); if (image != null) { - item.ImageInfos.Add(image); + list.Add(image); } } + + item.ImageInfos = list.ToArray(list.Count); } public string ToValueString(ItemImageInfo image) @@ -1678,7 +1681,7 @@ namespace Emby.Server.Implementations.Data return parsedValue; } return (MetadataFields?)null; - }).Where(i => i.HasValue).Select(i => i.Value).ToList(); + }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); } index++; } @@ -1687,7 +1690,7 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.Studios = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + item.Studios = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } index++; } @@ -1696,7 +1699,7 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.Tags = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + item.Tags = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } index++; } @@ -1873,7 +1876,7 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.ProductionLocations = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + item.ProductionLocations = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); } index++; } @@ -1882,7 +1885,7 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.ThemeSongIds = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList(); + item.ThemeSongIds = SplitToGuids(reader.GetString(index)); } index++; } @@ -1891,7 +1894,7 @@ namespace Emby.Server.Implementations.Data { if (!reader.IsDBNull(index)) { - item.ThemeVideoIds = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList(); + item.ThemeVideoIds = SplitToGuids(reader.GetString(index)); } index++; } @@ -1950,12 +1953,26 @@ namespace Emby.Server.Implementations.Data return item; } + private Guid[] SplitToGuids(string value) + { + var ids = value.Split('|'); + + var result = new Guid[ids.Length]; + + for (var i = 0; i < result.Length; i++) + { + result[i] = new Guid(ids[i]); + } + + return result; + } + /// <summary> /// Gets the critic reviews. /// </summary> /// <param name="itemId">The item id.</param> /// <returns>Task{IEnumerable{ItemReview}}.</returns> - public IEnumerable<ItemReview> GetCriticReviews(Guid itemId) + public List<ItemReview> GetCriticReviews(Guid itemId) { return new List<ItemReview>(); } @@ -2206,7 +2223,7 @@ namespace Emby.Server.Implementations.Data return false; } - private List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields)) + private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields)) .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) .ToList(); @@ -2548,11 +2565,11 @@ namespace Emby.Server.Implementations.Data } } - query.ExcludeItemIds = excludeIds.ToArray(); + query.ExcludeItemIds = excludeIds.ToArray(excludeIds.Count); query.ExcludeProviderIds = item.ProviderIds; } - return list.ToArray(); + return list.ToArray(list.Count); } private void BindSimilarParams(InternalItemsQuery query, IStatement statement) @@ -2595,7 +2612,7 @@ namespace Emby.Server.Implementations.Data if (groups.Count > 0) { - return " Group by " + string.Join(",", groups.ToArray()); + return " Group by " + string.Join(",", groups.ToArray(groups.Count)); } return string.Empty; @@ -2632,7 +2649,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); commandText += whereText; @@ -2689,7 +2706,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); commandText += whereText; @@ -2842,7 +2859,7 @@ namespace Emby.Server.Implementations.Data var returnList = GetItemList(query); return new QueryResult<BaseItem> { - Items = returnList.ToArray(), + Items = returnList.ToArray(returnList.Count), TotalRecordCount = returnList.Count }; } @@ -2865,7 +2882,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); var whereTextWithoutPaging = whereText; @@ -2926,8 +2943,7 @@ namespace Emby.Server.Implementations.Data return connection.RunInTransaction(db => { var result = new QueryResult<BaseItem>(); - var statements = PrepareAllSafe(db, statementTexts) - .ToList(); + var statements = PrepareAllSafe(db, statementTexts); if (!isReturningZeroItems) { @@ -2981,7 +2997,7 @@ namespace Emby.Server.Implementations.Data LogQueryTime("GetItems", commandText, now); - result.Items = list.ToArray(); + result.Items = list.ToArray(list.Count); return result; }, ReadTransactionMode); @@ -3133,7 +3149,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); commandText += whereText; @@ -3204,7 +3220,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); commandText += whereText; @@ -3277,7 +3293,7 @@ namespace Emby.Server.Implementations.Data var returnList = GetItemIdsList(query); return new QueryResult<Guid> { - Items = returnList.ToArray(), + Items = returnList.ToArray(returnList.Count), TotalRecordCount = returnList.Count }; } @@ -3292,7 +3308,7 @@ namespace Emby.Server.Implementations.Data var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); var whereTextWithoutPaging = whereText; @@ -3355,8 +3371,7 @@ namespace Emby.Server.Implementations.Data { var result = new QueryResult<Guid>(); - var statements = PrepareAllSafe(db, statementTexts) - .ToList(); + var statements = PrepareAllSafe(db, statementTexts); if (!isReturningZeroItems) { @@ -3399,7 +3414,7 @@ namespace Emby.Server.Implementations.Data LogQueryTime("GetItemIds", commandText, now); - result.Items = list.ToArray(); + result.Items = list.ToArray(list.Count); return result; }, ReadTransactionMode); @@ -3604,7 +3619,7 @@ namespace Emby.Server.Implementations.Data } if (programAttribtues.Count > 0) { - whereClauses.Add("(" + string.Join(" OR ", programAttribtues.ToArray()) + ")"); + whereClauses.Add("(" + string.Join(" OR ", programAttribtues.ToArray(programAttribtues.Count)) + ")"); } } @@ -5129,9 +5144,9 @@ namespace Emby.Server.Implementations.Data var itemCountColumns = new List<Tuple<string, string>>(); - var typesToCount = query.IncludeItemTypes.ToList(); + var typesToCount = query.IncludeItemTypes; - if (typesToCount.Count > 0) + if (typesToCount.Length > 0) { var itemCountColumnQuery = "select group_concat(type, '|')" + GetFromText("B"); @@ -5191,7 +5206,7 @@ namespace Emby.Server.Implementations.Data var whereText = " where Type=@SelectType"; - if (typesToCount.Count == 0) + if (typesToCount.Length == 0) { whereText += " And CleanName In (Select CleanValue from ItemValues where " + typeClause + " AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))"; } @@ -5269,8 +5284,7 @@ namespace Emby.Server.Implementations.Data var list = new List<Tuple<BaseItem, ItemCounts>>(); var result = new QueryResult<Tuple<BaseItem, ItemCounts>>(); - var statements = PrepareAllSafe(db, statementTexts) - .ToList(); + var statements = PrepareAllSafe(db, statementTexts); if (!isReturningZeroItems) { @@ -5345,7 +5359,7 @@ namespace Emby.Server.Implementations.Data { result.TotalRecordCount = list.Count; } - result.Items = list.ToArray(); + result.Items = list.ToArray(list.Count); return result; @@ -5354,11 +5368,11 @@ namespace Emby.Server.Implementations.Data } } - private ItemCounts GetItemCounts(IReadOnlyList<IResultSetValue> reader, int countStartColumn, List<string> typesToCount) + private ItemCounts GetItemCounts(IReadOnlyList<IResultSetValue> reader, int countStartColumn, string[] typesToCount) { var counts = new ItemCounts(); - if (typesToCount.Count == 0) + if (typesToCount.Length == 0) { return counts; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index daa5ff642..098e11720 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -649,12 +649,12 @@ namespace Emby.Server.Implementations.Dto dto.GameSystem = item.GameSystemName; } - private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images) + private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images) { return images .Select(p => GetImageCacheTag(item, p)) .Where(i => i != null) - .ToList(); + .ToArray(); } private string GetImageCacheTag(BaseItem item, ImageType type) @@ -766,7 +766,7 @@ namespace Emby.Server.Implementations.Dto } } - dto.People = list.ToArray(); + dto.People = list.ToArray(list.Count); } /// <summary> @@ -1049,12 +1049,12 @@ namespace Emby.Server.Implementations.Dto { if (!string.IsNullOrWhiteSpace(item.Tagline)) { - dto.Taglines = new List<string> { item.Tagline }; + dto.Taglines = new string[] { item.Tagline }; } if (dto.Taglines == null) { - dto.Taglines = new List<string>(); + dto.Taglines = new string[]{}; } } @@ -1430,9 +1430,9 @@ namespace Emby.Server.Implementations.Dto if (fields.Contains(ItemFields.ProductionLocations)) { - if (item.ProductionLocations.Count > 0 || item is Movie) + if (item.ProductionLocations.Length > 0 || item is Movie) { - dto.ProductionLocations = item.ProductionLocations.ToArray(); + dto.ProductionLocations = item.ProductionLocations; } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index ed69c9755..62f23bb53 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -21,6 +21,7 @@ <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> @@ -40,6 +41,8 @@ <Compile Include="AppBase\BaseApplicationPaths.cs" /> <Compile Include="AppBase\BaseConfigurationManager.cs" /> <Compile Include="AppBase\ConfigurationHelper.cs" /> + <Compile Include="ApplicationHost.cs" /> + <Compile Include="ApplicationPathHelper.cs" /> <Compile Include="Branding\BrandingConfigurationFactory.cs" /> <Compile Include="Browser\BrowserLauncher.cs" /> <Compile Include="Channels\ChannelConfigurations.cs" /> @@ -52,6 +55,24 @@ <Compile Include="Collections\CollectionManager.cs" /> <Compile Include="Collections\CollectionsDynamicFolder.cs" /> <Compile Include="Configuration\ServerConfigurationManager.cs" /> + <Compile Include="Cryptography\ASN1.cs" /> + <Compile Include="Cryptography\ASN1Convert.cs" /> + <Compile Include="Cryptography\BitConverterLE.cs" /> + <Compile Include="Cryptography\CertificateGenerator.cs" /> + <Compile Include="Cryptography\CryptoConvert.cs" /> + <Compile Include="Cryptography\PfxGenerator.cs" /> + <Compile Include="Cryptography\PKCS1.cs" /> + <Compile Include="Cryptography\PKCS12.cs" /> + <Compile Include="Cryptography\PKCS7.cs" /> + <Compile Include="Cryptography\PKCS8.cs" /> + <Compile Include="Cryptography\X501Name.cs" /> + <Compile Include="Cryptography\X509Builder.cs" /> + <Compile Include="Cryptography\X509Certificate.cs" /> + <Compile Include="Cryptography\X509CertificateBuilder.cs" /> + <Compile Include="Cryptography\X509CertificateCollection.cs" /> + <Compile Include="Cryptography\X509Extension.cs" /> + <Compile Include="Cryptography\X509Extensions.cs" /> + <Compile Include="Cryptography\X520Attributes.cs" /> <Compile Include="Data\ManagedConnection.cs" /> <Compile Include="Data\SqliteDisplayPreferencesRepository.cs" /> <Compile Include="Data\SqliteItemRepository.cs" /> @@ -63,6 +84,7 @@ <Compile Include="Devices\DeviceRepository.cs" /> <Compile Include="Dto\DtoService.cs" /> <Compile Include="EntryPoints\AutomaticRestartEntryPoint.cs" /> + <Compile Include="EntryPoints\ExternalPortForwarding.cs" /> <Compile Include="EntryPoints\KeepServerAwake.cs" /> <Compile Include="EntryPoints\LibraryChangedNotifier.cs" /> <Compile Include="EntryPoints\LoadRegistrations.cs" /> @@ -78,6 +100,7 @@ <Compile Include="FFMpeg\FFMpegInfo.cs" /> <Compile Include="FFMpeg\FFMpegInstallInfo.cs" /> <Compile Include="FFMpeg\FFMpegLoader.cs" /> + <Compile Include="HttpServerFactory.cs" /> <Compile Include="HttpServer\FileWriter.cs" /> <Compile Include="HttpServer\HttpListenerHost.cs" /> <Compile Include="HttpServer\HttpResultFactory.cs" /> @@ -99,7 +122,9 @@ <Compile Include="Images\BaseDynamicImageProvider.cs" /> <Compile Include="IO\AsyncStreamCopier.cs" /> <Compile Include="IO\FileRefresher.cs" /> + <Compile Include="IO\LibraryMonitor.cs" /> <Compile Include="IO\MbLinkShortcutHandler.cs" /> + <Compile Include="IO\MemoryStreamProvider.cs" /> <Compile Include="IO\ThrottledStream.cs" /> <Compile Include="Library\CoreResolutionIgnoreRule.cs" /> <Compile Include="Library\LibraryManager.cs" /> @@ -170,6 +195,8 @@ <Compile Include="LiveTv\TunerHosts\MulticastStream.cs" /> <Compile Include="LiveTv\TunerHosts\QueueStream.cs" /> <Compile Include="Localization\LocalizationManager.cs" /> + <Compile Include="Localization\TextLocalizer.cs" /> + <Compile Include="Logging\ConsoleLogger.cs" /> <Compile Include="Logging\UnhandledExceptionWriter.cs" /> <Compile Include="MediaEncoder\EncodingManager.cs" /> <Compile Include="Migrations\IVersionMigration.cs" /> @@ -249,6 +276,7 @@ <Compile Include="Sorting\StartDateComparer.cs" /> <Compile Include="Sorting\StudioComparer.cs" /> <Compile Include="StartupOptions.cs" /> + <Compile Include="SystemEvents.cs" /> <Compile Include="TV\SeriesPostScanTask.cs" /> <Compile Include="TV\TVSeriesManager.cs" /> <Compile Include="Udp\UdpServer.cs" /> @@ -260,6 +288,26 @@ <EmbeddedResource Include="Localization\iso6392.txt" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\Emby.Common.Implementations\Emby.Common.Implementations.csproj"> + <Project>{1e37a338-9f57-4b70-bd6d-bb9c591e319b}</Project> + <Name>Emby.Common.Implementations</Name> + </ProjectReference> + <ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj"> + <Project>{805844ab-e92f-45e6-9d99-4f6d48d129a5}</Project> + <Name>Emby.Dlna</Name> + </ProjectReference> + <ProjectReference Include="..\Emby.Drawing\Emby.Drawing.csproj"> + <Project>{08fff49b-f175-4807-a2b5-73b0ebd9f716}</Project> + <Name>Emby.Drawing</Name> + </ProjectReference> + <ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj"> + <Project>{89ab4548-770d-41fd-a891-8daff44f452c}</Project> + <Name>Emby.Photos</Name> + </ProjectReference> + <ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj"> + <Project>{4fd51ac5-2c16-4308-a993-c3a84f3b4582}</Project> + <Name>MediaBrowser.Api</Name> + </ProjectReference> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project> <Name>MediaBrowser.Common</Name> @@ -268,6 +316,10 @@ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> <Name>MediaBrowser.Controller</Name> </ProjectReference> + <ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj"> + <Project>{7ef9f3e0-697d-42f3-a08f-19deb5f84392}</Project> + <Name>MediaBrowser.LocalMetadata</Name> + </ProjectReference> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> <Name>MediaBrowser.Model</Name> @@ -280,10 +332,29 @@ <Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project> <Name>MediaBrowser.Server.Implementations</Name> </ProjectReference> + <ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj"> + <Project>{5624b7b5-b5a7-41d8-9f10-cc5611109619}</Project> + <Name>MediaBrowser.WebDashboard</Name> + </ProjectReference> + <ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj"> + <Project>{23499896-b135-4527-8574-c26e926ea99e}</Project> + <Name>MediaBrowser.XbmcMetadata</Name> + </ProjectReference> + <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj"> + <Project>{cb7f2326-6497-4a3d-ba03-48513b17a7be}</Project> + <Name>Mono.Nat</Name> + </ProjectReference> + <ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj"> + <Project>{4a4402d4-e910-443b-b8fc-2c18286a2ca0}</Project> + <Name>OpenSubtitlesHandler</Name> + </ProjectReference> <ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj"> <Project>{1d74413b-e7cf-455b-b021-f52bdf881542}</Project> <Name>SocketHttpListener</Name> </ProjectReference> + <Reference Include="Emby.Server.MediaEncoding"> + <HintPath>..\ThirdParty\emby\Emby.Server.MediaEncoding.dll</HintPath> + </Reference> <Reference Include="Emby.XmlTv, Version=1.0.6387.29335, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\Emby.XmlTv.1.0.9\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath> <Private>True</Private> @@ -292,6 +363,15 @@ <HintPath>..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath> <Private>True</Private> </Reference> + <Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> + <HintPath>..\packages\Microsoft.IO.RecyclableMemoryStream.1.2.2\lib\net45\Microsoft.IO.RecyclableMemoryStream.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.Text, Version=4.5.12.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\ServiceStack.Text.4.5.12\lib\net45\ServiceStack.Text.dll</HintPath> + </Reference> + <Reference Include="SimpleInjector, Version=4.0.8.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> + <HintPath>..\packages\SimpleInjector.4.0.8\lib\net45\SimpleInjector.dll</HintPath> + </Reference> <Reference Include="SQLitePCL.pretty, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\SQLitePCL.pretty.1.1.0\lib\portable-net45+netcore45+wpa81+wp8\SQLitePCL.pretty.dll</HintPath> <Private>True</Private> @@ -299,10 +379,10 @@ </ItemGroup> <ItemGroup> <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL"> - <HintPath>..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath> </Reference> <Reference Include="System" /> + <Reference Include="System.Configuration" /> <Reference Include="System.Core" /> <Reference Include="System.Runtime.Serialization" /> <Reference Include="System.Xml.Linq" /> diff --git a/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 2c7e6a487..c96799b2f 100644 --- a/Emby.Server.Core/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Net; +using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -11,9 +12,9 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Threading; using Mono.Nat; -using System.Threading.Tasks; +using MediaBrowser.Model.Extensions; -namespace Emby.Server.Core.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { public class ExternalPortForwarding : IServerEntryPoint { @@ -50,7 +51,7 @@ namespace Emby.Server.Core.EntryPoints values.Add(config.EnableHttps.ToString()); values.Add(_appHost.EnableHttps.ToString()); - return string.Join("|", values.ToArray()); + return string.Join("|", values.ToArray(values.Count)); } void _config_ConfigurationUpdated(object sender, EventArgs e) diff --git a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs index 9fbe06673..99d39ffe0 100644 --- a/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.EntryPoints { @@ -58,7 +59,7 @@ namespace Emby.Server.Implementations.EntryPoints session.ApplicationVersion }; - var key = string.Join("_", keys.ToArray()).GetMD5(); + var key = string.Join("_", keys.ToArray(keys.Count)).GetMD5(); _apps.GetOrAdd(key, guid => GetNewClientInfo(session)); } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 05f78eba9..f150e4785 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -125,13 +125,6 @@ namespace Emby.Server.Implementations.HttpServer return _appHost.CreateInstance(type); } - private ServiceController CreateServiceController() - { - var types = _restServices.Select(r => r.GetType()).ToArray(); - - return new ServiceController(() => types); - } - /// <summary> /// Applies the request filters. Returns whether or not the request has been handled /// and no more processing should be done. @@ -186,7 +179,7 @@ namespace Emby.Server.Implementations.HttpServer attributes.Sort((x, y) => x.Priority - y.Priority); - return attributes.ToArray(); + return attributes.ToArray(attributes.Count); } /// <summary> @@ -697,11 +690,13 @@ namespace Emby.Server.Implementations.HttpServer { _restServices.AddRange(services); - ServiceController = CreateServiceController(); + ServiceController = new ServiceController(); _logger.Info("Calling ServiceStack AppHost.Init"); - ServiceController.Init(this); + var types = _restServices.Select(r => r.GetType()).ToArray(); + + ServiceController.Init(this, types); var requestFilters = _appHost.GetExports<IRequestFilter>().ToList(); foreach (var filter in requestFilters) @@ -741,7 +736,7 @@ namespace Emby.Server.Implementations.HttpServer }); } - return routes.ToArray(); + return routes.ToArray(routes.Count); } public Func<string, object> GetParseFn(Type propertyType) diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs index f0e75eea4..de30dc30a 100644 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using MediaBrowser.Model.Services; using SocketHttpListener.Net; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.HttpServer { @@ -29,7 +30,7 @@ namespace Emby.Server.Implementations.HttpServer } else { - var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray()); + var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray(headers.Count)); logger.Info("HTTP {0} {1}. {2}", method, url, headerText); } diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs index 4fbe0ed94..4e8dd7362 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/HttpUtility.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Text; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.HttpServer.SocketSharp { @@ -585,7 +586,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp WriteCharBytes(bytes, ch, e); } - byte[] buf = bytes.ToArray(); + byte[] buf = bytes.ToArray(bytes.Count); bytes = null; return e.GetString(buf, 0, buf.Length); diff --git a/Emby.Server.Core/HttpServerFactory.cs b/Emby.Server.Implementations/HttpServerFactory.cs index e16cbea0e..b1d78e6f4 100644 --- a/Emby.Server.Core/HttpServerFactory.cs +++ b/Emby.Server.Implementations/HttpServerFactory.cs @@ -1,9 +1,7 @@ using System; using System.IO; using System.Net.Security; -using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; -using System.Threading; using System.Threading.Tasks; using Emby.Common.Implementations.Net; using Emby.Server.Implementations.HttpServer; @@ -21,7 +19,7 @@ using MediaBrowser.Model.Text; using ServiceStack.Text.Jsv; using SocketHttpListener.Primitives; -namespace Emby.Server.Core +namespace Emby.Server.Implementations { /// <summary> /// Class ServerFactory diff --git a/Emby.Server.Core/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index ebc5e5e55..c452c01be 100644 --- a/Emby.Server.Core/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -13,9 +13,8 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Threading; -using Emby.Server.Implementations.IO; -namespace Emby.Server.Core.IO +namespace Emby.Server.Implementations.IO { public class LibraryMonitor : ILibraryMonitor { diff --git a/Emby.Server.Core/IO/MemoryStreamProvider.cs b/Emby.Server.Implementations/IO/MemoryStreamProvider.cs index f6dd1ecbc..eca76203c 100644 --- a/Emby.Server.Core/IO/MemoryStreamProvider.cs +++ b/Emby.Server.Implementations/IO/MemoryStreamProvider.cs @@ -2,7 +2,7 @@ using MediaBrowser.Model.IO; using Microsoft.IO; -namespace Emby.Server.Core.IO +namespace Emby.Server.Implementations.IO { public class RecyclableMemoryStreamProvider : IMemoryStreamFactory { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 8e6f63f72..3f9ea79c6 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -293,20 +293,16 @@ namespace Emby.Server.Implementations.Images return true; } - protected List<BaseItem> GetFinalItems(List<BaseItem> items) + protected List<BaseItem> GetFinalItems(IEnumerable<BaseItem> items) { return GetFinalItems(items, 4); } - protected virtual List<BaseItem> GetFinalItems(List<BaseItem> items, int limit) + protected virtual List<BaseItem> GetFinalItems(IEnumerable<BaseItem> items, int limit) { - // Rotate the images once every x days - var random = DateTime.Now.DayOfYear % MaxImageAgeDays; - return items - .OrderBy(i => (random + string.Empty + items.IndexOf(i)).GetMD5()) + .OrderBy(i => Guid.NewGuid()) .Take(limit) - .OrderBy(i => i.Name) .ToList(); } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 5c7dc2507..271dac153 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -136,14 +136,6 @@ namespace Emby.Server.Implementations.Library /// <value>The configuration manager.</value> private IServerConfigurationManager ConfigurationManager { get; set; } - /// <summary> - /// A collection of items that may be referenced from multiple physical places in the library - /// (typically, multiple user roots). We store them here and be sure they all reference a - /// single instance. - /// </summary> - /// <value>The by reference items.</value> - private ConcurrentDictionary<Guid, BaseItem> ByReferenceItems { get; set; } - private readonly Func<ILibraryMonitor> _libraryMonitorFactory; private readonly Func<IProviderManager> _providerManagerFactory; private readonly Func<IUserViewManager> _userviewManager; @@ -186,7 +178,6 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; _userviewManager = userviewManager; - ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>(); _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>(); ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -560,22 +551,6 @@ namespace Emby.Server.Implementations.Library return key.GetMD5(); } - /// <summary> - /// Ensure supplied item has only one instance throughout - /// </summary> - /// <param name="item">The item.</param> - /// <returns>The proper instance to the item</returns> - public BaseItem GetOrAddByReferenceItem(BaseItem item) - { - // Add this item to our list if not there already - if (!ByReferenceItems.TryAdd(item.Id, item)) - { - // Already there - return the existing reference - item = ByReferenceItems[item.Id]; - } - return item; - } - public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null) { @@ -1298,7 +1273,7 @@ namespace Emby.Server.Implementations.Library return item; } - public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) + public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) { if (query.Recursive && query.ParentId.HasValue) { @@ -1317,7 +1292,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetItemList(query); } - public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query) + public List<BaseItem> GetItemList(InternalItemsQuery query) { return GetItemList(query, true); } @@ -1341,7 +1316,7 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetCount(query); } - public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents) + public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents) { SetTopParentIdsOrAncestors(query, parents); @@ -1515,9 +1490,11 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetItems(query); } + var list = ItemRepository.GetItemList(query); + return new QueryResult<BaseItem> { - Items = ItemRepository.GetItemList(query).ToArray() + Items = list.ToArray(list.Count) }; } @@ -2610,7 +2587,7 @@ namespace Emby.Server.Implementations.Library var resolvers = new IItemResolver[] { - new GenericVideoResolver<Trailer>(this) + new GenericVideoResolver<Trailer>(this, _fileSystem) }; return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers) diff --git a/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs b/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs index e64980dff..83bee89e0 100644 --- a/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs +++ b/Emby.Server.Implementations/Library/LocalTrailerPostScanTask.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.Library Recursive = true, DtoOptions = new DtoOptions(false) - }).ToArray(); + }); var numComplete = 0; @@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Library progress.Report(100); } - private async Task AssignTrailers(IHasTrailers item, BaseItem[] channelTrailers) + private async Task AssignTrailers(IHasTrailers item, IEnumerable<BaseItem> channelTrailers) { if (item is Game) { diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index f0d07cc3c..7f77170ad 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -19,27 +19,27 @@ namespace Emby.Server.Implementations.Library _libraryManager = libraryManager; } - public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions) { var list = new List<Audio> { item }; - return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)); + return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList(); } - public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions) { return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions) { return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromFolder(Folder item, User user, DtoOptions dtoOptions) { var genres = item .GetRecursiveChildren(user, new InternalItemsQuery(user) @@ -55,12 +55,12 @@ namespace Emby.Server.Implementations.Library return GetInstantMixFromGenres(genres, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromPlaylist(Playlist item, User user, DtoOptions dtoOptions) { return GetInstantMixFromGenres(item.Genres, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions) { var genreIds = genres.DistinctNames().Select(i => { @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Library return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); } - public IEnumerable<Audio> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user, DtoOptions dtoOptions) { return _libraryManager.GetItemList(new InternalItemsQuery(user) { @@ -92,10 +92,10 @@ namespace Emby.Server.Implementations.Library DtoOptions = dtoOptions - }).Cast<Audio>(); + }); } - public IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) + public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) { var genre = item as MusicGenre; if (genre != null) @@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Library return GetInstantMixFromFolder(folder, user, dtoOptions); } - return new Audio[] { }; + return new List<BaseItem>(); } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index ef32eccea..fa4f026f4 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -6,6 +6,7 @@ using System; using System.IO; using System.Linq; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; namespace Emby.Server.Implementations.Library.Resolvers @@ -18,9 +19,11 @@ namespace Emby.Server.Implementations.Library.Resolvers where T : Video, new() { protected readonly ILibraryManager LibraryManager; + protected readonly IFileSystem FileSystem; - protected BaseVideoResolver(ILibraryManager libraryManager) + protected BaseVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) { + FileSystem = fileSystem; LibraryManager = libraryManager; } @@ -271,7 +274,7 @@ namespace Emby.Server.Implementations.Library.Resolvers return false; } - return directoryService.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase)); + return FileSystem.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase)); } /// <summary> diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 69563e5a0..d88076b99 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -23,11 +23,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies /// </summary> public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver { - public MovieResolver(ILibraryManager libraryManager) - : base(libraryManager) - { - } - /// <summary> /// Gets the priority. /// </summary> @@ -452,8 +447,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i => { - var subFileEntries = directoryService.GetFileSystemEntries(i) - .ToList(); + var subFileEntries = directoryService.GetFileSystemEntries(i); var subfolders = subFileEntries .Where(e => e.IsDirectory) @@ -547,5 +541,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return !validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase); } + + public MovieResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem) + { + } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 8bbda3b62..04312f277 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Library.Resolvers var filename = Path.GetFileNameWithoutExtension(args.Path); // Make sure the image doesn't belong to a video file - if (args.DirectoryService.GetFilePaths(_fileSystem.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(args.GetLibraryOptions(), i, filename))) + if (_fileSystem.GetFilePaths(_fileSystem.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(args.GetLibraryOptions(), i, filename))) { return null; } diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index bdab8552a..7d1c4d65a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using System.Linq; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers.TV { @@ -11,10 +12,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV /// </summary> public class EpisodeResolver : BaseVideoResolver<Episode> { - public EpisodeResolver(ILibraryManager libraryManager) : base(libraryManager) - { - } - /// <summary> /// Resolves the specified args. /// </summary> @@ -76,5 +73,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } + + public EpisodeResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem) + { + } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs index b5e1bf5f7..5c7a528f5 100644 --- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library.Resolvers { @@ -9,11 +10,6 @@ namespace Emby.Server.Implementations.Library.Resolvers /// </summary> public class VideoResolver : BaseVideoResolver<Video> { - public VideoResolver(ILibraryManager libraryManager) - : base(libraryManager) - { - } - protected override Video Resolve(ItemResolveArgs args) { if (args.Parent != null) @@ -33,12 +29,16 @@ namespace Emby.Server.Implementations.Library.Resolvers { get { return ResolverPriority.Last; } } + + public VideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem) + { + } } public class GenericVideoResolver<T> : BaseVideoResolver<T> where T : Video, new () { - public GenericVideoResolver(ILibraryManager libraryManager) : base(libraryManager) + public GenericVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem) { } } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 6f63322c8..658558ec0 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Extensions; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Library { @@ -163,8 +164,8 @@ namespace Emby.Server.Implementations.Library var mediaItems = _libraryManager.GetItemList(new InternalItemsQuery(user) { NameContains = searchTerm, - ExcludeItemTypes = excludeItemTypes.ToArray(), - IncludeItemTypes = includeItemTypes.ToArray(), + ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count), + IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count), Limit = query.Limit, IncludeItemsByName = string.IsNullOrWhiteSpace(query.ParentId), ParentId = string.IsNullOrWhiteSpace(query.ParentId) ? (Guid?)null : new Guid(query.ParentId), diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index a277b693a..0d4303b16 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -15,6 +15,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Library { @@ -231,7 +232,7 @@ namespace Emby.Server.Implementations.Library return list; } - private IEnumerable<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options) + private List<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options) { var parentId = request.ParentId; @@ -325,7 +326,7 @@ namespace Emby.Server.Implementations.Library Limit = limit * 5, IsPlayed = isPlayed, DtoOptions = options, - MediaTypes = mediaTypes.ToArray() + MediaTypes = mediaTypes.ToArray(mediaTypes.Count) }; if (parents.Count == 0) diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index c2eb30ee4..39630cf96 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -55,28 +55,21 @@ namespace Emby.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) { - var people = _libraryManager.GetPeople(new InternalPeopleQuery()); - - var dict = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); - - foreach (var person in people) - { - dict[person.Name] = true; - } + var people = _libraryManager.GetPeopleNames(new InternalPeopleQuery()); var numComplete = 0; - _logger.Debug("Will refresh {0} people", dict.Count); + var numPeople = people.Count; - var numPeople = dict.Count; + _logger.Debug("Will refresh {0} people", numPeople); - foreach (var person in dict) + foreach (var person in people) { cancellationToken.ThrowIfCancellationRequested(); try { - var item = _libraryManager.GetPerson(person.Key); + var item = _libraryManager.GetPerson(person); var options = new MetadataRefreshOptions(_fileSystem) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 6173068cc..90121b496 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return "ts"; } - return "mp4"; + return "mkv"; } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index f8ebdd05a..619d2378d 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.LiveTv { try { - dto.ParentBackdropImageTags = new List<string> + dto.ParentBackdropImageTags = new string[] { _imageProcessor.GetImageCacheTag(librarySeries, image) }; @@ -218,14 +218,14 @@ namespace Emby.Server.Implementations.LiveTv } } - if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Count == 0) + if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Length == 0) { image = program.GetImageInfo(ImageType.Backdrop, 0); if (image != null) { try { - dto.ParentBackdropImageTags = new List<string> + dto.ParentBackdropImageTags = new string[] { _imageProcessor.GetImageCacheTag(program, image) }; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 9b832bd4a..3fbbc8390 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.LiveTv } } - public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) + public async Task<QueryResult<BaseItem>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); @@ -208,15 +208,7 @@ namespace Emby.Server.Implementations.LiveTv internalQuery.OrderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending)); } - var channelResult = _libraryManager.GetItemsResult(internalQuery); - - var result = new QueryResult<LiveTvChannel> - { - Items = channelResult.Items.Cast<LiveTvChannel>().ToArray(), - TotalRecordCount = channelResult.TotalRecordCount - }; - - return result; + return _libraryManager.GetItemsResult(internalQuery); } public LiveTvChannel GetInternalChannel(string id) @@ -993,7 +985,9 @@ namespace Emby.Server.Implementations.LiveTv var queryResult = _libraryManager.QueryItems(internalQuery); - var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user) + .ConfigureAwait(false)); + var returnArray = returnList.ToArray(returnList.Count); var result = new QueryResult<BaseItemDto> { @@ -1077,7 +1071,9 @@ namespace Emby.Server.Implementations.LiveTv var user = _userManager.GetUserById(query.UserId); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) + .ConfigureAwait(false)); + var returnArray = returnList.ToArray(returnList.Count); var result = new QueryResult<BaseItemDto> { @@ -1639,7 +1635,7 @@ namespace Emby.Server.Implementations.LiveTv { MediaTypes = new[] { MediaType.Video }, Recursive = true, - AncestorIds = folderIds.Select(i => i.ToString("N")).ToArray(), + AncestorIds = folderIds.Select(i => i.ToString("N")).ToArray(folderIds.Count), IsFolder = false, IsVirtualItem = false, Limit = query.Limit, @@ -1647,9 +1643,9 @@ namespace Emby.Server.Implementations.LiveTv SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, EnableTotalRecordCount = query.EnableTotalRecordCount, - IncludeItemTypes = includeItemTypes.ToArray(), - ExcludeItemTypes = excludeItemTypes.ToArray(), - Genres = genres.ToArray(), + IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count), + ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count), + Genres = genres.ToArray(genres.Count), DtoOptions = dtoOptions }); } @@ -1695,17 +1691,20 @@ namespace Emby.Server.Implementations.LiveTv var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) { Recursive = true, - AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(), + AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(folders.Count), Limit = query.Limit, SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, EnableTotalRecordCount = query.EnableTotalRecordCount, - IncludeItemTypes = includeItemTypes.ToArray(), - ExcludeItemTypes = excludeItemTypes.ToArray(), + IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count), + ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count), DtoOptions = options }); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) + .ConfigureAwait(false)); + + var returnArray = returnList.ToArray(returnList.Count); return new QueryResult<BaseItemDto> { @@ -1996,7 +1995,9 @@ namespace Emby.Server.Implementations.LiveTv var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false); - var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user) + .ConfigureAwait(false)); + var returnArray = returnList.ToArray(returnList.Count); return new QueryResult<BaseItemDto> { @@ -2084,7 +2085,7 @@ namespace Emby.Server.Implementations.LiveTv var returnArray = returnList .OrderBy(i => i.StartDate) - .ToArray(); + .ToArray(returnList.Count); return new QueryResult<TimerInfoDto> { @@ -2341,7 +2342,7 @@ namespace Emby.Server.Implementations.LiveTv TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }, DtoOptions = options - }).ToList() : new List<BaseItem>(); + }) : new List<BaseItem>(); RemoveFields(options); @@ -2705,7 +2706,7 @@ namespace Emby.Server.Implementations.LiveTv return new QueryResult<BaseItemDto> { - Items = groups.ToArray(), + Items = groups.ToArray(groups.Count), TotalRecordCount = groups.Count }; } @@ -2992,7 +2993,7 @@ namespace Emby.Server.Implementations.LiveTv Name = tunerChannelId, Value = providerChannelId }); - listingsProviderInfo.ChannelMappings = list.ToArray(); + listingsProviderInfo.ChannelMappings = list.ToArray(list.Count); } _config.SaveConfiguration("livetv", config); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index a7e69776c..5436a12b8 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.LiveTv { @@ -102,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv openKeys.Add(item.GetType().Name); openKeys.Add(item.Id.ToString("N")); openKeys.Add(source.Id ?? string.Empty); - source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray()); + source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray(openKeys.Count)); } // Dummy this up so that direct play checks can still run diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 64a41acef..8d3051a89 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.Localization /// Gets the cultures. /// </summary> /// <returns>IEnumerable{CultureDto}.</returns> - public IEnumerable<CultureDto> GetCultures() + public List<CultureDto> GetCultures() { var type = GetType(); var path = type.Namespace + ".iso6392.txt"; @@ -169,14 +169,14 @@ namespace Emby.Server.Implementations.Localization return list.Where(i => !string.IsNullOrWhiteSpace(i.Name) && !string.IsNullOrWhiteSpace(i.DisplayName) && !string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) && - !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)); + !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToList(); } /// <summary> /// Gets the countries. /// </summary> /// <returns>IEnumerable{CountryInfo}.</returns> - public IEnumerable<CountryInfo> GetCountries() + public List<CountryInfo> GetCountries() { var type = GetType(); var path = type.Namespace + ".countries.json"; diff --git a/Emby.Server.Core/Localization/TextLocalizer.cs b/Emby.Server.Implementations/Localization/TextLocalizer.cs index 1e8ccbbfa..5188a959e 100644 --- a/Emby.Server.Core/Localization/TextLocalizer.cs +++ b/Emby.Server.Implementations/Localization/TextLocalizer.cs @@ -3,9 +3,8 @@ using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using Emby.Server.Implementations.Localization; -namespace Emby.Server.Core.Localization +namespace Emby.Server.Implementations.Localization { public class TextLocalizer : ITextLocalizer { diff --git a/Emby.Server.Core/Logging/ConsoleLogger.cs b/Emby.Server.Implementations/Logging/ConsoleLogger.cs index 01eca7b7e..51199f08a 100644 --- a/Emby.Server.Core/Logging/ConsoleLogger.cs +++ b/Emby.Server.Implementations/Logging/ConsoleLogger.cs @@ -1,10 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using MediaBrowser.Model.Logging; -namespace Emby.Server.Core.Logging +namespace Emby.Server.Implementations.Logging { public class ConsoleLogger : IConsoleLogger { diff --git a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs index 40752db80..ff152c9e9 100644 --- a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs +++ b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs @@ -12,6 +12,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Notifications; using SQLitePCL.pretty; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Notifications { @@ -83,13 +84,13 @@ namespace Emby.Server.Implementations.Notifications clauses.Add("UserId=?"); paramList.Add(query.UserId.ToGuidBlob()); - var whereClause = " where " + string.Join(" And ", clauses.ToArray()); + var whereClause = " where " + string.Join(" And ", clauses.ToArray(clauses.Count)); using (WriteLock.Read()) { using (var connection = CreateConnection(true)) { - result.TotalRecordCount = connection.Query("select count(Id) from Notifications" + whereClause, paramList.ToArray()).SelectScalarInt().First(); + result.TotalRecordCount = connection.Query("select count(Id) from Notifications" + whereClause, paramList.ToArray(paramList.Count)).SelectScalarInt().First(); var commandText = string.Format("select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId from Notifications{0} order by IsRead asc, Date desc", whereClause); @@ -110,12 +111,12 @@ namespace Emby.Server.Implementations.Notifications var resultList = new List<Notification>(); - foreach (var row in connection.Query(commandText, paramList.ToArray())) + foreach (var row in connection.Query(commandText, paramList.ToArray(paramList.Count))) { resultList.Add(GetNotification(row)); } - result.Notifications = resultList.ToArray(); + result.Notifications = resultList.ToArray(resultList.Count); } } @@ -280,7 +281,7 @@ namespace Emby.Server.Implementations.Notifications public async Task MarkRead(IEnumerable<string> notificationIdList, string userId, bool isRead, CancellationToken cancellationToken) { var list = notificationIdList.ToList(); - var idArray = list.Select(i => new Guid(i)).ToArray(); + var idArray = list.Select(i => new Guid(i)).ToArray(list.Count); await MarkReadInternal(idArray, userId, isRead, cancellationToken).ConfigureAwait(false); @@ -290,7 +291,7 @@ namespace Emby.Server.Implementations.Notifications { NotificationsMarkedRead(this, new NotificationReadEventArgs { - IdList = list.ToArray(), + IdList = list.ToArray(list.Count), IsRead = isRead, UserId = userId }); diff --git a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs index 0239ee336..0d89ba84f 100644 --- a/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs +++ b/Emby.Server.Implementations/Notifications/WebSocketNotifier.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Plugins; using System.Linq; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Notifications { @@ -33,7 +34,7 @@ namespace Emby.Server.Implementations.Notifications list.Add(e.UserId); list.Add(e.IsRead.ToString().ToLower()); - var msg = string.Join("|", list.ToArray()); + var msg = string.Join("|", list.ToArray(list.Count)); _serverManager.SendWebSocketMessage("NotificationsMarkedRead", msg); } diff --git a/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs b/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs index e23285425..f7c65f63d 100644 --- a/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs +++ b/Emby.Server.Implementations/Photos/PhotoAlbumImageProvider.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Photos protected override List<BaseItem> GetItemsWithImages(IHasMetadata item) { var photoAlbum = (PhotoAlbum)item; - var items = GetFinalItems(photoAlbum.Children.ToList()); + var items = GetFinalItems(photoAlbum.Children); return items; } diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index f5d0e314e..c2e860339 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -65,8 +65,7 @@ namespace Emby.Server.Implementations.Playlists return null; }) .Where(i => i != null) - .DistinctBy(i => i.Id) - .ToList(); + .DistinctBy(i => i.Id); return GetFinalItems(items); } @@ -93,7 +92,7 @@ namespace Emby.Server.Implementations.Playlists ImageTypes = new[] { ImageType.Primary }, DtoOptions = new DtoOptions(false) - }).ToList(); + }); return GetFinalItems(items); } @@ -125,7 +124,7 @@ namespace Emby.Server.Implementations.Playlists ImageTypes = new[] { ImageType.Primary }, DtoOptions = new DtoOptions(false) - }).ToList(); + }); return GetFinalItems(items); } diff --git a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index 967e7ddd8..91ae2afd1 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -15,6 +15,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.ScheduledTasks { @@ -142,7 +143,7 @@ namespace Emby.Server.Implementations.ScheduledTasks _fileSystem.CreateDirectory(parentPath); - _fileSystem.WriteAllText(failHistoryPath, string.Join("|", previouslyFailedImages.ToArray())); + _fileSystem.WriteAllText(failHistoryPath, string.Join("|", previouslyFailedImages.ToArray(previouslyFailedImages.Count))); } numComplete++; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 9ec0af6bb..d512ff4fb 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -11,6 +11,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using SQLitePCL.pretty; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Security { @@ -174,13 +175,13 @@ namespace Emby.Server.Implementations.Security var whereTextWithoutPaging = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); if (startIndex > 0) { var pagingWhereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})", pagingWhereText, @@ -189,7 +190,7 @@ namespace Emby.Server.Implementations.Security var whereText = whereClauses.Count == 0 ? string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); + " where " + string.Join(" AND ", whereClauses.ToArray(whereClauses.Count)); commandText += whereText; @@ -236,7 +237,7 @@ namespace Emby.Server.Implementations.Security } } - result.Items = list.ToArray(); + result.Items = list.ToArray(list.Count); return result; }, ReadTransactionMode); diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index 1c530144c..4ad56411a 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -15,17 +15,15 @@ namespace Emby.Server.Implementations.Services public class ServiceController { public static ServiceController Instance; - private readonly Func<IEnumerable<Type>> _resolveServicesFn; - public ServiceController(Func<IEnumerable<Type>> resolveServicesFn) + public ServiceController() { Instance = this; - _resolveServicesFn = resolveServicesFn; } - public void Init(HttpListenerHost appHost) + public void Init(HttpListenerHost appHost, Type[] serviceTypes) { - foreach (var serviceType in _resolveServicesFn()) + foreach (var serviceType in serviceTypes) { RegisterService(appHost, serviceType); } diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index e0b5e69c0..4a2199c89 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -5,6 +5,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Services { @@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.Services } if (reqFilters.Count > 0) - actionCtx.RequestFilters = reqFilters.OrderBy(i => i.Priority).ToArray(); + actionCtx.RequestFilters = reqFilters.OrderBy(i => i.Priority).ToArray(reqFilters.Count); actions.Add(actionCtx); } diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 255b20919..da5e74f1f 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using System.Text; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Services { @@ -142,13 +143,13 @@ namespace Emby.Server.Implementations.Services } } - var components = componentsList.ToArray(); + var components = componentsList.ToArray(componentsList.Count); this.TotalComponentsCount = components.Length; this.literalsToMatch = new string[this.TotalComponentsCount]; this.variablesNames = new string[this.TotalComponentsCount]; this.isWildcard = new bool[this.TotalComponentsCount]; - this.componentsWithSeparators = hasSeparators.ToArray(); + this.componentsWithSeparators = hasSeparators.ToArray(hasSeparators.Count); this.PathComponentsCount = this.componentsWithSeparators.Length; string firstLiteralMatch = null; @@ -268,7 +269,7 @@ namespace Emby.Server.Implementations.Services propertyInfos.InsertRange(0, newPropertyInfos); } - return propertyInfos.ToArray(); + return propertyInfos.ToArray(propertyInfos.Count); } return GetTypesPublicProperties(type) @@ -285,7 +286,7 @@ namespace Emby.Server.Implementations.Services if (mi != null && mi.IsStatic) continue; pis.Add(pi); } - return pis.ToArray(); + return pis.ToArray(pis.Count); } @@ -450,7 +451,7 @@ namespace Emby.Server.Implementations.Services } } - withPathInfoParts = totalComponents.ToArray(); + withPathInfoParts = totalComponents.ToArray(totalComponents.Count); return true; } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 763ca9f24..0b39cfc47 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -32,6 +32,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Threading; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Session { @@ -1000,7 +1001,7 @@ namespace Emby.Server.Implementations.Session command.PlayCommand = PlayCommand.PlayNow; } - command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray(); + command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray(items.Count); if (user != null) { @@ -1033,7 +1034,7 @@ namespace Emby.Server.Implementations.Session if (episodes.Count > 0) { - command.ItemIds = episodes.Select(i => i.Id.ToString("N")).ToArray(); + command.ItemIds = episodes.Select(i => i.Id.ToString("N")).ToArray(episodes.Count); } } } diff --git a/Emby.Server.Implementations/Social/SharingRepository.cs b/Emby.Server.Implementations/Social/SharingRepository.cs index 46e9205bb..a2a1f879a 100644 --- a/Emby.Server.Implementations/Social/SharingRepository.cs +++ b/Emby.Server.Implementations/Social/SharingRepository.cs @@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Social; using SQLitePCL.pretty; +using MediaBrowser.Model.Extensions; namespace Emby.Server.Implementations.Social { @@ -86,7 +87,7 @@ namespace Emby.Server.Implementations.Social var paramList = new List<object>(); paramList.Add(id.ToGuidBlob()); - foreach (var row in connection.Query(commandText, paramList.ToArray())) + foreach (var row in connection.Query(commandText, paramList.ToArray(paramList.Count))) { return GetSocialShareInfo(row); } diff --git a/Emby.Server.Core/SystemEvents.cs b/Emby.Server.Implementations/SystemEvents.cs index 8d5cd4ad8..dfff92f1e 100644 --- a/Emby.Server.Core/SystemEvents.cs +++ b/Emby.Server.Implementations/SystemEvents.cs @@ -3,7 +3,7 @@ using MediaBrowser.Common.Events; using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; -namespace MediaBrowser.Server.Startup.Common +namespace Emby.Server.Implementations { public class SystemEvents : ISystemEvents { diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index ac5bd128a..98e0c4080 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.UserViews }).DistinctBy(i => i.Id); - return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8); + return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8); } protected override bool Supports(IHasMetadata item) @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.UserViews DtoOptions = new DtoOptions(false) }); - return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8); + return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8); } protected override bool Supports(IHasMetadata item) diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index 6805080aa..885dfec58 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -62,9 +62,9 @@ namespace Emby.Server.Implementations.UserViews IsMovie = true, DtoOptions = new DtoOptions(false) - }).ToList(); + }); - return GetFinalItems(programs).ToList(); + return GetFinalItems(programs); } if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) || @@ -133,10 +133,10 @@ namespace Emby.Server.Implementations.UserViews if (isUsingCollectionStrip) { - return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8); + return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)), 8); } - return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary)).ToList()); + return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary))); } protected override bool Supports(IHasMetadata item) diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 03336c936..c86812ee0 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -2,6 +2,9 @@ <packages> <package id="Emby.XmlTv" version="1.0.9" targetFramework="net46" /> <package id="MediaBrowser.Naming" version="1.0.5" targetFramework="portable45-net45+win8" /> + <package id="Microsoft.IO.RecyclableMemoryStream" version="1.2.2" targetFramework="net46" /> + <package id="ServiceStack.Text" version="4.5.12" targetFramework="net46" /> + <package id="SimpleInjector" version="4.0.8" targetFramework="net46" /> <package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" /> - <package id="SQLitePCLRaw.core" version="1.1.7" targetFramework="net46" /> + <package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net46" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 34d0bd413..21552617d 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -1,27 +1,15 @@ -using MediaBrowser.Api.Playback; -using MediaBrowser.Common.Configuration; +using System; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Session; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Threading; +using System.Collections.Generic; +using System.Threading; namespace MediaBrowser.Api { @@ -53,11 +41,6 @@ namespace MediaBrowser.Api public readonly ITimerFactory TimerFactory; public readonly IProcessFactory ProcessFactory; - /// <summary> - /// The active transcoding jobs - /// </summary> - private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>(); - private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = new Dictionary<string, SemaphoreSlim>(); @@ -81,39 +64,21 @@ namespace MediaBrowser.Api ResultFactory = resultFactory; Instance = this; - _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; - _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; } - public SemaphoreSlim GetTranscodingLock(string outputPath) + public static string[] Split(string value, char separator, bool removeEmpty) { - lock (_transcodingLocks) + if (string.IsNullOrWhiteSpace(value)) { - SemaphoreSlim result; - if (!_transcodingLocks.TryGetValue(outputPath, out result)) - { - result = new SemaphoreSlim(1, 1); - _transcodingLocks[outputPath] = result; - } - - return result; + return new string[] { }; } - } - private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) - { - if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) + if (removeEmpty) { - PingTranscodingJob(e.PlaySessionId, e.IsPaused); + return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries); } - } - void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) - { - if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) - { - PingTranscodingJob(e.PlaySessionId, e.IsPaused); - } + return value.Split(separator); } /// <summary> @@ -121,41 +86,6 @@ namespace MediaBrowser.Api /// </summary> public void Run() { - try - { - DeleteEncodedMediaCache(); - } - catch (FileNotFoundException) - { - // Don't clutter the log - } - catch (IOException) - { - // Don't clutter the log - } - catch (Exception ex) - { - Logger.ErrorException("Error deleting encoded media cache", ex); - } - } - - public EncodingOptions GetEncodingOptions() - { - return _config.GetConfiguration<EncodingOptions>("encoding"); - } - - /// <summary> - /// Deletes the encoded media cache. - /// </summary> - private void DeleteEncodedMediaCache() - { - var path = _config.ApplicationPaths.TranscodingTempPath; - - foreach (var file in _fileSystem.GetFilePaths(path, true) - .ToList()) - { - _fileSystem.DeleteFile(file); - } } /// <summary> @@ -163,660 +93,6 @@ namespace MediaBrowser.Api /// </summary> public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - var list = _activeTranscodingJobs.ToList(); - var jobCount = list.Count; - - Parallel.ForEach(list, j => KillTranscodingJob(j, false, path => true)); - - // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files - if (jobCount > 0) - { - var task = Task.Delay(1000); - Task.WaitAll(task); - } - } - - /// <summary> - /// Called when [transcode beginning]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="playSessionId">The play session identifier.</param> - /// <param name="liveStreamId">The live stream identifier.</param> - /// <param name="transcodingJobId">The transcoding job identifier.</param> - /// <param name="type">The type.</param> - /// <param name="process">The process.</param> - /// <param name="deviceId">The device id.</param> - /// <param name="state">The state.</param> - /// <param name="cancellationTokenSource">The cancellation token source.</param> - /// <returns>TranscodingJob.</returns> - public TranscodingJob OnTranscodeBeginning(string path, - string playSessionId, - string liveStreamId, - string transcodingJobId, - TranscodingJobType type, - IProcess process, - string deviceId, - StreamState state, - CancellationTokenSource cancellationTokenSource) - { - lock (_activeTranscodingJobs) - { - var job = new TranscodingJob(Logger, TimerFactory) - { - Type = type, - Path = path, - Process = process, - ActiveRequestCount = 1, - DeviceId = deviceId, - CancellationTokenSource = cancellationTokenSource, - Id = transcodingJobId, - PlaySessionId = playSessionId, - LiveStreamId = liveStreamId, - MediaSource = state.MediaSource - }; - - _activeTranscodingJobs.Add(job); - - ReportTranscodingProgress(job, state, null, null, null, null, null); - - return job; - } - } - - public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) - { - var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; - - if (job != null) - { - job.Framerate = framerate; - job.CompletionPercentage = percentComplete; - job.TranscodingPositionTicks = ticks; - job.BytesTranscoded = bytesTranscoded; - job.BitRate = bitRate; - } - - var deviceId = state.Request.DeviceId; - - if (!string.IsNullOrWhiteSpace(deviceId)) - { - var audioCodec = state.ActualOutputAudioCodec; - var videoCodec = state.ActualOutputVideoCodec; - - _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo - { - Bitrate = bitRate ?? state.TotalOutputBitrate, - AudioCodec = audioCodec, - VideoCodec = videoCodec, - Container = state.OutputContainer, - Framerate = framerate, - CompletionPercentage = percentComplete, - Width = state.OutputWidth, - Height = state.OutputHeight, - AudioChannels = state.OutputAudioChannels, - IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase), - IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase), - TranscodeReasons = state.TranscodeReasons - }); - } - } - - /// <summary> - /// <summary> - /// The progressive - /// </summary> - /// Called when [transcode failed to start]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="type">The type.</param> - /// <param name="state">The state.</param> - public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state) - { - lock (_activeTranscodingJobs) - { - var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - - if (job != null) - { - _activeTranscodingJobs.Remove(job); - } - } - - lock (_transcodingLocks) - { - _transcodingLocks.Remove(path); - } - - if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) - { - _sessionManager.ClearTranscodingInfo(state.Request.DeviceId); - } - } - - /// <summary> - /// Determines whether [has active transcoding job] [the specified path]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="type">The type.</param> - /// <returns><c>true</c> if [has active transcoding job] [the specified path]; otherwise, <c>false</c>.</returns> - public bool HasActiveTranscodingJob(string path, TranscodingJobType type) - { - return GetTranscodingJob(path, type) != null; - } - - public TranscodingJob GetTranscodingJob(string path, TranscodingJobType type) - { - lock (_activeTranscodingJobs) - { - return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - } - } - - public TranscodingJob GetTranscodingJob(string playSessionId) - { - lock (_activeTranscodingJobs) - { - return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase)); - } - } - - /// <summary> - /// Called when [transcode begin request]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="type">The type.</param> - public TranscodingJob OnTranscodeBeginRequest(string path, TranscodingJobType type) - { - lock (_activeTranscodingJobs) - { - var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - - if (job == null) - { - return null; - } - - OnTranscodeBeginRequest(job); - - return job; - } - } - - public void OnTranscodeBeginRequest(TranscodingJob job) - { - job.ActiveRequestCount++; - - if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive) - { - job.StopKillTimer(); - } - } - - public void OnTranscodeEndRequest(TranscodingJob job) - { - job.ActiveRequestCount--; - //Logger.Debug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount); - if (job.ActiveRequestCount <= 0) - { - PingTimer(job, false); - } - } - internal void PingTranscodingJob(string playSessionId, bool? isUserPaused) - { - if (string.IsNullOrEmpty(playSessionId)) - { - throw new ArgumentNullException("playSessionId"); - } - - //Logger.Debug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused); - - List<TranscodingJob> jobs; - - lock (_activeTranscodingJobs) - { - // This is really only needed for HLS. - // Progressive streams can stop on their own reliably - jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList(); - } - - foreach (var job in jobs) - { - if (isUserPaused.HasValue) - { - //Logger.Debug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id); - job.IsUserPaused = isUserPaused.Value; - } - PingTimer(job, true); - } - } - - private void PingTimer(TranscodingJob job, bool isProgressCheckIn) - { - if (job.HasExited) - { - job.StopKillTimer(); - return; - } - - var timerDuration = 10000; - - if (job.Type != TranscodingJobType.Progressive) - { - timerDuration = 60000; - } - - job.PingTimeout = timerDuration; - job.LastPingDate = DateTime.UtcNow; - - // Don't start the timer for playback checkins with progressive streaming - if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn) - { - job.StartKillTimer(OnTranscodeKillTimerStopped); - } - else - { - job.ChangeKillTimerIfStarted(); - } - } - - /// <summary> - /// Called when [transcode kill timer stopped]. - /// </summary> - /// <param name="state">The state.</param> - private void OnTranscodeKillTimerStopped(object state) - { - var job = (TranscodingJob)state; - - if (!job.HasExited && job.Type != TranscodingJobType.Progressive) - { - var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds; - - if (timeSinceLastPing < job.PingTimeout) - { - job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout); - return; - } - } - - Logger.Info("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - - KillTranscodingJob(job, true, path => true); - } - - /// <summary> - /// Kills the single transcoding job. - /// </summary> - /// <param name="deviceId">The device id.</param> - /// <param name="playSessionId">The play session identifier.</param> - /// <param name="deleteFiles">The delete files.</param> - /// <returns>Task.</returns> - internal void KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles) - { - KillTranscodingJobs(j => - { - if (!string.IsNullOrWhiteSpace(playSessionId)) - { - return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase); - } - - return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase); - - }, deleteFiles); - } - - /// <summary> - /// Kills the transcoding jobs. - /// </summary> - /// <param name="killJob">The kill job.</param> - /// <param name="deleteFiles">The delete files.</param> - /// <returns>Task.</returns> - private void KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles) - { - var jobs = new List<TranscodingJob>(); - - lock (_activeTranscodingJobs) - { - // This is really only needed for HLS. - // Progressive streams can stop on their own reliably - jobs.AddRange(_activeTranscodingJobs.Where(killJob)); - } - - if (jobs.Count == 0) - { - return; - } - - foreach (var job in jobs) - { - KillTranscodingJob(job, false, deleteFiles); - } - } - - /// <summary> - /// Kills the transcoding job. - /// </summary> - /// <param name="job">The job.</param> - /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param> - /// <param name="delete">The delete.</param> - private async void KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete) - { - job.DisposeKillTimer(); - - Logger.Debug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - - lock (_activeTranscodingJobs) - { - _activeTranscodingJobs.Remove(job); - - if (!job.CancellationTokenSource.IsCancellationRequested) - { - job.CancellationTokenSource.Cancel(); - } - } - - lock (_transcodingLocks) - { - _transcodingLocks.Remove(job.Path); - } - - lock (job.ProcessLock) - { - if (job.TranscodingThrottler != null) - { - job.TranscodingThrottler.Stop(); - } - - var process = job.Process; - - var hasExited = job.HasExited; - - if (!hasExited) - { - try - { - Logger.Info("Stopping ffmpeg process with q command for {0}", job.Path); - - //process.Kill(); - process.StandardInput.WriteLine("q"); - - // Need to wait because killing is asynchronous - if (!process.WaitForExit(5000)) - { - Logger.Info("Killing ffmpeg process for {0}", job.Path); - process.Kill(); - } - } - catch (Exception ex) - { - Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); - } - } - } - - if (delete(job.Path)) - { - DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); - } - - if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) - { - try - { - await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error closing live stream for {0}", ex, job.Path); - } - } - } - - private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) - { - if (retryCount >= 10) - { - return; - } - - Logger.Info("Deleting partial stream file(s) {0}", path); - - await Task.Delay(delayMs).ConfigureAwait(false); - - try - { - if (jobType == TranscodingJobType.Progressive) - { - DeleteProgressivePartialStreamFiles(path); - } - else - { - DeleteHlsPartialStreamFiles(path); - } - } - catch (FileNotFoundException) - { - - } - catch (IOException) - { - //Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path); - - DeletePartialStreamFiles(path, jobType, retryCount + 1, 500); - } - catch - { - //Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path); - } - } - - /// <summary> - /// Deletes the progressive partial stream files. - /// </summary> - /// <param name="outputFilePath">The output file path.</param> - private void DeleteProgressivePartialStreamFiles(string outputFilePath) - { - _fileSystem.DeleteFile(outputFilePath); - } - - /// <summary> - /// Deletes the HLS partial stream files. - /// </summary> - /// <param name="outputFilePath">The output file path.</param> - private void DeleteHlsPartialStreamFiles(string outputFilePath) - { - var directory = _fileSystem.GetDirectoryName(outputFilePath); - var name = Path.GetFileNameWithoutExtension(outputFilePath); - - var filesToDelete = _fileSystem.GetFilePaths(directory) - .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1) - .ToList(); - - Exception e = null; - - foreach (var file in filesToDelete) - { - try - { - //Logger.Debug("Deleting HLS file {0}", file); - _fileSystem.DeleteFile(file); - } - catch (FileNotFoundException) - { - - } - catch (IOException ex) - { - e = ex; - //Logger.ErrorException("Error deleting HLS file {0}", ex, file); - } - } - - if (e != null) - { - throw e; - } - } - } - - /// <summary> - /// Class TranscodingJob - /// </summary> - public class TranscodingJob - { - /// <summary> - /// Gets or sets the play session identifier. - /// </summary> - /// <value>The play session identifier.</value> - public string PlaySessionId { get; set; } - /// <summary> - /// Gets or sets the live stream identifier. - /// </summary> - /// <value>The live stream identifier.</value> - public string LiveStreamId { get; set; } - - public bool IsLiveOutput { get; set; } - - /// <summary> - /// Gets or sets the path. - /// </summary> - /// <value>The path.</value> - public MediaSourceInfo MediaSource { get; set; } - public string Path { get; set; } - /// <summary> - /// Gets or sets the type. - /// </summary> - /// <value>The type.</value> - public TranscodingJobType Type { get; set; } - /// <summary> - /// Gets or sets the process. - /// </summary> - /// <value>The process.</value> - public IProcess Process { get; set; } - public ILogger Logger { get; private set; } - /// <summary> - /// Gets or sets the active request count. - /// </summary> - /// <value>The active request count.</value> - public int ActiveRequestCount { get; set; } - /// <summary> - /// Gets or sets the kill timer. - /// </summary> - /// <value>The kill timer.</value> - private ITimer KillTimer { get; set; } - - private readonly ITimerFactory _timerFactory; - - public string DeviceId { get; set; } - - public CancellationTokenSource CancellationTokenSource { get; set; } - - public object ProcessLock = new object(); - - public bool HasExited { get; set; } - public bool IsUserPaused { get; set; } - - public string Id { get; set; } - - public float? Framerate { get; set; } - public double? CompletionPercentage { get; set; } - - public long? BytesDownloaded { get; set; } - public long? BytesTranscoded { get; set; } - public int? BitRate { get; set; } - - public long? TranscodingPositionTicks { get; set; } - public long? DownloadPositionTicks { get; set; } - - public TranscodingThrottler TranscodingThrottler { get; set; } - - private readonly object _timerLock = new object(); - - public DateTime LastPingDate { get; set; } - public int PingTimeout { get; set; } - - public TranscodingJob(ILogger logger, ITimerFactory timerFactory) - { - Logger = logger; - _timerFactory = timerFactory; - } - - public void StopKillTimer() - { - lock (_timerLock) - { - if (KillTimer != null) - { - KillTimer.Change(Timeout.Infinite, Timeout.Infinite); - } - } - } - - public void DisposeKillTimer() - { - lock (_timerLock) - { - if (KillTimer != null) - { - KillTimer.Dispose(); - KillTimer = null; - } - } - } - - public void StartKillTimer(Action<object> callback) - { - StartKillTimer(callback, PingTimeout); - } - - public void StartKillTimer(Action<object> callback, int intervalMs) - { - if (HasExited) - { - return; - } - - lock (_timerLock) - { - if (KillTimer == null) - { - //Logger.Debug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer = _timerFactory.Create(callback, this, intervalMs, Timeout.Infinite); - } - else - { - //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer.Change(intervalMs, Timeout.Infinite); - } - } - } - - public void ChangeKillTimerIfStarted() - { - if (HasExited) - { - return; - } - - lock (_timerLock) - { - if (KillTimer != null) - { - var intervalMs = PingTimeout; - - //Logger.Debug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer.Change(intervalMs, Timeout.Infinite); - } - } } } } diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 5d81e9ea5..41b17e535 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Api var result = ((Folder)item).GetItemList(GetItemsQuery(request, user)); - return ToOptimizedResult(GetFilters(result.ToArray())); + return ToOptimizedResult(GetFilters(result)); } private QueryFilters GetFilters(BaseItem[] items) diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index 2d161ccfd..0ce57a16a 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api { @@ -227,11 +228,13 @@ namespace MediaBrowser.Api SimilarTo = item, DtoOptions = dtoOptions - }).ToList(); + }); + + var returnList = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)); var result = new QueryResult<BaseItemDto> { - Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(), + Items = returnList.ToArray(returnList.Count), TotalRecordCount = itemsResult.Count }; diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index a360e1183..318360336 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -279,13 +279,16 @@ namespace MediaBrowser.Api.Images var itemImages = item.ImageInfos; - foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type))) + foreach (var image in itemImages) { - var info = GetImageInfo(item, image, null); - - if (info != null) + if (!item.AllowsMultipleImages(image.Type)) { - list.Add(info); + var info = GetImageInfo(item, image, null); + + if (info != null) + { + list.Add(info); + } } } diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 19220cdec..a4b6c3956 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -66,8 +66,8 @@ namespace MediaBrowser.Api { ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(), ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToList(), - Countries = _localizationManager.GetCountries().ToList(), - Cultures = _localizationManager.GetCultures().ToList() + Countries = _localizationManager.GetCountries(), + Cultures = _localizationManager.GetCultures() }; if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName) && @@ -269,7 +269,7 @@ namespace MediaBrowser.Api if (request.Studios != null) { - item.Studios = request.Studios.Select(x => x.Name).ToList(); + item.Studios = request.Studios.Select(x => x.Name).ToArray(); } if (request.DateCreated.HasValue) @@ -285,7 +285,7 @@ namespace MediaBrowser.Api if (request.ProductionLocations != null) { - item.ProductionLocations = request.ProductionLocations.ToList(); + item.ProductionLocations = request.ProductionLocations; } item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode; diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 3bb119cba..7dd1afaf4 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -29,6 +29,7 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Services; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api.Library { @@ -460,22 +461,22 @@ namespace MediaBrowser.Api.Library EnableImages = false } - }).ToArray(); + }); if (!string.IsNullOrWhiteSpace(request.ImdbId)) { - movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToArray(); + movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToList(); } else if (!string.IsNullOrWhiteSpace(request.TmdbId)) { - movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToArray(); + movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList(); } else { - movies = new BaseItem[] { }; + movies = new List<BaseItem>(); } - if (movies.Length > 0) + if (movies.Count > 0) { foreach (var item in movies) { @@ -732,7 +733,8 @@ namespace MediaBrowser.Api.Library { DeleteFileLocation = true }); - }).ToArray(); + + }).ToArray(ids.Length); Task.WaitAll(tasks); } @@ -758,7 +760,7 @@ namespace MediaBrowser.Api.Library { var reviews = _itemRepo.GetCriticReviews(new Guid(request.Id)); - var reviewsArray = reviews.ToArray(); + var reviewsArray = reviews.ToArray(reviews.Count); var result = new QueryResult<ItemReview> { @@ -833,7 +835,7 @@ namespace MediaBrowser.Api.Library throw new ResourceNotFoundException("Item not found."); } - while (item.ThemeSongIds.Count == 0 && request.InheritFromParent && item.GetParent() != null) + while (item.ThemeSongIds.Length == 0 && request.InheritFromParent && item.GetParent() != null) { item = item.GetParent(); } @@ -882,7 +884,7 @@ namespace MediaBrowser.Api.Library throw new ResourceNotFoundException("Item not found."); } - while (item.ThemeVideoIds.Count == 0 && request.InheritFromParent && item.GetParent() != null) + while (item.ThemeVideoIds.Length == 0 && request.InheritFromParent && item.GetParent() != null) { item = item.GetParent(); } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 837a0f6a6..09d4cdfa9 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -16,13 +16,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; -using MediaBrowser.Api.Playback.Progressive; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api.LiveTv { @@ -734,7 +733,7 @@ namespace MediaBrowser.Api.LiveTv outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); - return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, null, Logger, _environment, CancellationToken.None) + return new ProgressiveFileCopier(_fileSystem, path, outputHeaders, Logger, _environment, CancellationToken.None) { AllowEndOfFile = false }; @@ -753,7 +752,7 @@ namespace MediaBrowser.Api.LiveTv outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); - return new ProgressiveFileCopier(directStreamProvider, outputHeaders, null, Logger, _environment, CancellationToken.None) + return new ProgressiveFileCopier(directStreamProvider, outputHeaders, Logger, _environment, CancellationToken.None) { AllowEndOfFile = false }; @@ -921,7 +920,9 @@ namespace MediaBrowser.Api.LiveTv options.AddCurrentProgram = request.AddCurrentProgram; - var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user).ConfigureAwait(false)).ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user) + .ConfigureAwait(false)); + var returnArray = returnList.ToArray(returnList.Count); var result = new QueryResult<BaseItemDto> { @@ -962,7 +963,7 @@ namespace MediaBrowser.Api.LiveTv { var query = new ProgramQuery { - ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(), + ChannelIds = ApiEntryPoint.Split(request.ChannelIds, ',', true), UserId = request.UserId, HasAired = request.HasAired, EnableTotalRecordCount = request.EnableTotalRecordCount diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs index d84d889fa..20466c5f6 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs @@ -1,23 +1,19 @@ -using MediaBrowser.Model.Logging; -using System; +using System; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Controller.Net; -using System.Collections.Generic; - -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; -namespace MediaBrowser.Api.Playback.Progressive +namespace MediaBrowser.Api.LiveTv { public class ProgressiveFileCopier : IAsyncStreamWriter, IHasHeaders { private readonly IFileSystem _fileSystem; - private readonly TranscodingJob _job; private readonly ILogger _logger; private readonly string _path; private readonly CancellationToken _cancellationToken; @@ -32,22 +28,20 @@ namespace MediaBrowser.Api.Playback.Progressive private readonly IDirectStreamProvider _directStreamProvider; private readonly IEnvironmentInfo _environment; - public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) + public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) { _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; - _job = job; _logger = logger; _cancellationToken = cancellationToken; _environment = environment; } - public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; - _job = job; _logger = logger; _cancellationToken = cancellationToken; _environment = environment; @@ -77,61 +71,48 @@ namespace MediaBrowser.Api.Playback.Progressive { cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token; - try + if (_directStreamProvider != null) { - if (_directStreamProvider != null) - { - await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); - return; - } + await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); + return; + } - var eofCount = 0; + var eofCount = 0; - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - var allowAsyncFileRead = _environment.OperatingSystem != OperatingSystem.Windows; + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + var allowAsyncFileRead = _environment.OperatingSystem != OperatingSystem.Windows; - using (var inputStream = GetInputStream(allowAsyncFileRead)) + using (var inputStream = GetInputStream(allowAsyncFileRead)) + { + if (StartPosition > 0) { - if (StartPosition > 0) + inputStream.Position = StartPosition; + } + + while (eofCount < 20 || !AllowEndOfFile) + { + int bytesRead; + if (allowAsyncFileRead) + { + bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); + } + else { - inputStream.Position = StartPosition; + bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false); } - while (eofCount < 20 || !AllowEndOfFile) + //var position = fs.Position; + //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); + + if (bytesRead == 0) { - int bytesRead; - if (allowAsyncFileRead) - { - bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - } - else - { - bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - } - - //var position = fs.Position; - //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) - { - if (_job == null || _job.HasExited) - { - eofCount++; - } - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - else - { - eofCount = 0; - } + eofCount++; + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; } - } - } - finally - { - if (_job != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(_job); } } } @@ -152,11 +133,6 @@ namespace MediaBrowser.Api.Playback.Progressive _bytesWritten += bytesRead; totalBytesRead += bytesRead; - - if (_job != null) - { - _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); - } } } @@ -179,11 +155,6 @@ namespace MediaBrowser.Api.Playback.Progressive _bytesWritten += bytesRead; totalBytesRead += bytesRead; - - if (_job != null) - { - _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); - } } } diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index 90b963149..eee340a87 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -85,7 +85,7 @@ namespace MediaBrowser.Api /// <returns>System.Object.</returns> public object Get(GetCountries request) { - var result = _localization.GetCountries().ToList(); + var result = _localization.GetCountries(); return ToOptimizedResult(result); } @@ -97,7 +97,7 @@ namespace MediaBrowser.Api /// <returns>System.Object.</returns> public object Get(GetCultures request) { - var result = _localization.GetCultures().ToList(); + var result = _localization.GetCultures(); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 88889e5e7..810b0f6b2 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -48,9 +48,7 @@ <Compile Include="Dlna\DlnaService.cs" /> <Compile Include="FilterService.cs" /> <Compile Include="IHasDtoOptions.cs" /> - <Compile Include="Playback\MediaInfoService.cs" /> - <Compile Include="Playback\TranscodingThrottler.cs" /> - <Compile Include="Playback\UniversalAudioService.cs" /> + <Compile Include="LiveTv\ProgressiveFileCopier.cs" /> <Compile Include="PlaylistService.cs" /> <Compile Include="Reports\Activities\ReportActivitiesBuilder.cs" /> <Compile Include="Reports\Common\HeaderActivitiesMetadata.cs" /> @@ -101,18 +99,6 @@ <Compile Include="NotificationsService.cs" /> <Compile Include="PackageReviewService.cs" /> <Compile Include="PackageService.cs" /> - <Compile Include="Playback\Hls\BaseHlsService.cs" /> - <Compile Include="Playback\Hls\DynamicHlsService.cs" /> - <Compile Include="Playback\Hls\HlsSegmentService.cs" /> - <Compile Include="Playback\Hls\VideoHlsService.cs" /> - <Compile Include="Playback\Progressive\AudioService.cs" /> - <Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" /> - <Compile Include="Playback\BaseStreamingService.cs" /> - <Compile Include="Playback\Progressive\ProgressiveStreamWriter.cs" /> - <Compile Include="Playback\StaticRemoteStreamWriter.cs" /> - <Compile Include="Playback\StreamRequest.cs" /> - <Compile Include="Playback\StreamState.cs" /> - <Compile Include="Playback\Progressive\VideoService.cs" /> <Compile Include="PluginService.cs" /> <Compile Include="Images\RemoteImageService.cs" /> <Compile Include="ScheduledTasks\ScheduledTaskService.cs" /> @@ -126,7 +112,6 @@ <Compile Include="System\ActivityLogWebSocketListener.cs" /> <Compile Include="System\SystemService.cs" /> <Compile Include="Movies\TrailersService.cs" /> - <Compile Include="TestService.cs" /> <Compile Include="TvShowsService.cs" /> <Compile Include="UserLibrary\ArtistsService.cs" /> <Compile Include="UserLibrary\BaseItemsByNameService.cs" /> @@ -136,7 +121,6 @@ <Compile Include="UserLibrary\ItemsService.cs" /> <Compile Include="UserLibrary\MusicGenresService.cs" /> <Compile Include="UserLibrary\PersonsService.cs" /> - <Compile Include="UserLibrary\PlaystateService.cs" /> <Compile Include="UserLibrary\StudiosService.cs" /> <Compile Include="UserLibrary\UserLibraryService.cs" /> <Compile Include="UserLibrary\UserViewsService.cs" /> diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index e20fa2cca..43d8f3ecf 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -158,17 +158,19 @@ namespace MediaBrowser.Api.Movies var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) { Limit = request.Limit, - IncludeItemTypes = itemTypes.ToArray(), + IncludeItemTypes = itemTypes.ToArray(itemTypes.Count), IsMovie = true, SimilarTo = item, EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).ToList(); + }); + + var returnList = await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false); var result = new QueryResult<BaseItemDto> { - Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(), + Items = returnList.ToArray(returnList.Count), TotalRecordCount = itemsResult.Count }; @@ -200,7 +202,7 @@ namespace MediaBrowser.Api.Movies DtoOptions = dtoOptions }; - var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList(); + var recentlyPlayedMovies = _libraryManager.GetItemList(query); var itemTypes = new List<string> { typeof(Movie).Name }; if (_config.Configuration.EnableExternalContentInSuggestions) @@ -211,19 +213,19 @@ namespace MediaBrowser.Api.Movies var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user) { - IncludeItemTypes = itemTypes.ToArray(), + IncludeItemTypes = itemTypes.ToArray(itemTypes.Count), IsMovie = true, SortBy = new[] { ItemSortBy.Random }, SortOrder = SortOrder.Descending, Limit = 10, IsFavoriteOrLiked = true, - ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(), + ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(recentlyPlayedMovies.Count), EnableGroupByMetadataKey = true, ParentId = parentIdGuid, Recursive = true, DtoOptions = dtoOptions - }).ToList(); + }); var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList(); // Get recently played directors @@ -300,7 +302,7 @@ namespace MediaBrowser.Api.Movies // Account for duplicates by imdb id, since the database doesn't support this yet Limit = itemLimit + 2, PersonTypes = new[] { PersonType.Director }, - IncludeItemTypes = itemTypes.ToArray(), + IncludeItemTypes = itemTypes.ToArray(itemTypes.Count), IsMovie = true, EnableGroupByMetadataKey = true, DtoOptions = dtoOptions @@ -311,12 +313,14 @@ namespace MediaBrowser.Api.Movies if (items.Count > 0) { + var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result; + yield return new RecommendationDto { BaselineItemName = name, CategoryId = name.GetMD5().ToString("N"), RecommendationType = type, - Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray() + Items = returnItems.ToArray(returnItems.Count) }; } } @@ -338,7 +342,7 @@ namespace MediaBrowser.Api.Movies Person = name, // Account for duplicates by imdb id, since the database doesn't support this yet Limit = itemLimit + 2, - IncludeItemTypes = itemTypes.ToArray(), + IncludeItemTypes = itemTypes.ToArray(itemTypes.Count), IsMovie = true, EnableGroupByMetadataKey = true, DtoOptions = dtoOptions @@ -349,12 +353,14 @@ namespace MediaBrowser.Api.Movies if (items.Count > 0) { + var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result; + yield return new RecommendationDto { BaselineItemName = name, CategoryId = name.GetMD5().ToString("N"), RecommendationType = type, - Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray() + Items = returnItems.ToArray(returnItems.Count) }; } } @@ -374,28 +380,30 @@ namespace MediaBrowser.Api.Movies var similar = _libraryManager.GetItemList(new InternalItemsQuery(user) { Limit = itemLimit, - IncludeItemTypes = itemTypes.ToArray(), + IncludeItemTypes = itemTypes.ToArray(itemTypes.Count), IsMovie = true, SimilarTo = item, EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).ToList(); + }); if (similar.Count > 0) { + var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).Result; + yield return new RecommendationDto { BaselineItemName = item.Name, CategoryId = item.Id.ToString("N"), RecommendationType = type, - Items = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).Result.ToArray() + Items = returnItems.ToArray(returnItems.Count) }; } } } - private IEnumerable<string> GetActors(IEnumerable<BaseItem> items) + private IEnumerable<string> GetActors(List<BaseItem> items) { var people = _libraryManager.GetPeople(new InternalPeopleQuery { @@ -406,7 +414,7 @@ namespace MediaBrowser.Api.Movies MaxListOrder = 3 }); - var itemIds = items.Select(i => i.Id).ToList(); + var itemIds = items.Select(i => i.Id).ToList(items.Count); return people .Where(i => itemIds.Contains(i.ItemId)) @@ -414,7 +422,7 @@ namespace MediaBrowser.Api.Movies .DistinctNames(); } - private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items) + private IEnumerable<string> GetDirectors(List<BaseItem> items) { var people = _libraryManager.GetPeople(new InternalPeopleQuery { @@ -424,7 +432,7 @@ namespace MediaBrowser.Api.Movies } }); - var itemIds = items.Select(i => i.Id).ToList(); + var itemIds = items.Select(i => i.Id).ToList(items.Count); return people .Where(i => itemIds.Contains(i.ItemId)) diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index 3cb29de07..b0f086ec5 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api.Music { @@ -180,16 +181,19 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - private async Task<object> GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions) + private async Task<object> GetResult(List<BaseItem> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions) { - var list = items.ToList(); + var list = items; var result = new ItemsResult { TotalRecordCount = list.Count }; - result.Items = (await _dtoService.GetBaseItemDtos(list.Take(request.Limit ?? list.Count), dtoOptions, user).ConfigureAwait(false)).ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(list.Take(request.Limit ?? list.Count), dtoOptions, user) + .ConfigureAwait(false)); + + result.Items = returnList.ToArray(returnList.Count); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs deleted file mode 100644 index d157f1b65..000000000 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ /dev/null @@ -1,1024 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Diagnostics; - -namespace MediaBrowser.Api.Playback -{ - /// <summary> - /// Class BaseStreamingService - /// </summary> - public abstract class BaseStreamingService : BaseApiService - { - /// <summary> - /// Gets or sets the application paths. - /// </summary> - /// <value>The application paths.</value> - protected IServerConfigurationManager ServerConfigurationManager { get; private set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - protected IUserManager UserManager { get; private set; } - - /// <summary> - /// Gets or sets the library manager. - /// </summary> - /// <value>The library manager.</value> - protected ILibraryManager LibraryManager { get; private set; } - - /// <summary> - /// Gets or sets the iso manager. - /// </summary> - /// <value>The iso manager.</value> - protected IIsoManager IsoManager { get; private set; } - - /// <summary> - /// Gets or sets the media encoder. - /// </summary> - /// <value>The media encoder.</value> - protected IMediaEncoder MediaEncoder { get; private set; } - - protected IFileSystem FileSystem { get; private set; } - - protected IDlnaManager DlnaManager { get; private set; } - protected IDeviceManager DeviceManager { get; private set; } - protected ISubtitleEncoder SubtitleEncoder { get; private set; } - protected IMediaSourceManager MediaSourceManager { get; private set; } - protected IZipClient ZipClient { get; private set; } - protected IJsonSerializer JsonSerializer { get; private set; } - - public static IServerApplicationHost AppHost; - public static IHttpClient HttpClient; - protected IAuthorizationContext AuthorizationContext { get; private set; } - - protected EncodingHelper EncodingHelper { get; set; } - - /// <summary> - /// Initializes a new instance of the <see cref="BaseStreamingService" /> class. - /// </summary> - protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) - { - JsonSerializer = jsonSerializer; - AuthorizationContext = authorizationContext; - ZipClient = zipClient; - MediaSourceManager = mediaSourceManager; - DeviceManager = deviceManager; - SubtitleEncoder = subtitleEncoder; - DlnaManager = dlnaManager; - FileSystem = fileSystem; - ServerConfigurationManager = serverConfig; - UserManager = userManager; - LibraryManager = libraryManager; - IsoManager = isoManager; - MediaEncoder = mediaEncoder; - EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder); - } - - /// <summary> - /// Gets the command line arguments. - /// </summary> - protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding); - - /// <summary> - /// Gets the type of the transcoding job. - /// </summary> - /// <value>The type of the transcoding job.</value> - protected abstract TranscodingJobType TranscodingJobType { get; } - - /// <summary> - /// Gets the output file extension. - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.String.</returns> - protected virtual string GetOutputFileExtension(StreamState state) - { - return Path.GetExtension(state.RequestedUrl); - } - - /// <summary> - /// Gets the output file path. - /// </summary> - private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension) - { - var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath; - - var data = GetCommandLineArguments("dummy\\dummy", encodingOptions, state, false); - - data += "-" + (state.Request.DeviceId ?? string.Empty); - data += "-" + (state.Request.PlaySessionId ?? string.Empty); - - var dataHash = data.GetMD5().ToString("N"); - - if (EnableOutputInSubFolder) - { - return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLower()); - } - - return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLower()); - } - - protected virtual bool EnableOutputInSubFolder - { - get { return false; } - } - - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - protected virtual string GetDefaultH264Preset() - { - return "superfast"; - } - - private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource) - { - if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath)) - { - state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false); - } - - if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId)) - { - var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest - { - OpenToken = state.MediaSource.OpenToken - - }, cancellationTokenSource.Token).ConfigureAwait(false); - - EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl); - - if (state.VideoRequest != null) - { - EncodingHelper.TryStreamCopy(state); - } - } - - if (state.MediaSource.BufferMs.HasValue) - { - await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - - /// <summary> - /// Starts the FFMPEG. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationTokenSource">The cancellation token source.</param> - /// <param name="workingDirectory">The working directory.</param> - /// <returns>Task.</returns> - protected async Task<TranscodingJob> StartFfMpeg(StreamState state, - string outputPath, - CancellationTokenSource cancellationTokenSource, - string workingDirectory = null) - { - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath)); - - await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); - - if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var auth = AuthorizationContext.GetAuthorizationInfo(Request); - if (!string.IsNullOrWhiteSpace(auth.UserId)) - { - var user = UserManager.GetUserById(auth.UserId); - if (!user.Policy.EnableVideoPlaybackTranscoding) - { - ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); - - throw new ArgumentException("User does not have access to video transcoding"); - } - } - } - - var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - - var transcodingId = Guid.NewGuid().ToString("N"); - var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true); - - var process = ApiEntryPoint.Instance.ProcessFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - - FileName = MediaEncoder.EncoderPath, - Arguments = commandLineArgs, - - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true, - WorkingDirectory = !string.IsNullOrWhiteSpace(workingDirectory) ? workingDirectory : null - }); - - var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, - state.Request.PlaySessionId, - state.MediaSource.LiveStreamId, - transcodingId, - TranscodingJobType, - process, - state.Request.DeviceId, - state, - cancellationTokenSource); - - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - Logger.Info(commandLineLogMessage); - - var logFilePrefix = "ffmpeg-transcode"; - if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - logFilePrefix = "ffmpeg-directstream"; - } - else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - logFilePrefix = "ffmpeg-remux"; - } - - var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt"); - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(logFilePath)); - - // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); - - var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); - - process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state); - - try - { - process.Start(); - } - catch (Exception ex) - { - Logger.ErrorException("Error starting ffmpeg", ex); - - ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); - - throw; - } - - // MUST read both stdout and stderr asynchronously or a deadlock may occurr - //process.BeginOutputReadLine(); - - state.TranscodingJob = transcodingJob; - - // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream); - - // Wait for the file to exist before proceeeding - while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited) - { - await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); - } - - if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited) - { - await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); - - if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited) - { - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - - if (!transcodingJob.HasExited) - { - StartThrottler(state, transcodingJob); - } - - return transcodingJob; - } - - private void StartThrottler(StreamState state, TranscodingJob transcodingJob) - { - if (EnableThrottling(state)) - { - transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager, ApiEntryPoint.Instance.TimerFactory, FileSystem); - state.TranscodingThrottler.Start(); - } - } - - private bool EnableThrottling(StreamState state) - { - return false; - //// do not use throttling with hardware encoders - //return state.InputProtocol == MediaProtocol.File && - // state.RunTimeTicks.HasValue && - // state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && - // state.IsInputVideo && - // state.VideoType == VideoType.VideoFile && - // !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && - // string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase); - } - - /// <summary> - /// Processes the exited. - /// </summary> - /// <param name="process">The process.</param> - /// <param name="job">The job.</param> - /// <param name="state">The state.</param> - private void OnFfMpegProcessExited(IProcess process, TranscodingJob job, StreamState state) - { - if (job != null) - { - job.HasExited = true; - } - - Logger.Debug("Disposing stream resources"); - state.Dispose(); - - try - { - Logger.Info("FFMpeg exited with code {0}", process.ExitCode); - } - catch - { - Logger.Error("FFMpeg exited with an error."); - } - - // This causes on exited to be called twice: - //try - //{ - // // Dispose the process - // process.Dispose(); - //} - //catch (Exception ex) - //{ - // Logger.ErrorException("Error disposing ffmpeg.", ex); - //} - } - - /// <summary> - /// Parses the parameters. - /// </summary> - /// <param name="request">The request.</param> - private void ParseParams(StreamRequest request) - { - var vals = request.Params.Split(';'); - - var videoRequest = request as VideoStreamRequest; - - for (var i = 0; i < vals.Length; i++) - { - var val = vals[i]; - - if (string.IsNullOrWhiteSpace(val)) - { - continue; - } - - if (i == 0) - { - request.DeviceProfileId = val; - } - else if (i == 1) - { - request.DeviceId = val; - } - else if (i == 2) - { - request.MediaSourceId = val; - } - else if (i == 3) - { - request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - else if (i == 4) - { - if (videoRequest != null) - { - videoRequest.VideoCodec = val; - } - } - else if (i == 5) - { - request.AudioCodec = val; - } - else if (i == 6) - { - if (videoRequest != null) - { - videoRequest.AudioStreamIndex = int.Parse(val, UsCulture); - } - } - else if (i == 7) - { - if (videoRequest != null) - { - videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture); - } - } - else if (i == 8) - { - if (videoRequest != null) - { - videoRequest.VideoBitRate = int.Parse(val, UsCulture); - } - } - else if (i == 9) - { - request.AudioBitRate = int.Parse(val, UsCulture); - } - else if (i == 10) - { - request.MaxAudioChannels = int.Parse(val, UsCulture); - } - else if (i == 11) - { - if (videoRequest != null) - { - videoRequest.MaxFramerate = float.Parse(val, UsCulture); - } - } - else if (i == 12) - { - if (videoRequest != null) - { - videoRequest.MaxWidth = int.Parse(val, UsCulture); - } - } - else if (i == 13) - { - if (videoRequest != null) - { - videoRequest.MaxHeight = int.Parse(val, UsCulture); - } - } - else if (i == 14) - { - request.StartTimeTicks = long.Parse(val, UsCulture); - } - else if (i == 15) - { - if (videoRequest != null) - { - videoRequest.Level = val; - } - } - else if (i == 16) - { - if (videoRequest != null) - { - videoRequest.MaxRefFrames = int.Parse(val, UsCulture); - } - } - else if (i == 17) - { - if (videoRequest != null) - { - videoRequest.MaxVideoBitDepth = int.Parse(val, UsCulture); - } - } - else if (i == 18) - { - if (videoRequest != null) - { - videoRequest.Profile = val; - } - } - else if (i == 19) - { - // cabac no longer used - } - else if (i == 20) - { - request.PlaySessionId = val; - } - else if (i == 21) - { - // api_key - } - else if (i == 22) - { - request.LiveStreamId = val; - } - else if (i == 23) - { - // Duplicating ItemId because of MediaMonkey - } - else if (i == 24) - { - if (videoRequest != null) - { - videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - } - else if (i == 25) - { - if (!string.IsNullOrWhiteSpace(val) && videoRequest != null) - { - SubtitleDeliveryMethod method; - if (Enum.TryParse(val, out method)) - { - videoRequest.SubtitleMethod = method; - } - } - } - else if (i == 26) - { - request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture); - } - else if (i == 27) - { - if (videoRequest != null) - { - videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - } - else if (i == 28) - { - request.Tag = val; - } - else if (i == 29) - { - if (videoRequest != null) - { - videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - } - else if (i == 30) - { - request.SubtitleCodec = val; - } - else if (i == 31) - { - if (videoRequest != null) - { - videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - } - else if (i == 32) - { - if (videoRequest != null) - { - videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - } - else if (i == 33) - { - request.TranscodeReasons = val; - } - } - } - - /// <summary> - /// Parses the dlna headers. - /// </summary> - /// <param name="request">The request.</param> - private void ParseDlnaHeaders(StreamRequest request) - { - if (!request.StartTimeTicks.HasValue) - { - var timeSeek = GetHeader("TimeSeekRange.dlna.org"); - - request.StartTimeTicks = ParseTimeSeekHeader(timeSeek); - } - } - - /// <summary> - /// Parses the time seek header. - /// </summary> - private long? ParseTimeSeekHeader(string value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return null; - } - - if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0) - { - throw new ArgumentException("Invalid timeseek header"); - } - value = value.Substring(4).Split(new[] { '-' }, 2)[0]; - - if (value.IndexOf(':') == -1) - { - // Parses npt times in the format of '417.33' - double seconds; - if (double.TryParse(value, NumberStyles.Any, UsCulture, out seconds)) - { - return TimeSpan.FromSeconds(seconds).Ticks; - } - - throw new ArgumentException("Invalid timeseek header"); - } - - // Parses npt times in the format of '10:19:25.7' - var tokens = value.Split(new[] { ':' }, 3); - double secondsSum = 0; - var timeFactor = 3600; - - foreach (var time in tokens) - { - double digit; - if (double.TryParse(time, NumberStyles.Any, UsCulture, out digit)) - { - secondsSum += digit * timeFactor; - } - else - { - throw new ArgumentException("Invalid timeseek header"); - } - timeFactor /= 60; - } - return TimeSpan.FromSeconds(secondsSum).Ticks; - } - - /// <summary> - /// Gets the state. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>StreamState.</returns> - protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken) - { - ParseDlnaHeaders(request); - - if (!string.IsNullOrWhiteSpace(request.Params)) - { - ParseParams(request); - } - - var url = Request.PathInfo; - - if (string.IsNullOrEmpty(request.AudioCodec)) - { - request.AudioCodec = EncodingHelper.InferAudioCodec(url); - } - - var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*|| - string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/; - - var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType) - { - Request = request, - RequestedUrl = url, - UserAgent = Request.UserAgent, - EnableDlnaHeaders = enableDlnaHeaders - }; - - var auth = AuthorizationContext.GetAuthorizationInfo(Request); - if (!string.IsNullOrWhiteSpace(auth.UserId)) - { - state.User = UserManager.GetUserById(auth.UserId); - } - - //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || - // (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || - // (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) - //{ - // state.SegmentLength = 6; - //} - - if (state.VideoRequest != null) - { - if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec)) - { - state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); - } - } - - if (!string.IsNullOrWhiteSpace(request.AudioCodec)) - { - state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i)) - ?? state.SupportedAudioCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) - { - state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i)) - ?? state.SupportedSubtitleCodecs.FirstOrDefault(); - } - - var item = LibraryManager.GetItemById(request.Id); - - state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - - //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? - // item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); - //if (primaryImage != null) - //{ - // state.AlbumCoverPath = primaryImage.Path; - //} - - MediaSourceInfo mediaSource = null; - if (string.IsNullOrWhiteSpace(request.LiveStreamId)) - { - TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? - ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) - : null; - - if (currentJob != null) - { - mediaSource = currentJob.MediaSource; - } - - if (mediaSource == null) - { - var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList(); - - mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); - - if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase)) - { - mediaSource = mediaSources.First(); - } - } - } - else - { - var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false); - mediaSource = liveStreamInfo.Item1; - state.DirectStreamProvider = liveStreamInfo.Item2; - } - - var videoRequest = request as VideoStreamRequest; - - EncodingHelper.AttachMediaSourceInfo(state, mediaSource, url); - - var container = Path.GetExtension(state.RequestedUrl); - - if (string.IsNullOrEmpty(container)) - { - container = request.Container; - } - - if (string.IsNullOrEmpty(container)) - { - container = request.Static ? - StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio) : - GetOutputFileExtension(state); - } - - state.OutputContainer = (container ?? string.Empty).TrimStart('.'); - - state.OutputAudioBitrate = EncodingHelper.GetAudioBitrateParam(state.Request, state.AudioStream); - - state.OutputAudioCodec = state.Request.AudioCodec; - - state.OutputAudioChannels = EncodingHelper.GetNumAudioChannelsParam(state.Request, state.AudioStream, state.OutputAudioCodec); - - if (videoRequest != null) - { - state.OutputVideoCodec = state.VideoRequest.VideoCodec; - state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - - if (videoRequest != null) - { - EncodingHelper.TryStreamCopy(state); - } - - if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var resolution = ResolutionNormalizer.Normalize( - state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, - state.OutputVideoBitrate.Value, - state.VideoStream == null ? null : state.VideoStream.Codec, - state.OutputVideoCodec, - videoRequest.MaxWidth, - videoRequest.MaxHeight); - - videoRequest.MaxWidth = resolution.MaxWidth; - videoRequest.MaxHeight = resolution.MaxHeight; - } - - ApplyDeviceProfileSettings(state); - } - else - { - ApplyDeviceProfileSettings(state); - } - - var ext = string.IsNullOrWhiteSpace(state.OutputContainer) - ? GetOutputFileExtension(state) - : ("." + state.OutputContainer); - - var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); - - state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext); - - return state; - } - - private void ApplyDeviceProfileSettings(StreamState state) - { - var headers = Request.Headers.ToDictionary(); - - if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId)) - { - state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId); - } - else - { - if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) - { - var caps = DeviceManager.GetCapabilities(state.Request.DeviceId); - - if (caps != null) - { - state.DeviceProfile = caps.DeviceProfile; - } - else - { - state.DeviceProfile = DlnaManager.GetProfile(headers); - } - } - } - - var profile = state.DeviceProfile; - - if (profile == null) - { - // Don't use settings from the default profile. - // Only use a specific profile if it was requested. - return; - } - - var audioCodec = state.ActualOutputAudioCodec; - var videoCodec = state.ActualOutputVideoCodec; - - var mediaProfile = state.VideoRequest == null ? - profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) : - profile.GetVideoMediaProfile(state.OutputContainer, - audioCodec, - videoCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetVideoProfile, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TargetTimestamp, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC); - - if (mediaProfile != null) - { - state.MimeType = mediaProfile.MimeType; - } - - if (!state.Request.Static) - { - var transcodingProfile = state.VideoRequest == null ? - profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) : - profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec); - - if (transcodingProfile != null) - { - state.EstimateContentLength = transcodingProfile.EstimateContentLength; - state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; - state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - - if (state.VideoRequest != null) - { - state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps; - state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; - } - } - } - } - - /// <summary> - /// Adds the dlna headers. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed) - { - if (!state.EnableDlnaHeaders) - { - return; - } - - var profile = state.DeviceProfile; - - var transferMode = GetHeader("transferMode.dlna.org"); - responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode; - responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*"; - - if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase)) - { - if (state.RunTimeTicks.HasValue) - { - var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds; - responseHeaders["MediaInfo.sec"] = string.Format("SEC_Duration={0};", Convert.ToInt32(ms).ToString(CultureInfo.InvariantCulture)); - } - } - - if (state.RunTimeTicks.HasValue && !isStaticallyStreamed && profile != null) - { - AddTimeSeekResponseHeaders(state, responseHeaders); - } - - if (profile == null) - { - profile = DlnaManager.GetDefaultProfile(); - } - - var audioCodec = state.ActualOutputAudioCodec; - - if (state.VideoRequest == null) - { - responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile) - .BuildAudioHeader( - state.OutputContainer, - audioCodec, - state.OutputAudioBitrate, - state.OutputAudioSampleRate, - state.OutputAudioChannels, - state.OutputAudioBitDepth, - isStaticallyStreamed, - state.RunTimeTicks, - state.TranscodeSeekInfo - ); - } - else - { - var videoCodec = state.ActualOutputVideoCodec; - - responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile) - .BuildVideoHeader( - state.OutputContainer, - videoCodec, - audioCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetTimestamp, - isStaticallyStreamed, - state.RunTimeTicks, - state.TargetVideoProfile, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TranscodeSeekInfo, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC - - ).FirstOrDefault() ?? string.Empty; - } - - foreach (var item in responseHeaders) - { - Request.Response.AddHeader(item.Key, item.Value); - } - } - - private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders) - { - var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture); - var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture); - - responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds); - responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds); - } - } -} diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs deleted file mode 100644 index 83157c703..000000000 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ /dev/null @@ -1,331 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; - -namespace MediaBrowser.Api.Playback.Hls -{ - /// <summary> - /// Class BaseHlsService - /// </summary> - public abstract class BaseHlsService : BaseStreamingService - { - /// <summary> - /// Gets the audio arguments. - /// </summary> - protected abstract string GetAudioArguments(StreamState state, EncodingOptions encodingOptions); - - /// <summary> - /// Gets the video arguments. - /// </summary> - protected abstract string GetVideoArguments(StreamState state, EncodingOptions encodingOptions); - - /// <summary> - /// Gets the segment file extension. - /// </summary> - protected string GetSegmentFileExtension(StreamRequest request) - { - var segmentContainer = request.SegmentContainer; - if (!string.IsNullOrWhiteSpace(segmentContainer)) - { - return "." + segmentContainer; - } - - return ".ts"; - } - - /// <summary> - /// Gets the type of the transcoding job. - /// </summary> - /// <value>The type of the transcoding job.</value> - protected override TranscodingJobType TranscodingJobType - { - get { return TranscodingJobType.Hls; } - } - - /// <summary> - /// Processes the request. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="isLive">if set to <c>true</c> [is live].</param> - /// <returns>System.Object.</returns> - protected async Task<object> ProcessRequest(StreamRequest request, bool isLive) - { - return await ProcessRequestAsync(request, isLive).ConfigureAwait(false); - } - - /// <summary> - /// Processes the request async. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="isLive">if set to <c>true</c> [is live].</param> - /// <returns>Task{System.Object}.</returns> - /// <exception cref="ArgumentException">A video bitrate is required - /// or - /// An audio bitrate is required</exception> - private async Task<object> ProcessRequestAsync(StreamRequest request, bool isLive) - { - var cancellationTokenSource = new CancellationTokenSource(); - - var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); - - TranscodingJob job = null; - var playlist = state.OutputFilePath; - - if (!FileSystem.FileExists(playlist)) - { - var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlist); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); - try - { - if (!FileSystem.FileExists(playlist)) - { - // If the playlist doesn't already exist, startup ffmpeg - try - { - job = await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false); - job.IsLiveOutput = isLive; - } - catch - { - state.Dispose(); - throw; - } - - var minSegments = state.MinSegments; - if (minSegments > 0) - { - await WaitForMinimumSegmentCount(playlist, minSegments, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - } - finally - { - transcodingLock.Release(); - } - } - - if (isLive) - { - job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); - - if (job != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(job); - } - return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - var audioBitrate = state.OutputAudioBitrate ?? 0; - var videoBitrate = state.OutputVideoBitrate ?? 0; - - var baselineStreamBitrate = 64000; - - var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate); - - job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); - - if (job != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(job); - } - - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - private string GetLivePlaylistText(string path, int segmentLength) - { - using (var stream = FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite)) - { - using (var reader = new StreamReader(stream)) - { - var text = reader.ReadToEnd(); - - text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT"); - - var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture); - - text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase); - //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase); - - return text; - } - } - } - - private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate) - { - var builder = new StringBuilder(); - - builder.AppendLine("#EXTM3U"); - - // Pad a little to satisfy the apple hls validator - var paddedBitrate = Convert.ToInt32(bitrate * 1.15); - - // Main stream - builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture)); - var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8"); - builder.AppendLine(playlistUrl); - - return builder.ToString(); - } - - protected virtual async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken) - { - Logger.Debug("Waiting for {0} segments in {1}", segmentCount, playlist); - - while (!cancellationToken.IsCancellationRequested) - { - try - { - // Need to use FileShareMode.ReadWrite because we're reading the file at the same time it's being written - using (var fileStream = GetPlaylistFileStream(playlist)) - { - using (var reader = new StreamReader(fileStream)) - { - var count = 0; - - while (!reader.EndOfStream) - { - var line = reader.ReadLine(); - - if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) - { - count++; - if (count >= segmentCount) - { - Logger.Debug("Finished waiting for {0} segments in {1}", segmentCount, playlist); - return; - } - } - } - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - } - } - catch (IOException) - { - // May get an error if the file is locked - } - - await Task.Delay(50, cancellationToken).ConfigureAwait(false); - } - } - - protected Stream GetPlaylistFileStream(string path) - { - var tmpPath = path + ".tmp"; - tmpPath = path; - - try - { - return FileSystem.GetFileStream(tmpPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, FileOpenOptions.SequentialScan); - } - catch (IOException) - { - return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, FileOpenOptions.SequentialScan); - } - } - - protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) - { - var itsOffsetMs = 0; - - var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture)); - - var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false); - - var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); - - // If isEncoding is true we're actually starting ffmpeg - var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0"; - - var baseUrlParam = string.Empty; - - if (state.Request is GetLiveHlsStream) - { - baseUrlParam = string.Format(" -hls_base_url \"{0}/\"", - "hls/" + Path.GetFileNameWithoutExtension(outputPath)); - } - - var useGenericSegmenter = true; - if (useGenericSegmenter) - { - var outputTsArg = Path.Combine(FileSystem.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); - - var timeDeltaParam = String.Empty; - - var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); - if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) - { - segmentFormat = "mpegts"; - } - - baseUrlParam = string.Format("\"{0}/\"", "hls/" + Path.GetFileNameWithoutExtension(outputPath)); - - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_entry_prefix {12} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", - inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), - threads, - EncodingHelper.GetMapArgs(state), - GetVideoArguments(state, encodingOptions), - GetAudioArguments(state, encodingOptions), - state.SegmentLength.ToString(UsCulture), - startNumberParam, - outputPath, - outputTsArg, - timeDeltaParam, - segmentFormat, - baseUrlParam - ).Trim(); - } - - // add when stream copying? - // -avoid_negative_ts make_zero -fflags +genpts - - var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"", - itsOffset, - inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), - threads, - EncodingHelper.GetMapArgs(state), - GetVideoArguments(state, encodingOptions), - GetAudioArguments(state, encodingOptions), - state.SegmentLength.ToString(UsCulture), - startNumberParam, - state.HlsListSize.ToString(UsCulture), - baseUrlParam, - outputPath - ).Trim(); - - return args; - } - - protected override string GetDefaultH264Preset() - { - return "veryfast"; - } - - protected virtual int GetStartNumber(StreamState state) - { - return 0; - } - - public BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) - { - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs deleted file mode 100644 index 6744fbd92..000000000 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ /dev/null @@ -1,970 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Services; -using MimeTypes = MediaBrowser.Model.Net.MimeTypes; - -namespace MediaBrowser.Api.Playback.Hls -{ - /// <summary> - /// Options is needed for chromecast. Threw Head in there since it's related - /// </summary> - [Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")] - [Route("/Videos/{Id}/master.m3u8", "HEAD", Summary = "Gets a video stream using HTTP live streaming.")] - public class GetMasterHlsVideoPlaylist : VideoStreamRequest, IMasterHlsRequest - { - public bool EnableAdaptiveBitrateStreaming { get; set; } - - public GetMasterHlsVideoPlaylist() - { - EnableAdaptiveBitrateStreaming = true; - } - } - - [Route("/Audio/{Id}/master.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")] - [Route("/Audio/{Id}/master.m3u8", "HEAD", Summary = "Gets an audio stream using HTTP live streaming.")] - public class GetMasterHlsAudioPlaylist : StreamRequest, IMasterHlsRequest - { - public bool EnableAdaptiveBitrateStreaming { get; set; } - - public GetMasterHlsAudioPlaylist() - { - EnableAdaptiveBitrateStreaming = true; - } - } - - public interface IMasterHlsRequest - { - bool EnableAdaptiveBitrateStreaming { get; set; } - } - - [Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")] - public class GetVariantHlsVideoPlaylist : VideoStreamRequest - { - } - - [Route("/Audio/{Id}/main.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")] - public class GetVariantHlsAudioPlaylist : StreamRequest - { - } - - [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] - public class GetHlsVideoSegment : VideoStreamRequest - { - public string PlaylistId { get; set; } - - /// <summary> - /// Gets or sets the segment id. - /// </summary> - /// <value>The segment id.</value> - public string SegmentId { get; set; } - } - - [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] - public class GetHlsAudioSegment : StreamRequest - { - public string PlaylistId { get; set; } - - /// <summary> - /// Gets or sets the segment id. - /// </summary> - /// <value>The segment id.</value> - public string SegmentId { get; set; } - } - - [Authenticated] - public class DynamicHlsService : BaseHlsService - { - - public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) - { - NetworkManager = networkManager; - } - - protected INetworkManager NetworkManager { get; private set; } - - public Task<object> Get(GetMasterHlsVideoPlaylist request) - { - return GetMasterPlaylistInternal(request, "GET"); - } - - public Task<object> Head(GetMasterHlsVideoPlaylist request) - { - return GetMasterPlaylistInternal(request, "HEAD"); - } - - public Task<object> Get(GetMasterHlsAudioPlaylist request) - { - return GetMasterPlaylistInternal(request, "GET"); - } - - public Task<object> Head(GetMasterHlsAudioPlaylist request) - { - return GetMasterPlaylistInternal(request, "HEAD"); - } - - public Task<object> Get(GetVariantHlsVideoPlaylist request) - { - return GetVariantPlaylistInternal(request, true, "main"); - } - - public Task<object> Get(GetVariantHlsAudioPlaylist request) - { - return GetVariantPlaylistInternal(request, false, "main"); - } - - public Task<object> Get(GetHlsVideoSegment request) - { - return GetDynamicSegment(request, request.SegmentId); - } - - public Task<object> Get(GetHlsAudioSegment request) - { - return GetDynamicSegment(request, request.SegmentId); - } - - private async Task<object> GetDynamicSegment(StreamRequest request, string segmentId) - { - if ((request.StartTimeTicks ?? 0) > 0) - { - throw new ArgumentException("StartTimeTicks is not allowed."); - } - - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - - var requestedIndex = int.Parse(segmentId, NumberStyles.Integer, UsCulture); - - var state = await GetState(request, cancellationToken).ConfigureAwait(false); - - var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); - - var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex); - - var segmentExtension = GetSegmentFileExtension(state.Request); - - TranscodingJob job = null; - - if (FileSystem.FileExists(segmentPath)) - { - job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); - } - - var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlistPath); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); - var released = false; - var startTranscoding = false; - - try - { - if (FileSystem.FileExists(segmentPath)) - { - job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - transcodingLock.Release(); - released = true; - return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); - } - else - { - var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); - var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength; - - if (currentTranscodingIndex == null) - { - Logger.Debug("Starting transcoding because currentTranscodingIndex=null"); - startTranscoding = true; - } - else if (requestedIndex < currentTranscodingIndex.Value) - { - Logger.Debug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", requestedIndex, currentTranscodingIndex); - startTranscoding = true; - } - else if (requestedIndex - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange) - { - Logger.Debug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", requestedIndex - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, requestedIndex); - startTranscoding = true; - } - if (startTranscoding) - { - // If the playlist doesn't already exist, startup ffmpeg - try - { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); - - if (currentTranscodingIndex.HasValue) - { - DeleteLastFile(playlistPath, segmentExtension, 0); - } - - request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex); - - job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); - } - catch - { - state.Dispose(); - throw; - } - - //await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false); - } - else - { - job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - if (job.TranscodingThrottler != null) - { - job.TranscodingThrottler.UnpauseTranscoding(); - } - } - } - } - finally - { - if (!released) - { - transcodingLock.Release(); - } - } - - //Logger.Info("waiting for {0}", segmentPath); - //while (!File.Exists(segmentPath)) - //{ - // await Task.Delay(50, cancellationToken).ConfigureAwait(false); - //} - - Logger.Info("returning {0}", segmentPath); - job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); - } - - private const int BufferSize = 81920; - - private long GetStartPositionTicks(StreamState state, int requestedIndex) - { - double startSeconds = 0; - var lengths = GetSegmentLengths(state); - - if (requestedIndex >= lengths.Length) - { - var msg = string.Format("Invalid segment index requested: {0} - Segment count: {1}", requestedIndex, lengths.Length); - throw new ArgumentException(msg); - } - - for (var i = 0; i < requestedIndex; i++) - { - startSeconds += lengths[i]; - } - - var position = TimeSpan.FromSeconds(startSeconds).Ticks; - return position; - } - - private long GetEndPositionTicks(StreamState state, int requestedIndex) - { - double startSeconds = 0; - var lengths = GetSegmentLengths(state); - - if (requestedIndex >= lengths.Length) - { - var msg = string.Format("Invalid segment index requested: {0} - Segment count: {1}", requestedIndex, lengths.Length); - throw new ArgumentException(msg); - } - - for (var i = 0; i <= requestedIndex; i++) - { - startSeconds += lengths[i]; - } - - var position = TimeSpan.FromSeconds(startSeconds).Ticks; - return position; - } - - private double[] GetSegmentLengths(StreamState state) - { - var result = new List<double>(); - - var ticks = state.RunTimeTicks ?? 0; - - var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks; - - while (ticks > 0) - { - var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks; - - result.Add(TimeSpan.FromTicks(length).TotalSeconds); - - ticks -= length; - } - - return result.ToArray(); - } - - public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) - { - var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType); - - if (job == null || job.HasExited) - { - return null; - } - - var file = GetLastTranscodingFile(playlist, segmentExtension, FileSystem); - - if (file == null) - { - return null; - } - - var playlistFilename = Path.GetFileNameWithoutExtension(playlist); - - var indexString = Path.GetFileNameWithoutExtension(file.Name).Substring(playlistFilename.Length); - - return int.Parse(indexString, NumberStyles.Integer, UsCulture); - } - - private void DeleteLastFile(string playlistPath, string segmentExtension, int retryCount) - { - var file = GetLastTranscodingFile(playlistPath, segmentExtension, FileSystem); - - if (file != null) - { - DeleteFile(file.FullName, retryCount); - } - } - - private void DeleteFile(string path, int retryCount) - { - if (retryCount >= 5) - { - return; - } - - Logger.Debug("Deleting partial HLS file {0}", path); - - try - { - FileSystem.DeleteFile(path); - } - catch (IOException ex) - { - Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path); - - var task = Task.Delay(100); - Task.WaitAll(task); - DeleteFile(path, retryCount + 1); - } - catch (Exception ex) - { - Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path); - } - } - - private static FileSystemMetadata GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem) - { - var folder = fileSystem.GetDirectoryName(playlist); - - var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty; - - try - { - return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false) - .Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(fileSystem.GetLastWriteTimeUtc) - .FirstOrDefault(); - } - catch (IOException) - { - return null; - } - } - - protected override int GetStartNumber(StreamState state) - { - return GetStartNumber(state.VideoRequest); - } - - private int GetStartNumber(VideoStreamRequest request) - { - var segmentId = "0"; - - var segmentRequest = request as GetHlsVideoSegment; - if (segmentRequest != null) - { - segmentId = segmentRequest.SegmentId; - } - - return int.Parse(segmentId, NumberStyles.Integer, UsCulture); - } - - private string GetSegmentPath(StreamState state, string playlist, int index) - { - var folder = FileSystem.GetDirectoryName(playlist); - - var filename = Path.GetFileNameWithoutExtension(playlist); - - return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state.Request)); - } - - private async Task<object> GetSegmentResult(StreamState state, - string playlistPath, - string segmentPath, - string segmentExtension, - int segmentIndex, - TranscodingJob transcodingJob, - CancellationToken cancellationToken) - { - var segmentFileExists = FileSystem.FileExists(segmentPath); - - // If all transcoding has completed, just return immediately - if (transcodingJob != null && transcodingJob.HasExited && segmentFileExists) - { - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } - - if (segmentFileExists) - { - var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); - - // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready - if (segmentIndex < currentTranscodingIndex) - { - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } - } - - var segmentFilename = Path.GetFileName(segmentPath); - - while (!cancellationToken.IsCancellationRequested) - { - try - { - var text = FileSystem.ReadAllText(playlistPath, Encoding.UTF8); - - // If it appears in the playlist, it's done - if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) - { - if (!segmentFileExists) - { - segmentFileExists = FileSystem.FileExists(segmentPath); - } - if (segmentFileExists) - { - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } - //break; - } - } - catch (IOException) - { - // May get an error if the file is locked - } - - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - - cancellationToken.ThrowIfCancellationRequested(); - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } - - private Task<object> GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob) - { - var segmentEndingPositionTicks = GetEndPositionTicks(state, index); - - return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - Path = segmentPath, - FileShare = FileShareMode.ReadWrite, - OnComplete = () => - { - if (transcodingJob != null) - { - transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); - ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); - } - } - }); - } - - private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method) - { - var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); - - if (string.IsNullOrEmpty(request.MediaSourceId)) - { - throw new ArgumentException("MediaSourceId is required"); - } - - var playlistText = string.Empty; - - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - var audioBitrate = state.OutputAudioBitrate ?? 0; - var videoBitrate = state.OutputVideoBitrate ?? 0; - - playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate); - } - - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - private string GetMasterPlaylistFileText(StreamState state, int totalBitrate) - { - var builder = new StringBuilder(); - - builder.AppendLine("#EXTM3U"); - - var isLiveStream = state.IsSegmentedLiveStream; - - var queryStringIndex = Request.RawUrl.IndexOf('?'); - var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - - // from universal audio service - if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer)) - { - queryString += "&SegmentContainer=" + state.Request.SegmentContainer; - } - // from universal audio service - if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1) - { - queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons; - } - - // Main stream - var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8"; - - playlistUrl += queryString; - - var request = state.Request; - - var subtitleStreams = state.MediaSource - .MediaStreams - .Where(i => i.IsTextSubtitleStream) - .ToList(); - - var subtitleGroup = subtitleStreams.Count > 0 && - request is GetMasterHlsVideoPlaylist && - (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest.EnableSubtitlesInManifest) ? - "subs" : - null; - - // If we're burning in subtitles then don't add additional subs to the manifest - if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) - { - subtitleGroup = null; - } - - if (!string.IsNullOrWhiteSpace(subtitleGroup)) - { - AddSubtitles(state, subtitleStreams, builder); - } - - AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup); - - if (EnableAdaptiveBitrateStreaming(state, isLiveStream)) - { - var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0; - - // By default, vary by just 200k - var variation = GetBitrateVariation(totalBitrate); - - var newBitrate = totalBitrate - variation; - var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation); - AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup); - - variation *= 2; - newBitrate = totalBitrate - variation; - variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation); - AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup); - } - - return builder.ToString(); - } - - private string ReplaceBitrate(string url, int oldValue, int newValue) - { - return url.Replace( - "videobitrate=" + oldValue.ToString(UsCulture), - "videobitrate=" + newValue.ToString(UsCulture), - StringComparison.OrdinalIgnoreCase); - } - - private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder) - { - var selectedIndex = state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Hls ? (int?)null : state.SubtitleStream.Index; - - foreach (var stream in subtitles) - { - const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\""; - - var name = stream.DisplayTitle; - - var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index; - var isForced = stream.IsForced; - - var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}", - state.Request.MediaSourceId, - stream.Index.ToString(UsCulture), - 30.ToString(UsCulture), - AuthorizationContext.GetAuthorizationInfo(Request).Token); - - var line = string.Format(format, - name, - isDefault ? "YES" : "NO", - isForced ? "YES" : "NO", - url, - stream.Language ?? "Unknown"); - - builder.AppendLine(line); - } - } - - private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream) - { - // Within the local network this will likely do more harm than good. - if (Request.IsLocal || NetworkManager.IsInLocalNetwork(Request.RemoteIp)) - { - return false; - } - - var request = state.Request as IMasterHlsRequest; - if (request != null && !request.EnableAdaptiveBitrateStreaming) - { - return false; - } - - if (isLiveStream || string.IsNullOrWhiteSpace(state.MediaPath)) - { - // Opening live streams is so slow it's not even worth it - return false; - } - - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (!state.IsOutputVideo) - { - return false; - } - - // Having problems in android - return false; - //return state.VideoRequest.VideoBitRate.HasValue; - } - - private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup) - { - var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(UsCulture) + ",AVERAGE-BANDWIDTH=" + bitrate.ToString(UsCulture); - - // tvos wants resolution, codecs, framerate - //if (state.TargetFramerate.HasValue) - //{ - // header += string.Format(",FRAME-RATE=\"{0}\"", state.TargetFramerate.Value.ToString(CultureInfo.InvariantCulture)); - //} - - if (!string.IsNullOrWhiteSpace(subtitleGroup)) - { - header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup); - } - - builder.AppendLine(header); - builder.AppendLine(url); - } - - private int GetBitrateVariation(int bitrate) - { - // By default, vary by just 50k - var variation = 50000; - - if (bitrate >= 10000000) - { - variation = 2000000; - } - else if (bitrate >= 5000000) - { - variation = 1500000; - } - else if (bitrate >= 3000000) - { - variation = 1000000; - } - else if (bitrate >= 2000000) - { - variation = 500000; - } - else if (bitrate >= 1000000) - { - variation = 300000; - } - else if (bitrate >= 600000) - { - variation = 200000; - } - else if (bitrate >= 400000) - { - variation = 100000; - } - - return variation; - } - - private async Task<object> GetVariantPlaylistInternal(StreamRequest request, bool isOutputVideo, string name) - { - var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); - - var segmentLengths = GetSegmentLengths(state); - - var builder = new StringBuilder(); - - builder.AppendLine("#EXTM3U"); - builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); - builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(UsCulture)); - builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); - - var queryStringIndex = Request.RawUrl.IndexOf('?'); - var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - - //if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1) - //{ - // queryString = string.Empty; - //} - - var index = 0; - - foreach (var length in segmentLengths) - { - builder.AppendLine("#EXTINF:" + length.ToString("0.0000", UsCulture) + ", nodesc"); - - builder.AppendLine(string.Format("hls1/{0}/{1}{2}{3}", - - name, - index.ToString(UsCulture), - GetSegmentFileExtension(request), - queryString)); - - index++; - } - - builder.AppendLine("#EXT-X-ENDLIST"); - - var playlistText = builder.ToString(); - - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions) - { - var audioCodec = EncodingHelper.GetAudioEncoder(state); - - if (!state.IsOutputVideo) - { - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return "-acodec copy"; - } - - var audioTranscodeParams = new List<string>(); - - audioTranscodeParams.Add("-acodec " + audioCodec); - - if (state.OutputAudioBitrate.HasValue) - { - audioTranscodeParams.Add("-ab " + state.OutputAudioBitrate.Value.ToString(UsCulture)); - } - - if (state.OutputAudioChannels.HasValue) - { - audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(UsCulture)); - } - - if (state.OutputAudioSampleRate.HasValue) - { - audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture)); - } - - audioTranscodeParams.Add("-vn"); - return string.Join(" ", audioTranscodeParams.ToArray()); - } - - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.EnableBreakOnNonKeyFrames(videoCodec)) - { - return "-codec:a:0 copy -copypriorss:a:0 0"; - } - - return "-codec:a:0 copy"; - } - - var args = "-codec:a:0 " + audioCodec; - - var channels = state.OutputAudioChannels; - - if (channels.HasValue) - { - args += " -ac " + channels.Value; - } - - var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) - { - args += " -ab " + bitrate.Value.ToString(UsCulture); - } - - if (state.OutputAudioSampleRate.HasValue) - { - args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture); - } - - args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true); - - return args; - } - - protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions) - { - if (!state.IsOutputVideo) - { - return string.Empty; - } - - var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - var args = "-codec:v:0 " + codec; - - if (state.EnableMpegtsM2TsMode) - { - args += " -mpegts_m2ts_mode 1"; - } - - // See if we can save come cpu cycles by avoiding encoding - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) - { - args += " -bsf:v h264_mp4toannexb"; - } - - //args += " -flags -global_header"; - } - else - { - var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", - state.SegmentLength.ToString(UsCulture)); - - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - - args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg; - - //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; - - // Add resolution params, if specified - if (!hasGraphicalSubs) - { - args += EncodingHelper.GetOutputSizeParam(state, codec, true); - } - - // This is for internal graphical subs - if (hasGraphicalSubs) - { - args += EncodingHelper.GetGraphicalSubtitleParam(state, codec); - } - - //args += " -flags -global_header"; - } - - if (args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1) - { - args += " -copyts"; - } - - if (!string.IsNullOrEmpty(state.OutputVideoSync)) - { - args += " -vsync " + state.OutputVideoSync; - } - - args += EncodingHelper.GetOutputFFlags(state); - - return args; - } - - protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) - { - var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false); - - var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); - - // If isEncoding is true we're actually starting ffmpeg - var startNumber = GetStartNumber(state); - var startNumberParam = isEncoding ? startNumber.ToString(UsCulture) : "0"; - - var mapArgs = state.IsOutputVideo ? EncodingHelper.GetMapArgs(state) : string.Empty; - - var outputTsArg = Path.Combine(FileSystem.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); - - var timeDeltaParam = String.Empty; - - if (isEncoding && startNumber > 0) - { - var startTime = state.SegmentLength * startNumber; - timeDeltaParam = string.Format("-segment_time_delta -{0}", startTime); - } - - var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); - if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) - { - segmentFormat = "mpegts"; - } - - var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - var breakOnNonKeyFrames = state.EnableBreakOnNonKeyFrames(videoCodec); - - var breakOnNonKeyFramesArg = breakOnNonKeyFrames ? " -break_non_keyframes 1" : ""; - - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0{12} -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", - inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), - threads, - mapArgs, - GetVideoArguments(state, encodingOptions), - GetAudioArguments(state, encodingOptions), - state.SegmentLength.ToString(UsCulture), - startNumberParam, - outputPath, - outputTsArg, - timeDeltaParam, - segmentFormat, - breakOnNonKeyFramesArg - ).Trim(); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs deleted file mode 100644 index 52cc02528..000000000 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ /dev/null @@ -1,163 +0,0 @@ -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Playback.Hls -{ - /// <summary> - /// Class GetHlsAudioSegment - /// </summary> - // Can't require authentication just yet due to seeing some requests come from Chrome without full query string - //[Authenticated] - [Route("/Audio/{Id}/hls/{SegmentId}/stream.mp3", "GET")] - [Route("/Audio/{Id}/hls/{SegmentId}/stream.aac", "GET")] - public class GetHlsAudioSegmentLegacy - { - // TODO: Deprecate with new iOS app - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - public string Id { get; set; } - - /// <summary> - /// Gets or sets the segment id. - /// </summary> - /// <value>The segment id.</value> - public string SegmentId { get; set; } - } - - /// <summary> - /// Class GetHlsVideoSegment - /// </summary> - [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")] - [Authenticated] - public class GetHlsPlaylistLegacy - { - // TODO: Deprecate with new iOS app - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - public string Id { get; set; } - - public string PlaylistId { get; set; } - } - - [Route("/Videos/ActiveEncodings", "DELETE")] - [Authenticated] - public class StopEncodingProcess - { - [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string DeviceId { get; set; } - - [ApiMember(Name = "PlaySessionId", Description = "The play session id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string PlaySessionId { get; set; } - } - - /// <summary> - /// Class GetHlsVideoSegment - /// </summary> - // Can't require authentication just yet due to seeing some requests come from Chrome without full query string - //[Authenticated] - [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] - public class GetHlsVideoSegmentLegacy : VideoStreamRequest - { - public string PlaylistId { get; set; } - - /// <summary> - /// Gets or sets the segment id. - /// </summary> - /// <value>The segment id.</value> - public string SegmentId { get; set; } - } - - public class HlsSegmentService : BaseApiService - { - private readonly IServerApplicationPaths _appPaths; - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; - - public HlsSegmentService(IServerApplicationPaths appPaths, IServerConfigurationManager config, IFileSystem fileSystem) - { - _appPaths = appPaths; - _config = config; - _fileSystem = fileSystem; - } - - public Task<object> Get(GetHlsPlaylistLegacy request) - { - var file = request.PlaylistId + Path.GetExtension(Request.PathInfo); - file = Path.Combine(_appPaths.TranscodingTempPath, file); - - return GetFileResult(file, file); - } - - public void Delete(StopEncodingProcess request) - { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetHlsVideoSegmentLegacy request) - { - var file = request.SegmentId + Path.GetExtension(Request.PathInfo); - - var transcodeFolderPath = _config.ApplicationPaths.TranscodingTempPath; - file = Path.Combine(transcodeFolderPath, file); - - var normalizedPlaylistId = request.PlaylistId; - - var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath) - .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); - - return GetFileResult(file, playlistPath); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetHlsAudioSegmentLegacy request) - { - // TODO: Deprecate with new iOS app - var file = request.SegmentId + Path.GetExtension(Request.PathInfo); - file = Path.Combine(_appPaths.TranscodingTempPath, file); - - return ResultFactory.GetStaticFileResult(Request, file, FileShareMode.ReadWrite); - } - - private Task<object> GetFileResult(string path, string playlistPath) - { - var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); - - return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - Path = path, - FileShare = FileShareMode.ReadWrite, - OnComplete = () => - { - if (transcodingJob != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); - } - } - }); - } - } -} diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs deleted file mode 100644 index 9b3c8a08f..000000000 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ /dev/null @@ -1,137 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using System; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Playback.Hls -{ - [Route("/Videos/{Id}/live.m3u8", "GET")] - public class GetLiveHlsStream : VideoStreamRequest - { - } - - /// <summary> - /// Class VideoHlsService - /// </summary> - [Authenticated] - public class VideoHlsService : BaseHlsService - { - public object Get(GetLiveHlsStream request) - { - return ProcessRequest(request, true); - } - - /// <summary> - /// Gets the audio arguments. - /// </summary> - protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions) - { - var codec = EncodingHelper.GetAudioEncoder(state); - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return "-codec:a:0 copy"; - } - - var args = "-codec:a:0 " + codec; - - var channels = state.OutputAudioChannels; - - if (channels.HasValue) - { - args += " -ac " + channels.Value; - } - - var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) - { - args += " -ab " + bitrate.Value.ToString(UsCulture); - } - - if (state.OutputAudioSampleRate.HasValue) - { - args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture); - } - - args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true); - - return args; - } - - /// <summary> - /// Gets the video arguments. - /// </summary> - protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions) - { - if (!state.IsOutputVideo) - { - return string.Empty; - } - - var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - var args = "-codec:v:0 " + codec; - - if (state.EnableMpegtsM2TsMode) - { - args += " -mpegts_m2ts_mode 1"; - } - - // See if we can save come cpu cycles by avoiding encoding - if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) - { - // if h264_mp4toannexb is ever added, do not use it for live tv - if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && - !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) - { - args += " -bsf:v h264_mp4toannexb"; - } - } - else - { - var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", - state.SegmentLength.ToString(UsCulture)); - - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - - args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg; - - // Add resolution params, if specified - if (!hasGraphicalSubs) - { - args += EncodingHelper.GetOutputSizeParam(state, codec); - } - - // This is for internal graphical subs - if (hasGraphicalSubs) - { - args += EncodingHelper.GetGraphicalSubtitleParam(state, codec); - } - } - - args += " -flags -global_header"; - - if (!string.IsNullOrEmpty(state.OutputVideoSync)) - { - args += " -vsync " + state.OutputVideoSync; - } - - args += EncodingHelper.GetOutputFFlags(state); - - return args; - } - - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) - { - } - } -} diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs deleted file mode 100644 index 8eb940291..000000000 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ /dev/null @@ -1,614 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Session; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Playback -{ - [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")] - public class GetPlaybackInfo : IReturn<PlaybackInfoResponse> - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - } - - [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")] - public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse> - { - } - - [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")] - public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse> - { - } - - [Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")] - public class CloseMediaSource : IReturnVoid - { - [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string LiveStreamId { get; set; } - } - - [Route("/Playback/BitrateTest", "GET")] - public class GetBitrateTestBytes - { - [ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")] - public long Size { get; set; } - - public GetBitrateTestBytes() - { - // 100k - Size = 102400; - } - } - - [Authenticated] - public class MediaInfoService : BaseApiService - { - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IDeviceManager _deviceManager; - private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; - private readonly INetworkManager _networkManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly IUserManager _userManager; - private readonly IJsonSerializer _json; - private readonly IAuthorizationContext _authContext; - - public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager, IJsonSerializer json, IAuthorizationContext authContext) - { - _mediaSourceManager = mediaSourceManager; - _deviceManager = deviceManager; - _libraryManager = libraryManager; - _config = config; - _networkManager = networkManager; - _mediaEncoder = mediaEncoder; - _userManager = userManager; - _json = json; - _authContext = authContext; - } - - public object Get(GetBitrateTestBytes request) - { - var bytes = new byte[request.Size]; - - for (var i = 0; i < bytes.Length; i++) - { - bytes[i] = 0; - } - - return ResultFactory.GetResult(bytes, "application/octet-stream"); - } - - public async Task<object> Get(GetPlaybackInfo request) - { - var result = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }).ConfigureAwait(false); - return ToOptimizedResult(result); - } - - public async Task<object> Post(OpenMediaSource request) - { - var result = await OpenMediaSource(request).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - private async Task<LiveStreamResponse> OpenMediaSource(OpenMediaSource request) - { - var authInfo = _authContext.GetAuthorizationInfo(Request); - - var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false); - - var profile = request.DeviceProfile; - if (profile == null) - { - var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); - if (caps != null) - { - profile = caps.DeviceProfile; - } - } - - if (profile != null) - { - var item = _libraryManager.GetItemById(request.ItemId); - - SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, - request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, - request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, true, true, true); - } - else - { - if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl)) - { - result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId; - } - } - - if (result.MediaSource != null) - { - NormalizeMediaSourceContainer(result.MediaSource, profile, DlnaProfileType.Video); - } - - return result; - } - - public void Post(CloseMediaSource request) - { - var task = _mediaSourceManager.CloseLiveStream(request.LiveStreamId); - Task.WaitAll(task); - } - - public async Task<PlaybackInfoResponse> GetPlaybackInfo(GetPostedPlaybackInfo request) - { - var authInfo = _authContext.GetAuthorizationInfo(Request); - - var profile = request.DeviceProfile; - - //Logger.Info("GetPostedPlaybackInfo profile: {0}", _json.SerializeToString(profile)); - - if (profile == null) - { - var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); - if (caps != null) - { - profile = caps.DeviceProfile; - } - } - - var info = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false); - - if (profile != null) - { - var mediaSourceId = request.MediaSourceId; - - SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, request.EnableTranscoding, request.AllowVideoStreamCopy, request.AllowAudioStreamCopy); - } - - if (request.AutoOpenLiveStream) - { - var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId) ? info.MediaSources.FirstOrDefault() : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId, StringComparison.Ordinal)); - - if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId)) - { - var openStreamResult = await OpenMediaSource(new OpenMediaSource - { - AudioStreamIndex = request.AudioStreamIndex, - DeviceProfile = request.DeviceProfile, - EnableDirectPlay = request.EnableDirectPlay, - EnableDirectStream = request.EnableDirectStream, - ItemId = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MaxStreamingBitrate = request.MaxStreamingBitrate, - PlaySessionId = info.PlaySessionId, - StartTimeTicks = request.StartTimeTicks, - SubtitleStreamIndex = request.SubtitleStreamIndex, - UserId = request.UserId, - OpenToken = mediaSource.OpenToken, - EnableMediaProbe = request.EnableMediaProbe - - }).ConfigureAwait(false); - - info.MediaSources = new List<MediaSourceInfo> { openStreamResult.MediaSource }; - } - } - - if (info.MediaSources != null) - { - foreach (var mediaSource in info.MediaSources) - { - NormalizeMediaSourceContainer(mediaSource, profile, DlnaProfileType.Video); - } - } - - return info; - } - - private void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type) - { - mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, profile, type); - } - - public async Task<object> Post(GetPostedPlaybackInfo request) - { - var result = await GetPlaybackInfo(request).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - private T Clone<T>(T obj) - { - // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it - // Should we move this directly into MediaSourceManager? - - var json = _json.SerializeToString(obj); - return _json.DeserializeFromString<T>(json); - } - - private async Task<PlaybackInfoResponse> GetPlaybackInfo(string id, string userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null) - { - var result = new PlaybackInfoResponse(); - - if (string.IsNullOrWhiteSpace(liveStreamId)) - { - IEnumerable<MediaSourceInfo> mediaSources; - try - { - mediaSources = await _mediaSourceManager.GetPlayackMediaSources(id, userId, true, supportedLiveMediaTypes, CancellationToken.None).ConfigureAwait(false); - } - catch (PlaybackException ex) - { - mediaSources = new List<MediaSourceInfo>(); - result.ErrorCode = ex.ErrorCode; - } - - result.MediaSources = mediaSources.ToList(); - - if (!string.IsNullOrWhiteSpace(mediaSourceId)) - { - result.MediaSources = result.MediaSources - .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)) - .ToList(); - } - } - else - { - var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false); - - result.MediaSources = new List<MediaSourceInfo> { mediaSource }; - } - - if (result.MediaSources.Count == 0) - { - if (!result.ErrorCode.HasValue) - { - result.ErrorCode = PlaybackErrorCode.NoCompatibleStream; - } - } - else - { - result.MediaSources = Clone(result.MediaSources); - - result.PlaySessionId = Guid.NewGuid().ToString("N"); - } - - return result; - } - - private void SetDeviceSpecificData(string itemId, - PlaybackInfoResponse result, - DeviceProfile profile, - AuthorizationInfo auth, - long? maxBitrate, - long startTimeTicks, - string mediaSourceId, - int? audioStreamIndex, - int? subtitleStreamIndex, - int? maxAudioChannels, - string userId, - bool enableDirectPlay, - bool forceDirectPlayRemoteMediaSource, - bool enableDirectStream, - bool enableTranscoding, - bool allowVideoStreamCopy, - bool allowAudioStreamCopy) - { - var item = _libraryManager.GetItemById(itemId); - - foreach (var mediaSource in result.MediaSources) - { - SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, forceDirectPlayRemoteMediaSource, enableDirectStream, enableTranscoding, allowVideoStreamCopy, allowAudioStreamCopy); - } - - SortMediaSources(result, maxBitrate); - } - - private void SetDeviceSpecificData(BaseItem item, - MediaSourceInfo mediaSource, - DeviceProfile profile, - AuthorizationInfo auth, - long? maxBitrate, - long startTimeTicks, - string mediaSourceId, - int? audioStreamIndex, - int? subtitleStreamIndex, - int? maxAudioChannels, - string playSessionId, - string userId, - bool enableDirectPlay, - bool forceDirectPlayRemoteMediaSource, - bool enableDirectStream, - bool enableTranscoding, - bool allowVideoStreamCopy, - bool allowAudioStreamCopy) - { - var streamBuilder = new StreamBuilder(_mediaEncoder, Logger); - - var options = new VideoOptions - { - MediaSources = new List<MediaSourceInfo> { mediaSource }, - Context = EncodingContext.Streaming, - DeviceId = auth.DeviceId, - ItemId = item.Id.ToString("N"), - Profile = profile, - MaxAudioChannels = maxAudioChannels - }; - - if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) - { - options.MediaSourceId = mediaSourceId; - options.AudioStreamIndex = audioStreamIndex; - options.SubtitleStreamIndex = subtitleStreamIndex; - } - - var user = _userManager.GetUserById(userId); - - if (!enableDirectPlay) - { - mediaSource.SupportsDirectPlay = false; - } - if (!enableDirectStream) - { - mediaSource.SupportsDirectStream = false; - } - if (!enableTranscoding) - { - mediaSource.SupportsTranscoding = false; - } - - if (item is Audio) - { - Logger.Info("User policy for {0}. EnableAudioPlaybackTranscoding: {1}", user.Name, user.Policy.EnableAudioPlaybackTranscoding); - } - else - { - Logger.Info("User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}", - user.Name, - user.Policy.EnablePlaybackRemuxing, - user.Policy.EnableVideoPlaybackTranscoding, - user.Policy.EnableAudioPlaybackTranscoding); - } - - if (mediaSource.SupportsDirectPlay) - { - if (mediaSource.IsRemote && forceDirectPlayRemoteMediaSource) - { - } - else - { - var supportsDirectStream = mediaSource.SupportsDirectStream; - - // Dummy this up to fool StreamBuilder - mediaSource.SupportsDirectStream = true; - options.MaxBitrate = maxBitrate; - - if (item is Audio) - { - if (!user.Policy.EnableAudioPlaybackTranscoding) - { - options.ForceDirectPlay = true; - } - } - else if (item is Video) - { - if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing) - { - options.ForceDirectPlay = true; - } - } - - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); - - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectPlay = false; - } - - // Set this back to what it was - mediaSource.SupportsDirectStream = supportsDirectStream; - - if (streamInfo != null) - { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - } - } - } - - if (mediaSource.SupportsDirectStream) - { - options.MaxBitrate = GetMaxBitrate(maxBitrate, user); - - if (item is Audio) - { - if (!user.Policy.EnableAudioPlaybackTranscoding) - { - options.ForceDirectStream = true; - } - } - else if (item is Video) - { - if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing) - { - options.ForceDirectStream = true; - } - } - - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); - - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectStream = false; - } - - if (streamInfo != null) - { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - } - } - - if (mediaSource.SupportsTranscoding) - { - options.MaxBitrate = GetMaxBitrate(maxBitrate, user); - - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? - streamBuilder.BuildAudioItem(options) : - streamBuilder.BuildVideoItem(options); - - if (streamInfo != null) - { - streamInfo.PlaySessionId = playSessionId; - - if (streamInfo.PlayMethod == PlayMethod.Transcode) - { - streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); - - if (!allowVideoStreamCopy) - { - mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; - } - if (!allowAudioStreamCopy) - { - mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; - } - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - } - - // Do this after the above so that StartPositionTicks is set - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - } - } - } - - private long? GetMaxBitrate(long? clientMaxBitrate, User user) - { - var maxBitrate = clientMaxBitrate; - var remoteClientMaxBitrate = user == null ? 0 : user.Policy.RemoteClientBitrateLimit; - - if (remoteClientMaxBitrate <= 0) - { - remoteClientMaxBitrate = _config.Configuration.RemoteClientBitrateLimit; - } - - if (remoteClientMaxBitrate > 0) - { - var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.RemoteIp); - - Logger.Info("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.RemoteIp, isInLocalNetwork); - if (!isInLocalNetwork) - { - maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate); - } - } - - return maxBitrate; - } - - private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken) - { - var profiles = info.GetSubtitleProfiles(false, "-", accessToken); - mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex; - - mediaSource.TranscodeReasons = info.TranscodeReasons; - - foreach (var profile in profiles) - { - foreach (var stream in mediaSource.MediaStreams) - { - if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index) - { - stream.DeliveryMethod = profile.DeliveryMethod; - - if (profile.DeliveryMethod == SubtitleDeliveryMethod.External) - { - stream.DeliveryUrl = profile.Url.TrimStart('-'); - stream.IsExternalUrl = profile.IsExternalUrl; - } - } - } - } - } - - private void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate) - { - var originalList = result.MediaSources.ToList(); - - result.MediaSources = result.MediaSources.OrderBy(i => - { - // Nothing beats direct playing a file - if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File) - { - return 0; - } - - return 1; - - }).ThenBy(i => - { - // Let's assume direct streaming a file is just as desirable as direct playing a remote url - if (i.SupportsDirectPlay || i.SupportsDirectStream) - { - return 0; - } - - return 1; - - }).ThenBy(i => - { - switch (i.Protocol) - { - case MediaProtocol.File: - return 0; - default: - return 1; - } - - }).ThenBy(i => - { - if (maxBitrate.HasValue) - { - if (i.Bitrate.HasValue) - { - if (i.Bitrate.Value <= maxBitrate.Value) - { - return 0; - } - - return 2; - } - } - - return 1; - - }).ThenBy(originalList.IndexOf) - .ToList(); - } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs deleted file mode 100644 index 44e096dd7..000000000 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ /dev/null @@ -1,67 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using System.Collections.Generic; -using System.Threading.Tasks; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; - -namespace MediaBrowser.Api.Playback.Progressive -{ - /// <summary> - /// Class GetAudioStream - /// </summary> - [Route("/Audio/{Id}/stream.{Container}", "GET", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/stream", "GET", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/stream.{Container}", "HEAD", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/stream", "HEAD", Summary = "Gets an audio stream")] - public class GetAudioStream : StreamRequest - { - } - - /// <summary> - /// Class AudioService - /// </summary> - // TODO: In order to autheneticate this in the future, Dlna playback will require updating - //[Authenticated] - public class AudioService : BaseProgressiveStreamingService - { - public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext, imageProcessor, environmentInfo) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetAudioStream request) - { - return ProcessRequest(request, false); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Head(GetAudioStream request) - { - return ProcessRequest(request, true); - } - - protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) - { - return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath); - } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs deleted file mode 100644 index 95b8c3329..000000000 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ /dev/null @@ -1,429 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; - -namespace MediaBrowser.Api.Playback.Progressive -{ - /// <summary> - /// Class BaseProgressiveStreamingService - /// </summary> - public abstract class BaseProgressiveStreamingService : BaseStreamingService - { - protected readonly IImageProcessor ImageProcessor; - protected readonly IEnvironmentInfo EnvironmentInfo; - - public BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) - { - ImageProcessor = imageProcessor; - EnvironmentInfo = environmentInfo; - } - - /// <summary> - /// Gets the output file extension. - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.String.</returns> - protected override string GetOutputFileExtension(StreamState state) - { - var ext = base.GetOutputFileExtension(state); - - if (!string.IsNullOrEmpty(ext)) - { - return ext; - } - - var isVideoRequest = state.VideoRequest != null; - - // Try to infer based on the desired video codec - if (isVideoRequest) - { - var videoCodec = state.VideoRequest.VideoCodec; - - if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) - { - return ".ts"; - } - if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return ".ogv"; - } - if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return ".webm"; - } - if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return ".asf"; - } - } - - // Try to infer based on the desired audio codec - if (!isVideoRequest) - { - var audioCodec = state.Request.AudioCodec; - - if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".aac"; - } - if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".mp3"; - } - if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".ogg"; - } - if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".wma"; - } - } - - return null; - } - - /// <summary> - /// Gets the type of the transcoding job. - /// </summary> - /// <value>The type of the transcoding job.</value> - protected override TranscodingJobType TranscodingJobType - { - get { return TranscodingJobType.Progressive; } - } - - /// <summary> - /// Processes the request. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> - /// <returns>Task.</returns> - protected async Task<object> ProcessRequest(StreamRequest request, bool isHeadRequest) - { - var cancellationTokenSource = new CancellationTokenSource(); - - var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); - - var responseHeaders = new Dictionary<string, string>(); - - if (request.Static && state.DirectStreamProvider != null) - { - AddDlnaHeaders(state, responseHeaders, true); - - using (state) - { - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - // TODO: Don't hardcode this - outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts"); - - return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) - { - AllowEndOfFile = false - }; - } - } - - // Static remote stream - if (request.Static && state.InputProtocol == MediaProtocol.Http) - { - AddDlnaHeaders(state, responseHeaders, true); - - using (state) - { - return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); - } - } - - if (request.Static && state.InputProtocol != MediaProtocol.File) - { - throw new ArgumentException(string.Format("Input protocol {0} cannot be streamed statically.", state.InputProtocol)); - } - - var outputPath = state.OutputFilePath; - var outputPathExists = FileSystem.FileExists(outputPath); - - var transcodingJob = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); - var isTranscodeCached = outputPathExists && transcodingJob != null; - - AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached); - - // Static stream - if (request.Static) - { - var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath); - - using (state) - { - if (state.MediaSource.IsInfiniteStream) - { - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - outputHeaders["Content-Type"] = contentType; - - return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) - { - AllowEndOfFile = false - }; - } - - TimeSpan? cacheDuration = null; - - if (!string.IsNullOrEmpty(request.Tag)) - { - cacheDuration = TimeSpan.FromDays(365); - } - - return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - ResponseHeaders = responseHeaders, - ContentType = contentType, - IsHeadRequest = isHeadRequest, - Path = state.MediaPath, - CacheDuration = cacheDuration - - }).ConfigureAwait(false); - } - } - - //// Not static but transcode cache file exists - //if (isTranscodeCached && state.VideoRequest == null) - //{ - // var contentType = state.GetMimeType(outputPath); - - // try - // { - // if (transcodingJob != null) - // { - // ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob); - // } - - // return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - // { - // ResponseHeaders = responseHeaders, - // ContentType = contentType, - // IsHeadRequest = isHeadRequest, - // Path = outputPath, - // FileShare = FileShareMode.ReadWrite, - // OnComplete = () => - // { - // if (transcodingJob != null) - // { - // ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); - // } - // } - - // }).ConfigureAwait(false); - // } - // finally - // { - // state.Dispose(); - // } - //} - - // Need to start ffmpeg - try - { - return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); - } - catch - { - state.Dispose(); - - throw; - } - } - - /// <summary> - /// Gets the static remote stream result. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> - /// <param name="cancellationTokenSource">The cancellation token source.</param> - /// <returns>Task{System.Object}.</returns> - private async Task<object> GetStaticRemoteStreamResult(StreamState state, Dictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource) - { - string useragent = null; - state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); - - var trySupportSeek = false; - - var options = new HttpRequestOptions - { - Url = state.MediaPath, - UserAgent = useragent, - BufferContent = false, - CancellationToken = cancellationTokenSource.Token - }; - - if (trySupportSeek) - { - if (!string.IsNullOrWhiteSpace(Request.QueryString["Range"])) - { - options.RequestHeaders["Range"] = Request.QueryString["Range"]; - } - } - var response = await HttpClient.GetResponse(options).ConfigureAwait(false); - - if (trySupportSeek) - { - foreach (var name in new[] { "Content-Range", "Accept-Ranges" }) - { - var val = response.Headers[name]; - if (!string.IsNullOrWhiteSpace(val)) - { - responseHeaders[name] = val; - } - } - } - else - { - responseHeaders["Accept-Ranges"] = "none"; - } - - // Seeing cases of -1 here - if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) - { - responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); - } - - if (isHeadRequest) - { - using (response) - { - return ResultFactory.GetResult(new byte[] { }, response.ContentType, responseHeaders); - } - } - - var result = new StaticRemoteStreamWriter(response); - - result.Headers["Content-Type"] = response.ContentType; - - // Add the response headers to the result object - foreach (var header in responseHeaders) - { - result.Headers[header.Key] = header.Value; - } - - return result; - } - - /// <summary> - /// Gets the stream result. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> - /// <param name="cancellationTokenSource">The cancellation token source.</param> - /// <returns>Task{System.Object}.</returns> - private async Task<object> GetStreamResult(StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource) - { - // Use the command line args with a dummy playlist path - var outputPath = state.OutputFilePath; - - responseHeaders["Accept-Ranges"] = "none"; - - var contentType = state.GetMimeType(outputPath); - - // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response - // What we really want to do is hunt that down and remove that - var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; - - if (contentLength.HasValue) - { - responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture); - } - - // Headers only - if (isHeadRequest) - { - var streamResult = ResultFactory.GetResult(new byte[] { }, contentType, responseHeaders); - - var hasHeaders = streamResult as IHasHeaders; - if (hasHeaders != null) - { - if (contentLength.HasValue) - { - hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - } - else - { - if (hasHeaders.Headers.ContainsKey("Content-Length")) - { - hasHeaders.Headers.Remove("Content-Length"); - } - } - } - - return streamResult; - } - - var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); - try - { - TranscodingJob job; - - if (!FileSystem.FileExists(outputPath)) - { - job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false); - } - else - { - job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); - state.Dispose(); - } - - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - outputHeaders["Content-Type"] = contentType; - - // Add the response headers to the result object - foreach (var item in responseHeaders) - { - outputHeaders[item.Key] = item.Value; - } - - return new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, EnvironmentInfo, CancellationToken.None); - } - finally - { - transcodingLock.Release(); - } - } - - /// <summary> - /// Gets the length of the estimated content. - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.Nullable{System.Int64}.</returns> - private long? GetEstimatedContentLength(StreamState state) - { - var totalBitrate = state.TotalOutputBitrate ?? 0; - - if (totalBitrate > 0 && state.RunTimeTicks.HasValue) - { - return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8); - } - - return null; - } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs deleted file mode 100644 index a41b4cbf5..000000000 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ /dev/null @@ -1,100 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using System.Threading.Tasks; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; - -namespace MediaBrowser.Api.Playback.Progressive -{ - /// <summary> - /// Class GetVideoStream - /// </summary> - [Route("/Videos/{Id}/stream.mpegts", "GET")] - [Route("/Videos/{Id}/stream.ts", "GET")] - [Route("/Videos/{Id}/stream.webm", "GET")] - [Route("/Videos/{Id}/stream.asf", "GET")] - [Route("/Videos/{Id}/stream.wmv", "GET")] - [Route("/Videos/{Id}/stream.ogv", "GET")] - [Route("/Videos/{Id}/stream.mp4", "GET")] - [Route("/Videos/{Id}/stream.m4v", "GET")] - [Route("/Videos/{Id}/stream.mkv", "GET")] - [Route("/Videos/{Id}/stream.mpeg", "GET")] - [Route("/Videos/{Id}/stream.mpg", "GET")] - [Route("/Videos/{Id}/stream.avi", "GET")] - [Route("/Videos/{Id}/stream.m2ts", "GET")] - [Route("/Videos/{Id}/stream.3gp", "GET")] - [Route("/Videos/{Id}/stream.wmv", "GET")] - [Route("/Videos/{Id}/stream.wtv", "GET")] - [Route("/Videos/{Id}/stream.mov", "GET")] - [Route("/Videos/{Id}/stream.iso", "GET")] - [Route("/Videos/{Id}/stream.flv", "GET")] - [Route("/Videos/{Id}/stream", "GET")] - [Route("/Videos/{Id}/stream.ts", "HEAD")] - [Route("/Videos/{Id}/stream.webm", "HEAD")] - [Route("/Videos/{Id}/stream.asf", "HEAD")] - [Route("/Videos/{Id}/stream.wmv", "HEAD")] - [Route("/Videos/{Id}/stream.ogv", "HEAD")] - [Route("/Videos/{Id}/stream.mp4", "HEAD")] - [Route("/Videos/{Id}/stream.m4v", "HEAD")] - [Route("/Videos/{Id}/stream.mkv", "HEAD")] - [Route("/Videos/{Id}/stream.mpeg", "HEAD")] - [Route("/Videos/{Id}/stream.mpg", "HEAD")] - [Route("/Videos/{Id}/stream.avi", "HEAD")] - [Route("/Videos/{Id}/stream.3gp", "HEAD")] - [Route("/Videos/{Id}/stream.wmv", "HEAD")] - [Route("/Videos/{Id}/stream.wtv", "HEAD")] - [Route("/Videos/{Id}/stream.m2ts", "HEAD")] - [Route("/Videos/{Id}/stream.mov", "HEAD")] - [Route("/Videos/{Id}/stream.iso", "HEAD")] - [Route("/Videos/{Id}/stream.flv", "HEAD")] - [Route("/Videos/{Id}/stream", "HEAD")] - public class GetVideoStream : VideoStreamRequest - { - - } - - /// <summary> - /// Class VideoService - /// </summary> - // TODO: In order to autheneticate this in the future, Dlna playback will require updating - //[Authenticated] - public class VideoService : BaseProgressiveStreamingService - { - public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext, imageProcessor, environmentInfo) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetVideoStream request) - { - return ProcessRequest(request, false); - } - - /// <summary> - /// Heads the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Head(GetVideoStream request) - { - return ProcessRequest(request, true); - } - - protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) - { - return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset()); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs deleted file mode 100644 index 6bb3b6b80..000000000 --- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs +++ /dev/null @@ -1,47 +0,0 @@ -using MediaBrowser.Common.Net; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Playback -{ - /// <summary> - /// Class StaticRemoteStreamWriter - /// </summary> - public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders - { - /// <summary> - /// The _input stream - /// </summary> - private readonly HttpResponseInfo _response; - - /// <summary> - /// The _options - /// </summary> - private readonly IDictionary<string, string> _options = new Dictionary<string, string>(); - - public StaticRemoteStreamWriter(HttpResponseInfo response) - { - _response = response; - } - - /// <summary> - /// Gets the options. - /// </summary> - /// <value>The options.</value> - public IDictionary<string, string> Headers - { - get { return _options; } - } - - public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) - { - using (_response) - { - await _response.Content.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs deleted file mode 100644 index 176b68f37..000000000 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ /dev/null @@ -1,63 +0,0 @@ -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Playback -{ - /// <summary> - /// Class StreamRequest - /// </summary> - public class StreamRequest : BaseEncodingJobOptions - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - - [ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Container { get; set; } - - /// <summary> - /// Gets or sets the audio codec. - /// </summary> - /// <value>The audio codec.</value> - [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AudioCodec { get; set; } - - [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceProfileId { get; set; } - - public string Params { get; set; } - public string PlaySessionId { get; set; } - public string Tag { get; set; } - public string SegmentContainer { get; set; } - - public int? SegmentLength { get; set; } - public int? MinSegments { get; set; } - } - - public class VideoStreamRequest : StreamRequest - { - /// <summary> - /// Gets a value indicating whether this instance has fixed resolution. - /// </summary> - /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value> - public bool HasFixedResolution - { - get - { - return Width.HasValue || Height.HasValue; - } - } - - public bool EnableSubtitlesInManifest { get; set; } - } -} diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs deleted file mode 100644 index 6c098deea..000000000 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ /dev/null @@ -1,259 +0,0 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Net; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using MediaBrowser.Controller.MediaEncoding; - -namespace MediaBrowser.Api.Playback -{ - public class StreamState : EncodingJobInfo, IDisposable - { - private readonly ILogger _logger; - private readonly IMediaSourceManager _mediaSourceManager; - - public string RequestedUrl { get; set; } - - public StreamRequest Request - { - get { return (StreamRequest)BaseRequest; } - set - { - BaseRequest = value; - - IsVideoRequest = VideoRequest != null; - } - } - - public TranscodingThrottler TranscodingThrottler { get; set; } - - public VideoStreamRequest VideoRequest - { - get { return Request as VideoStreamRequest; } - } - - /// <summary> - /// Gets or sets the log file stream. - /// </summary> - /// <value>The log file stream.</value> - public Stream LogFileStream { get; set; } - public IDirectStreamProvider DirectStreamProvider { get; set; } - - public string WaitForPath { get; set; } - - public bool IsOutputVideo - { - get { return Request is VideoStreamRequest; } - } - - public int SegmentLength - { - get - { - if (Request.SegmentLength.HasValue) - { - return Request.SegmentLength.Value; - } - - if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var userAgent = UserAgent ?? string.Empty; - - if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) - { - if (IsSegmentedLiveStream) - { - return 6; - } - - return 6; - } - - if (IsSegmentedLiveStream) - { - return 3; - } - return 6; - } - - return 3; - } - } - - public int MinSegments - { - get - { - if (Request.MinSegments.HasValue) - { - return Request.MinSegments.Value; - } - - return SegmentLength >= 10 ? 2 : 3; - } - } - - public int HlsListSize - { - get - { - return 0; - } - } - - public string UserAgent { get; set; } - - public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType) - : base(logger, transcodingType) - { - _mediaSourceManager = mediaSourceManager; - _logger = logger; - } - - public string MimeType { get; set; } - - public bool EstimateContentLength { get; set; } - public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - - public long? EncodingDurationTicks { get; set; } - - public string GetMimeType(string outputPath, bool enableStreamDefault = true) - { - if (!string.IsNullOrEmpty(MimeType)) - { - return MimeType; - } - - return MimeTypes.GetMimeType(outputPath, enableStreamDefault); - } - - public bool EnableDlnaHeaders { get; set; } - - public void Dispose() - { - DisposeTranscodingThrottler(); - DisposeLiveStream(); - DisposeLogStream(); - DisposeIsoMount(); - - TranscodingJob = null; - } - - private void DisposeLogStream() - { - if (LogFileStream != null) - { - try - { - LogFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing log stream", ex); - } - - LogFileStream = null; - } - } - - private void DisposeTranscodingThrottler() - { - if (TranscodingThrottler != null) - { - try - { - TranscodingThrottler.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing TranscodingThrottler", ex); - } - - TranscodingThrottler = null; - } - } - - private async void DisposeLiveStream() - { - if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) - { - try - { - await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error closing media source", ex); - } - } - } - - public string OutputFilePath { get; set; } - - public string ActualOutputVideoCodec - { - get - { - var codec = OutputVideoCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = VideoStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; - } - } - - public string ActualOutputAudioCodec - { - get - { - var codec = OutputAudioCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = AudioStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; - } - } - - public DeviceProfile DeviceProfile { get; set; } - - public TranscodingJob TranscodingJob; - public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) - { - ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); - } - } -} diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs deleted file mode 100644 index c42d0c3e4..000000000 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ /dev/null @@ -1,176 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Logging; -using System; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Threading; - -namespace MediaBrowser.Api.Playback -{ - public class TranscodingThrottler : IDisposable - { - private readonly TranscodingJob _job; - private readonly ILogger _logger; - private ITimer _timer; - private bool _isPaused; - private readonly IConfigurationManager _config; - private readonly ITimerFactory _timerFactory; - private readonly IFileSystem _fileSystem; - - public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config, ITimerFactory timerFactory, IFileSystem fileSystem) - { - _job = job; - _logger = logger; - _config = config; - _timerFactory = timerFactory; - _fileSystem = fileSystem; - } - - private EncodingOptions GetOptions() - { - return _config.GetConfiguration<EncodingOptions>("encoding"); - } - - public void Start() - { - _timer = _timerFactory.Create(TimerCallback, null, 5000, 5000); - } - - private void TimerCallback(object state) - { - if (_job.HasExited) - { - DisposeTimer(); - return; - } - - var options = GetOptions(); - - if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds)) - { - PauseTranscoding(); - } - else - { - UnpauseTranscoding(); - } - } - - private void PauseTranscoding() - { - if (!_isPaused) - { - _logger.Debug("Sending pause command to ffmpeg"); - - try - { - _job.Process.StandardInput.Write("c"); - _isPaused = true; - } - catch (Exception ex) - { - _logger.ErrorException("Error pausing transcoding", ex); - } - } - } - - public void UnpauseTranscoding() - { - if (_isPaused) - { - _logger.Debug("Sending unpause command to ffmpeg"); - - try - { - _job.Process.StandardInput.WriteLine(); - _isPaused = false; - } - catch (Exception ex) - { - _logger.ErrorException("Error unpausing transcoding", ex); - } - } - } - - private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds) - { - var bytesDownloaded = job.BytesDownloaded ?? 0; - var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; - var downloadPositionTicks = job.DownloadPositionTicks ?? 0; - - var path = job.Path; - var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; - - if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) - { - // HLS - time-based consideration - - var targetGap = gapLengthInTicks; - var gap = transcodingPositionTicks - downloadPositionTicks; - - if (gap < targetGap) - { - //_logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); - return false; - } - - //_logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); - return true; - } - - if (bytesDownloaded > 0 && transcodingPositionTicks > 0) - { - // Progressive Streaming - byte-based consideration - - try - { - var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length; - - // Estimate the bytes the transcoder should be ahead - double gapFactor = gapLengthInTicks; - gapFactor /= transcodingPositionTicks; - var targetGap = bytesTranscoded * gapFactor; - - var gap = bytesTranscoded - bytesDownloaded; - - if (gap < targetGap) - { - //_logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); - return false; - } - - //_logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); - return true; - } - catch - { - //_logger.Error("Error getting output size"); - return false; - } - } - - //_logger.Debug("No throttle data for " + path); - return false; - } - - public void Stop() - { - DisposeTimer(); - UnpauseTranscoding(); - } - - public void Dispose() - { - DisposeTimer(); - } - - private void DisposeTimer() - { - if (_timer != null) - { - _timer.Dispose(); - _timer = null; - } - } - } -} diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs deleted file mode 100644 index 0211fe75e..000000000 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ /dev/null @@ -1,349 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Api.Playback.Hls; -using MediaBrowser.Api.Playback.Progressive; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; - -namespace MediaBrowser.Api.Playback -{ - public class BaseUniversalRequest - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - - public string UserId { get; set; } - public string AudioCodec { get; set; } - public string Container { get; set; } - - public int? MaxAudioChannels { get; set; } - public int? TranscodingAudioChannels { get; set; } - - public long? MaxStreamingBitrate { get; set; } - - [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public long? StartTimeTicks { get; set; } - - public string TranscodingContainer { get; set; } - public string TranscodingProtocol { get; set; } - public int? MaxAudioSampleRate { get; set; } - public int? MaxAudioBitDepth { get; set; } - - public bool EnableRedirection { get; set; } - public bool EnableRemoteMedia { get; set; } - public bool BreakOnNonKeyFrames { get; set; } - - public BaseUniversalRequest() - { - EnableRedirection = true; - } - } - - [Route("/Audio/{Id}/universal.{Container}", "GET", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/universal", "GET", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/universal.{Container}", "HEAD", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/universal", "HEAD", Summary = "Gets an audio stream")] - public class GetUniversalAudioStream : BaseUniversalRequest - { - } - - [Authenticated] - public class UniversalAudioService : BaseApiService - { - public UniversalAudioService(IServerConfigurationManager serverConfigurationManager, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, IDeviceManager deviceManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, INetworkManager networkManager, IEnvironmentInfo environmentInfo) - { - ServerConfigurationManager = serverConfigurationManager; - UserManager = userManager; - LibraryManager = libraryManager; - IsoManager = isoManager; - MediaEncoder = mediaEncoder; - FileSystem = fileSystem; - DlnaManager = dlnaManager; - DeviceManager = deviceManager; - SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - ZipClient = zipClient; - JsonSerializer = jsonSerializer; - AuthorizationContext = authorizationContext; - ImageProcessor = imageProcessor; - NetworkManager = networkManager; - EnvironmentInfo = environmentInfo; - } - - protected IServerConfigurationManager ServerConfigurationManager { get; private set; } - protected IUserManager UserManager { get; private set; } - protected ILibraryManager LibraryManager { get; private set; } - protected IIsoManager IsoManager { get; private set; } - protected IMediaEncoder MediaEncoder { get; private set; } - protected IFileSystem FileSystem { get; private set; } - protected IDlnaManager DlnaManager { get; private set; } - protected IDeviceManager DeviceManager { get; private set; } - protected ISubtitleEncoder SubtitleEncoder { get; private set; } - protected IMediaSourceManager MediaSourceManager { get; private set; } - protected IZipClient ZipClient { get; private set; } - protected IJsonSerializer JsonSerializer { get; private set; } - protected IAuthorizationContext AuthorizationContext { get; private set; } - protected IImageProcessor ImageProcessor { get; private set; } - protected INetworkManager NetworkManager { get; private set; } - protected IEnvironmentInfo EnvironmentInfo { get; private set; } - - public Task<object> Get(GetUniversalAudioStream request) - { - return GetUniversalStream(request, false); - } - - public Task<object> Head(GetUniversalAudioStream request) - { - return GetUniversalStream(request, true); - } - - private DeviceProfile GetDeviceProfile(GetUniversalAudioStream request) - { - var deviceProfile = new DeviceProfile(); - - var directPlayProfiles = new List<DirectPlayProfile>(); - - var containers = (request.Container ?? string.Empty).Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - foreach (var container in containers) - { - var parts = container.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - - var audioCodecs = parts.Length == 1 ? null : string.Join(",", parts.Skip(1).ToArray()); - - directPlayProfiles.Add(new DirectPlayProfile - { - Type = DlnaProfileType.Audio, - Container = parts[0], - AudioCodec = audioCodecs - }); - } - - deviceProfile.DirectPlayProfiles = directPlayProfiles.ToArray(); - - deviceProfile.TranscodingProfiles = new[] - { - new TranscodingProfile - { - Type = DlnaProfileType.Audio, - Context = EncodingContext.Streaming, - Container = request.TranscodingContainer, - AudioCodec = request.AudioCodec, - Protocol = request.TranscodingProtocol, - BreakOnNonKeyFrames = request.BreakOnNonKeyFrames, - MaxAudioChannels = request.TranscodingAudioChannels.HasValue ? request.TranscodingAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : null - } - }; - - var codecProfiles = new List<CodecProfile>(); - var conditions = new List<ProfileCondition>(); - - if (request.MaxAudioSampleRate.HasValue) - { - // codec profile - conditions.Add(new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - IsRequired = false, - Property = ProfileConditionValue.AudioSampleRate, - Value = request.MaxAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) - }); - } - - if (request.MaxAudioBitDepth.HasValue) - { - // codec profile - conditions.Add(new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - IsRequired = false, - Property = ProfileConditionValue.AudioBitDepth, - Value = request.MaxAudioBitDepth.Value.ToString(CultureInfo.InvariantCulture) - }); - } - - if (request.MaxAudioChannels.HasValue) - { - // codec profile - conditions.Add(new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - IsRequired = false, - Property = ProfileConditionValue.AudioChannels, - Value = request.MaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) - }); - } - - if (conditions.Count > 0) - { - // codec profile - codecProfiles.Add(new CodecProfile - { - Type = CodecType.Audio, - Container = request.Container, - Conditions = conditions.ToArray() - }); - } - - deviceProfile.CodecProfiles = codecProfiles.ToArray(); - - return deviceProfile; - } - - private async Task<object> GetUniversalStream(GetUniversalAudioStream request, bool isHeadRequest) - { - var deviceProfile = GetDeviceProfile(request); - - AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId; - - var mediaInfoService = new MediaInfoService(MediaSourceManager, DeviceManager, LibraryManager, ServerConfigurationManager, NetworkManager, MediaEncoder, UserManager, JsonSerializer, AuthorizationContext) - { - Request = Request - }; - - var playbackInfoResult = await mediaInfoService.GetPlaybackInfo(new GetPostedPlaybackInfo - { - Id = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MaxStreamingBitrate = request.MaxStreamingBitrate, - StartTimeTicks = request.StartTimeTicks, - UserId = request.UserId, - DeviceProfile = deviceProfile, - MediaSourceId = request.MediaSourceId - - }).ConfigureAwait(false); - - var mediaSource = playbackInfoResult.MediaSources[0]; - - if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http) - { - if (request.EnableRedirection) - { - if (mediaSource.IsRemote && request.EnableRemoteMedia) - { - return ResultFactory.GetRedirectResult(mediaSource.Path); - } - } - } - - var isStatic = mediaSource.SupportsDirectStream; - - if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) - { - var service = new DynamicHlsService(ServerConfigurationManager, - UserManager, - LibraryManager, - IsoManager, - MediaEncoder, - FileSystem, - DlnaManager, - SubtitleEncoder, - DeviceManager, - MediaSourceManager, - ZipClient, - JsonSerializer, - AuthorizationContext, - NetworkManager) - { - Request = Request - }; - - var transcodingProfile = deviceProfile.TranscodingProfiles[0]; - - var newRequest = new GetMasterHlsAudioPlaylist - { - AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)), - AudioCodec = transcodingProfile.AudioCodec, - Container = ".m3u8", - DeviceId = request.DeviceId, - Id = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MediaSourceId = mediaSource.Id, - PlaySessionId = playbackInfoResult.PlaySessionId, - StartTimeTicks = request.StartTimeTicks, - Static = isStatic, - SegmentContainer = request.TranscodingContainer, - AudioSampleRate = request.MaxAudioSampleRate, - MaxAudioBitDepth = request.MaxAudioBitDepth, - BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames, - TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()) - }; - - if (isHeadRequest) - { - return await service.Head(newRequest).ConfigureAwait(false); - } - return await service.Get(newRequest).ConfigureAwait(false); - } - else - { - var service = new AudioService(ServerConfigurationManager, - UserManager, - LibraryManager, - IsoManager, - MediaEncoder, - FileSystem, - DlnaManager, - SubtitleEncoder, - DeviceManager, - MediaSourceManager, - ZipClient, - JsonSerializer, - AuthorizationContext, - ImageProcessor, - EnvironmentInfo) - { - Request = Request - }; - - var newRequest = new GetAudioStream - { - AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)), - AudioCodec = request.AudioCodec, - Container = isStatic ? null : ("." + mediaSource.TranscodingContainer), - DeviceId = request.DeviceId, - Id = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MediaSourceId = mediaSource.Id, - PlaySessionId = playbackInfoResult.PlaySessionId, - StartTimeTicks = request.StartTimeTicks, - Static = isStatic, - AudioSampleRate = request.MaxAudioSampleRate, - MaxAudioBitDepth = request.MaxAudioBitDepth, - TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()) - }; - - if (isHeadRequest) - { - return await service.Head(newRequest).ConfigureAwait(false); - } - return await service.Get(newRequest).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index 9f37bb70a..aef16e442 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.Querying; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api { @@ -192,8 +193,10 @@ namespace MediaBrowser.Api var dtoOptions = GetDtoOptions(_authContext, request); - var dtos = (await _dtoService.GetBaseItemDtos(items.Select(i => i.Item2), dtoOptions, user).ConfigureAwait(false)) - .ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(items.Select(i => i.Item2), dtoOptions, user) + .ConfigureAwait(false)); + var dtos = returnList + .ToArray(returnList.Count); var index = 0; foreach (var item in dtos) diff --git a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs index 9c3dde6a4..e137ffc46 100644 --- a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs +++ b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs @@ -458,7 +458,7 @@ namespace MediaBrowser.Api.Reports break; case HeaderMetadata.Network: - option.Column = (i, r) => this.GetListAsString(i.Studios); + option.Column = (i, r) => this.GetListAsString(i.Studios.ToList()); option.ItemID = (i) => this.GetStudioID(i.Studios.FirstOrDefault()); option.Header.ItemViewType = ItemViewType.ItemByNameDetails; option.Header.SortField = "Studio,SortName"; diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index ad3a84aaf..77e29d6cc 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -159,9 +159,9 @@ namespace MediaBrowser.Api IncludeStudios = request.IncludeStudios, StartIndex = request.StartIndex, UserId = request.UserId, - IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(), - ExcludeItemTypes = (request.ExcludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(), - MediaTypes = (request.MediaTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(), + IncludeItemTypes = ApiEntryPoint.Split(request.IncludeItemTypes, ',', true), + ExcludeItemTypes = ApiEntryPoint.Split(request.ExcludeItemTypes, ',', true), + MediaTypes = ApiEntryPoint.Split(request.MediaTypes, ',', true), ParentId = request.ParentId, IsKids = request.IsKids, diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 358d09c18..fe40ceeeb 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -477,7 +477,7 @@ namespace MediaBrowser.Api.Session { var command = new PlayRequest { - ItemIds = request.ItemIds.Split(',').ToArray(), + ItemIds = request.ItemIds.Split(','), PlayCommand = request.PlayCommand, StartPositionTicks = request.StartPositionTicks diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 93aad0ef0..a4b14d2d4 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api { @@ -80,7 +81,7 @@ namespace MediaBrowser.Api var query = new InternalItemsQuery(user) { - IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(), + IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(includeTypes.Length), Recursive = true, DtoOptions = dtoOptions }; @@ -107,7 +108,7 @@ namespace MediaBrowser.Api return new QueryResult<BaseItemDto> { - Items = dtos.ToArray(), + Items = dtos.ToArray(dtos.Count), TotalRecordCount = items.Count }; diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs index 99411ffdc..2456dd6c0 100644 --- a/MediaBrowser.Api/SuggestionsService.cs +++ b/MediaBrowser.Api/SuggestionsService.cs @@ -8,6 +8,7 @@ using System; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api { @@ -71,7 +72,7 @@ namespace MediaBrowser.Api return new QueryResult<BaseItemDto> { TotalRecordCount = result.TotalRecordCount, - Items = dtoList.ToArray() + Items = dtoList.ToArray(dtoList.Count) }; } diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs index a9cec0914..cbff7cc2e 100644 --- a/MediaBrowser.Api/System/SystemService.cs +++ b/MediaBrowser.Api/System/SystemService.cs @@ -118,12 +118,11 @@ namespace MediaBrowser.Api.System public object Get(GetServerLogs request) { - List<FileSystemMetadata> files; + IEnumerable<FileSystemMetadata> files; try { - files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath, new[] { ".txt" }, true, false) - .ToList(); + files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath, new[] { ".txt" }, true, false); } catch (IOException) { diff --git a/MediaBrowser.Api/TestService.cs b/MediaBrowser.Api/TestService.cs deleted file mode 100644 index 5340b816c..000000000 --- a/MediaBrowser.Api/TestService.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api -{ - [Route("/Test/String", "GET")] - public class GetString - { - } - - [Route("/Test/OptimizedString", "GET")] - public class GetOptimizedString - { - } - - [Route("/Test/Bytes", "GET")] - public class GetBytes - { - } - - [Route("/Test/OptimizedBytes", "GET")] - public class GetOptimizedBytes - { - } - - [Route("/Test/Stream", "GET")] - public class GetStream - { - } - - [Route("/Test/OptimizedStream", "GET")] - public class GetOptimizedStream - { - } - - [Route("/Test/BytesWithContentType", "GET")] - public class GetBytesWithContentType - { - } - - public class TestService : BaseApiService - { - public object Get(GetString request) - { - return "Welcome to Emby!"; - } - public object Get(GetOptimizedString request) - { - return ToOptimizedResult("Welcome to Emby!"); - } - public object Get(GetBytes request) - { - return Encoding.UTF8.GetBytes("Welcome to Emby!"); - } - public object Get(GetOptimizedBytes request) - { - return ToOptimizedResult(Encoding.UTF8.GetBytes("Welcome to Emby!")); - } - public object Get(GetBytesWithContentType request) - { - return ApiEntryPoint.Instance.ResultFactory.GetResult(Encoding.UTF8.GetBytes("Welcome to Emby!"), "text/html"); - } - public object Get(GetStream request) - { - return new MemoryStream(Encoding.UTF8.GetBytes("Welcome to Emby!")); - } - public object Get(GetOptimizedStream request) - { - return ToOptimizedResult(new MemoryStream(Encoding.UTF8.GetBytes("Welcome to Emby!"))); - } - } -} diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 3f00f8ecf..891221d41 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Threading.Tasks; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api { @@ -326,11 +327,13 @@ namespace MediaBrowser.Api SimilarTo = item, DtoOptions = dtoOptions - }).ToList(); + }); + + var returnList = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)); var result = new QueryResult<BaseItemDto> { - Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(), + Items = returnList.ToArray(returnList.Count), TotalRecordCount = itemsResult.Count }; @@ -360,9 +363,10 @@ namespace MediaBrowser.Api Recursive = true, DtoOptions = options - }).ToList(); + }); - var returnItems = (await _dtoService.GetBaseItemDtos(itemsResult, options, user).ConfigureAwait(false)).ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(itemsResult, options, user).ConfigureAwait(false)); + var returnItems = returnList.ToArray(returnList.Count); var result = new ItemsResult { @@ -394,7 +398,8 @@ namespace MediaBrowser.Api var user = _userManager.GetUserById(request.UserId); - var returnItems = (await _dtoService.GetBaseItemDtos(result.Items, options, user).ConfigureAwait(false)).ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(result.Items, options, user).ConfigureAwait(false)); + var returnItems = returnList.ToArray(returnList.Count); return ToOptimizedSerializedResultUsingCache(new ItemsResult { @@ -449,8 +454,8 @@ namespace MediaBrowser.Api var dtoOptions = GetDtoOptions(_authContext, request); - var returnItems = (await _dtoService.GetBaseItemDtos(seasons, dtoOptions, user).ConfigureAwait(false)) - .ToArray(); + var returnList = (await _dtoService.GetBaseItemDtos(seasons, dtoOptions, user).ConfigureAwait(false)); + var returnItems = returnList.ToArray(returnList.Count); return new ItemsResult { @@ -556,8 +561,8 @@ namespace MediaBrowser.Api var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit); - var dtos = (await _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user).ConfigureAwait(false)) - .ToArray(); + var returnDtos = (await _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user).ConfigureAwait(false)); + var dtos = returnDtos.ToArray(returnDtos.Count); return new ItemsResult { diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 24d0a7d52..30e64d89d 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -10,6 +10,7 @@ using System.Linq; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api.UserLibrary { @@ -210,7 +211,7 @@ namespace MediaBrowser.Api.UserLibrary return new ItemsResult { - Items = dtos.ToArray(), + Items = dtos.ToArray(result.Items.Length), TotalRecordCount = result.TotalRecordCount }; } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 3415d01f1..074c1b60a 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -461,7 +461,7 @@ namespace MediaBrowser.Api.UserLibrary /// Gets the image types. /// </summary> /// <returns>IEnumerable{ImageType}.</returns> - public IEnumerable<ImageType> GetImageTypes() + public ImageType[] GetImageTypes() { var val = ImageTypes; @@ -470,7 +470,7 @@ namespace MediaBrowser.Api.UserLibrary return new ImageType[] { }; } - return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)); + return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray(); } /// <summary> diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 01e1ce769..b9231629b 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api.UserLibrary { @@ -127,7 +128,7 @@ namespace MediaBrowser.Api.UserLibrary return new ItemsResult { TotalRecordCount = result.TotalRecordCount, - Items = dtoList.ToArray() + Items = dtoList.ToArray(dtoList.Count) }; } @@ -238,8 +239,8 @@ namespace MediaBrowser.Api.UserLibrary PersonIds = request.GetPersonIds(), PersonTypes = request.GetPersonTypes(), Years = request.GetYears(), - ImageTypes = request.GetImageTypes().ToArray(), - VideoTypes = request.GetVideoTypes().ToArray(), + ImageTypes = request.GetImageTypes(), + VideoTypes = request.GetVideoTypes(), AdjacentTo = request.AdjacentTo, ItemIds = request.GetItemIds(), MinPlayers = request.MinPlayers, diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs deleted file mode 100644 index 98b4a5d5d..000000000 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ /dev/null @@ -1,451 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Session; -using System; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class MarkPlayedItem - /// </summary> - [Route("/Users/{UserId}/PlayedItems/{Id}", "POST", Summary = "Marks an item as played")] - public class MarkPlayedItem : IReturn<UserItemDataDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } - - [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any). Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string DatePlayed { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// <summary> - /// Class MarkUnplayedItem - /// </summary> - [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE", Summary = "Marks an item as unplayed")] - public class MarkUnplayedItem : IReturn<UserItemDataDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Sessions/Playing", "POST", Summary = "Reports playback has started within a session")] - public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid - { - } - - [Route("/Sessions/Playing/Progress", "POST", Summary = "Reports playback progress within a session")] - public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid - { - } - - [Route("/Sessions/Playing/Ping", "POST", Summary = "Pings a playback session")] - public class PingPlaybackSession : IReturnVoid - { - [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlaySessionId { get; set; } - } - - [Route("/Sessions/Playing/Stopped", "POST", Summary = "Reports playback has stopped within a session")] - public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid - { - } - - /// <summary> - /// Class OnPlaybackStart - /// </summary> - [Route("/Users/{UserId}/PlayingItems/{Id}", "POST", Summary = "Reports that a user has begun playing an item")] - public class OnPlaybackStart : IReturnVoid - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MediaSourceId { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes. - /// </summary> - /// <value><c>true</c> if likes; otherwise, <c>false</c>.</value> - [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool CanSeek { get; set; } - - [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? AudioStreamIndex { get; set; } - - [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? SubtitleStreamIndex { get; set; } - - [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public PlayMethod PlayMethod { get; set; } - - [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string LiveStreamId { get; set; } - - [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlaySessionId { get; set; } - } - - /// <summary> - /// Class OnPlaybackProgress - /// </summary> - [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST", Summary = "Reports a user's playback progress")] - public class OnPlaybackProgress : IReturnVoid - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MediaSourceId { get; set; } - - /// <summary> - /// Gets or sets the position ticks. - /// </summary> - /// <value>The position ticks.</value> - [ApiMember(Name = "PositionTicks", Description = "Optional. The current position, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public long? PositionTicks { get; set; } - - [ApiMember(Name = "IsPaused", Description = "Indicates if the player is paused.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool IsPaused { get; set; } - - [ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool IsMuted { get; set; } - - [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? AudioStreamIndex { get; set; } - - [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? SubtitleStreamIndex { get; set; } - - [ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? VolumeLevel { get; set; } - - [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public PlayMethod PlayMethod { get; set; } - - [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string LiveStreamId { get; set; } - - [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlaySessionId { get; set; } - - [ApiMember(Name = "RepeatMode", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public RepeatMode RepeatMode { get; set; } - } - - /// <summary> - /// Class OnPlaybackStopped - /// </summary> - [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE", Summary = "Reports that a user has stopped playing an item")] - public class OnPlaybackStopped : IReturnVoid - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "NextMediaType", Description = "The next media type that will play", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string NextMediaType { get; set; } - - /// <summary> - /// Gets or sets the position ticks. - /// </summary> - /// <value>The position ticks.</value> - [ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")] - public long? PositionTicks { get; set; } - - [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string LiveStreamId { get; set; } - - [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlaySessionId { get; set; } - } - - [Authenticated] - public class PlaystateService : BaseApiService - { - private readonly IUserManager _userManager; - private readonly IUserDataManager _userDataRepository; - private readonly ILibraryManager _libraryManager; - private readonly ISessionManager _sessionManager; - private readonly ISessionContext _sessionContext; - private readonly IAuthorizationContext _authContext; - - public PlaystateService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, ISessionManager sessionManager, ISessionContext sessionContext, IAuthorizationContext authContext) - { - _userManager = userManager; - _userDataRepository = userDataRepository; - _libraryManager = libraryManager; - _sessionManager = sessionManager; - _sessionContext = sessionContext; - _authContext = authContext; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public async Task<object> Post(MarkPlayedItem request) - { - var result = await MarkPlayed(request).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - private async Task<UserItemDataDto> MarkPlayed(MarkPlayedItem request) - { - var user = _userManager.GetUserById(request.UserId); - - DateTime? datePlayed = null; - - if (!string.IsNullOrEmpty(request.DatePlayed)) - { - datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); - } - - var session = await GetSession(_sessionContext).ConfigureAwait(false); - - var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); - - foreach (var additionalUserInfo in session.AdditionalUsers) - { - var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId); - - await UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed).ConfigureAwait(false); - } - - return dto; - } - - private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId) - { - if (method == PlayMethod.Transcode) - { - var job = string.IsNullOrWhiteSpace(playSessionId) ? null : ApiEntryPoint.Instance.GetTranscodingJob(playSessionId); - if (job == null) - { - return PlayMethod.DirectPlay; - } - } - - return method; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(OnPlaybackStart request) - { - Post(new ReportPlaybackStart - { - CanSeek = request.CanSeek, - ItemId = request.Id, - MediaSourceId = request.MediaSourceId, - AudioStreamIndex = request.AudioStreamIndex, - SubtitleStreamIndex = request.SubtitleStreamIndex, - PlayMethod = request.PlayMethod, - PlaySessionId = request.PlaySessionId, - LiveStreamId = request.LiveStreamId - }); - } - - public void Post(ReportPlaybackStart request) - { - request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId); - - request.SessionId = GetSession(_sessionContext).Result.Id; - - var task = _sessionManager.OnPlaybackStart(request); - - Task.WaitAll(task); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(OnPlaybackProgress request) - { - Post(new ReportPlaybackProgress - { - ItemId = request.Id, - PositionTicks = request.PositionTicks, - IsMuted = request.IsMuted, - IsPaused = request.IsPaused, - MediaSourceId = request.MediaSourceId, - AudioStreamIndex = request.AudioStreamIndex, - SubtitleStreamIndex = request.SubtitleStreamIndex, - VolumeLevel = request.VolumeLevel, - PlayMethod = request.PlayMethod, - PlaySessionId = request.PlaySessionId, - LiveStreamId = request.LiveStreamId, - RepeatMode = request.RepeatMode - }); - } - - public void Post(ReportPlaybackProgress request) - { - request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId); - - request.SessionId = GetSession(_sessionContext).Result.Id; - - var task = _sessionManager.OnPlaybackProgress(request); - - Task.WaitAll(task); - } - - public void Post(PingPlaybackSession request) - { - ApiEntryPoint.Instance.PingTranscodingJob(request.PlaySessionId, null); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(OnPlaybackStopped request) - { - Post(new ReportPlaybackStopped - { - ItemId = request.Id, - PositionTicks = request.PositionTicks, - MediaSourceId = request.MediaSourceId, - PlaySessionId = request.PlaySessionId, - LiveStreamId = request.LiveStreamId, - NextMediaType = request.NextMediaType - }); - } - - public void Post(ReportPlaybackStopped request) - { - Logger.Debug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty); - - if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) - { - ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); - } - - request.SessionId = GetSession(_sessionContext).Result.Id; - - var task = _sessionManager.OnPlaybackStopped(request); - - Task.WaitAll(task); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Delete(MarkUnplayedItem request) - { - var task = MarkUnplayed(request); - - return ToOptimizedResult(task.Result); - } - - private async Task<UserItemDataDto> MarkUnplayed(MarkUnplayedItem request) - { - var user = _userManager.GetUserById(request.UserId); - - var session = await GetSession(_sessionContext).ConfigureAwait(false); - - var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); - - foreach (var additionalUserInfo in session.AdditionalUsers) - { - var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId); - - await UpdatePlayedStatus(additionalUser, request.Id, false, null).ConfigureAwait(false); - } - - return dto; - } - - /// <summary> - /// Updates the played status. - /// </summary> - /// <param name="user">The user.</param> - /// <param name="itemId">The item id.</param> - /// <param name="wasPlayed">if set to <c>true</c> [was played].</param> - /// <param name="datePlayed">The date played.</param> - /// <returns>Task.</returns> - private async Task<UserItemDataDto> UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed) - { - var item = _libraryManager.GetItemById(itemId); - - if (wasPlayed) - { - await item.MarkPlayed(user, datePlayed, true).ConfigureAwait(false); - } - else - { - await item.MarkUnplayed(user).ConfigureAwait(false); - } - - return _userDataRepository.GetUserDataDto(item, user); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 4bb3de882..ee1162687 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Api.UserLibrary { @@ -312,7 +313,7 @@ namespace MediaBrowser.Api.UserLibrary var list = _userViewManager.GetLatestItems(new LatestItemsQuery { GroupItems = request.GroupItems, - IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(), + IncludeItemTypes = ApiEntryPoint.Split(request.IncludeItemTypes, ',', true), IsPlayed = request.IsPlayed, Limit = request.Limit, ParentId = request.ParentId, @@ -486,8 +487,7 @@ namespace MediaBrowser.Api.UserLibrary var dtoOptions = GetDtoOptions(_authContext, request); - var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)) - .ToArray(); + var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray(); var result = new ItemsResult { diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index e8ebbdb70..5520737b4 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -80,9 +80,9 @@ namespace MediaBrowser.Controller.Entities public List<string> PhysicalLocationsList { get; set; } - protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService) + protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) { - return CreateResolveArgs(directoryService, true).FileSystemChildren; + return CreateResolveArgs(directoryService, true).FileSystemChildren.ToArray(); } private List<Guid> _childrenIds = null; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 7a37b2e02..559806ac4 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.Entities.Audio public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken) { - var items = GetRecursiveChildren().ToList(); + var items = GetRecursiveChildren(); var songs = items.OfType<Audio>().ToList(); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 7c375b937..16ad00827 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -41,17 +41,22 @@ namespace MediaBrowser.Controller.Entities /// </summary> public abstract class BaseItem : IHasMetadata, IHasLookupInfo<ItemLookupInfo> { + protected static Guid[] EmptyGuidArray = new Guid[] { }; + protected static MetadataFields[] EmptyMetadataFieldsArray = new MetadataFields[] { }; + protected static string[] EmptyStringArray = new string[] { }; + protected static ItemImageInfo[] EmptyItemImageInfoArray = new ItemImageInfo[] { }; + protected BaseItem() { - ThemeSongIds = new List<Guid>(); - ThemeVideoIds = new List<Guid>(); - Tags = new List<string>(); + ThemeSongIds = EmptyGuidArray; + ThemeVideoIds = EmptyGuidArray; + Tags = EmptyStringArray; Genres = new List<string>(); - Studios = new List<string>(); + Studios = EmptyStringArray; ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - LockedFields = new List<MetadataFields>(); - ImageInfos = new List<ItemImageInfo>(); - ProductionLocations = new List<string>(); + LockedFields = EmptyMetadataFieldsArray; + ImageInfos = EmptyItemImageInfoArray; + ProductionLocations = EmptyStringArray; } public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; @@ -73,9 +78,9 @@ namespace MediaBrowser.Controller.Entities public static string ThemeVideosFolderName = "backdrops"; [IgnoreDataMember] - public List<Guid> ThemeSongIds { get; set; } + public Guid[] ThemeSongIds { get; set; } [IgnoreDataMember] - public List<Guid> ThemeVideoIds { get; set; } + public Guid[] ThemeVideoIds { get; set; } [IgnoreDataMember] public string PreferredMetadataCountryCode { get; set; } @@ -89,7 +94,7 @@ namespace MediaBrowser.Controller.Entities public string Tagline { get; set; } [IgnoreDataMember] - public List<ItemImageInfo> ImageInfos { get; set; } + public ItemImageInfo[] ImageInfos { get; set; } [IgnoreDataMember] public bool IsVirtualItem { get; set; } @@ -547,7 +552,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The locked fields.</value> [IgnoreDataMember] - public List<MetadataFields> LockedFields { get; set; } + public MetadataFields[] LockedFields { get; set; } /// <summary> /// Gets the type of the media. @@ -842,7 +847,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The studios.</value> [IgnoreDataMember] - public List<string> Studios { get; set; } + public string[] Studios { get; set; } /// <summary> /// Gets or sets the genres. @@ -856,10 +861,10 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The tags.</value> [IgnoreDataMember] - public List<string> Tags { get; set; } + public string[] Tags { get; set; } [IgnoreDataMember] - public List<string> ProductionLocations { get; set; } + public string[] ProductionLocations { get; set; } /// <summary> /// Gets or sets the home page URL. @@ -968,11 +973,11 @@ namespace MediaBrowser.Controller.Entities /// Loads the theme songs. /// </summary> /// <returns>List{Audio.Audio}.</returns> - private static IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) + private static Audio.Audio[] LoadThemeSongs(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { var files = fileSystemChildren.Where(i => i.IsDirectory) .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) - .SelectMany(i => directoryService.GetFiles(i.FullName)) + .SelectMany(i => FileSystem.GetFiles(i.FullName)) .ToList(); // Support plex/xbmc convention @@ -997,18 +1002,18 @@ namespace MediaBrowser.Controller.Entities return audio; // Sort them so that the list can be easily compared for changes - }).OrderBy(i => i.Path).ToList(); + }).OrderBy(i => i.Path).ToArray(); } /// <summary> /// Loads the video backdrops. /// </summary> /// <returns>List{Video}.</returns> - private static IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) + private static Video[] LoadThemeVideos(IEnumerable<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) { var files = fileSystemChildren.Where(i => i.IsDirectory) .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase)) - .SelectMany(i => directoryService.GetFiles(i.FullName)); + .SelectMany(i => FileSystem.GetFiles(i.FullName)); return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) .OfType<Video>() @@ -1027,7 +1032,7 @@ namespace MediaBrowser.Controller.Entities return item; // Sort them so that the list can be easily compared for changes - }).OrderBy(i => i.Path).ToList(); + }).OrderBy(i => i.Path).ToArray(); } public Task RefreshMetadata(CancellationToken cancellationToken) @@ -1153,7 +1158,7 @@ namespace MediaBrowser.Controller.Entities return themeSongsChanged || themeVideosChanged || localTrailersChanged; } - protected virtual IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService) + protected virtual FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) { var path = ContainingFolderPath; @@ -1179,9 +1184,9 @@ namespace MediaBrowser.Controller.Entities private async Task<bool> RefreshThemeVideos(BaseItem item, MetadataRefreshOptions options, IEnumerable<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { - var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService).ToList(); + var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService); - var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList(); + var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToArray(newThemeVideos.Length); var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds); @@ -1210,8 +1215,8 @@ namespace MediaBrowser.Controller.Entities /// </summary> private async Task<bool> RefreshThemeSongs(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) { - var newThemeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService).ToList(); - var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList(); + var newThemeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService); + var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToArray(newThemeSongs.Length); var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds); @@ -1713,12 +1718,28 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException("name"); } - if (!Studios.Contains(name, StringComparer.OrdinalIgnoreCase)) + var current = Studios; + + if (!current.Contains(name, StringComparer.OrdinalIgnoreCase)) { - Studios.Add(name); + if (current.Length == 0) + { + Studios = new[] { name }; + } + else + { + var list = current.ToArray(current.Length + 1); + list[list.Length - 1] = name; + Studios = list; + } } } + public void SetStudios(IEnumerable<string> names) + { + Studios = names.Distinct().ToArray(); + } + /// <summary> /// Adds a genre to the item /// </summary> @@ -1838,10 +1859,18 @@ namespace MediaBrowser.Controller.Entities if (existingImage != null) { - ImageInfos.Remove(existingImage); + existingImage.Path = image.Path; + existingImage.DateModified = image.DateModified; + existingImage.IsPlaceholder = image.IsPlaceholder; } - ImageInfos.Add(image); + else + { + var currentCount = ImageInfos.Length; + var newList = ImageInfos.ToArray(currentCount + 1); + newList[currentCount] = image; + ImageInfos = newList; + } } public void SetImagePath(ImageType type, int index, FileSystemMetadata file) @@ -1855,7 +1884,10 @@ namespace MediaBrowser.Controller.Entities if (image == null) { - ImageInfos.Add(GetImageInfo(file, type)); + var currentCount = ImageInfos.Length; + var newList = ImageInfos.ToArray(currentCount + 1); + newList[currentCount] = GetImageInfo(file, type); + ImageInfos = newList; } else { @@ -1896,7 +1928,12 @@ namespace MediaBrowser.Controller.Entities public void RemoveImage(ItemImageInfo image) { - ImageInfos.Remove(image); + RemoveImages(new List<ItemImageInfo> { image }); + } + + public void RemoveImages(List<ItemImageInfo> deletedImages) + { + ImageInfos = ImageInfos.Except(deletedImages).ToArray(); } public virtual Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) @@ -1913,7 +1950,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.IsLocalFile) .Select(i => FileSystem.GetDirectoryName(i.Path)) .Distinct(StringComparer.OrdinalIgnoreCase) - .SelectMany(directoryService.GetFilePaths) + .SelectMany(i => FileSystem.GetFilePaths(i)) .ToList(); var deletedImages = ImageInfos @@ -1922,7 +1959,7 @@ namespace MediaBrowser.Controller.Entities if (deletedImages.Count > 0) { - ImageInfos = ImageInfos.Except(deletedImages).ToList(); + ImageInfos = ImageInfos.Except(deletedImages).ToArray(); } return deletedImages.Count > 0; @@ -2042,10 +2079,25 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.IsLocalFile && !newImagePaths.Contains(i.Path, StringComparer.OrdinalIgnoreCase) && !FileSystem.FileExists(i.Path)) .ToList(); - ImageInfos = ImageInfos.Except(deleted).ToList(); + if (deleted.Count > 0) + { + ImageInfos = ImageInfos.Except(deleted).ToArray(); + } } - ImageInfos.AddRange(newImageList.Select(i => GetImageInfo(i, imageType))); + if (newImageList.Count > 0) + { + var currentCount = ImageInfos.Length; + var newList = ImageInfos.ToArray(currentCount + newImageList.Count); + + foreach (var image in newImageList) + { + newList[currentCount] = GetImageInfo(image, imageType); + currentCount++; + } + + ImageInfos = newList; + } return newImageList.Count > 0; } @@ -2086,7 +2138,7 @@ namespace MediaBrowser.Controller.Entities var extensions = new[] { ".nfo", ".xml", ".srt" }.ToList(); extensions.AddRange(SupportedImageExtensionsList); - return FileSystem.GetFiles(FileSystem.GetDirectoryName(Path), extensions.ToArray(), false, false) + return FileSystem.GetFiles(FileSystem.GetDirectoryName(Path), extensions.ToArray(extensions.Count), false, false) .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.FullName).StartsWith(filename, StringComparison.OrdinalIgnoreCase)) .ToList(); } @@ -2246,12 +2298,12 @@ namespace MediaBrowser.Controller.Entities if (!item.Studios.SequenceEqual(ownedItem.Studios, StringComparer.Ordinal)) { newOptions.ForceSave = true; - ownedItem.Studios = item.Studios.ToList(); + ownedItem.Studios = item.Studios; } if (!item.ProductionLocations.SequenceEqual(ownedItem.ProductionLocations, StringComparer.Ordinal)) { newOptions.ForceSave = true; - ownedItem.ProductionLocations = item.ProductionLocations.ToList(); + ownedItem.ProductionLocations = item.ProductionLocations; } if (item.CommunityRating != ownedItem.CommunityRating) { @@ -2310,7 +2362,9 @@ namespace MediaBrowser.Controller.Entities public string GetEtag(User user) { - return string.Join("|", GetEtagValues(user).ToArray()).GetMD5().ToString("N"); + var list = GetEtagValues(user); + + return string.Join("|", list.ToArray(list.Count)).GetMD5().ToString("N"); } protected virtual List<string> GetEtagValues(User user) diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index d88b7da34..9e9624aae 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -165,9 +165,9 @@ namespace MediaBrowser.Controller.Entities public List<string> PhysicalLocationsList { get; set; } public List<Guid> PhysicalFolderIds { get; set; } - protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService) + protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) { - return CreateResolveArgs(directoryService, true).FileSystemChildren; + return CreateResolveArgs(directoryService, true).FileSystemChildren.ToArray(); } private bool _requiresRefresh; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index a3f097f24..80a1b5e2a 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -20,6 +20,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.Channels; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Controller.Entities { @@ -791,7 +792,7 @@ namespace MediaBrowser.Controller.Entities query.StartIndex = null; query.Limit = null; - var itemsList = LibraryManager.GetItemList(query); + IEnumerable<BaseItem> itemsList = LibraryManager.GetItemList(query); var user = query.User; if (user != null) @@ -970,7 +971,7 @@ namespace MediaBrowser.Controller.Entities return GetItemsInternal(query); } - public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query) + public BaseItem[] GetItemList(InternalItemsQuery query) { query.EnableTotalRecordCount = false; @@ -983,9 +984,9 @@ namespace MediaBrowser.Controller.Entities var ids = query.ItemIds.ToList(); // Try to preserve order - result = result.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray(); + return result.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray(); } - return result; + return result.ToArray(result.Count); } return GetItemsInternal(query).Items; diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index deae692cc..aec67c3db 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.Entities { public Game() { - MultiPartGameFiles = new List<string>(); + MultiPartGameFiles = EmptyStringArray; RemoteTrailers = new List<MediaUrl>(); LocalTrailerIds = new List<Guid>(); RemoteTrailerIds = new List<Guid>(); @@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Holds the paths to the game files in the event this is a multipart game /// </summary> - public List<string> MultiPartGameFiles { get; set; } + public string[] MultiPartGameFiles { get; set; } public override List<string> GetUserDataKeys() { diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index a76f2fec3..59d9bd9f9 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -100,7 +100,7 @@ namespace MediaBrowser.Controller.Entities /// Gets the locked fields. /// </summary> /// <value>The locked fields.</value> - List<MetadataFields> LockedFields { get; } + MetadataFields[] LockedFields { get; } /// <summary> /// Gets the images. @@ -244,6 +244,8 @@ namespace MediaBrowser.Controller.Entities /// <param name="image">The image.</param> void RemoveImage(ItemImageInfo image); + void RemoveImages(List<ItemImageInfo> images); + /// <summary> /// Updates to repository. /// </summary> @@ -263,7 +265,7 @@ namespace MediaBrowser.Controller.Entities int? ProductionYear { get; set; } - List<string> Tags { get; set; } + string[] Tags { get; set; } } public static class HasMetadataExtensions diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 229e63f13..851e20520 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -231,7 +231,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <returns>List<Guid>.</returns> public List<Guid> GetTrailerIds() { - var list = LocalTrailerIds.ToList(); + var list = LocalTrailerIds.ToList(LocalTrailerIds.Count); list.AddRange(RemoteTrailerIds); return list; } @@ -345,14 +345,13 @@ namespace MediaBrowser.Controller.Entities.TV query.IsVirtualUnaired = false; } - var allItems = LibraryManager.GetItemList(query).ToList(); + var allItems = LibraryManager.GetItemList(query); - var allSeriesEpisodes = allItems.OfType<Episode>().ToList(); + var allSeriesEpisodes = allItems.OfType<Episode>(); var allEpisodes = allItems.OfType<Season>() .SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes, options)) - .Reverse() - .ToList(); + .Reverse(); // Specials could appear twice based on above - once in season 0, once in the aired season // This depends on settings for that series @@ -365,20 +364,22 @@ namespace MediaBrowser.Controller.Entities.TV { // Refresh bottom up, children first, then the boxset // By then hopefully the movies within will have Tmdb collection values - var items = GetRecursiveChildren().ToList(); + var items = GetRecursiveChildren(); - var seasons = items.OfType<Season>().ToList(); - var otherItems = items.Except(seasons).ToList(); - - var totalItems = seasons.Count + otherItems.Count; + var totalItems = items.Count; var numComplete = 0; // Refresh current item await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); // Refresh seasons - foreach (var item in seasons) + foreach (var item in items) { + if (!(item is Season)) + { + continue; + } + cancellationToken.ThrowIfCancellationRequested(); await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); @@ -390,8 +391,13 @@ namespace MediaBrowser.Controller.Entities.TV } // Refresh episodes and other children - foreach (var item in otherItems) + foreach (var item in items) { + if ((item is Season)) + { + continue; + } + cancellationToken.ThrowIfCancellationRequested(); var skipItem = false; diff --git a/MediaBrowser.Controller/Entities/TagExtensions.cs b/MediaBrowser.Controller/Entities/TagExtensions.cs index 0e1df72cd..e5d8f35d9 100644 --- a/MediaBrowser.Controller/Entities/TagExtensions.cs +++ b/MediaBrowser.Controller/Entities/TagExtensions.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Controller.Entities { @@ -12,9 +14,21 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException("name"); } - if (!item.Tags.Contains(name, StringComparer.OrdinalIgnoreCase)) + var current = item.Tags; + + if (!current.Contains(name, StringComparer.OrdinalIgnoreCase)) { - item.Tags.Add(name); + if (current.Length == 0) + { + item.Tags = new[] { name }; + } + else + { + var list = current.ToArray(current.Length + 1); + list[list.Length - 1] = name; + + item.Tags = list; + } } } } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 91e24caeb..b7cf50ee3 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -1463,7 +1463,7 @@ namespace MediaBrowser.Controller.Entities { var filterValue = query.HasThemeSong.Value; - var themeCount = item.ThemeSongIds.Count; + var themeCount = item.ThemeSongIds.Length; var ok = filterValue ? themeCount > 0 : themeCount == 0; if (!ok) @@ -1476,7 +1476,7 @@ namespace MediaBrowser.Controller.Entities { var filterValue = query.HasThemeVideo.Value; - var themeCount = item.ThemeVideoIds.Count; + var themeCount = item.ThemeVideoIds.Length; var ok = filterValue ? themeCount > 0 : themeCount == 0; if (!ok) diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 044ecf9b1..585e2a478 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Controller.Entities { @@ -803,7 +804,7 @@ namespace MediaBrowser.Controller.Entities } } - return string.Join("/", terms.ToArray()); + return string.Join("/", terms.ToArray(terms.Count)); } } diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs index 97b778d0c..64a7610fe 100644 --- a/MediaBrowser.Controller/IO/FileData.cs +++ b/MediaBrowser.Controller/IO/FileData.cs @@ -13,6 +13,17 @@ namespace MediaBrowser.Controller.IO /// </summary> public static class FileData { + private static Dictionary<string, FileSystemMetadata> GetFileSystemDictionary(FileSystemMetadata[] list) + { + var dict = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase); + + foreach (var file in list) + { + dict[file.FullName] = file; + } + return dict; + } + /// <summary> /// Gets the filtered file system entries. /// </summary> @@ -42,13 +53,13 @@ namespace MediaBrowser.Controller.IO throw new ArgumentNullException("args"); } + var entries = directoryService.GetFileSystemEntries(path); + if (!resolveShortcuts && flattenFolderDepth == 0) { - return directoryService.GetFileSystemDictionary(path); + return GetFileSystemDictionary(entries); } - var entries = directoryService.GetFileSystemEntries(path); - var dict = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase); foreach (var entry in entries) diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index bf2ebac84..025254d4b 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -185,13 +185,6 @@ namespace MediaBrowser.Controller.Library SortOrder sortOrder); /// <summary> - /// Ensure supplied item has only one instance throughout - /// </summary> - /// <param name="item">The item.</param> - /// <returns>The proper instance to the item</returns> - BaseItem GetOrAddByReferenceItem(BaseItem item); - - /// <summary> /// Gets the user root folder. /// </summary> /// <returns>UserRootFolder.</returns> @@ -527,14 +520,14 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="query">The query.</param> /// <returns>QueryResult<BaseItem>.</returns> - IEnumerable<BaseItem> GetItemList(InternalItemsQuery query); + List<BaseItem> GetItemList(InternalItemsQuery query); - IEnumerable<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent); + List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent); /// <summary> /// Gets the items. /// </summary> - IEnumerable<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents); + List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents); /// <summary> /// Gets the items result. diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs index 95ba671b4..535e6df7e 100644 --- a/MediaBrowser.Controller/Library/IMusicManager.cs +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -10,16 +10,16 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Gets the instant mix from song. /// </summary> - IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions); - + List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions); + /// <summary> /// Gets the instant mix from artist. /// </summary> - IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions); - + List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions); + /// <summary> /// Gets the instant mix from genre. /// </summary> - IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions); + List<BaseItem> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 288b30278..d6855b792 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -284,7 +284,7 @@ namespace MediaBrowser.Controller.LiveTv /// <summary> /// Gets the internal channels. /// </summary> - Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken); + Task<QueryResult<BaseItem>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken); /// <summary> /// Gets the internal recordings. diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs index b025011d7..81e294069 100644 --- a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using MediaBrowser.Model.Services; namespace MediaBrowser.Controller.Net @@ -53,8 +52,7 @@ namespace MediaBrowser.Controller.Net public IEnumerable<string> GetRoles() { - return (Roles ?? string.Empty).Split(',') - .Where(i => !string.IsNullOrWhiteSpace(i)); + return (Roles ?? string.Empty).Split(new []{ ',' }, StringSplitOptions.RemoveEmptyEntries); } } diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index b26b44258..3fc92fa2c 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Persistence /// </summary> /// <param name="itemId">The item id.</param> /// <returns>Task{IEnumerable{ItemReview}}.</returns> - IEnumerable<ItemReview> GetCriticReviews(Guid itemId); + List<ItemReview> GetCriticReviews(Guid itemId); /// <summary> /// Saves the critic reviews. diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 6d220f3a3..337bb23a4 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -13,10 +13,10 @@ namespace MediaBrowser.Controller.Providers public class DirectoryService : IDirectoryService { private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; + private readonly IFileSystem _fileSystem; - private readonly ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>> _cache = - new ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>>(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = + new ConcurrentDictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase); @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Providers public DirectoryService(ILogger logger, IFileSystem fileSystem) { _logger = logger; - _fileSystem = fileSystem; + _fileSystem = fileSystem; } public DirectoryService(IFileSystem fileSystem) @@ -32,28 +32,23 @@ namespace MediaBrowser.Controller.Providers { } - public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path) + public FileSystemMetadata[] GetFileSystemEntries(string path) { return GetFileSystemEntries(path, false); } - public Dictionary<string, FileSystemMetadata> GetFileSystemDictionary(string path) - { - return GetFileSystemDictionary(path, false); - } - - private Dictionary<string, FileSystemMetadata> GetFileSystemDictionary(string path, bool clearCache) + private FileSystemMetadata[] GetFileSystemEntries(string path, bool clearCache) { if (string.IsNullOrWhiteSpace(path)) { throw new ArgumentNullException("path"); } - Dictionary<string, FileSystemMetadata> entries; + FileSystemMetadata[] entries; if (clearCache) { - Dictionary<string, FileSystemMetadata> removed; + FileSystemMetadata[] removed; _cache.TryRemove(path, out removed); } @@ -62,37 +57,22 @@ namespace MediaBrowser.Controller.Providers { //_logger.Debug("Getting files for " + path); - entries = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase); - try { // using EnumerateFileSystemInfos doesn't handle reparse points (symlinks) - var list = _fileSystem.GetFileSystemEntries(path) - .ToList(); - - // Seeing dupes on some users file system for some reason - foreach (var item in list) - { - entries[item.FullName] = item; - } + entries = _fileSystem.GetFileSystemEntries(path).ToArray(); } catch (IOException) { + entries = new FileSystemMetadata[] { }; } - //var group = entries.ToLookup(i => _fileSystem.GetDirectoryName(i.FullName)).ToList(); - _cache.TryAdd(path, entries); } return entries; } - private IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool clearCache) - { - return GetFileSystemDictionary(path, clearCache).Values; - } - public IEnumerable<FileSystemMetadata> GetFiles(string path) { return GetFiles(path, false); @@ -103,16 +83,6 @@ namespace MediaBrowser.Controller.Providers return GetFileSystemEntries(path, clearCache).Where(i => !i.IsDirectory); } - public IEnumerable<string> GetFilePaths(string path) - { - return _fileSystem.GetFilePaths(path); - } - - public IEnumerable<string> GetFilePaths(string path, bool clearCache) - { - return _fileSystem.GetFilePaths(path); - } - public FileSystemMetadata GetFile(string path) { FileSystemMetadata file; @@ -133,10 +103,5 @@ namespace MediaBrowser.Controller.Providers return file; //return _fileSystem.GetFileInfo(path); } - - public IEnumerable<FileSystemMetadata> GetDirectories(string path) - { - return GetFileSystemEntries(path, false).Where(i => i.IsDirectory); - } } } diff --git a/MediaBrowser.Controller/Providers/IDirectoryService.cs b/MediaBrowser.Controller/Providers/IDirectoryService.cs index 1b203f32c..374703948 100644 --- a/MediaBrowser.Controller/Providers/IDirectoryService.cs +++ b/MediaBrowser.Controller/Providers/IDirectoryService.cs @@ -1,18 +1,12 @@ using System.Collections.Generic; - -using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.Providers { public interface IDirectoryService { - IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path); - IEnumerable<FileSystemMetadata> GetDirectories(string path); + FileSystemMetadata[] GetFileSystemEntries(string path); IEnumerable<FileSystemMetadata> GetFiles(string path); - IEnumerable<string> GetFilePaths(string path); - IEnumerable<string> GetFilePaths(string path, bool clearCache); FileSystemMetadata GetFile(string path); - Dictionary<string, FileSystemMetadata> GetFileSystemDictionary(string path); } }
\ No newline at end of file diff --git a/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs b/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs deleted file mode 100644 index db57dcbfd..000000000 --- a/MediaBrowser.LocalMetadata/Images/ImagesByNameImageProvider.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -using MediaBrowser.Model.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Providers; - -namespace MediaBrowser.LocalMetadata.Images -{ - public class ImagesByNameImageProvider : ILocalImageFileProvider, IHasOrder - { - private readonly IFileSystem _fileSystem; - private readonly IServerConfigurationManager _config; - - public ImagesByNameImageProvider(IFileSystem fileSystem, IServerConfigurationManager config) - { - _fileSystem = fileSystem; - _config = config; - } - - public string Name - { - get { return "Images By Name"; } - } - - public bool Supports(IHasMetadata item) - { - return item is CollectionFolder; - } - - public int Order - { - get - { - // Run after LocalImageProvider, and after CollectionFolderImageProvider - return 2; - } - } - - public List<LocalImageInfo> GetImages(IHasMetadata item, IDirectoryService directoryService) - { - var name = _fileSystem.GetValidFilename(item.Name); - - var path = Path.Combine(_config.ApplicationPaths.GeneralPath, name); - - try - { - return new LocalImageProvider(_fileSystem).GetImages(item, path, false, directoryService); - } - catch (IOException) - { - return new List<LocalImageInfo>(); - } - } - } -} diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 643c37427..1b61f079e 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -103,23 +103,7 @@ namespace MediaBrowser.LocalMetadata.Images public List<LocalImageInfo> GetImages(IHasMetadata item, IEnumerable<string> paths, bool arePathsInMediaFolders, IDirectoryService directoryService) { - IEnumerable<FileSystemMetadata> files; - - if (arePathsInMediaFolders) - { - files = paths.SelectMany(i => _fileSystem.GetFiles(i, BaseItem.SupportedImageExtensions, true, false)); - } - else - { - files = paths.SelectMany(directoryService.GetFiles) - .Where(i => - { - var ext = i.Extension; - - return !string.IsNullOrEmpty(ext) && - BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - }); - } + IEnumerable<FileSystemMetadata> files = paths.SelectMany(i => _fileSystem.GetFiles(i, BaseItem.SupportedImageExtensions, true, false)); files = files .OrderBy(i => BaseItem.SupportedImageExtensionsList.IndexOf(i.Extension ?? string.Empty)); diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 9a7371a66..0986ffdc2 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -39,7 +39,6 @@ <Compile Include="BaseXmlProvider.cs" /> <Compile Include="Images\CollectionFolderImageProvider.cs" /> <Compile Include="Images\EpisodeLocalImageProvider.cs" /> - <Compile Include="Images\ImagesByNameImageProvider.cs" /> <Compile Include="Images\InternalMetadataFolderImageProvider.cs" /> <Compile Include="Images\LocalImageProvider.cs" /> <Compile Include="Parsers\BaseItemXmlParser.cs" /> diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index c728e2ce2..d9bc35822 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -246,7 +246,7 @@ namespace MediaBrowser.LocalMetadata.Parsers var person = item as Person; if (person != null) { - person.ProductionLocations = new List<string> { val }; + person.ProductionLocations = new string[] { val }; } } @@ -267,13 +267,11 @@ namespace MediaBrowser.LocalMetadata.Parsers case "LockedFields": { - var fields = new List<MetadataFields>(); - var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { - var list = val.Split('|').Select(i => + item.LockedFields = val.Split('|').Select(i => { MetadataFields field; @@ -284,13 +282,9 @@ namespace MediaBrowser.LocalMetadata.Parsers return null; - }).Where(i => i.HasValue).Select(i => i.Value); - - fields.AddRange(list); + }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); } - item.LockedFields = fields; - break; } @@ -933,6 +927,8 @@ namespace MediaBrowser.LocalMetadata.Parsers reader.MoveToContent(); reader.Read(); + var tags = new List<string>(); + // Loop through each element while (!reader.EOF && reader.ReadState == ReadState.Interactive) { @@ -946,7 +942,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(tag)) { - item.AddTag(tag); + tags.Add(tag); } break; } @@ -961,6 +957,8 @@ namespace MediaBrowser.LocalMetadata.Parsers reader.Read(); } } + + item.Tags = tags.Distinct(StringComparer.Ordinal).ToArray(); } /// <summary> diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 0112d4efa..dbfd9a40c 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -302,9 +302,9 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteElementString("LockData", item.IsLocked.ToString().ToLower()); - if (item.LockedFields.Count > 0) + if (item.LockedFields.Length > 0) { - writer.WriteElementString("LockedFields", string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray())); + writer.WriteElementString("LockedFields", string.Join("|", item.LockedFields)); } if (item.CriticRating.HasValue) @@ -377,7 +377,7 @@ namespace MediaBrowser.LocalMetadata.Savers } } - if (item.ProductionLocations.Count > 0) + if (item.ProductionLocations.Length > 0) { writer.WriteStartElement("Countries"); @@ -469,7 +469,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteEndElement(); } - if (item.Studios.Count > 0) + if (item.Studios.Length > 0) { writer.WriteStartElement("Studios"); @@ -481,7 +481,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteEndElement(); } - if (item.Tags.Count > 0) + if (item.Tags.Length > 0) { writer.WriteStartElement("Tags"); diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs deleted file mode 100644 index 219b1f3c5..000000000 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ /dev/null @@ -1,201 +0,0 @@ -using BDInfo; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.MediaInfo; -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Text; - -namespace MediaBrowser.MediaEncoding.BdInfo -{ - /// <summary> - /// Class BdInfoExaminer - /// </summary> - public class BdInfoExaminer : IBlurayExaminer - { - private readonly IFileSystem _fileSystem; - private readonly ITextEncoding _textEncoding; - - public BdInfoExaminer(IFileSystem fileSystem, ITextEncoding textEncoding) - { - _fileSystem = fileSystem; - _textEncoding = textEncoding; - } - - /// <summary> - /// Gets the disc info. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>BlurayDiscInfo.</returns> - public BlurayDiscInfo GetDiscInfo(string path) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - var bdrom = new BDROM(path, _fileSystem, _textEncoding); - - bdrom.Scan(); - - // Get the longest playlist - var playlist = bdrom.PlaylistFiles.Values.OrderByDescending(p => p.TotalLength).FirstOrDefault(p => p.IsValid); - - var outputStream = new BlurayDiscInfo - { - MediaStreams = new List<MediaStream>() - }; - - if (playlist == null) - { - return outputStream; - } - - outputStream.Chapters = playlist.Chapters; - - outputStream.RunTimeTicks = TimeSpan.FromSeconds(playlist.TotalLength).Ticks; - - var mediaStreams = new List<MediaStream>(); - - foreach (var stream in playlist.SortedStreams) - { - var videoStream = stream as TSVideoStream; - - if (videoStream != null) - { - AddVideoStream(mediaStreams, videoStream); - continue; - } - - var audioStream = stream as TSAudioStream; - - if (audioStream != null) - { - AddAudioStream(mediaStreams, audioStream); - continue; - } - - var textStream = stream as TSTextStream; - - if (textStream != null) - { - AddSubtitleStream(mediaStreams, textStream); - continue; - } - - var graphicsStream = stream as TSGraphicsStream; - - if (graphicsStream != null) - { - AddSubtitleStream(mediaStreams, graphicsStream); - } - } - - outputStream.MediaStreams = mediaStreams; - - outputStream.PlaylistName = playlist.Name; - - if (playlist.StreamClips != null && playlist.StreamClips.Any()) - { - // Get the files in the playlist - outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToList(); - } - - return outputStream; - } - - /// <summary> - /// Adds the video stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="videoStream">The video stream.</param> - private void AddVideoStream(List<MediaStream> streams, TSVideoStream videoStream) - { - var mediaStream = new MediaStream - { - BitRate = Convert.ToInt32(videoStream.BitRate), - Width = videoStream.Width, - Height = videoStream.Height, - Codec = videoStream.CodecShortName, - IsInterlaced = videoStream.IsInterlaced, - Type = MediaStreamType.Video, - Index = streams.Count - }; - - if (videoStream.FrameRateDenominator > 0) - { - float frameRateEnumerator = videoStream.FrameRateEnumerator; - float frameRateDenominator = videoStream.FrameRateDenominator; - - mediaStream.AverageFrameRate = mediaStream.RealFrameRate = frameRateEnumerator / frameRateDenominator; - } - - streams.Add(mediaStream); - } - - /// <summary> - /// Adds the audio stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="audioStream">The audio stream.</param> - private void AddAudioStream(List<MediaStream> streams, TSAudioStream audioStream) - { - var stream = new MediaStream - { - Codec = audioStream.CodecShortName, - Language = audioStream.LanguageCode, - Channels = audioStream.ChannelCount, - SampleRate = audioStream.SampleRate, - Type = MediaStreamType.Audio, - Index = streams.Count - }; - - var bitrate = Convert.ToInt32(audioStream.BitRate); - - if (bitrate > 0) - { - stream.BitRate = bitrate; - } - - if (audioStream.LFE > 0) - { - stream.Channels = audioStream.ChannelCount + 1; - } - - streams.Add(stream); - } - - /// <summary> - /// Adds the subtitle stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="textStream">The text stream.</param> - private void AddSubtitleStream(List<MediaStream> streams, TSTextStream textStream) - { - streams.Add(new MediaStream - { - Language = textStream.LanguageCode, - Codec = textStream.CodecShortName, - Type = MediaStreamType.Subtitle, - Index = streams.Count - }); - } - - /// <summary> - /// Adds the subtitle stream. - /// </summary> - /// <param name="streams">The streams.</param> - /// <param name="textStream">The text stream.</param> - private void AddSubtitleStream(List<MediaStream> streams, TSGraphicsStream textStream) - { - streams.Add(new MediaStream - { - Language = textStream.LanguageCode, - Codec = textStream.CodecShortName, - Type = MediaStreamType.Subtitle, - Index = streams.Count - }); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs deleted file mode 100644 index 16c67655a..000000000 --- a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs +++ /dev/null @@ -1,58 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; -using System.Collections.Generic; -using System.IO; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; - -namespace MediaBrowser.MediaEncoding.Configuration -{ - public class EncodingConfigurationFactory : IConfigurationFactory - { - private readonly IFileSystem _fileSystem; - - public EncodingConfigurationFactory(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } - - public IEnumerable<ConfigurationStore> GetConfigurations() - { - return new[] - { - new EncodingConfigurationStore(_fileSystem) - }; - } - } - - public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration - { - private readonly IFileSystem _fileSystem; - - public EncodingConfigurationStore(IFileSystem fileSystem) - { - ConfigurationType = typeof(EncodingOptions); - Key = "encoding"; - _fileSystem = fileSystem; - } - - public void Validate(object oldConfig, object newConfig) - { - var oldEncodingConfig = (EncodingOptions)oldConfig; - var newEncodingConfig = (EncodingOptions)newConfig; - - var newPath = newEncodingConfig.TranscodingTempPath; - - if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(oldEncodingConfig.TranscodingTempPath ?? string.Empty, newPath)) - { - // Validate - if (!_fileSystem.DirectoryExists(newPath)) - { - throw new FileNotFoundException(string.Format("{0} does not exist.", newPath)); - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs deleted file mode 100644 index 566e7946d..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ /dev/null @@ -1,62 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using System; -using MediaBrowser.Model.Diagnostics; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class AudioEncoder : BaseEncoder - { - public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory) - { - } - - protected override string GetCommandLineArguments(EncodingJob state) - { - var encodingOptions = GetEncodingOptions(); - - return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, state.OutputFilePath); - } - - protected override string GetOutputFileExtension(EncodingJob state) - { - var ext = base.GetOutputFileExtension(state); - - if (!string.IsNullOrEmpty(ext)) - { - return ext; - } - - var audioCodec = state.Options.AudioCodec; - - if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".aac"; - } - if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".mp3"; - } - if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".ogg"; - } - if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".wma"; - } - - return null; - } - - protected override bool IsVideoEncoder - { - get { return false; } - } - - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs deleted file mode 100644 index a291a9852..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ /dev/null @@ -1,375 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.Dlna; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public abstract class BaseEncoder - { - protected readonly MediaEncoder MediaEncoder; - protected readonly ILogger Logger; - protected readonly IServerConfigurationManager ConfigurationManager; - protected readonly IFileSystem FileSystem; - protected readonly IIsoManager IsoManager; - protected readonly ILibraryManager LibraryManager; - protected readonly ISessionManager SessionManager; - protected readonly ISubtitleEncoder SubtitleEncoder; - protected readonly IMediaSourceManager MediaSourceManager; - protected IProcessFactory ProcessFactory; - - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - protected EncodingHelper EncodingHelper; - - protected BaseEncoder(MediaEncoder mediaEncoder, - ILogger logger, - IServerConfigurationManager configurationManager, - IFileSystem fileSystem, - IIsoManager isoManager, - ILibraryManager libraryManager, - ISessionManager sessionManager, - ISubtitleEncoder subtitleEncoder, - IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) - { - MediaEncoder = mediaEncoder; - Logger = logger; - ConfigurationManager = configurationManager; - FileSystem = fileSystem; - IsoManager = isoManager; - LibraryManager = libraryManager; - SessionManager = sessionManager; - SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - ProcessFactory = processFactory; - - EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder); - } - - public async Task<EncodingJob> Start(EncodingJobOptions options, - IProgress<double> progress, - CancellationToken cancellationToken) - { - var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager, MediaEncoder) - .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); - - encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(encodingJob.OutputFilePath)); - - encodingJob.ReadInputAtNativeFramerate = options.ReadInputAtNativeFramerate; - - await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false); - - var commandLineArgs = GetCommandLineArguments(encodingJob); - - var process = ProcessFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - - FileName = MediaEncoder.EncoderPath, - Arguments = commandLineArgs, - - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - }); - - var workingDirectory = GetWorkingDirectory(options); - if (!string.IsNullOrWhiteSpace(workingDirectory)) - { - process.StartInfo.WorkingDirectory = workingDirectory; - } - - OnTranscodeBeginning(encodingJob); - - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - Logger.Info(commandLineLogMessage); - - var logFilePath = Path.Combine(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt"); - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(logFilePath)); - - // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); - - var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await encodingJob.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationToken).ConfigureAwait(false); - - process.Exited += (sender, args) => OnFfMpegProcessExited(process, encodingJob); - - try - { - process.Start(); - } - catch (Exception ex) - { - Logger.ErrorException("Error starting ffmpeg", ex); - - OnTranscodeFailedToStart(encodingJob.OutputFilePath, encodingJob); - - throw; - } - - cancellationToken.Register(() => Cancel(process, encodingJob)); - - // MUST read both stdout and stderr asynchronously or a deadlock may occurr - //process.BeginOutputReadLine(); - - // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - new JobLogger(Logger).StartStreamingLog(encodingJob, process.StandardError.BaseStream, encodingJob.LogFileStream); - - // Wait for the file to exist before proceeeding - while (!FileSystem.FileExists(encodingJob.OutputFilePath) && !encodingJob.HasExited) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - - return encodingJob; - } - - private void Cancel(IProcess process, EncodingJob job) - { - Logger.Info("Killing ffmpeg process for {0}", job.OutputFilePath); - - //process.Kill(); - process.StandardInput.WriteLine("q"); - - job.IsCancelled = true; - } - - /// <summary> - /// Processes the exited. - /// </summary> - /// <param name="process">The process.</param> - /// <param name="job">The job.</param> - private void OnFfMpegProcessExited(IProcess process, EncodingJob job) - { - job.HasExited = true; - - Logger.Debug("Disposing stream resources"); - job.Dispose(); - - var isSuccesful = false; - - try - { - var exitCode = process.ExitCode; - Logger.Info("FFMpeg exited with code {0}", exitCode); - - isSuccesful = exitCode == 0; - } - catch - { - Logger.Error("FFMpeg exited with an error."); - } - - if (isSuccesful && !job.IsCancelled) - { - job.TaskCompletionSource.TrySetResult(true); - } - else if (job.IsCancelled) - { - try - { - DeleteFiles(job); - } - catch - { - } - try - { - job.TaskCompletionSource.TrySetException(new OperationCanceledException()); - } - catch - { - } - } - else - { - try - { - DeleteFiles(job); - } - catch - { - } - try - { - job.TaskCompletionSource.TrySetException(new Exception("Encoding failed")); - } - catch - { - } - } - - // This causes on exited to be called twice: - //try - //{ - // // Dispose the process - // process.Dispose(); - //} - //catch (Exception ex) - //{ - // Logger.ErrorException("Error disposing ffmpeg.", ex); - //} - } - - protected virtual void DeleteFiles(EncodingJob job) - { - FileSystem.DeleteFile(job.OutputFilePath); - } - - private void OnTranscodeBeginning(EncodingJob job) - { - job.ReportTranscodingProgress(null, null, null, null, null); - } - - private void OnTranscodeFailedToStart(string path, EncodingJob job) - { - if (!string.IsNullOrWhiteSpace(job.Options.DeviceId)) - { - SessionManager.ClearTranscodingInfo(job.Options.DeviceId); - } - } - - protected abstract bool IsVideoEncoder { get; } - - protected virtual string GetWorkingDirectory(EncodingJobOptions options) - { - return null; - } - - protected EncodingOptions GetEncodingOptions() - { - return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding"); - } - - protected abstract string GetCommandLineArguments(EncodingJob job); - - private string GetOutputFilePath(EncodingJob state) - { - var folder = string.IsNullOrWhiteSpace(state.Options.OutputDirectory) ? - ConfigurationManager.ApplicationPaths.TranscodingTempPath : - state.Options.OutputDirectory; - - var outputFileExtension = GetOutputFileExtension(state); - - var filename = state.Id + (outputFileExtension ?? string.Empty).ToLower(); - return Path.Combine(folder, filename); - } - - protected virtual string GetOutputFileExtension(EncodingJob state) - { - if (!string.IsNullOrWhiteSpace(state.Options.OutputContainer)) - { - return "." + state.Options.OutputContainer; - } - - return null; - } - - /// <summary> - /// Gets the name of the output video codec - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.String.</returns> - protected string GetVideoDecoder(EncodingJob state) - { - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - // Only use alternative encoders for video files. - // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully - // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. - if (state.VideoType != VideoType.VideoFile) - { - return null; - } - - if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) - { - if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - switch (state.MediaSource.VideoStream.Codec.ToLower()) - { - case "avc": - case "h264": - if (MediaEncoder.SupportsDecoder("h264_qsv")) - { - // Seeing stalls and failures with decoding. Not worth it compared to encoding. - return "-c:v h264_qsv "; - } - break; - case "mpeg2video": - if (MediaEncoder.SupportsDecoder("mpeg2_qsv")) - { - return "-c:v mpeg2_qsv "; - } - break; - case "vc1": - if (MediaEncoder.SupportsDecoder("vc1_qsv")) - { - return "-c:v vc1_qsv "; - } - break; - } - } - } - - // leave blank so ffmpeg will decide - return null; - } - - private async Task AcquireResources(EncodingJob state, CancellationToken cancellationToken) - { - if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath)) - { - state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false); - } - - if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Options.LiveStreamId)) - { - var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest - { - OpenToken = state.MediaSource.OpenToken - - }, cancellationToken).ConfigureAwait(false); - - EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, null); - - if (state.IsVideoRequest) - { - EncodingHelper.TryStreamCopy(state); - } - } - - if (state.MediaSource.BufferMs.HasValue) - { - await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs deleted file mode 100644 index 59f3576ec..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncoderValidator - { - private readonly ILogger _logger; - private readonly IProcessFactory _processFactory; - - public EncoderValidator(ILogger logger, IProcessFactory processFactory) - { - _logger = logger; - _processFactory = processFactory; - } - - public Tuple<List<string>, List<string>> Validate(string encoderPath) - { - _logger.Info("Validating media encoder at {0}", encoderPath); - - var decoders = GetDecoders(encoderPath); - var encoders = GetEncoders(encoderPath); - - _logger.Info("Encoder validation complete"); - - return new Tuple<List<string>, List<string>>(decoders, encoders); - } - - public bool ValidateVersion(string encoderAppPath, bool logOutput) - { - string output = string.Empty; - try - { - output = GetProcessOutput(encoderAppPath, "-version"); - } - catch (Exception ex) - { - if (logOutput) - { - _logger.ErrorException("Error validating encoder", ex); - } - } - - if (string.IsNullOrWhiteSpace(output)) - { - return false; - } - - _logger.Info("ffmpeg info: {0}", output); - - if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - - output = " " + output + " "; - - for (var i = 2013; i <= 2015; i++) - { - var yearString = i.ToString(CultureInfo.InvariantCulture); - if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - } - - return true; - } - - private List<string> GetDecoders(string encoderAppPath) - { - string output = string.Empty; - try - { - output = GetProcessOutput(encoderAppPath, "-decoders"); - } - catch (Exception ) - { - //_logger.ErrorException("Error detecting available decoders", ex); - } - - var found = new List<string>(); - var required = new[] - { - "mpeg2video", - "h264_qsv", - "hevc_qsv", - "mpeg2_qsv", - "vc1_qsv", - "h264_cuvid", - "hevc_cuvid", - "dts", - "ac3", - "aac", - "mp3", - "h264", - "hevc" - }; - - foreach (var codec in required) - { - var srch = " " + codec + " "; - - if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1) - { - _logger.Info("Decoder available: " + codec); - found.Add(codec); - } - } - - return found; - } - - private List<string> GetEncoders(string encoderAppPath) - { - string output = null; - try - { - output = GetProcessOutput(encoderAppPath, "-encoders"); - } - catch - { - } - - var found = new List<string>(); - var required = new[] - { - "libx264", - "libx265", - "mpeg4", - "msmpeg4", - "libvpx", - "libvpx-vp9", - "aac", - "libmp3lame", - "libopus", - "libvorbis", - "srt", - "h264_nvenc", - "hevc_nvenc", - "h264_qsv", - "hevc_qsv", - "h264_omx", - "hevc_omx", - "h264_vaapi", - "hevc_vaapi", - "ac3" - }; - - output = output ?? string.Empty; - - var index = 0; - - foreach (var codec in required) - { - var srch = " " + codec + " "; - - if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1) - { - if (index < required.Length - 1) - { - _logger.Info("Encoder available: " + codec); - } - - found.Add(codec); - } - index++; - } - - return found; - } - - private string GetProcessOutput(string path, string arguments) - { - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = path, - Arguments = arguments, - IsHidden = true, - ErrorDialog = false, - RedirectStandardOutput = true - }); - - _logger.Info("Running {0} {1}", path, arguments); - - using (process) - { - process.Start(); - - try - { - return process.StandardOutput.ReadToEnd(); - } - catch - { - _logger.Info("Killing process {0} {1}", path, arguments); - - // Hate having to do this - try - { - process.Kill(); - } - catch (Exception ex1) - { - _logger.ErrorException("Error killing process", ex1); - } - - throw; - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs deleted file mode 100644 index d53701feb..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ /dev/null @@ -1,197 +0,0 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Net; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncodingJob : EncodingJobInfo, IDisposable - { - public bool HasExited { get; internal set; } - public bool IsCancelled { get; internal set; } - - public Stream LogFileStream { get; set; } - public IProgress<double> Progress { get; set; } - public TaskCompletionSource<bool> TaskCompletionSource; - - public EncodingJobOptions Options - { - get { return (EncodingJobOptions) BaseRequest; } - set { BaseRequest = value; } - } - - public string Id { get; set; } - - public string MimeType { get; set; } - public bool EstimateContentLength { get; set; } - public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - public long? EncodingDurationTicks { get; set; } - - public string ItemType { get; set; } - - public string GetMimeType(string outputPath) - { - if (!string.IsNullOrEmpty(MimeType)) - { - return MimeType; - } - - return MimeTypes.GetMimeType(outputPath); - } - - private readonly ILogger _logger; - private readonly IMediaSourceManager _mediaSourceManager; - - public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) : - base(logger, TranscodingJobType.Progressive) - { - _logger = logger; - _mediaSourceManager = mediaSourceManager; - Id = Guid.NewGuid().ToString("N"); - - _logger = logger; - TaskCompletionSource = new TaskCompletionSource<bool>(); - } - - public void Dispose() - { - DisposeLiveStream(); - DisposeLogStream(); - DisposeIsoMount(); - } - - private void DisposeLogStream() - { - if (LogFileStream != null) - { - try - { - LogFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing log stream", ex); - } - - LogFileStream = null; - } - } - - private async void DisposeLiveStream() - { - if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Options.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) - { - try - { - await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error closing media source", ex); - } - } - } - - public string OutputFilePath { get; set; } - - public string ActualOutputVideoCodec - { - get - { - var codec = OutputVideoCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = VideoStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; - } - } - - public string ActualOutputAudioCodec - { - get - { - var codec = OutputAudioCodec; - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var stream = AudioStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; - } - - return codec; - } - } - - public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) - { - var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; - - // job.Framerate = framerate; - - if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue) - { - var pct = ticks.Value / RunTimeTicks.Value; - percentComplete = pct * 100; - } - - if (percentComplete.HasValue) - { - Progress.Report(percentComplete.Value); - } - - // job.TranscodingPositionTicks = ticks; - // job.BytesTranscoded = bytesTranscoded; - - var deviceId = Options.DeviceId; - - if (!string.IsNullOrWhiteSpace(deviceId)) - { - var audioCodec = ActualOutputVideoCodec; - var videoCodec = ActualOutputVideoCodec; - - // SessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo - // { - // Bitrate = job.TotalOutputBitrate, - // AudioCodec = audioCodec, - // VideoCodec = videoCodec, - // Container = job.Options.OutputContainer, - // Framerate = framerate, - // CompletionPercentage = percentComplete, - // Width = job.OutputWidth, - // Height = job.OutputHeight, - // AudioChannels = job.OutputAudioChannels, - // IsAudioDirect = string.Equals(job.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase), - // IsVideoDirect = string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) - // }); - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs deleted file mode 100644 index 50d3d254a..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ /dev/null @@ -1,305 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncodingJobFactory - { - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IConfigurationManager _config; - private readonly IMediaEncoder _mediaEncoder; - - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder) - { - _logger = logger; - _libraryManager = libraryManager; - _mediaSourceManager = mediaSourceManager; - _config = config; - _mediaEncoder = mediaEncoder; - } - - public async Task<EncodingJob> CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken) - { - var request = options; - - if (string.IsNullOrEmpty(request.AudioCodec)) - { - request.AudioCodec = InferAudioCodec(request.OutputContainer); - } - - var state = new EncodingJob(_logger, _mediaSourceManager) - { - Options = options, - IsVideoRequest = isVideoRequest, - Progress = progress - }; - - if (!string.IsNullOrWhiteSpace(request.VideoCodec)) - { - state.SupportedVideoCodecs = request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.AudioCodec)) - { - state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) - { - state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); - request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToSubtitleCodec(i)) - ?? state.SupportedSubtitleCodecs.FirstOrDefault(); - } - - var item = _libraryManager.GetItemById(request.ItemId); - state.ItemType = item.GetType().Name; - - state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - - var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? - item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); - - if (primaryImage != null) - { - state.AlbumCoverPath = primaryImage.Path; - } - - var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false); - - var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - - var videoRequest = state.Options; - - encodingHelper.AttachMediaSourceInfo(state, mediaSource, null); - - //var container = Path.GetExtension(state.RequestedUrl); - - //if (string.IsNullOrEmpty(container)) - //{ - // container = request.Static ? - // state.InputContainer : - // (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.'); - //} - - //state.OutputContainer = (container ?? string.Empty).TrimStart('.'); - - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.Options, state.AudioStream); - - state.OutputAudioCodec = state.Options.AudioCodec; - - state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec); - - if (videoRequest != null) - { - state.OutputVideoCodec = state.Options.VideoCodec; - state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec); - - if (state.OutputVideoBitrate.HasValue) - { - var resolution = ResolutionNormalizer.Normalize( - state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, - state.OutputVideoBitrate.Value, - state.VideoStream == null ? null : state.VideoStream.Codec, - state.OutputVideoCodec, - videoRequest.MaxWidth, - videoRequest.MaxHeight); - - videoRequest.MaxWidth = resolution.MaxWidth; - videoRequest.MaxHeight = resolution.MaxHeight; - } - } - - ApplyDeviceProfileSettings(state); - - if (videoRequest != null) - { - encodingHelper.TryStreamCopy(state); - } - - //state.OutputFilePath = GetOutputFilePath(state); - - return state; - } - - protected EncodingOptions GetEncodingOptions() - { - return _config.GetConfiguration<EncodingOptions>("encoding"); - } - - /// <summary> - /// Infers the video codec. - /// </summary> - /// <param name="container">The container.</param> - /// <returns>System.Nullable{VideoCodecs}.</returns> - private static string InferVideoCodec(string container) - { - var ext = "." + (container ?? string.Empty); - - if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase)) - { - return "wmv"; - } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) - { - return "vpx"; - } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) - { - return "theora"; - } - if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) - { - return "h264"; - } - - return "copy"; - } - - private string InferAudioCodec(string container) - { - var ext = "." + (container ?? string.Empty); - - if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase)) - { - return "mp3"; - } - if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) - { - return "aac"; - } - if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) - { - return "wma"; - } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - - return "copy"; - } - - /// <summary> - /// Determines whether the specified stream is H264. - /// </summary> - /// <param name="stream">The stream.</param> - /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns> - protected bool IsH264(MediaStream stream) - { - var codec = stream.Codec ?? string.Empty; - - return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || - codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; - } - - private static int GetVideoProfileScore(string profile) - { - var list = new List<string> - { - "Constrained Baseline", - "Baseline", - "Extended", - "Main", - "High", - "Progressive High", - "Constrained High" - }; - - return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase)); - } - - private void ApplyDeviceProfileSettings(EncodingJob state) - { - var profile = state.Options.DeviceProfile; - - if (profile == null) - { - // Don't use settings from the default profile. - // Only use a specific profile if it was requested. - return; - } - - var audioCodec = state.ActualOutputAudioCodec; - - var videoCodec = state.ActualOutputVideoCodec; - var outputContainer = state.Options.OutputContainer; - - var mediaProfile = state.IsVideoRequest ? - profile.GetAudioMediaProfile(outputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) : - profile.GetVideoMediaProfile(outputContainer, - audioCodec, - videoCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetVideoProfile, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TargetTimestamp, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC); - - if (mediaProfile != null) - { - state.MimeType = mediaProfile.MimeType; - } - - var transcodingProfile = state.IsVideoRequest ? - profile.GetAudioTranscodingProfile(outputContainer, audioCodec) : - profile.GetVideoTranscodingProfile(outputContainer, audioCodec, videoCodec); - - if (transcodingProfile != null) - { - state.EstimateContentLength = transcodingProfile.EstimateContentLength; - state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; - state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - - state.Options.CopyTimestamps = transcodingProfile.CopyTimestamps; - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs deleted file mode 100644 index dc3cb5f5e..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MediaBrowser.Model.MediaInfo; -using System.Collections.Generic; -using System.Linq; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public static class EncodingUtils - { - public static string GetInputArgument(List<string> inputFiles, MediaProtocol protocol) - { - if (protocol != MediaProtocol.File) - { - var url = inputFiles.First(); - - return string.Format("\"{0}\"", url); - } - - return GetConcatInputArgument(inputFiles); - } - - /// <summary> - /// Gets the concat input argument. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <returns>System.String.</returns> - private static string GetConcatInputArgument(IReadOnlyList<string> inputFiles) - { - // Get all streams - // If there's more than one we'll need to use the concat command - if (inputFiles.Count > 1) - { - var files = string.Join("|", inputFiles.Select(NormalizePath).ToArray()); - - return string.Format("concat:\"{0}\"", files); - } - - // Determine the input path for video files - return GetFileInputArgument(inputFiles[0]); - } - - /// <summary> - /// Gets the file input argument. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>System.String.</returns> - private static string GetFileInputArgument(string path) - { - if (path.IndexOf("://") != -1) - { - return string.Format("\"{0}\"", path); - } - - // Quotes are valid path characters in linux and they need to be escaped here with a leading \ - path = NormalizePath(path); - - return string.Format("file:\"{0}\"", path); - } - - /// <summary> - /// Normalizes the path. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>System.String.</returns> - private static string NormalizePath(string path) - { - // Quotes are valid path characters in linux and they need to be escaped here with a leading \ - return path.Replace("\"", "\\\""); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs deleted file mode 100644 index e21292cbd..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Common.Configuration; - -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class FontConfigLoader - { - private readonly IHttpClient _httpClient; - private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; - private readonly IZipClient _zipClient; - private readonly IFileSystem _fileSystem; - - private readonly string[] _fontUrls = - { - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z" - }; - - public FontConfigLoader(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, IZipClient zipClient, IFileSystem fileSystem) - { - _httpClient = httpClient; - _appPaths = appPaths; - _logger = logger; - _zipClient = zipClient; - _fileSystem = fileSystem; - } - - /// <summary> - /// Extracts the fonts. - /// </summary> - /// <param name="targetPath">The target path.</param> - /// <returns>Task.</returns> - public async Task DownloadFonts(string targetPath) - { - try - { - var fontsDirectory = Path.Combine(targetPath, "fonts"); - - _fileSystem.CreateDirectory(fontsDirectory); - - const string fontFilename = "ARIALUNI.TTF"; - - var fontFile = Path.Combine(fontsDirectory, fontFilename); - - if (_fileSystem.FileExists(fontFile)) - { - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - } - else - { - // Kick this off, but no need to wait on it - var task = Task.Run(async () => - { - await DownloadFontFile(fontsDirectory, fontFilename, new SimpleProgress<double>()).ConfigureAwait(false); - - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - }); - } - } - catch (HttpException ex) - { - // Don't let the server crash because of this - _logger.ErrorException("Error downloading ffmpeg font files", ex); - } - catch (Exception ex) - { - // Don't let the server crash because of this - _logger.ErrorException("Error writing ffmpeg font files", ex); - } - } - - /// <summary> - /// Downloads the font file. - /// </summary> - /// <param name="fontsDirectory">The fonts directory.</param> - /// <param name="fontFilename">The font filename.</param> - /// <returns>Task.</returns> - private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress) - { - var existingFile = _fileSystem - .GetFilePaths(_appPaths.ProgramDataPath, true) - .FirstOrDefault(i => string.Equals(fontFilename, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase)); - - if (existingFile != null) - { - try - { - _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true); - return; - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error copying file", ex); - } - } - - string tempFile = null; - - foreach (var url in _fontUrls) - { - progress.Report(0); - - try - { - tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = url, - Progress = progress - - }).ConfigureAwait(false); - - break; - } - catch (Exception ex) - { - // The core can function without the font file, so handle this - _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url); - } - } - - if (string.IsNullOrEmpty(tempFile)) - { - return; - } - - Extract7zArchive(tempFile, fontsDirectory); - - try - { - _fileSystem.DeleteFile(tempFile); - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); - } - } - private void Extract7zArchive(string archivePath, string targetPath) - { - _logger.Info("Extracting {0} to {1}", archivePath, targetPath); - - _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); - } - - /// <summary> - /// Writes the font config file. - /// </summary> - /// <param name="fontsDirectory">The fonts directory.</param> - /// <returns>Task.</returns> - private async Task WriteFontConfigFile(string fontsDirectory) - { - const string fontConfigFilename = "fonts.conf"; - var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); - - if (!_fileSystem.FileExists(fontConfigFile)) - { - var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory); - - var bytes = Encoding.UTF8.GetBytes(contents); - - using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileOpenMode.Create, FileAccessMode.Write, - FileShareMode.Read, true)) - { - await fileStream.WriteAsync(bytes, 0, bytes.Length); - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs deleted file mode 100644 index a2ebe0832..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ /dev/null @@ -1,1129 +0,0 @@ -using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.MediaEncoding.Probing; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.System; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - /// <summary> - /// Class MediaEncoder - /// </summary> - public class MediaEncoder : IMediaEncoder, IDisposable - { - /// <summary> - /// The _logger - /// </summary> - private readonly ILogger _logger; - - /// <summary> - /// Gets the json serializer. - /// </summary> - /// <value>The json serializer.</value> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// The _thumbnail resource pool - /// </summary> - private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); - - public string FFMpegPath { get; private set; } - - public string FFProbePath { get; private set; } - - protected readonly IServerConfigurationManager ConfigurationManager; - protected readonly IFileSystem FileSystem; - protected readonly ILiveTvManager LiveTvManager; - protected readonly IIsoManager IsoManager; - protected readonly ILibraryManager LibraryManager; - protected readonly IChannelManager ChannelManager; - protected readonly ISessionManager SessionManager; - protected readonly Func<ISubtitleEncoder> SubtitleEncoder; - protected readonly Func<IMediaSourceManager> MediaSourceManager; - private readonly IHttpClient _httpClient; - private readonly IZipClient _zipClient; - private readonly IProcessFactory _processFactory; - private readonly IMemoryStreamFactory _memoryStreamProvider; - - private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); - private readonly bool _hasExternalEncoder; - private readonly string _originalFFMpegPath; - private readonly string _originalFFProbePath; - private readonly int DefaultImageExtractionTimeoutMs; - private readonly bool EnableEncoderFontFile; - - private readonly IEnvironmentInfo _environmentInfo; - - public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, - int defaultImageExtractionTimeoutMs, - bool enableEncoderFontFile, IEnvironmentInfo environmentInfo) - { - _logger = logger; - _jsonSerializer = jsonSerializer; - ConfigurationManager = configurationManager; - FileSystem = fileSystem; - LiveTvManager = liveTvManager; - IsoManager = isoManager; - LibraryManager = libraryManager; - ChannelManager = channelManager; - SessionManager = sessionManager; - SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - _httpClient = httpClient; - _zipClient = zipClient; - _memoryStreamProvider = memoryStreamProvider; - _processFactory = processFactory; - DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; - EnableEncoderFontFile = enableEncoderFontFile; - _environmentInfo = environmentInfo; - FFProbePath = ffProbePath; - FFMpegPath = ffMpegPath; - _originalFFProbePath = ffProbePath; - _originalFFMpegPath = ffMpegPath; - - _hasExternalEncoder = hasExternalEncoder; - - SetEnvironmentVariable(); - } - - private readonly object _logLock = new object(); - public void SetLogFilename(string name) - { - lock (_logLock) - { - try - { - _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=" + name + ":level=32"); - } - catch (Exception ex) - { - _logger.ErrorException("Error setting FFREPORT environment variable", ex); - } - } - } - - public void ClearLogFilename() - { - lock (_logLock) - { - try - { - _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", null); - } - catch (Exception ex) - { - //_logger.ErrorException("Error setting FFREPORT environment variable", ex); - } - } - } - - private void SetEnvironmentVariable() - { - try - { - //_environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32"); - } - catch (Exception ex) - { - _logger.ErrorException("Error setting FFREPORT environment variable", ex); - } - try - { - //_environmentInfo.SetUserEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32"); - } - catch (Exception ex) - { - _logger.ErrorException("Error setting FFREPORT environment variable", ex); - } - } - - public string EncoderLocationType - { - get - { - if (_hasExternalEncoder) - { - return "External"; - } - - if (string.IsNullOrWhiteSpace(FFMpegPath)) - { - return null; - } - - if (IsSystemInstalledPath(FFMpegPath)) - { - return "System"; - } - - return "Custom"; - } - } - - private bool IsSystemInstalledPath(string path) - { - if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1) - { - return true; - } - - return false; - } - - public async Task Init() - { - InitPaths(); - - if (!string.IsNullOrWhiteSpace(FFMpegPath)) - { - var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath); - - SetAvailableDecoders(result.Item1); - SetAvailableEncoders(result.Item2); - - if (EnableEncoderFontFile) - { - var directory = FileSystem.GetDirectoryName(FFMpegPath); - - if (!string.IsNullOrWhiteSpace(directory) && FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory)) - { - await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, FileSystem).DownloadFonts(directory).ConfigureAwait(false); - } - } - } - } - - private void InitPaths() - { - ConfigureEncoderPaths(); - - if (_hasExternalEncoder) - { - LogPaths(); - return; - } - - // If the path was passed in, save it into config now. - var encodingOptions = GetEncodingOptions(); - var appPath = encodingOptions.EncoderAppPath; - - var valueToSave = FFMpegPath; - - if (!string.IsNullOrWhiteSpace(valueToSave)) - { - // if using system variable, don't save this. - if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder) - { - valueToSave = null; - } - } - - if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal)) - { - encodingOptions.EncoderAppPath = valueToSave; - ConfigurationManager.SaveConfiguration("encoding", encodingOptions); - } - } - - public void UpdateEncoderPath(string path, string pathType) - { - if (_hasExternalEncoder) - { - return; - } - - _logger.Info("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty); - - Tuple<string, string> newPaths; - - if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) - { - path = "ffmpeg"; - - newPaths = TestForInstalledVersions(); - } - else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException("path"); - } - - if (!FileSystem.FileExists(path) && !FileSystem.DirectoryExists(path)) - { - throw new ResourceNotFoundException(); - } - newPaths = GetEncoderPaths(path); - } - else - { - throw new ArgumentException("Unexpected pathType value"); - } - - if (string.IsNullOrWhiteSpace(newPaths.Item1)) - { - throw new ResourceNotFoundException("ffmpeg not found"); - } - if (string.IsNullOrWhiteSpace(newPaths.Item2)) - { - throw new ResourceNotFoundException("ffprobe not found"); - } - - path = newPaths.Item1; - - if (!ValidateVersion(path, true)) - { - throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); - } - - var config = GetEncodingOptions(); - config.EncoderAppPath = path; - ConfigurationManager.SaveConfiguration("encoding", config); - - Init(); - } - - private bool ValidateVersion(string path, bool logOutput) - { - return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput); - } - - private void ConfigureEncoderPaths() - { - if (_hasExternalEncoder) - { - return; - } - - var appPath = GetEncodingOptions().EncoderAppPath; - - if (string.IsNullOrWhiteSpace(appPath)) - { - appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg"); - } - - var newPaths = GetEncoderPaths(appPath); - if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath)) - { - newPaths = TestForInstalledVersions(); - } - - if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2)) - { - FFMpegPath = newPaths.Item1; - FFProbePath = newPaths.Item2; - } - - LogPaths(); - } - - private Tuple<string, string> GetEncoderPaths(string configuredPath) - { - var appPath = configuredPath; - - if (!string.IsNullOrWhiteSpace(appPath)) - { - if (FileSystem.DirectoryExists(appPath)) - { - return GetPathsFromDirectory(appPath); - } - - if (FileSystem.FileExists(appPath)) - { - return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath)); - } - } - - return new Tuple<string, string>(null, null); - } - - private Tuple<string, string> TestForInstalledVersions() - { - string encoderPath = null; - string probePath = null; - - if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true)) - { - encoderPath = _originalFFMpegPath; - probePath = _originalFFProbePath; - } - - if (string.IsNullOrWhiteSpace(encoderPath)) - { - if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false)) - { - encoderPath = "ffmpeg"; - probePath = "ffprobe"; - } - } - - return new Tuple<string, string>(encoderPath, probePath); - } - - private Tuple<string, string> GetPathsFromDirectory(string path) - { - // Since we can't predict the file extension, first try directly within the folder - // If that doesn't pan out, then do a recursive search - var files = FileSystem.GetFilePaths(path); - - var excludeExtensions = new[] { ".c" }; - - var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - - if (string.IsNullOrWhiteSpace(ffmpegPath) || !FileSystem.FileExists(ffmpegPath)) - { - files = FileSystem.GetFilePaths(path, true); - - ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - - if (!string.IsNullOrWhiteSpace(ffmpegPath)) - { - ffprobePath = GetProbePathFromEncoderPath(ffmpegPath); - } - } - - return new Tuple<string, string>(ffmpegPath, ffprobePath); - } - - private string GetProbePathFromEncoderPath(string appPath) - { - return FileSystem.GetFilePaths(FileSystem.GetDirectoryName(appPath)) - .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); - } - - private void LogPaths() - { - _logger.Info("FFMpeg: {0}", FFMpegPath ?? "not found"); - _logger.Info("FFProbe: {0}", FFProbePath ?? "not found"); - } - - private EncodingOptions GetEncodingOptions() - { - return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding"); - } - - private List<string> _encoders = new List<string>(); - public void SetAvailableEncoders(List<string> list) - { - _encoders = list.ToList(); - //_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray())); - } - - private List<string> _decoders = new List<string>(); - public void SetAvailableDecoders(List<string> list) - { - _decoders = list.ToList(); - //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); - } - - public bool SupportsEncoder(string encoder) - { - return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase); - } - - public bool SupportsDecoder(string decoder) - { - return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); - } - - public bool CanEncodeToAudioCodec(string codec) - { - if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) - { - codec = "libopus"; - } - else if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) - { - codec = "libmp3lame"; - } - - return SupportsEncoder(codec); - } - - public bool CanEncodeToSubtitleCodec(string codec) - { - // TODO - return true; - } - - /// <summary> - /// Gets the encoder path. - /// </summary> - /// <value>The encoder path.</value> - public string EncoderPath - { - get { return FFMpegPath; } - } - - /// <summary> - /// Gets the media info. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken) - { - var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters; - - var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames); - - var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length); - string analyzeDuration; - - if (request.AnalyzeDurationMs > 0) - { - analyzeDuration = "-analyzeduration " + - (request.AnalyzeDurationMs * 1000).ToString(CultureInfo.InvariantCulture); - } - else - { - analyzeDuration = EncodingHelper.GetAnalyzeDurationArgument(inputFiles.Length); - } - - probeSize = probeSize + " " + analyzeDuration; - probeSize = probeSize.Trim(); - - var forceEnableLogging = request.Protocol != MediaProtocol.File; - - return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters, - probeSize, request.MediaType == DlnaProfileType.Audio, request.VideoType, forceEnableLogging, cancellationToken); - } - - /// <summary> - /// Gets the input argument. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <param name="protocol">The protocol.</param> - /// <returns>System.String.</returns> - /// <exception cref="System.ArgumentException">Unrecognized InputType</exception> - public string GetInputArgument(string[] inputFiles, MediaProtocol protocol) - { - return EncodingUtils.GetInputArgument(inputFiles.ToList(), protocol); - } - - /// <summary> - /// Gets the media info internal. - /// </summary> - /// <returns>Task{MediaInfoResult}.</returns> - private async Task<MediaInfo> GetMediaInfoInternal(string inputPath, - string primaryPath, - MediaProtocol protocol, - bool extractChapters, - string probeSizeArgument, - bool isAudio, - VideoType videoType, - bool forceEnableLogging, - CancellationToken cancellationToken) - { - var args = extractChapters - ? "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_chapters -show_format" - : "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format"; - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both or ffmpeg may hang due to deadlocks. See comments below. - RedirectStandardOutput = true, - FileName = FFProbePath, - Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(), - - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - }); - - if (forceEnableLogging) - { - _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - } - else - { - _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - } - - using (var processWrapper = new ProcessWrapper(process, this, _logger)) - { - StartProcess(processWrapper); - - try - { - //process.BeginErrorReadLine(); - - var result = _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream); - - if (result == null || (result.streams == null && result.format == null)) - { - throw new Exception("ffprobe failed - streams and format are both null."); - } - - if (result.streams != null) - { - // Normalize aspect ratio if invalid - foreach (var stream in result.streams) - { - if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) - { - stream.display_aspect_ratio = string.Empty; - } - if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) - { - stream.sample_aspect_ratio = string.Empty; - } - } - } - - return new ProbeResultNormalizer(_logger, FileSystem, _memoryStreamProvider).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); - } - catch - { - StopProcess(processWrapper, 100); - - throw; - } - } - } - - /// <summary> - /// The us culture - /// </summary> - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken) - { - return ExtractImage(new[] { path }, null, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken); - } - - public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) - { - return ExtractImage(inputFiles, container, videoStream, null, protocol, false, threedFormat, offset, cancellationToken); - } - - public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken) - { - return ExtractImage(inputFiles, container, imageStream, imageStreamIndex, protocol, false, null, null, cancellationToken); - } - - private async Task<string> ExtractImage(string[] inputFiles, string container, MediaStream videoStream, int? imageStreamIndex, MediaProtocol protocol, bool isAudio, - Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) - { - var inputArgument = GetInputArgument(inputFiles, protocol); - - if (isAudio) - { - if (imageStreamIndex.HasValue && imageStreamIndex.Value > 0) - { - // It seems for audio files we need to subtract 1 (for the audio stream??) - imageStreamIndex = imageStreamIndex.Value - 1; - } - } - else - { - try - { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, cancellationToken).ConfigureAwait(false); - } - catch (ArgumentException) - { - throw; - } - catch - { - _logger.Error("I-frame image extraction failed, will attempt standard way. Input: {0}", inputArgument); - } - } - - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, cancellationToken).ConfigureAwait(false); - } - - private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); - FileSystem.CreateDirectory(FileSystem.GetDirectoryName(tempExtractPath)); - - // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. - // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar - var vf = "scale=600:trunc(600/dar/2)*2"; - - if (threedFormat.HasValue) - { - switch (threedFormat.Value) - { - case Video3DFormat.HalfSideBySide: - vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. - break; - case Video3DFormat.FullSideBySide: - vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600. - break; - case Video3DFormat.HalfTopAndBottom: - vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600 - break; - case Video3DFormat.FullTopAndBottom: - vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600 - break; - default: - break; - } - } - - var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; - - var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase); - // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. - var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty; - - var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) : - string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg); - - var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1); - var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1); - - if (!string.IsNullOrWhiteSpace(probeSizeArgument)) - { - args = probeSizeArgument + " " + args; - } - - if (!string.IsNullOrWhiteSpace(analyzeDurationArgument)) - { - args = analyzeDurationArgument + " " + args; - } - - if (offset.HasValue) - { - args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args; - } - - var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder()); - if (videoStream != null) - { - var decoder = encodinghelper.GetVideoDecoder(VideoType.VideoFile, videoStream, GetEncodingOptions()); - if (!string.IsNullOrWhiteSpace(decoder)) - { - args = decoder + " " + args; - } - } - - if (!string.IsNullOrWhiteSpace(container)) - { - var inputFormat = encodinghelper.GetInputFormat(container); - if (!string.IsNullOrWhiteSpace(inputFormat)) - { - args = "-f " + inputFormat + " " + args; - } - } - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = FFMpegPath, - Arguments = args, - IsHidden = true, - ErrorDialog = false - }); - - _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - using (var processWrapper = new ProcessWrapper(process, this, _logger)) - { - bool ranToCompletion; - - StartProcess(processWrapper); - - var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs; - if (timeoutMs <= 0) - { - timeoutMs = DefaultImageExtractionTimeoutMs; - } - - ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false); - - if (!ranToCompletion) - { - StopProcess(processWrapper, 1000); - } - - var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - var file = FileSystem.GetFileInfo(tempExtractPath); - - if (exitCode == -1 || !file.Exists || file.Length == 0) - { - var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath); - - _logger.Error(msg); - - throw new Exception(msg); - } - - return tempExtractPath; - } - } - - public string GetTimeParameter(long ticks) - { - var time = TimeSpan.FromTicks(ticks); - - return GetTimeParameter(time); - } - - public string GetTimeParameter(TimeSpan time) - { - return time.ToString(@"hh\:mm\:ss\.fff", UsCulture); - } - - public async Task ExtractVideoImagesOnInterval(string[] inputFiles, - string container, - MediaStream videoStream, - MediaProtocol protocol, - Video3DFormat? threedFormat, - TimeSpan interval, - string targetDirectory, - string filenamePrefix, - int? maxWidth, - CancellationToken cancellationToken) - { - var resourcePool = _thumbnailResourcePool; - - var inputArgument = GetInputArgument(inputFiles, protocol); - - var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture); - - if (maxWidth.HasValue) - { - var maxWidthParam = maxWidth.Value.ToString(UsCulture); - - vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam); - } - - FileSystem.CreateDirectory(targetDirectory); - var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg"); - - var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf); - - var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1); - var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1); - - if (!string.IsNullOrWhiteSpace(probeSizeArgument)) - { - args = probeSizeArgument + " " + args; - } - - if (!string.IsNullOrWhiteSpace(analyzeDurationArgument)) - { - args = analyzeDurationArgument + " " + args; - } - - var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder()); - if (videoStream != null) - { - var decoder = encodinghelper.GetVideoDecoder(VideoType.VideoFile, videoStream, GetEncodingOptions()); - if (!string.IsNullOrWhiteSpace(decoder)) - { - args = decoder + " " + args; - } - } - - if (!string.IsNullOrWhiteSpace(container)) - { - var inputFormat = encodinghelper.GetInputFormat(container); - if (!string.IsNullOrWhiteSpace(inputFormat)) - { - args = "-f " + inputFormat + " " + args; - } - } - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = FFMpegPath, - Arguments = args, - IsHidden = true, - ErrorDialog = false - }); - - _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments); - - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - bool ranToCompletion = false; - - using (var processWrapper = new ProcessWrapper(process, this, _logger)) - { - try - { - StartProcess(processWrapper); - - // Need to give ffmpeg enough time to make all the thumbnails, which could be a while, - // but we still need to detect if the process hangs. - // Making the assumption that as long as new jpegs are showing up, everything is good. - - bool isResponsive = true; - int lastCount = 0; - - while (isResponsive) - { - if (await process.WaitForExitAsync(30000).ConfigureAwait(false)) - { - ranToCompletion = true; - break; - } - - cancellationToken.ThrowIfCancellationRequested(); - - var jpegCount = FileSystem.GetFilePaths(targetDirectory) - .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); - - isResponsive = (jpegCount > lastCount); - lastCount = jpegCount; - } - - if (!ranToCompletion) - { - StopProcess(processWrapper, 1000); - } - } - finally - { - resourcePool.Release(); - } - - var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - - if (exitCode == -1) - { - var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument); - - _logger.Error(msg); - - throw new Exception(msg); - } - } - } - - public async Task<string> EncodeAudio(EncodingJobOptions options, - IProgress<double> progress, - CancellationToken cancellationToken) - { - var job = await new AudioEncoder(this, - _logger, - ConfigurationManager, - FileSystem, - IsoManager, - LibraryManager, - SessionManager, - SubtitleEncoder(), - MediaSourceManager(), - _processFactory) - .Start(options, progress, cancellationToken).ConfigureAwait(false); - - await job.TaskCompletionSource.Task.ConfigureAwait(false); - - return job.OutputFilePath; - } - - public async Task<string> EncodeVideo(EncodingJobOptions options, - IProgress<double> progress, - CancellationToken cancellationToken) - { - var job = await new VideoEncoder(this, - _logger, - ConfigurationManager, - FileSystem, - IsoManager, - LibraryManager, - SessionManager, - SubtitleEncoder(), - MediaSourceManager(), - _processFactory) - .Start(options, progress, cancellationToken).ConfigureAwait(false); - - await job.TaskCompletionSource.Task.ConfigureAwait(false); - - return job.OutputFilePath; - } - - private void StartProcess(ProcessWrapper process) - { - process.Process.Start(); - - lock (_runningProcesses) - { - _runningProcesses.Add(process); - } - } - private void StopProcess(ProcessWrapper process, int waitTimeMs) - { - try - { - if (process.Process.WaitForExit(waitTimeMs)) - { - return; - } - } - catch (Exception ex) - { - _logger.Error("Error in WaitForExit", ex); - } - - try - { - _logger.Info("Killing ffmpeg process"); - - process.Process.Kill(); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing process", ex); - } - } - - private void StopProcesses() - { - List<ProcessWrapper> proceses; - lock (_runningProcesses) - { - proceses = _runningProcesses.ToList(); - _runningProcesses.Clear(); - } - - foreach (var process in proceses) - { - if (!process.HasExited) - { - StopProcess(process, 500); - } - } - } - - public string EscapeSubtitleFilterPath(string path) - { - // https://ffmpeg.org/ffmpeg-filters.html#Notes-on-filtergraph-escaping - // We need to double escape - - return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''"); - } - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> - public void Dispose() - { - Dispose(true); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> - protected virtual void Dispose(bool dispose) - { - if (dispose) - { - StopProcesses(); - } - } - - private class ProcessWrapper : IDisposable - { - public readonly IProcess Process; - public bool HasExited; - public int? ExitCode; - private readonly MediaEncoder _mediaEncoder; - private readonly ILogger _logger; - - public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger) - { - Process = process; - _mediaEncoder = mediaEncoder; - _logger = logger; - Process.Exited += Process_Exited; - } - - void Process_Exited(object sender, EventArgs e) - { - var process = (IProcess)sender; - - HasExited = true; - - try - { - ExitCode = process.ExitCode; - } - catch - { - } - - DisposeProcess(process); - } - - private void DisposeProcess(IProcess process) - { - lock (_mediaEncoder._runningProcesses) - { - _mediaEncoder._runningProcesses.Remove(this); - } - - try - { - process.Dispose(); - } - catch - { - } - } - - private bool _disposed; - private readonly object _syncLock = new object(); - public void Dispose() - { - lock (_syncLock) - { - if (!_disposed) - { - if (Process != null) - { - Process.Exited -= Process_Exited; - DisposeProcess(Process); - } - } - - _disposed = true; - } - } - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs deleted file mode 100644 index 96c126923..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ /dev/null @@ -1,66 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using System; -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Model.Diagnostics; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class VideoEncoder : BaseEncoder - { - public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory) - { - } - - protected override string GetCommandLineArguments(EncodingJob state) - { - // Get the output codec name - var encodingOptions = GetEncodingOptions(); - - return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, state.OutputFilePath, "superfast"); - } - - protected override string GetOutputFileExtension(EncodingJob state) - { - var ext = base.GetOutputFileExtension(state); - - if (!string.IsNullOrEmpty(ext)) - { - return ext; - } - - var videoCodec = state.Options.VideoCodec; - - if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) - { - return ".ts"; - } - if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return ".ogv"; - } - if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return ".webm"; - } - if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return ".asf"; - } - - return null; - } - - protected override bool IsVideoEncoder - { - get { return true; } - } - - } -}
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj deleted file mode 100644 index 142e1c627..000000000 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ /dev/null @@ -1,100 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<Project ToolsVersion="12.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> - <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> - <ProjectGuid>{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}</ProjectGuid> - <OutputType>Library</OutputType> - <AppDesignerFolder>Properties</AppDesignerFolder> - <RootNamespace>MediaBrowser.MediaEncoding</RootNamespace> - <AssemblyName>MediaBrowser.MediaEncoding</AssemblyName> - <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>none</DebugType> - <Optimize>true</Optimize> - <OutputPath>bin\Release\</OutputPath> - <DefineConstants>TRACE</DefineConstants> - <ErrorReport>prompt</ErrorReport> - <WarningLevel>4</WarningLevel> - </PropertyGroup> - <ItemGroup> - <Compile Include="..\SharedVersion.cs"> - <Link>Properties\SharedVersion.cs</Link> - </Compile> - <Compile Include="BdInfo\BdInfoExaminer.cs" /> - <Compile Include="Configuration\EncodingConfigurationFactory.cs" /> - <Compile Include="Encoder\AudioEncoder.cs" /> - <Compile Include="Encoder\BaseEncoder.cs" /> - <Compile Include="Encoder\EncodingJob.cs" /> - <Compile Include="Encoder\EncodingJobFactory.cs" /> - <Compile Include="Encoder\EncodingUtils.cs" /> - <Compile Include="Encoder\EncoderValidator.cs" /> - <Compile Include="Encoder\FontConfigLoader.cs" /> - <Compile Include="Encoder\MediaEncoder.cs" /> - <Compile Include="Encoder\VideoEncoder.cs" /> - <Compile Include="Probing\FFProbeHelpers.cs" /> - <Compile Include="Probing\InternalMediaInfoResult.cs" /> - <Compile Include="Probing\ProbeResultNormalizer.cs" /> - <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Subtitles\ConfigurationExtension.cs" /> - <Compile Include="Subtitles\ISubtitleParser.cs" /> - <Compile Include="Subtitles\ISubtitleWriter.cs" /> - <Compile Include="Subtitles\JsonWriter.cs" /> - <Compile Include="Subtitles\OpenSubtitleDownloader.cs" /> - <Compile Include="Subtitles\ParserValues.cs" /> - <Compile Include="Subtitles\SrtParser.cs" /> - <Compile Include="Subtitles\SrtWriter.cs" /> - <Compile Include="Subtitles\AssParser.cs" /> - <Compile Include="Subtitles\SsaParser.cs" /> - <Compile Include="Subtitles\SubtitleEncoder.cs" /> - <Compile Include="Subtitles\TtmlWriter.cs" /> - <Compile Include="Subtitles\VttWriter.cs" /> - </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\BDInfo\BDInfo.csproj"> - <Project>{88ae38df-19d7-406f-a6a9-09527719a21e}</Project> - <Name>BDInfo</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> - <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project> - <Name>MediaBrowser.Common</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj"> - <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> - <Name>MediaBrowser.Controller</Name> - </ProjectReference> - <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> - <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> - <Name>MediaBrowser.Model</Name> - </ProjectReference> - <ProjectReference Include="..\OpenSubtitlesHandler\OpenSubtitlesHandler.csproj"> - <Project>{4a4402d4-e910-443b-b8fc-2c18286a2ca0}</Project> - <Name>OpenSubtitlesHandler</Name> - </ProjectReference> - </ItemGroup> - <ItemGroup> - <None Include="packages.config" /> - </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>
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets deleted file mode 100644 index e69ce0e64..000000000 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.nuget.targets +++ /dev/null @@ -1,6 +0,0 @@ -<?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>
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs deleted file mode 100644 index 396c85e21..000000000 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.MediaEncoding.Probing -{ - public static class FFProbeHelpers - { - /// <summary> - /// Normalizes the FF probe result. - /// </summary> - /// <param name="result">The result.</param> - public static void NormalizeFFProbeResult(InternalMediaInfoResult result) - { - if (result == null) - { - throw new ArgumentNullException("result"); - } - - if (result.format != null && result.format.tags != null) - { - result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags); - } - - if (result.streams != null) - { - // Convert all dictionaries to case insensitive - foreach (var stream in result.streams) - { - if (stream.tags != null) - { - stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags); - } - - if (stream.disposition != null) - { - stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition); - } - } - } - } - - /// <summary> - /// Gets a string from an FFProbeResult tags dictionary - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="key">The key.</param> - /// <returns>System.String.</returns> - public static string GetDictionaryValue(Dictionary<string, string> tags, string key) - { - if (tags == null) - { - return null; - } - - string val; - - tags.TryGetValue(key, out val); - return val; - } - - /// <summary> - /// Gets an int from an FFProbeResult tags dictionary - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="key">The key.</param> - /// <returns>System.Nullable{System.Int32}.</returns> - public static int? GetDictionaryNumericValue(Dictionary<string, string> tags, string key) - { - var val = GetDictionaryValue(tags, key); - - if (!string.IsNullOrEmpty(val)) - { - int i; - - if (int.TryParse(val, out i)) - { - return i; - } - } - - return null; - } - - /// <summary> - /// Gets a DateTime from an FFProbeResult tags dictionary - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="key">The key.</param> - /// <returns>System.Nullable{DateTime}.</returns> - public static DateTime? GetDictionaryDateTime(Dictionary<string, string> tags, string key) - { - var val = GetDictionaryValue(tags, key); - - if (!string.IsNullOrEmpty(val)) - { - DateTime i; - - if (DateTime.TryParse(val, out i)) - { - return i.ToUniversalTime(); - } - } - - return null; - } - - /// <summary> - /// Converts a dictionary to case insensitive - /// </summary> - /// <param name="dict">The dict.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private static Dictionary<string, string> ConvertDictionaryToCaseInSensitive(Dictionary<string, string> dict) - { - return new Dictionary<string, string>(dict, StringComparer.OrdinalIgnoreCase); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs deleted file mode 100644 index eef273250..000000000 --- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs +++ /dev/null @@ -1,341 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.MediaEncoding.Probing -{ - /// <summary> - /// Class MediaInfoResult - /// </summary> - public class InternalMediaInfoResult - { - /// <summary> - /// Gets or sets the streams. - /// </summary> - /// <value>The streams.</value> - public MediaStreamInfo[] streams { get; set; } - - /// <summary> - /// Gets or sets the format. - /// </summary> - /// <value>The format.</value> - public MediaFormatInfo format { get; set; } - - /// <summary> - /// Gets or sets the chapters. - /// </summary> - /// <value>The chapters.</value> - public MediaChapter[] Chapters { get; set; } - } - - public class MediaChapter - { - public int id { get; set; } - public string time_base { get; set; } - public long start { get; set; } - public string start_time { get; set; } - public long end { get; set; } - public string end_time { get; set; } - public Dictionary<string, string> tags { get; set; } - } - - /// <summary> - /// Represents a stream within the output - /// </summary> - public class MediaStreamInfo - { - /// <summary> - /// Gets or sets the index. - /// </summary> - /// <value>The index.</value> - public int index { get; set; } - - /// <summary> - /// Gets or sets the profile. - /// </summary> - /// <value>The profile.</value> - public string profile { get; set; } - - /// <summary> - /// Gets or sets the codec_name. - /// </summary> - /// <value>The codec_name.</value> - public string codec_name { get; set; } - - /// <summary> - /// Gets or sets the codec_long_name. - /// </summary> - /// <value>The codec_long_name.</value> - public string codec_long_name { get; set; } - - /// <summary> - /// Gets or sets the codec_type. - /// </summary> - /// <value>The codec_type.</value> - public string codec_type { get; set; } - - /// <summary> - /// Gets or sets the sample_rate. - /// </summary> - /// <value>The sample_rate.</value> - public string sample_rate { get; set; } - - /// <summary> - /// Gets or sets the channels. - /// </summary> - /// <value>The channels.</value> - public int channels { get; set; } - - /// <summary> - /// Gets or sets the channel_layout. - /// </summary> - /// <value>The channel_layout.</value> - public string channel_layout { get; set; } - - /// <summary> - /// Gets or sets the avg_frame_rate. - /// </summary> - /// <value>The avg_frame_rate.</value> - public string avg_frame_rate { get; set; } - - /// <summary> - /// Gets or sets the duration. - /// </summary> - /// <value>The duration.</value> - public string duration { get; set; } - - /// <summary> - /// Gets or sets the bit_rate. - /// </summary> - /// <value>The bit_rate.</value> - public string bit_rate { get; set; } - - /// <summary> - /// Gets or sets the width. - /// </summary> - /// <value>The width.</value> - public int width { get; set; } - - /// <summary> - /// Gets or sets the refs. - /// </summary> - /// <value>The refs.</value> - public int refs { get; set; } - - /// <summary> - /// Gets or sets the height. - /// </summary> - /// <value>The height.</value> - public int height { get; set; } - - /// <summary> - /// Gets or sets the display_aspect_ratio. - /// </summary> - /// <value>The display_aspect_ratio.</value> - public string display_aspect_ratio { get; set; } - - /// <summary> - /// Gets or sets the tags. - /// </summary> - /// <value>The tags.</value> - public Dictionary<string, string> tags { get; set; } - - /// <summary> - /// Gets or sets the bits_per_sample. - /// </summary> - /// <value>The bits_per_sample.</value> - public int bits_per_sample { get; set; } - - /// <summary> - /// Gets or sets the bits_per_raw_sample. - /// </summary> - /// <value>The bits_per_raw_sample.</value> - public int bits_per_raw_sample { get; set; } - - /// <summary> - /// Gets or sets the r_frame_rate. - /// </summary> - /// <value>The r_frame_rate.</value> - public string r_frame_rate { get; set; } - - /// <summary> - /// Gets or sets the has_b_frames. - /// </summary> - /// <value>The has_b_frames.</value> - public int has_b_frames { get; set; } - - /// <summary> - /// Gets or sets the sample_aspect_ratio. - /// </summary> - /// <value>The sample_aspect_ratio.</value> - public string sample_aspect_ratio { get; set; } - - /// <summary> - /// Gets or sets the pix_fmt. - /// </summary> - /// <value>The pix_fmt.</value> - public string pix_fmt { get; set; } - - /// <summary> - /// Gets or sets the level. - /// </summary> - /// <value>The level.</value> - public int level { get; set; } - - /// <summary> - /// Gets or sets the time_base. - /// </summary> - /// <value>The time_base.</value> - public string time_base { get; set; } - - /// <summary> - /// Gets or sets the start_time. - /// </summary> - /// <value>The start_time.</value> - public string start_time { get; set; } - - /// <summary> - /// Gets or sets the codec_time_base. - /// </summary> - /// <value>The codec_time_base.</value> - public string codec_time_base { get; set; } - - /// <summary> - /// Gets or sets the codec_tag. - /// </summary> - /// <value>The codec_tag.</value> - public string codec_tag { get; set; } - - /// <summary> - /// Gets or sets the codec_tag_string. - /// </summary> - /// <value>The codec_tag_string.</value> - public string codec_tag_string { get; set; } - - /// <summary> - /// Gets or sets the sample_fmt. - /// </summary> - /// <value>The sample_fmt.</value> - public string sample_fmt { get; set; } - - /// <summary> - /// Gets or sets the dmix_mode. - /// </summary> - /// <value>The dmix_mode.</value> - public string dmix_mode { get; set; } - - /// <summary> - /// Gets or sets the start_pts. - /// </summary> - /// <value>The start_pts.</value> - public string start_pts { get; set; } - - /// <summary> - /// Gets or sets the is_avc. - /// </summary> - /// <value>The is_avc.</value> - public string is_avc { get; set; } - - /// <summary> - /// Gets or sets the nal_length_size. - /// </summary> - /// <value>The nal_length_size.</value> - public string nal_length_size { get; set; } - - /// <summary> - /// Gets or sets the ltrt_cmixlev. - /// </summary> - /// <value>The ltrt_cmixlev.</value> - public string ltrt_cmixlev { get; set; } - - /// <summary> - /// Gets or sets the ltrt_surmixlev. - /// </summary> - /// <value>The ltrt_surmixlev.</value> - public string ltrt_surmixlev { get; set; } - - /// <summary> - /// Gets or sets the loro_cmixlev. - /// </summary> - /// <value>The loro_cmixlev.</value> - public string loro_cmixlev { get; set; } - - /// <summary> - /// Gets or sets the loro_surmixlev. - /// </summary> - /// <value>The loro_surmixlev.</value> - public string loro_surmixlev { get; set; } - - public string field_order { get; set; } - - /// <summary> - /// Gets or sets the disposition. - /// </summary> - /// <value>The disposition.</value> - public Dictionary<string, string> disposition { get; set; } - } - - /// <summary> - /// Class MediaFormat - /// </summary> - public class MediaFormatInfo - { - /// <summary> - /// Gets or sets the filename. - /// </summary> - /// <value>The filename.</value> - public string filename { get; set; } - - /// <summary> - /// Gets or sets the nb_streams. - /// </summary> - /// <value>The nb_streams.</value> - public int nb_streams { get; set; } - - /// <summary> - /// Gets or sets the format_name. - /// </summary> - /// <value>The format_name.</value> - public string format_name { get; set; } - - /// <summary> - /// Gets or sets the format_long_name. - /// </summary> - /// <value>The format_long_name.</value> - public string format_long_name { get; set; } - - /// <summary> - /// Gets or sets the start_time. - /// </summary> - /// <value>The start_time.</value> - public string start_time { get; set; } - - /// <summary> - /// Gets or sets the duration. - /// </summary> - /// <value>The duration.</value> - public string duration { get; set; } - - /// <summary> - /// Gets or sets the size. - /// </summary> - /// <value>The size.</value> - public string size { get; set; } - - /// <summary> - /// Gets or sets the bit_rate. - /// </summary> - /// <value>The bit_rate.</value> - public string bit_rate { get; set; } - - /// <summary> - /// Gets or sets the probe_score. - /// </summary> - /// <value>The probe_score.</value> - public int probe_score { get; set; } - - /// <summary> - /// Gets or sets the tags. - /// </summary> - /// <value>The tags.</value> - public Dictionary<string, string> tags { get; set; } - } -} diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs deleted file mode 100644 index 023100f44..000000000 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ /dev/null @@ -1,1389 +0,0 @@ -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; -using MediaBrowser.Model.IO; - -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Probing -{ - public class ProbeResultNormalizer - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IMemoryStreamFactory _memoryStreamProvider; - - public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, IMemoryStreamFactory memoryStreamProvider) - { - _logger = logger; - _fileSystem = fileSystem; - _memoryStreamProvider = memoryStreamProvider; - } - - public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType videoType, bool isAudio, string path, MediaProtocol protocol) - { - var info = new MediaInfo - { - Path = path, - Protocol = protocol - }; - - FFProbeHelpers.NormalizeFFProbeResult(data); - SetSize(data, info); - - var internalStreams = data.streams ?? new MediaStreamInfo[] { }; - - info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.format)) - .Where(i => i != null) - // Drop subtitle streams if we don't know the codec because it will just cause failures if we don't know how to handle them - .Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec)) - .ToList(); - - if (data.format != null) - { - info.Container = NormalizeFormat(data.format.format_name); - - if (!string.IsNullOrEmpty(data.format.bit_rate)) - { - int value; - if (int.TryParse(data.format.bit_rate, NumberStyles.Any, _usCulture, out value)) - { - info.Bitrate = value; - } - } - } - - var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - var tagStreamType = isAudio ? "audio" : "video"; - - if (data.streams != null) - { - var tagStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, tagStreamType, StringComparison.OrdinalIgnoreCase)); - - if (tagStream != null && tagStream.tags != null) - { - foreach (var pair in tagStream.tags) - { - tags[pair.Key] = pair.Value; - } - } - } - - if (data.format != null && data.format.tags != null) - { - foreach (var pair in data.format.tags) - { - tags[pair.Key] = pair.Value; - } - } - - FetchGenres(info, tags); - var overview = FFProbeHelpers.GetDictionaryValue(tags, "synopsis"); - - if (string.IsNullOrWhiteSpace(overview)) - { - overview = FFProbeHelpers.GetDictionaryValue(tags, "description"); - } - if (string.IsNullOrWhiteSpace(overview)) - { - overview = FFProbeHelpers.GetDictionaryValue(tags, "desc"); - } - - if (!string.IsNullOrWhiteSpace(overview)) - { - info.Overview = overview; - } - - var title = FFProbeHelpers.GetDictionaryValue(tags, "title"); - if (!string.IsNullOrWhiteSpace(title)) - { - info.Name = title; - } - - info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date"); - - // Several different forms of retaildate - info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ?? - FFProbeHelpers.GetDictionaryDateTime(tags, "date"); - - if (isAudio) - { - SetAudioRuntimeTicks(data, info); - - // tags are normally located under data.format, but we've seen some cases with ogg where they're part of the info stream - // so let's create a combined list of both - - SetAudioInfoFromTags(info, tags); - } - else - { - FetchStudios(info, tags, "copyright"); - - var iTunEXTC = FFProbeHelpers.GetDictionaryValue(tags, "iTunEXTC"); - if (!string.IsNullOrWhiteSpace(iTunEXTC)) - { - var parts = iTunEXTC.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - // Example - // mpaa|G|100|For crude humor - if (parts.Length > 1) - { - info.OfficialRating = parts[1]; - - if (parts.Length > 3) - { - info.OfficialRatingDescription = parts[3]; - } - } - } - - var itunesXml = FFProbeHelpers.GetDictionaryValue(tags, "iTunMOVI"); - if (!string.IsNullOrWhiteSpace(itunesXml)) - { - FetchFromItunesInfo(itunesXml, info); - } - - if (data.format != null && !string.IsNullOrEmpty(data.format.duration)) - { - info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks; - } - - FetchWtvInfo(info, data); - - if (data.Chapters != null) - { - info.Chapters = data.Chapters.Select(GetChapterInfo).ToList(); - } - - ExtractTimestamp(info); - - var stereoMode = GetDictionaryValue(tags, "stereo_mode"); - if (string.Equals(stereoMode, "left_right", StringComparison.OrdinalIgnoreCase)) - { - info.Video3DFormat = Video3DFormat.FullSideBySide; - } - - foreach (var mediaStream in info.MediaStreams) - { - if (mediaStream.Type == MediaStreamType.Audio && !mediaStream.BitRate.HasValue) - { - mediaStream.BitRate = GetEstimatedAudioBitrate(mediaStream.Codec, mediaStream.Channels); - } - } - - var videoStreamsBitrate = info.MediaStreams.Where(i => i.Type == MediaStreamType.Video).Select(i => i.BitRate ?? 0).Sum(); - // If ffprobe reported the container bitrate as being the same as the video stream bitrate, then it's wrong - if (videoStreamsBitrate == (info.Bitrate ?? 0)) - { - info.InferTotalBitrate(true); - } - } - - return info; - } - - private string NormalizeFormat(string format) - { - if (string.IsNullOrWhiteSpace(format)) - { - return null; - } - - if (string.Equals(format, "mpegvideo", StringComparison.OrdinalIgnoreCase)) - { - return "mpeg"; - } - - format = format.Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); - - return format; - } - - private int? GetEstimatedAudioBitrate(string codec, int? channels) - { - if (!channels.HasValue) - { - return null; - } - - var channelsValue = channels.Value; - - if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase) || - string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) - { - if (channelsValue <= 2) - { - return 192000; - } - - if (channelsValue >= 5) - { - return 320000; - } - } - - return null; - } - - private void FetchFromItunesInfo(string xml, MediaInfo info) - { - // Make things simpler and strip out the dtd - var plistIndex = xml.IndexOf("<plist", StringComparison.OrdinalIgnoreCase); - - if (plistIndex != -1) - { - xml = xml.Substring(plistIndex); - } - - xml = "<?xml version=\"1.0\"?>" + xml; - - // <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>cast</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Blender Foundation</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Janus Bager Kristensen</string>\n\t\t</dict>\n\t</array>\n\t<key>directors</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Sacha Goedegebure</string>\n\t\t</dict>\n\t</array>\n\t<key>studio</key>\n\t<string>Blender Foundation</string>\n</dict>\n</plist>\n - using (var stream = _memoryStreamProvider.CreateNew(Encoding.UTF8.GetBytes(xml))) - { - using (var streamReader = new StreamReader(stream)) - { - try - { - // Use XmlReader for best performance - using (var reader = XmlReader.Create(streamReader)) - { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "dict": - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subtree = reader.ReadSubtree()) - { - ReadFromDictNode(subtree, info); - } - break; - default: - reader.Skip(); - break; - } - } - else - { - reader.Read(); - } - } - } - } - catch (XmlException) - { - // I've seen probe examples where the iTunMOVI value is just "<" - // So we should not allow this to fail the entire probing operation - } - } - } - } - - private void ReadFromDictNode(XmlReader reader, MediaInfo info) - { - string currentKey = null; - List<NameValuePair> pairs = new List<NameValuePair>(); - - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "key": - if (!string.IsNullOrWhiteSpace(currentKey)) - { - ProcessPairs(currentKey, pairs, info); - } - currentKey = reader.ReadElementContentAsString(); - pairs = new List<NameValuePair>(); - break; - case "string": - var value = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(value)) - { - pairs.Add(new NameValuePair - { - Name = value, - Value = value - }); - } - break; - case "array": - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subtree = reader.ReadSubtree()) - { - if (!string.IsNullOrWhiteSpace(currentKey)) - { - pairs.AddRange(ReadValueArray(subtree)); - } - } - break; - default: - reader.Skip(); - break; - } - } - else - { - reader.Read(); - } - } - } - - private List<NameValuePair> ReadValueArray(XmlReader reader) - { - - List<NameValuePair> pairs = new List<NameValuePair>(); - - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "dict": - - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subtree = reader.ReadSubtree()) - { - var dict = GetNameValuePair(subtree); - if (dict != null) - { - pairs.Add(dict); - } - } - break; - default: - reader.Skip(); - break; - } - } - else - { - reader.Read(); - } - } - - return pairs; - } - - private void ProcessPairs(string key, List<NameValuePair> pairs, MediaInfo info) - { - if (string.Equals(key, "studio", StringComparison.OrdinalIgnoreCase)) - { - foreach (var pair in pairs) - { - info.Studios.Add(pair.Value); - } - - info.Studios = info.Studios - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - } - else if (string.Equals(key, "screenwriters", StringComparison.OrdinalIgnoreCase)) - { - foreach (var pair in pairs) - { - info.People.Add(new BaseItemPerson - { - Name = pair.Value, - Type = PersonType.Writer - }); - } - } - else if (string.Equals(key, "producers", StringComparison.OrdinalIgnoreCase)) - { - foreach (var pair in pairs) - { - info.People.Add(new BaseItemPerson - { - Name = pair.Value, - Type = PersonType.Producer - }); - } - } - else if (string.Equals(key, "directors", StringComparison.OrdinalIgnoreCase)) - { - foreach (var pair in pairs) - { - info.People.Add(new BaseItemPerson - { - Name = pair.Value, - Type = PersonType.Director - }); - } - } - } - - private NameValuePair GetNameValuePair(XmlReader reader) - { - string name = null; - string value = null; - - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "key": - name = reader.ReadElementContentAsString(); - break; - case "string": - value = reader.ReadElementContentAsString(); - break; - default: - reader.Skip(); - break; - } - } - else - { - reader.Read(); - } - } - - if (string.IsNullOrWhiteSpace(name) || - string.IsNullOrWhiteSpace(value)) - { - return null; - } - - return new NameValuePair - { - Name = name, - Value = value - }; - } - - private string NormalizeSubtitleCodec(string codec) - { - if (string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase)) - { - codec = "dvbsub"; - } - else if ((codec ?? string.Empty).IndexOf("PGS", StringComparison.OrdinalIgnoreCase) != -1) - { - codec = "PGSSUB"; - } - else if ((codec ?? string.Empty).IndexOf("DVD", StringComparison.OrdinalIgnoreCase) != -1) - { - codec = "DVDSUB"; - } - - return codec; - } - - /// <summary> - /// Converts ffprobe stream info to our MediaStream class - /// </summary> - /// <param name="isAudio">if set to <c>true</c> [is info].</param> - /// <param name="streamInfo">The stream info.</param> - /// <param name="formatInfo">The format info.</param> - /// <returns>MediaStream.</returns> - private MediaStream GetMediaStream(bool isAudio, MediaStreamInfo streamInfo, MediaFormatInfo formatInfo) - { - // These are mp4 chapters - if (string.Equals(streamInfo.codec_name, "mov_text", StringComparison.OrdinalIgnoreCase)) - { - // Edit: but these are also sometimes subtitles? - //return null; - } - - var stream = new MediaStream - { - Codec = streamInfo.codec_name, - Profile = streamInfo.profile, - Level = streamInfo.level, - Index = streamInfo.index, - PixelFormat = streamInfo.pix_fmt, - NalLengthSize = streamInfo.nal_length_size, - TimeBase = streamInfo.time_base, - CodecTimeBase = streamInfo.codec_time_base - }; - - if (string.Equals(streamInfo.is_avc, "true", StringComparison.OrdinalIgnoreCase) || - string.Equals(streamInfo.is_avc, "1", StringComparison.OrdinalIgnoreCase)) - { - stream.IsAVC = true; - } - else if (string.Equals(streamInfo.is_avc, "false", StringComparison.OrdinalIgnoreCase) || - string.Equals(streamInfo.is_avc, "0", StringComparison.OrdinalIgnoreCase)) - { - stream.IsAVC = false; - } - - if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase)) - { - stream.IsInterlaced = true; - } - - // Filter out junk - if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) - { - stream.CodecTag = streamInfo.codec_tag_string; - } - - if (streamInfo.tags != null) - { - stream.Language = GetDictionaryValue(streamInfo.tags, "language"); - stream.Comment = GetDictionaryValue(streamInfo.tags, "comment"); - stream.Title = GetDictionaryValue(streamInfo.tags, "title"); - } - - if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Audio; - - stream.Channels = streamInfo.channels; - - if (!string.IsNullOrEmpty(streamInfo.sample_rate)) - { - int value; - if (int.TryParse(streamInfo.sample_rate, NumberStyles.Any, _usCulture, out value)) - { - stream.SampleRate = value; - } - } - - stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout); - - if (streamInfo.bits_per_sample > 0) - { - stream.BitDepth = streamInfo.bits_per_sample; - } - else if (streamInfo.bits_per_raw_sample > 0) - { - stream.BitDepth = streamInfo.bits_per_raw_sample; - } - } - else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.Subtitle; - stream.Codec = NormalizeSubtitleCodec(stream.Codec); - } - else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = isAudio || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase) - ? MediaStreamType.EmbeddedImage - : MediaStreamType.Video; - - stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); - stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); - - if (isAudio || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || - string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)) - { - stream.Type = MediaStreamType.EmbeddedImage; - } - else if (string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)) - { - // How to differentiate between video and embedded image? - // The only difference I've seen thus far is presence of codec tag, also embedded images have high (unusual) framerates - if (!string.IsNullOrWhiteSpace(stream.CodecTag)) - { - stream.Type = MediaStreamType.Video; - } - else - { - stream.Type = MediaStreamType.EmbeddedImage; - } - } - else - { - stream.Type = MediaStreamType.Video; - } - - stream.Width = streamInfo.width; - stream.Height = streamInfo.height; - stream.AspectRatio = GetAspectRatio(streamInfo); - - if (streamInfo.bits_per_sample > 0) - { - stream.BitDepth = streamInfo.bits_per_sample; - } - else if (streamInfo.bits_per_raw_sample > 0) - { - stream.BitDepth = streamInfo.bits_per_raw_sample; - } - - //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || - // string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) || - // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); - - // http://stackoverflow.com/questions/17353387/how-to-detect-anamorphic-video-with-ffprobe - stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase); - - if (streamInfo.refs > 0) - { - stream.RefFrames = streamInfo.refs; - } - } - else - { - return null; - } - - // Get stream bitrate - var bitrate = 0; - - if (!string.IsNullOrEmpty(streamInfo.bit_rate)) - { - int value; - if (int.TryParse(streamInfo.bit_rate, NumberStyles.Any, _usCulture, out value)) - { - bitrate = value; - } - } - - if (bitrate == 0 && formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video) - { - // If the stream info doesn't have a bitrate get the value from the media format info - int value; - if (int.TryParse(formatInfo.bit_rate, NumberStyles.Any, _usCulture, out value)) - { - bitrate = value; - } - } - - if (bitrate > 0) - { - stream.BitRate = bitrate; - } - - if (streamInfo.disposition != null) - { - var isDefault = GetDictionaryValue(streamInfo.disposition, "default"); - var isForced = GetDictionaryValue(streamInfo.disposition, "forced"); - - stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase); - - stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); - } - - NormalizeStreamTitle(stream); - - return stream; - } - - private void NormalizeStreamTitle(MediaStream stream) - { - if (string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase)) - { - stream.Title = null; - } - - if (stream.Type == MediaStreamType.EmbeddedImage) - { - stream.Title = null; - } - } - - /// <summary> - /// Gets a string from an FFProbeResult tags dictionary - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="key">The key.</param> - /// <returns>System.String.</returns> - private string GetDictionaryValue(Dictionary<string, string> tags, string key) - { - if (tags == null) - { - return null; - } - - string val; - - tags.TryGetValue(key, out val); - return val; - } - - private string ParseChannelLayout(string input) - { - if (string.IsNullOrEmpty(input)) - { - return input; - } - - return input.Split('(').FirstOrDefault(); - } - - private string GetAspectRatio(MediaStreamInfo info) - { - var original = info.display_aspect_ratio; - - int height; - int width; - - var parts = (original ?? string.Empty).Split(':'); - if (!(parts.Length == 2 && - int.TryParse(parts[0], NumberStyles.Any, _usCulture, out width) && - int.TryParse(parts[1], NumberStyles.Any, _usCulture, out height) && - width > 0 && - height > 0)) - { - width = info.width; - height = info.height; - } - - if (width > 0 && height > 0) - { - double ratio = width; - ratio /= height; - - if (IsClose(ratio, 1.777777778, .03)) - { - return "16:9"; - } - - if (IsClose(ratio, 1.3333333333, .05)) - { - return "4:3"; - } - - if (IsClose(ratio, 1.41)) - { - return "1.41:1"; - } - - if (IsClose(ratio, 1.5)) - { - return "1.5:1"; - } - - if (IsClose(ratio, 1.6)) - { - return "1.6:1"; - } - - if (IsClose(ratio, 1.66666666667)) - { - return "5:3"; - } - - if (IsClose(ratio, 1.85, .02)) - { - return "1.85:1"; - } - - if (IsClose(ratio, 2.35, .025)) - { - return "2.35:1"; - } - - if (IsClose(ratio, 2.4, .025)) - { - return "2.40:1"; - } - } - - return original; - } - - private bool IsClose(double d1, double d2, double variance = .005) - { - return Math.Abs(d1 - d2) <= variance; - } - - /// <summary> - /// Gets a frame rate from a string value in ffprobe output - /// This could be a number or in the format of 2997/125. - /// </summary> - /// <param name="value">The value.</param> - /// <returns>System.Nullable{System.Single}.</returns> - private float? GetFrameRate(string value) - { - if (!string.IsNullOrEmpty(value)) - { - var parts = value.Split('/'); - - float result; - - if (parts.Length == 2) - { - result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture); - } - else - { - result = float.Parse(parts[0], _usCulture); - } - - return float.IsNaN(result) ? (float?)null : result; - } - - return null; - } - - private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data) - { - if (result.streams != null) - { - // Get the first info stream - var stream = result.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); - - if (stream != null) - { - // Get duration from stream properties - var duration = stream.duration; - - // If it's not there go into format properties - if (string.IsNullOrEmpty(duration)) - { - duration = result.format.duration; - } - - // If we got something, parse it - if (!string.IsNullOrEmpty(duration)) - { - data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks; - } - } - } - } - - private void SetSize(InternalMediaInfoResult data, Model.MediaInfo.MediaInfo info) - { - if (data.format != null) - { - if (!string.IsNullOrEmpty(data.format.size)) - { - info.Size = long.Parse(data.format.size, _usCulture); - } - else - { - info.Size = null; - } - } - } - - private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags) - { - var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer"); - if (!string.IsNullOrWhiteSpace(composer)) - { - foreach (var person in Split(composer, false)) - { - audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer }); - } - } - - //var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor"); - //if (!string.IsNullOrWhiteSpace(conductor)) - //{ - // foreach (var person in Split(conductor, false)) - // { - // audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor }); - // } - //} - - //var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist"); - //if (!string.IsNullOrWhiteSpace(lyricist)) - //{ - // foreach (var person in Split(lyricist, false)) - // { - // audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist }); - // } - //} - - // Check for writer some music is tagged that way as alternative to composer/lyricist - var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer"); - - if (!string.IsNullOrWhiteSpace(writer)) - { - foreach (var person in Split(writer, false)) - { - audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer }); - } - } - - audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album"); - - var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists"); - - if (!string.IsNullOrWhiteSpace(artists)) - { - audio.Artists = SplitArtists(artists, new[] { '/', ';' }, false) - .DistinctNames() - .ToList(); - } - else - { - var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist"); - if (string.IsNullOrWhiteSpace(artist)) - { - audio.Artists.Clear(); - } - else - { - audio.Artists = SplitArtists(artist, _nameDelimiters, true) - .DistinctNames() - .ToList(); - } - } - - var albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "albumartist"); - if (string.IsNullOrWhiteSpace(albumArtist)) - { - albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist"); - } - if (string.IsNullOrWhiteSpace(albumArtist)) - { - albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist"); - } - - if (string.IsNullOrWhiteSpace(albumArtist)) - { - audio.AlbumArtists = new List<string>(); - } - else - { - audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true) - .DistinctNames() - .ToList(); - - } - - if (audio.AlbumArtists.Count == 0) - { - audio.AlbumArtists = audio.Artists.Take(1).ToList(); - } - - // Track number - audio.IndexNumber = GetDictionaryDiscValue(tags, "track"); - - // Disc number - audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc"); - - // If we don't have a ProductionYear try and get it from PremiereDate - if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) - { - audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year; - } - - // There's several values in tags may or may not be present - FetchStudios(audio, tags, "organization"); - FetchStudios(audio, tags, "ensemble"); - FetchStudios(audio, tags, "publisher"); - FetchStudios(audio, tags, "label"); - - // These support mulitple values, but for now we only store the first. - var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); - audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, mb); - - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); - audio.SetProviderId(MetadataProviders.MusicBrainzArtist, mb); - - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); - audio.SetProviderId(MetadataProviders.MusicBrainzAlbum, mb); - - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); - audio.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, mb); - - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")); - if (mb == null) mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); - audio.SetProviderId(MetadataProviders.MusicBrainzTrack, mb); - } - - private string GetMultipleMusicBrainzId(string value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return null; - } - - return value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries) - .Select(i => i.Trim()) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); - } - - private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' }; - - /// <summary> - /// Splits the specified val. - /// </summary> - /// <param name="val">The val.</param> - /// <param name="allowCommaDelimiter">if set to <c>true</c> [allow comma delimiter].</param> - /// <returns>System.String[][].</returns> - private IEnumerable<string> Split(string val, bool allowCommaDelimiter) - { - // Only use the comma as a delimeter if there are no slashes or pipes. - // We want to be careful not to split names that have commas in them - var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? - _nameDelimiters : - new[] { ',' }; - - return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()); - } - - private const string ArtistReplaceValue = " | "; - - private IEnumerable<string> SplitArtists(string val, char[] delimiters, bool splitFeaturing) - { - if (splitFeaturing) - { - val = val.Replace(" featuring ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase) - .Replace(" feat. ", ArtistReplaceValue, StringComparison.OrdinalIgnoreCase); - } - - var artistsFound = new List<string>(); - - foreach (var whitelistArtist in GetSplitWhitelist()) - { - var originalVal = val; - val = val.Replace(whitelistArtist, "|", StringComparison.OrdinalIgnoreCase); - - if (!string.Equals(originalVal, val, StringComparison.OrdinalIgnoreCase)) - { - artistsFound.Add(whitelistArtist); - } - } - - var artists = val.Split(delimiters, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()); - - artistsFound.AddRange(artists); - return artistsFound; - } - - - private List<string> _splitWhiteList = null; - - private IEnumerable<string> GetSplitWhitelist() - { - if (_splitWhiteList == null) - { - _splitWhiteList = new List<string> - { - "AC/DC" - }; - } - - return _splitWhiteList; - } - - /// <summary> - /// Gets the studios from the tags collection - /// </summary> - /// <param name="info">The info.</param> - /// <param name="tags">The tags.</param> - /// <param name="tagName">Name of the tag.</param> - private void FetchStudios(MediaInfo info, Dictionary<string, string> tags, string tagName) - { - var val = FFProbeHelpers.GetDictionaryValue(tags, tagName); - - if (!string.IsNullOrEmpty(val)) - { - var studios = Split(val, true); - - foreach (var studio in studios) - { - // Sometimes the artist name is listed here, account for that - if (info.Artists.Contains(studio, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - if (info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - info.Studios.Add(studio); - } - - info.Studios = info.Studios - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - } - - /// <summary> - /// Gets the genres from the tags collection - /// </summary> - /// <param name="info">The information.</param> - /// <param name="tags">The tags.</param> - private void FetchGenres(MediaInfo info, Dictionary<string, string> tags) - { - var val = FFProbeHelpers.GetDictionaryValue(tags, "genre"); - - if (!string.IsNullOrEmpty(val)) - { - foreach (var genre in Split(val, true)) - { - info.Genres.Add(genre); - } - - info.Genres = info.Genres - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - } - - /// <summary> - /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3' - /// </summary> - /// <param name="tags">The tags.</param> - /// <param name="tagName">Name of the tag.</param> - /// <returns>System.Nullable{System.Int32}.</returns> - private int? GetDictionaryDiscValue(Dictionary<string, string> tags, string tagName) - { - var disc = FFProbeHelpers.GetDictionaryValue(tags, tagName); - - if (!string.IsNullOrEmpty(disc)) - { - disc = disc.Split('/')[0]; - - int num; - - if (int.TryParse(disc, out num)) - { - return num; - } - } - - return null; - } - - private ChapterInfo GetChapterInfo(MediaChapter chapter) - { - var info = new ChapterInfo(); - - if (chapter.tags != null) - { - string name; - if (chapter.tags.TryGetValue("title", out name)) - { - info.Name = name; - } - } - - // Limit accuracy to milliseconds to match xml saving - var secondsString = chapter.start_time; - double seconds; - - if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out seconds)) - { - var ms = Math.Round(TimeSpan.FromSeconds(seconds).TotalMilliseconds); - info.StartPositionTicks = TimeSpan.FromMilliseconds(ms).Ticks; - } - - return info; - } - - private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames) - - private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data) - { - if (data.format == null || data.format.tags == null) - { - return; - } - - var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre"); - - if (!string.IsNullOrWhiteSpace(genres)) - { - var genreList = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => i.Trim()) - .ToList(); - - // If this is empty then don't overwrite genres that might have been fetched earlier - if (genreList.Count > 0) - { - video.Genres = genreList; - } - } - - var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); - - if (!string.IsNullOrWhiteSpace(officialRating)) - { - video.OfficialRating = officialRating; - } - - var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); - - if (!string.IsNullOrEmpty(people)) - { - video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonType.Actor }) - .ToList(); - } - - var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); - if (!string.IsNullOrWhiteSpace(year)) - { - int val; - - if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val)) - { - video.ProductionYear = val; - } - } - - var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime"); - if (!string.IsNullOrWhiteSpace(premiereDateString)) - { - DateTime val; - - // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ - // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None) - if (DateTime.TryParse(year, null, DateTimeStyles.None, out val)) - { - video.PremiereDate = val.ToUniversalTime(); - } - } - - var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); - - var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle"); - - // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ - - // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910 - // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION - // OR -> COMMENT. SUBTITLE: DESCRIPTION - // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S] - // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S] - if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename - { - string[] parts = description.Split(':'); - if (parts.Length > 0) - { - string subtitle = parts[0]; - try - { - if (subtitle.Contains("/")) // It contains a episode number and season number - { - string[] numbers = subtitle.Split(' '); - video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]); - int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]); - - description = String.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it - } - else - throw new Exception(); // Switch to default parsing - } - catch // Default parsing - { - if (subtitle.Contains(".")) // skip the comment, keep the subtitle - description = String.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first - else - description = subtitle.Trim(); // Clean up whitespaces and save it - } - } - } - - if (!string.IsNullOrWhiteSpace(description)) - { - video.Overview = description; - } - } - - private void ExtractTimestamp(MediaInfo video) - { - if (video.VideoType == VideoType.VideoFile) - { - if (string.Equals(video.Container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) || - string.Equals(video.Container, "m2ts", StringComparison.OrdinalIgnoreCase) || - string.Equals(video.Container, "ts", StringComparison.OrdinalIgnoreCase)) - { - try - { - video.Timestamp = GetMpegTimestamp(video.Path); - - _logger.Debug("Video has {0} timestamp", video.Timestamp); - } - catch (Exception ex) - { - _logger.ErrorException("Error extracting timestamp info from {0}", ex, video.Path); - video.Timestamp = null; - } - } - } - } - - private TransportStreamTimestamp GetMpegTimestamp(string path) - { - var packetBuffer = new byte['Å']; - - using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read)) - { - fs.Read(packetBuffer, 0, packetBuffer.Length); - } - - if (packetBuffer[0] == 71) - { - return TransportStreamTimestamp.None; - } - - if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71)) - { - if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0)) - { - return TransportStreamTimestamp.Zero; - } - - return TransportStreamTimestamp.Valid; - } - - return TransportStreamTimestamp.None; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs deleted file mode 100644 index 53f4eb403..000000000 --- a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -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.MediaEncoding")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MediaBrowser.MediaEncoding")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// 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("05f49ab9-2a90-4332-9d41-7817a9cccd90")] - -// 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.*")]
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs deleted file mode 100644 index 6d723a087..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ /dev/null @@ -1,120 +0,0 @@ -using MediaBrowser.Model.Extensions; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class AssParser : ISubtitleParser - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - var eventIndex = 1; - using (var reader = new StreamReader(stream)) - { - string line; - while (reader.ReadLine() != "[Events]") - {} - var headers = ParseFieldHeaders(reader.ReadLine()); - - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - if(line.StartsWith("[")) - break; - if(string.IsNullOrEmpty(line)) - continue; - var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) }; - eventIndex++; - var sections = line.Substring(10).Split(','); - - subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]); - subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]); - - subEvent.Text = string.Join(",", sections.Skip(headers["Text"])); - RemoteNativeFormatting(subEvent); - - subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase); - - trackInfo.TrackEvents.Add(subEvent); - } - } - return trackInfo; - } - - long GetTicks(string time) - { - TimeSpan span; - return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out span) - ? span.Ticks: 0; - } - - private Dictionary<string,int> ParseFieldHeaders(string line) { - var fields = line.Substring(8).Split(',').Select(x=>x.Trim()).ToList(); - - var result = new Dictionary<string, int> { - {"Start", fields.IndexOf("Start")}, - {"End", fields.IndexOf("End")}, - {"Text", fields.IndexOf("Text")} - }; - return result; - } - - /// <summary> - /// Credit: https://github.com/SubtitleEdit/subtitleedit/blob/master/src/Logic/SubtitleFormats/AdvancedSubStationAlpha.cs - /// </summary> - private void RemoteNativeFormatting(SubtitleTrackEvent p) - { - int indexOfBegin = p.Text.IndexOf('{'); - string pre = string.Empty; - while (indexOfBegin >= 0 && p.Text.IndexOf('}') > indexOfBegin) - { - string s = p.Text.Substring(indexOfBegin); - if (s.StartsWith("{\\an1}", StringComparison.Ordinal) || - s.StartsWith("{\\an2}", StringComparison.Ordinal) || - s.StartsWith("{\\an3}", StringComparison.Ordinal) || - s.StartsWith("{\\an4}", StringComparison.Ordinal) || - s.StartsWith("{\\an5}", StringComparison.Ordinal) || - s.StartsWith("{\\an6}", StringComparison.Ordinal) || - s.StartsWith("{\\an7}", StringComparison.Ordinal) || - s.StartsWith("{\\an8}", StringComparison.Ordinal) || - s.StartsWith("{\\an9}", StringComparison.Ordinal)) - { - pre = s.Substring(0, 6); - } - else if (s.StartsWith("{\\an1\\", StringComparison.Ordinal) || - s.StartsWith("{\\an2\\", StringComparison.Ordinal) || - s.StartsWith("{\\an3\\", StringComparison.Ordinal) || - s.StartsWith("{\\an4\\", StringComparison.Ordinal) || - s.StartsWith("{\\an5\\", StringComparison.Ordinal) || - s.StartsWith("{\\an6\\", StringComparison.Ordinal) || - s.StartsWith("{\\an7\\", StringComparison.Ordinal) || - s.StartsWith("{\\an8\\", StringComparison.Ordinal) || - s.StartsWith("{\\an9\\", StringComparison.Ordinal)) - { - pre = s.Substring(0, 5) + "}"; - } - int indexOfEnd = p.Text.IndexOf('}'); - p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1); - - indexOfBegin = p.Text.IndexOf('{'); - } - p.Text = pre + p.Text; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs b/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs deleted file mode 100644 index 973c653a4..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public static class ConfigurationExtension - { - public static SubtitleOptions GetSubtitleConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration<SubtitleOptions>("subtitles"); - } - } - - public class SubtitleConfigurationFactory : IConfigurationFactory - { - public IEnumerable<ConfigurationStore> GetConfigurations() - { - return new List<ConfigurationStore> - { - new ConfigurationStore - { - Key = "subtitles", - ConfigurationType = typeof (SubtitleOptions) - } - }; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs deleted file mode 100644 index 75de81f46..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public interface ISubtitleParser - { - /// <summary> - /// Parses the specified stream. - /// </summary> - /// <param name="stream">The stream.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>SubtitleTrackInfo.</returns> - SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs deleted file mode 100644 index e28da9185..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.IO; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - /// <summary> - /// Interface ISubtitleWriter - /// </summary> - public interface ISubtitleWriter - { - /// <summary> - /// Writes the specified information. - /// </summary> - /// <param name="info">The information.</param> - /// <param name="stream">The stream.</param> - /// <param name="cancellationToken">The cancellation token.</param> - void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken); - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs deleted file mode 100644 index 474f712f9..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System.IO; -using System.Text; -using System.Threading; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class JsonWriter : ISubtitleWriter - { - private readonly IJsonSerializer _json; - - public JsonWriter(IJsonSerializer json) - { - _json = json; - } - - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - var json = _json.SerializeToString(info); - - writer.Write(json); - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs deleted file mode 100644 index 3954897ca..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs +++ /dev/null @@ -1,349 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; -using OpenSubtitlesHandler; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class OpenSubtitleDownloader : ISubtitleProvider, IDisposable - { - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - private readonly IServerConfigurationManager _config; - private readonly IEncryptionManager _encryption; - - private readonly IJsonSerializer _json; - private readonly IFileSystem _fileSystem; - - public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption, IJsonSerializer json, IFileSystem fileSystem) - { - _logger = logManager.GetLogger(GetType().Name); - _httpClient = httpClient; - _config = config; - _encryption = encryption; - _json = json; - _fileSystem = fileSystem; - - _config.NamedConfigurationUpdating += _config_NamedConfigurationUpdating; - - Utilities.HttpClient = httpClient; - OpenSubtitles.SetUserAgent("mediabrowser.tv"); - } - - private const string PasswordHashPrefix = "h:"; - void _config_NamedConfigurationUpdating(object sender, ConfigurationUpdateEventArgs e) - { - if (!string.Equals(e.Key, "subtitles", StringComparison.OrdinalIgnoreCase)) - { - return; - } - - var options = (SubtitleOptions)e.NewConfiguration; - - if (options != null && - !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && - !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - options.OpenSubtitlesPasswordHash = EncryptPassword(options.OpenSubtitlesPasswordHash); - } - } - - private string EncryptPassword(string password) - { - return PasswordHashPrefix + _encryption.EncryptString(password); - } - - private string DecryptPassword(string password) - { - if (password == null || - !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - return string.Empty; - } - - return _encryption.DecryptString(password.Substring(2)); - } - - public string Name - { - get { return "Open Subtitles"; } - } - - private SubtitleOptions GetOptions() - { - return _config.GetSubtitleConfiguration(); - } - - public IEnumerable<VideoContentType> SupportedMediaTypes - { - get - { - var options = GetOptions(); - - if (string.IsNullOrWhiteSpace(options.OpenSubtitlesUsername) || - string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash)) - { - return new VideoContentType[] { }; - } - - return new[] { VideoContentType.Episode, VideoContentType.Movie }; - } - } - - public Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken) - { - return GetSubtitlesInternal(id, GetOptions(), cancellationToken); - } - - private DateTime _lastRateLimitException; - private async Task<SubtitleResponse> GetSubtitlesInternal(string id, - SubtitleOptions options, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - var idParts = id.Split(new[] { '-' }, 3); - - var format = idParts[0]; - var language = idParts[1]; - var ossId = idParts[2]; - - var downloadsList = new[] { int.Parse(ossId, _usCulture) }; - - await Login(cancellationToken).ConfigureAwait(false); - - if ((DateTime.UtcNow - _lastRateLimitException).TotalHours < 1) - { - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - var resultDownLoad = await OpenSubtitles.DownloadSubtitlesAsync(downloadsList, cancellationToken).ConfigureAwait(false); - - if ((resultDownLoad.Status ?? string.Empty).IndexOf("407", StringComparison.OrdinalIgnoreCase) != -1) - { - _lastRateLimitException = DateTime.UtcNow; - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - if (!(resultDownLoad is MethodResponseSubtitleDownload)) - { - throw new Exception("Invalid response type"); - } - - var results = ((MethodResponseSubtitleDownload)resultDownLoad).Results; - - _lastRateLimitException = DateTime.MinValue; - - if (results.Count == 0) - { - var msg = string.Format("Subtitle with Id {0} was not found. Name: {1}. Status: {2}. Message: {3}", - ossId, - resultDownLoad.Name ?? string.Empty, - resultDownLoad.Status ?? string.Empty, - resultDownLoad.Message ?? string.Empty); - - throw new ResourceNotFoundException(msg); - } - - var data = Convert.FromBase64String(results.First().Data); - - return new SubtitleResponse - { - Format = format, - Language = language, - - Stream = new MemoryStream(Utilities.Decompress(new MemoryStream(data))) - }; - } - - private DateTime _lastLogin; - private async Task Login(CancellationToken cancellationToken) - { - if ((DateTime.UtcNow - _lastLogin).TotalSeconds < 60) - { - return; - } - - var options = GetOptions(); - - var user = options.OpenSubtitlesUsername ?? string.Empty; - var password = DecryptPassword(options.OpenSubtitlesPasswordHash); - - var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false); - - if (!(loginResponse is MethodResponseLogIn)) - { - throw new Exception("Authentication to OpenSubtitles failed."); - } - - _lastLogin = DateTime.UtcNow; - } - - public async Task<IEnumerable<NameIdPair>> GetSupportedLanguages(CancellationToken cancellationToken) - { - await Login(cancellationToken).ConfigureAwait(false); - - var result = OpenSubtitles.GetSubLanguages("en"); - if (!(result is MethodResponseGetSubLanguages)) - { - _logger.Error("Invalid response type"); - return new List<NameIdPair>(); - } - - var results = ((MethodResponseGetSubLanguages)result).Languages; - - return results.Select(i => new NameIdPair - { - Name = i.LanguageName, - Id = i.SubLanguageID - }); - } - - private string NormalizeLanguage(string language) - { - // Problem with Greek subtitle download #1349 - if (string.Equals(language, "gre", StringComparison.OrdinalIgnoreCase)) - { - - return "ell"; - } - - return language; - } - - public async Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest request, CancellationToken cancellationToken) - { - var imdbIdText = request.GetProviderId(MetadataProviders.Imdb); - long imdbId = 0; - - switch (request.ContentType) - { - case VideoContentType.Episode: - if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName)) - { - _logger.Debug("Episode information missing"); - return new List<RemoteSubtitleInfo>(); - } - break; - case VideoContentType.Movie: - if (string.IsNullOrEmpty(request.Name)) - { - _logger.Debug("Movie name missing"); - return new List<RemoteSubtitleInfo>(); - } - if (string.IsNullOrWhiteSpace(imdbIdText) || !long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId)) - { - _logger.Debug("Imdb id missing"); - return new List<RemoteSubtitleInfo>(); - } - break; - } - - if (string.IsNullOrEmpty(request.MediaPath)) - { - _logger.Debug("Path Missing"); - return new List<RemoteSubtitleInfo>(); - } - - await Login(cancellationToken).ConfigureAwait(false); - - var subLanguageId = NormalizeLanguage(request.Language); - string hash; - - using (var fileStream = _fileSystem.OpenRead(request.MediaPath)) - { - hash = Utilities.ComputeHash(fileStream); - } - var fileInfo = _fileSystem.GetFileInfo(request.MediaPath); - var movieByteSize = fileInfo.Length; - var searchImdbId = request.ContentType == VideoContentType.Movie ? imdbId.ToString(_usCulture) : ""; - var subtitleSearchParameters = request.ContentType == VideoContentType.Episode - ? new List<SubtitleSearchParameters> { - new SubtitleSearchParameters(subLanguageId, - query: request.SeriesName, - season: request.ParentIndexNumber.Value.ToString(_usCulture), - episode: request.IndexNumber.Value.ToString(_usCulture)) - } - : new List<SubtitleSearchParameters> { - new SubtitleSearchParameters(subLanguageId, imdbid: searchImdbId), - new SubtitleSearchParameters(subLanguageId, query: request.Name, imdbid: searchImdbId) - }; - var parms = new List<SubtitleSearchParameters> { - new SubtitleSearchParameters( subLanguageId, - movieHash: hash, - movieByteSize: movieByteSize, - imdbid: searchImdbId ), - }; - parms.AddRange(subtitleSearchParameters); - var result = await OpenSubtitles.SearchSubtitlesAsync(parms.ToArray(), cancellationToken).ConfigureAwait(false); - if (!(result is MethodResponseSubtitleSearch)) - { - _logger.Error("Invalid response type"); - return new List<RemoteSubtitleInfo>(); - } - - Predicate<SubtitleSearchResult> mediaFilter = - x => - request.ContentType == VideoContentType.Episode - ? !string.IsNullOrEmpty(x.SeriesSeason) && !string.IsNullOrEmpty(x.SeriesEpisode) && - int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber && - int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber - : !string.IsNullOrEmpty(x.IDMovieImdb) && long.Parse(x.IDMovieImdb, _usCulture) == imdbId; - - var results = ((MethodResponseSubtitleSearch)result).Results; - - // Avoid implicitly captured closure - var hasCopy = hash; - - return results.Where(x => x.SubBad == "0" && mediaFilter(x) && (!request.IsPerfectMatch || string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase))) - .OrderBy(x => (string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase) ? 0 : 1)) - .ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize)) - .ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture)) - .ThenByDescending(x => double.Parse(x.SubRating, _usCulture)) - .Select(i => new RemoteSubtitleInfo - { - Author = i.UserNickName, - Comment = i.SubAuthorComment, - CommunityRating = float.Parse(i.SubRating, _usCulture), - DownloadCount = int.Parse(i.SubDownloadsCnt, _usCulture), - Format = i.SubFormat, - ProviderName = Name, - ThreeLetterISOLanguageName = i.SubLanguageID, - - Id = i.SubFormat + "-" + i.SubLanguageID + "-" + i.IDSubtitleFile, - - Name = i.SubFileName, - DateCreated = DateTime.Parse(i.SubAddDate, _usCulture), - IsHashMatch = i.MovieHash == hasCopy - - }).Where(i => !string.Equals(i.Format, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Format, "idx", StringComparison.OrdinalIgnoreCase)); - } - - public void Dispose() - { - _config.NamedConfigurationUpdating -= _config_NamedConfigurationUpdating; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs deleted file mode 100644 index b8c2fef1e..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class ParserValues - { - public const string NewLine = "\r\n"; - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs deleted file mode 100644 index 2a6aa993c..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ /dev/null @@ -1,90 +0,0 @@ -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SrtParser : ISubtitleParser - { - private readonly ILogger _logger; - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public SrtParser(ILogger logger) - { - _logger = logger; - } - - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - using ( var reader = new StreamReader(stream)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - var subEvent = new SubtitleTrackEvent {Id = line}; - line = reader.ReadLine(); - - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - var time = Regex.Split(line, @"[\t ]*-->[\t ]*"); - - if (time.Length < 2) - { - // This occurs when subtitle text has an empty line as part of the text. - // Need to adjust the break statement below to resolve this. - _logger.Warn("Unrecognized line in srt: {0}", line); - continue; - } - subEvent.StartPositionTicks = GetTicks(time[0]); - var endTime = time[1]; - var idx = endTime.IndexOf(" ", StringComparison.Ordinal); - if (idx > 0) - endTime = endTime.Substring(0, idx); - subEvent.EndPositionTicks = GetTicks(endTime); - var multiline = new List<string>(); - while ((line = reader.ReadLine()) != null) - { - if (string.IsNullOrEmpty(line)) - { - break; - } - multiline.Add(line); - } - subEvent.Text = string.Join(ParserValues.NewLine, multiline); - subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, "<", "<", RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, ">", ">", RegexOptions.IgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, "<(\\/?(font|b|u|i|s))((\\s+(\\w|\\w[\\w\\-]*\\w)(\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)(\\/?)>", "<$1$3$7>", RegexOptions.IgnoreCase); - trackInfo.TrackEvents.Add(subEvent); - } - } - return trackInfo; - } - - long GetTicks(string time) { - TimeSpan span; - return TimeSpan.TryParseExact(time, @"hh\:mm\:ss\.fff", _usCulture, out span) - ? span.Ticks - : (TimeSpan.TryParseExact(time, @"hh\:mm\:ss\,fff", _usCulture, out span) - ? span.Ticks : 0); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs deleted file mode 100644 index c05929fde..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SrtWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - var index = 1; - - foreach (var trackEvent in info.TrackEvents) - { - cancellationToken.ThrowIfCancellationRequested(); - - writer.WriteLine(index.ToString(CultureInfo.InvariantCulture)); - writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks)); - - var text = trackEvent.Text; - - // TODO: Not sure how to handle these - text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); - - writer.WriteLine(text); - writer.WriteLine(string.Empty); - - index++; - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs deleted file mode 100644 index 6c760658d..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ /dev/null @@ -1,394 +0,0 @@ -using MediaBrowser.Model.Extensions; -using System; -using System.IO; -using System.Text; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - /// <summary> - /// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs - /// </summary> - public class SsaParser : ISubtitleParser - { - public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) - { - var trackInfo = new SubtitleTrackInfo(); - - using (var reader = new StreamReader(stream)) - { - bool eventsStarted = false; - - string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(','); - int indexLayer = 0; - int indexStart = 1; - int indexEnd = 2; - int indexStyle = 3; - int indexName = 4; - int indexEffect = 8; - int indexText = 9; - int lineNumber = 0; - - var header = new StringBuilder(); - - string line; - - while ((line = reader.ReadLine()) != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - lineNumber++; - if (!eventsStarted) - header.AppendLine(line); - - if (line.Trim().ToLower() == "[events]") - { - eventsStarted = true; - } - else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(";")) - { - // skip comment lines - } - else if (eventsStarted && line.Trim().Length > 0) - { - string s = line.Trim().ToLower(); - if (s.StartsWith("format:")) - { - if (line.Length > 10) - { - format = line.ToLower().Substring(8).Split(','); - for (int i = 0; i < format.Length; i++) - { - if (format[i].Trim().ToLower() == "layer") - indexLayer = i; - else if (format[i].Trim().ToLower() == "start") - indexStart = i; - else if (format[i].Trim().ToLower() == "end") - indexEnd = i; - else if (format[i].Trim().ToLower() == "text") - indexText = i; - else if (format[i].Trim().ToLower() == "effect") - indexEffect = i; - else if (format[i].Trim().ToLower() == "style") - indexStyle = i; - } - } - } - else if (!string.IsNullOrEmpty(s)) - { - string text = string.Empty; - string start = string.Empty; - string end = string.Empty; - string style = string.Empty; - string layer = string.Empty; - string effect = string.Empty; - string name = string.Empty; - - string[] splittedLine; - - if (s.StartsWith("dialogue:")) - splittedLine = line.Substring(10).Split(','); - else - splittedLine = line.Split(','); - - for (int i = 0; i < splittedLine.Length; i++) - { - if (i == indexStart) - start = splittedLine[i].Trim(); - else if (i == indexEnd) - end = splittedLine[i].Trim(); - else if (i == indexLayer) - layer = splittedLine[i]; - else if (i == indexEffect) - effect = splittedLine[i]; - else if (i == indexText) - text = splittedLine[i]; - else if (i == indexStyle) - style = splittedLine[i]; - else if (i == indexName) - name = splittedLine[i]; - else if (i > indexText) - text += "," + splittedLine[i]; - } - - try - { - var p = new SubtitleTrackEvent(); - - p.StartPositionTicks = GetTimeCodeFromString(start); - p.EndPositionTicks = GetTimeCodeFromString(end); - p.Text = GetFormattedText(text); - - trackInfo.TrackEvents.Add(p); - } - catch - { - } - } - } - } - - //if (header.Length > 0) - //subtitle.Header = header.ToString(); - - //subtitle.Renumber(1); - } - return trackInfo; - } - - private static long GetTimeCodeFromString(string time) - { - // h:mm:ss.cc - string[] timeCode = time.Split(':', '.'); - return new TimeSpan(0, int.Parse(timeCode[0]), - int.Parse(timeCode[1]), - int.Parse(timeCode[2]), - int.Parse(timeCode[3]) * 10).Ticks; - } - - public static string GetFormattedText(string text) - { - text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - - bool italic = false; - - for (int i = 0; i < 10; i++) // just look ten times... - { - if (text.Contains(@"{\fn")) - { - int start = text.IndexOf(@"{\fn"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\fn}")) - { - string fontName = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref fontName, ref extraTags, out italic); - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">"); - - int indexOfEndTag = text.IndexOf("{\\fn}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - - if (text.Contains(@"{\fs")) - { - int start = text.IndexOf(@"{\fs"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\fs}")) - { - string fontSize = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref fontSize, ref extraTags, out italic); - if (IsInteger(fontSize)) - { - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">"); - - int indexOfEndTag = text.IndexOf("{\\fs}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - } - - if (text.Contains(@"{\c")) - { - int start = text.IndexOf(@"{\c"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\c}")) - { - string color = text.Substring(start + 4, end - (start + 4)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref color, ref extraTags, out italic); - - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); - int indexOfEndTag = text.IndexOf("{\\c}", start); - if (indexOfEndTag > 0) - text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>"); - else - text += "</font>"; - } - } - - if (text.Contains(@"{\1c")) // "1" specifices primary color - { - int start = text.IndexOf(@"{\1c"); - int end = text.IndexOf('}', start); - if (end > 0 && !text.Substring(start).StartsWith("{\\1c}")) - { - string color = text.Substring(start + 5, end - (start + 5)); - string extraTags = string.Empty; - CheckAndAddSubTags(ref color, ref extraTags, out italic); - - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - text = text.Remove(start, end - start + 1); - if (italic) - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>"); - else - text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">"); - text += "</font>"; - } - } - - } - - text = text.Replace(@"{\i1}", "<i>"); - text = text.Replace(@"{\i0}", "</i>"); - text = text.Replace(@"{\i}", "</i>"); - if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>")) - text += "</i>"; - - text = text.Replace(@"{\u1}", "<u>"); - text = text.Replace(@"{\u0}", "</u>"); - text = text.Replace(@"{\u}", "</u>"); - if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>")) - text += "</u>"; - - text = text.Replace(@"{\b1}", "<b>"); - text = text.Replace(@"{\b0}", "</b>"); - text = text.Replace(@"{\b}", "</b>"); - if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>")) - text += "</b>"; - - return text; - } - - private static bool IsInteger(string s) - { - int i; - if (int.TryParse(s, out i)) - return true; - return false; - } - - private static int CountTagInText(string text, string tag) - { - int count = 0; - int index = text.IndexOf(tag); - while (index >= 0) - { - count++; - if (index == text.Length) - return count; - index = text.IndexOf(tag, index + 1); - } - return count; - } - - private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic) - { - italic = false; - int indexOfSPlit = tagName.IndexOf(@"\"); - if (indexOfSPlit > 0) - { - string rest = tagName.Substring(indexOfSPlit).TrimStart('\\'); - tagName = tagName.Remove(indexOfSPlit); - - for (int i = 0; i < 10; i++) - { - if (rest.StartsWith("fs") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontSize = rest; - if (indexOfSPlit > 0) - { - fontSize = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - extraTags += " size=\"" + fontSize.Substring(2) + "\""; - } - else if (rest.StartsWith("fn") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontName = rest; - if (indexOfSPlit > 0) - { - fontName = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - extraTags += " face=\"" + fontName.Substring(2) + "\""; - } - else if (rest.StartsWith("c") && rest.Length > 2) - { - indexOfSPlit = rest.IndexOf(@"\"); - string fontColor = rest; - if (indexOfSPlit > 0) - { - fontColor = rest.Substring(0, indexOfSPlit); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - - string color = fontColor.Substring(2); - color = color.Replace("&", string.Empty).TrimStart('H'); - color = color.PadLeft(6, '0'); - // switch to rrggbb from bbggrr - color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); - - extraTags += " color=\"" + color + "\""; - } - else if (rest.StartsWith("i1") && rest.Length > 1) - { - indexOfSPlit = rest.IndexOf(@"\"); - italic = true; - if (indexOfSPlit > 0) - { - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - else - { - rest = string.Empty; - } - } - else if (rest.Length > 0 && rest.Contains("\\")) - { - indexOfSPlit = rest.IndexOf(@"\"); - rest = rest.Substring(indexOfSPlit).TrimStart('\\'); - } - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs deleted file mode 100644 index 247c5274f..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ /dev/null @@ -1,738 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Text; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class SubtitleEncoder : ISubtitleEncoder - { - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly IMediaEncoder _mediaEncoder; - private readonly IJsonSerializer _json; - private readonly IHttpClient _httpClient; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMemoryStreamFactory _memoryStreamProvider; - private readonly IProcessFactory _processFactory; - private readonly ITextEncoding _textEncoding; - - public SubtitleEncoder(ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IJsonSerializer json, IHttpClient httpClient, IMediaSourceManager mediaSourceManager, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory, ITextEncoding textEncoding) - { - _libraryManager = libraryManager; - _logger = logger; - _appPaths = appPaths; - _fileSystem = fileSystem; - _mediaEncoder = mediaEncoder; - _json = json; - _httpClient = httpClient; - _mediaSourceManager = mediaSourceManager; - _memoryStreamProvider = memoryStreamProvider; - _processFactory = processFactory; - _textEncoding = textEncoding; - } - - private string SubtitleCachePath - { - get - { - return Path.Combine(_appPaths.DataPath, "subtitles"); - } - } - - private Stream ConvertSubtitles(Stream stream, - string inputFormat, - string outputFormat, - long startTimeTicks, - long? endTimeTicks, - bool preserveOriginalTimestamps, - CancellationToken cancellationToken) - { - var ms = _memoryStreamProvider.CreateNew(); - - try - { - var reader = GetReader(inputFormat, true); - - var trackInfo = reader.Parse(stream, cancellationToken); - - FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); - - var writer = GetWriter(outputFormat); - - writer.Write(trackInfo, ms, cancellationToken); - ms.Position = 0; - } - catch - { - ms.Dispose(); - throw; - } - - return ms; - } - - private void FilterEvents(SubtitleTrackInfo track, long startPositionTicks, long? endTimeTicks, bool preserveTimestamps) - { - // Drop subs that are earlier than what we're looking for - track.TrackEvents = track.TrackEvents - .SkipWhile(i => (i.StartPositionTicks - startPositionTicks) < 0 || (i.EndPositionTicks - startPositionTicks) < 0) - .ToList(); - - if (endTimeTicks.HasValue) - { - var endTime = endTimeTicks.Value; - - track.TrackEvents = track.TrackEvents - .TakeWhile(i => i.StartPositionTicks <= endTime) - .ToList(); - } - - if (!preserveTimestamps) - { - foreach (var trackEvent in track.TrackEvents) - { - trackEvent.EndPositionTicks -= startPositionTicks; - trackEvent.StartPositionTicks -= startPositionTicks; - } - } - } - - public async Task<Stream> GetSubtitles(string itemId, - string mediaSourceId, - int subtitleStreamIndex, - string outputFormat, - long startTimeTicks, - long? endTimeTicks, - bool preserveOriginalTimestamps, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(itemId)) - { - throw new ArgumentNullException("itemId"); - } - if (string.IsNullOrWhiteSpace(mediaSourceId)) - { - throw new ArgumentNullException("mediaSourceId"); - } - - var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false); - - var mediaSource = mediaSources - .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); - - var subtitleStream = mediaSource.MediaStreams - .First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex); - - var subtitle = await GetSubtitleStream(mediaSource, subtitleStream, cancellationToken) - .ConfigureAwait(false); - - var inputFormat = subtitle.Item2; - var writer = TryGetWriter(outputFormat); - - // Return the original if we don't have any way of converting it - if (writer == null) - { - return subtitle.Item1; - } - - // Return the original if the same format is being requested - // Character encoding was already handled in GetSubtitleStream - if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase)) - { - return subtitle.Item1; - } - - using (var stream = subtitle.Item1) - { - return ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken); - } - } - - private async Task<Tuple<Stream, string>> GetSubtitleStream(MediaSourceInfo mediaSource, - MediaStream subtitleStream, - CancellationToken cancellationToken) - { - var inputFiles = new[] { mediaSource.Path }; - - if (mediaSource.VideoType.HasValue) - { - if (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd) - { - var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id)); - inputFiles = mediaSourceItem.GetPlayableStreamFiles().ToArray(); - } - } - - var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false); - - var stream = await GetSubtitleStream(fileInfo.Item1, subtitleStream.Language, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false); - - return new Tuple<Stream, string>(stream, fileInfo.Item3); - } - - private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken) - { - if (requiresCharset) - { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = _textEncoding.GetDetectedEncodingName(bytes, language, true); - _logger.Debug("charset {0} detected for {1}", charset ?? "null", path); - - if (!string.IsNullOrEmpty(charset)) - { - using (var inputStream = _memoryStreamProvider.CreateNew(bytes)) - { - using (var reader = new StreamReader(inputStream, _textEncoding.GetEncodingFromCharset(charset))) - { - var text = await reader.ReadToEndAsync().ConfigureAwait(false); - - bytes = Encoding.UTF8.GetBytes(text); - - return _memoryStreamProvider.CreateNew(bytes); - } - } - } - } - - return _fileSystem.OpenRead(path); - } - - private async Task<Tuple<string, MediaProtocol, string, bool>> GetReadableFile(string mediaPath, - string[] inputFiles, - MediaProtocol protocol, - MediaStream subtitleStream, - CancellationToken cancellationToken) - { - if (!subtitleStream.IsExternal) - { - string outputFormat; - string outputCodec; - - if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || - string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase) || - string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = subtitleStream.Codec; - } - else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase)) - { - // Extract - outputCodec = "copy"; - outputFormat = "srt"; - } - else - { - // Extract - outputCodec = "srt"; - outputFormat = "srt"; - } - - // Extract - var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, "." + outputFormat); - - await ExtractTextSubtitle(inputFiles, protocol, subtitleStream.Index, outputCodec, outputPath, cancellationToken) - .ConfigureAwait(false); - - return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, outputFormat, false); - } - - var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec) - .TrimStart('.'); - - if (GetReader(currentFormat, false) == null) - { - // Convert - var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt"); - - await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, protocol, outputPath, cancellationToken).ConfigureAwait(false); - - return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, "srt", true); - } - - return new Tuple<string, MediaProtocol, string, bool>(subtitleStream.Path, protocol, currentFormat, true); - } - - private ISubtitleParser GetReader(string format, bool throwIfMissing) - { - if (string.IsNullOrEmpty(format)) - { - throw new ArgumentNullException("format"); - } - - if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) - { - return new SrtParser(_logger); - } - if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)) - { - return new SsaParser(); - } - if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) - { - return new AssParser(); - } - - if (throwIfMissing) - { - throw new ArgumentException("Unsupported format: " + format); - } - - return null; - } - - private ISubtitleWriter TryGetWriter(string format) - { - if (string.IsNullOrEmpty(format)) - { - throw new ArgumentNullException("format"); - } - - if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase)) - { - return new JsonWriter(_json); - } - if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) - { - return new SrtWriter(); - } - if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase)) - { - return new VttWriter(); - } - if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase)) - { - return new TtmlWriter(); - } - - return null; - } - - private ISubtitleWriter GetWriter(string format) - { - var writer = TryGetWriter(format); - - if (writer != null) - { - return writer; - } - - throw new ArgumentException("Unsupported format: " + format); - } - - /// <summary> - /// The _semaphoreLocks - /// </summary> - private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = - new ConcurrentDictionary<string, SemaphoreSlim>(); - - /// <summary> - /// Gets the lock. - /// </summary> - /// <param name="filename">The filename.</param> - /// <returns>System.Object.</returns> - private SemaphoreSlim GetLock(string filename) - { - return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); - } - - /// <summary> - /// Converts the text subtitle to SRT. - /// </summary> - /// <param name="inputPath">The input path.</param> - /// <param name="inputProtocol">The input protocol.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task ConvertTextSubtitleToSrt(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) - { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (!_fileSystem.FileExists(outputPath)) - { - await ConvertTextSubtitleToSrtInternal(inputPath, language, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false); - } - } - finally - { - semaphore.Release(); - } - } - - /// <summary> - /// Converts the text subtitle to SRT internal. - /// </summary> - /// <param name="inputPath">The input path.</param> - /// <param name="inputProtocol">The input protocol.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException"> - /// inputPath - /// or - /// outputPath - /// </exception> - private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); - - var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, inputProtocol, cancellationToken).ConfigureAwait(false); - - if (!string.IsNullOrEmpty(encodingParam)) - { - encodingParam = " -sub_charenc " + encodingParam; - } - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = _mediaEncoder.EncoderPath, - Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath), - - IsHidden = true, - ErrorDialog = false - }); - - _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - try - { - process.Start(); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting ffmpeg", ex); - - throw; - } - - var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false); - - if (!ranToCompletion) - { - try - { - _logger.Info("Killing ffmpeg subtitle conversion process"); - - process.Kill(); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing subtitle conversion process", ex); - } - } - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - if (_fileSystem.FileExists(outputPath)) - { - try - { - _logger.Info("Deleting converted subtitle due to failure: ", outputPath); - _fileSystem.DeleteFile(outputPath); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting converted subtitle {0}", ex, outputPath); - } - } - } - else if (!_fileSystem.FileExists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg subtitle conversion failed for {0}", inputPath); - - _logger.Error(msg); - - throw new Exception(msg); - } - await SetAssFont(outputPath).ConfigureAwait(false); - - _logger.Info("ffmpeg subtitle conversion succeeded for {0}", inputPath); - } - - /// <summary> - /// Extracts the text subtitle. - /// </summary> - /// <param name="inputFiles">The input files.</param> - /// <param name="protocol">The protocol.</param> - /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param> - /// <param name="outputCodec">The output codec.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentException">Must use inputPath list overload</exception> - private async Task ExtractTextSubtitle(string[] inputFiles, MediaProtocol protocol, int subtitleStreamIndex, - string outputCodec, string outputPath, CancellationToken cancellationToken) - { - var semaphore = GetLock(outputPath); - - await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - if (!_fileSystem.FileExists(outputPath)) - { - await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false); - } - } - finally - { - semaphore.Release(); - } - } - - private async Task ExtractTextSubtitleInternal(string inputPath, int subtitleStreamIndex, - string outputCodec, string outputPath, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(inputPath)) - { - throw new ArgumentNullException("inputPath"); - } - - if (string.IsNullOrEmpty(outputPath)) - { - throw new ArgumentNullException("outputPath"); - } - - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); - - var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath, - subtitleStreamIndex, outputCodec, outputPath); - - var process = _processFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - FileName = _mediaEncoder.EncoderPath, - Arguments = processArgs, - IsHidden = true, - ErrorDialog = false - }); - - _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - - try - { - process.Start(); - } - catch (Exception ex) - { - _logger.ErrorException("Error starting ffmpeg", ex); - - throw; - } - - var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false); - - if (!ranToCompletion) - { - try - { - _logger.Info("Killing ffmpeg subtitle extraction process"); - - process.Kill(); - } - catch (Exception ex) - { - _logger.ErrorException("Error killing subtitle extraction process", ex); - } - } - - var exitCode = ranToCompletion ? process.ExitCode : -1; - - process.Dispose(); - - var failed = false; - - if (exitCode == -1) - { - failed = true; - - try - { - _logger.Info("Deleting extracted subtitle due to failure: {0}", outputPath); - _fileSystem.DeleteFile(outputPath); - } - catch (FileNotFoundException) - { - - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath); - } - } - else if (!_fileSystem.FileExists(outputPath)) - { - failed = true; - } - - if (failed) - { - var msg = string.Format("ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath); - - _logger.Error(msg); - - throw new Exception(msg); - } - else - { - var msg = string.Format("ffmpeg subtitle extraction completed for {0} to {1}", inputPath, outputPath); - - _logger.Info(msg); - } - - if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase)) - { - await SetAssFont(outputPath).ConfigureAwait(false); - } - } - - /// <summary> - /// Sets the ass font. - /// </summary> - /// <param name="file">The file.</param> - /// <returns>Task.</returns> - private async Task SetAssFont(string file) - { - _logger.Info("Setting ass font within {0}", file); - - string text; - Encoding encoding; - - using (var fileStream = _fileSystem.OpenRead(file)) - { - using (var reader = new StreamReader(fileStream, true)) - { - encoding = reader.CurrentEncoding; - - text = await reader.ReadToEndAsync().ConfigureAwait(false); - } - } - - var newText = text.Replace(",Arial,", ",Arial Unicode MS,"); - - if (!string.Equals(text, newText)) - { - using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - using (var writer = new StreamWriter(fileStream, encoding)) - { - writer.Write(newText); - } - } - } - } - - private string GetSubtitleCachePath(string mediaPath, MediaProtocol protocol, int subtitleStreamIndex, string outputSubtitleExtension) - { - if (protocol == MediaProtocol.File) - { - var ticksParam = string.Empty; - - var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture) + ticksParam).GetMD5() + outputSubtitleExtension; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(SubtitleCachePath, prefix, filename); - } - else - { - var filename = (mediaPath + "_" + subtitleStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5() + outputSubtitleExtension; - - var prefix = filename.Substring(0, 1); - - return Path.Combine(SubtitleCachePath, prefix, filename); - } - } - - public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) - { - var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false); - - var charset = _textEncoding.GetDetectedEncodingName(bytes, language, true); - - _logger.Debug("charset {0} detected for {1}", charset ?? "null", path); - - return charset; - } - - private async Task<byte[]> GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken) - { - if (protocol == MediaProtocol.Http) - { - using (var file = await _httpClient.Get(path, cancellationToken).ConfigureAwait(false)) - { - using (var memoryStream = new MemoryStream()) - { - await file.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; - - return memoryStream.ToArray(); - } - } - } - if (protocol == MediaProtocol.File) - { - return _fileSystem.ReadAllBytes(path); - } - - throw new ArgumentOutOfRangeException("protocol"); - } - } -}
\ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs deleted file mode 100644 index c32005f89..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class TtmlWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - // Example: https://github.com/zmalltalker/ttml2vtt/blob/master/data/sample.xml - // Parser example: https://github.com/mozilla/popcorn-js/blob/master/parsers/parserTTML/popcorn.parserTTML.js - - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); - writer.WriteLine("<tt xmlns=\"http://www.w3.org/ns/ttml\" xmlns:tts=\"http://www.w3.org/2006/04/ttaf1#styling\" lang=\"no\">"); - - writer.WriteLine("<head>"); - writer.WriteLine("<styling>"); - writer.WriteLine("<style id=\"italic\" tts:fontStyle=\"italic\" />"); - writer.WriteLine("<style id=\"left\" tts:textAlign=\"left\" />"); - writer.WriteLine("<style id=\"center\" tts:textAlign=\"center\" />"); - writer.WriteLine("<style id=\"right\" tts:textAlign=\"right\" />"); - writer.WriteLine("</styling>"); - writer.WriteLine("</head>"); - - writer.WriteLine("<body>"); - writer.WriteLine("<div>"); - - foreach (var trackEvent in info.TrackEvents) - { - var text = trackEvent.Text; - - text = Regex.Replace(text, @"\\n", "<br/>", RegexOptions.IgnoreCase); - - writer.WriteLine("<p begin=\"{0}\" dur=\"{1}\">{2}</p>", - trackEvent.StartPositionTicks, - (trackEvent.EndPositionTicks - trackEvent.StartPositionTicks), - text); - } - - writer.WriteLine("</div>"); - writer.WriteLine("</body>"); - - writer.WriteLine("</tt>"); - } - } - - private string FormatTime(long ticks) - { - var time = TimeSpan.FromTicks(ticks); - - return string.Format(@"{0:hh\:mm\:ss\,fff}", time); - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs deleted file mode 100644 index 092add992..000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class VttWriter : ISubtitleWriter - { - public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - writer.WriteLine("WEBVTT"); - writer.WriteLine(string.Empty); - foreach (var trackEvent in info.TrackEvents) - { - cancellationToken.ThrowIfCancellationRequested(); - - TimeSpan startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks); - TimeSpan endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks); - - // make sure the start and end times are different and seqential - if (endTime.TotalMilliseconds <= startTime.TotalMilliseconds) - { - endTime = startTime.Add(TimeSpan.FromMilliseconds(1)); - } - - writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff}", startTime, endTime); - - var text = trackEvent.Text; - - // TODO: Not sure how to handle these - text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase); - - writer.WriteLine(text); - writer.WriteLine(string.Empty); - } - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/packages.config b/MediaBrowser.MediaEncoding/packages.config deleted file mode 100644 index 6b8deb9c9..000000000 --- a/MediaBrowser.MediaEncoding/packages.config +++ /dev/null @@ -1,3 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<packages> -</packages>
\ No newline at end of file diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs index 979cd343f..14b1875c1 100644 --- a/MediaBrowser.Model/Dlna/CodecProfile.cs +++ b/MediaBrowser.Model/Dlna/CodecProfile.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Xml.Serialization; using MediaBrowser.Model.Dlna; -using System.Linq; namespace MediaBrowser.Model.Dlna { diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index 291096f75..bd3dc6fd2 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -3,7 +3,6 @@ using MediaBrowser.Model.MediaInfo; using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; namespace MediaBrowser.Model.Dlna { diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index d99501875..e80f59be4 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 9c8e8b030..f863d8c95 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.Model.Dlna list.Add(string.Format("{0}={1}", pair.Name, pair.Value)); } - string queryString = string.Join("&", list.ToArray()); + string queryString = string.Join("&", list.ToArray(list.Count)); return GetUrl(baseUrl, queryString); } @@ -203,7 +203,7 @@ namespace MediaBrowser.Model.Dlna list.Add(pair.Value); } - return string.Format("Params={0}", string.Join(";", list.ToArray())); + return string.Format("Params={0}", string.Join(";", list.ToArray(list.Count))); } private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken, bool isDlna) diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index b1d918b16..92d8bfab2 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -163,7 +163,7 @@ namespace MediaBrowser.Model.Dto public string[] ProductionLocations { get; set; } - public List<string> MultiPartGameFiles { get; set; } + public string[] MultiPartGameFiles { get; set; } /// <summary> /// Gets or sets the path. @@ -201,7 +201,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the taglines. /// </summary> /// <value>The taglines.</value> - public List<string> Taglines { get; set; } + public string[] Taglines { get; set; } /// <summary> /// Gets or sets the genres. @@ -360,7 +360,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the parent backdrop image tags. /// </summary> /// <value>The parent backdrop image tags.</value> - public List<string> ParentBackdropImageTags { get; set; } + public string[] ParentBackdropImageTags { get; set; } /// <summary> /// Gets or sets the local trailer count. @@ -438,7 +438,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the tags. /// </summary> /// <value>The tags.</value> - public List<string> Tags { get; set; } + public string[] Tags { get; set; } /// <summary> /// Gets or sets the primary image aspect ratio, after image enhancements. @@ -560,13 +560,13 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the backdrop image tags. /// </summary> /// <value>The backdrop image tags.</value> - public List<string> BackdropImageTags { get; set; } + public string[] BackdropImageTags { get; set; } /// <summary> /// Gets or sets the screenshot image tags. /// </summary> /// <value>The screenshot image tags.</value> - public List<string> ScreenshotImageTags { get; set; } + public string[] ScreenshotImageTags { get; set; } /// <summary> /// Gets or sets the parent logo image tag. @@ -664,7 +664,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the locked fields. /// </summary> /// <value>The locked fields.</value> - public List<MetadataFields> LockedFields { get; set; } + public MetadataFields[] LockedFields { get; set; } /// <summary> /// Gets or sets the trailer count. @@ -778,27 +778,7 @@ namespace MediaBrowser.Model.Dto [IgnoreDataMember] public bool HasBackdrop { - get { return (BackdropImageTags != null && BackdropImageTags.Count > 0) || (ParentBackdropImageTags != null && ParentBackdropImageTags.Count > 0); } - } - - /// <summary> - /// Gets a value indicating whether this instance has primary image. - /// </summary> - /// <value><c>true</c> if this instance has primary image; otherwise, <c>false</c>.</value> - [IgnoreDataMember] - public bool HasPrimaryImage - { - get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Primary); } - } - - /// <summary> - /// Gets a value indicating whether this instance is video. - /// </summary> - /// <value><c>true</c> if this instance is video; otherwise, <c>false</c>.</value> - [IgnoreDataMember] - public bool IsVideo - { - get { return StringHelper.EqualsIgnoreCase(MediaType, Entities.MediaType.Video); } + get { return (BackdropImageTags != null && BackdropImageTags.Length > 0) || (ParentBackdropImageTags != null && ParentBackdropImageTags.Length > 0); } } /// <summary> diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 3cd3e7dde..747528cbf 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Model.Entities attributes.Add("Default"); } - return string.Join(" ", attributes.ToArray()); + return string.Join(" ", attributes.ToArray(attributes.Count)); } if (Type == MediaStreamType.Subtitle) @@ -107,7 +107,7 @@ namespace MediaBrowser.Model.Entities attributes.Add("Forced"); } - string name = string.Join(" ", attributes.ToArray()); + string name = string.Join(" ", attributes.ToArray(attributes.Count)); return name; } diff --git a/MediaBrowser.Model/Extensions/LinqExtensions.cs b/MediaBrowser.Model/Extensions/LinqExtensions.cs index 6b2bdb4c7..43e4a66cd 100644 --- a/MediaBrowser.Model/Extensions/LinqExtensions.cs +++ b/MediaBrowser.Model/Extensions/LinqExtensions.cs @@ -42,6 +42,32 @@ namespace MediaBrowser.Model.Extensions return source.DistinctBy(keySelector, null); } + public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source, int count) + { + if (source == null) throw new ArgumentNullException("source"); + if (count < 0) throw new ArgumentOutOfRangeException("count"); + var array = new TSource[count]; + int i = 0; + foreach (var item in source) + { + array[i++] = item; + } + return array; + } + + public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source, int count) + { + if (source == null) throw new ArgumentNullException("source"); + if (count < 0) throw new ArgumentOutOfRangeException("count"); + var array = new List<TSource>(count); + int i = 0; + foreach (var item in source) + { + array[i++] = item; + } + return array; + } + /// <summary> /// Returns all distinct elements of the given source, where "distinctness" /// is determined via a projection and the specified comparer for the projected type. diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index 4477d8de3..47cec1459 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -12,12 +12,12 @@ namespace MediaBrowser.Model.Globalization /// Gets the cultures. /// </summary> /// <returns>IEnumerable{CultureDto}.</returns> - IEnumerable<CultureDto> GetCultures(); + List<CultureDto> GetCultures(); /// <summary> /// Gets the countries. /// </summary> /// <returns>IEnumerable{CountryInfo}.</returns> - IEnumerable<CountryInfo> GetCountries(); + List<CountryInfo> GetCountries(); /// <summary> /// Gets the parental ratings. /// </summary> diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs index 71eb9914b..42550340b 100644 --- a/MediaBrowser.Model/Net/ISocket.cs +++ b/MediaBrowser.Model/Net/ISocket.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs index 46c0240cd..e1480f30a 100644 --- a/MediaBrowser.Model/Services/IHttpRequest.cs +++ b/MediaBrowser.Model/Services/IHttpRequest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using System.Threading.Tasks; diff --git a/MediaBrowser.Model/Services/IHttpResponse.cs b/MediaBrowser.Model/Services/IHttpResponse.cs index 377f303a7..cd9c07d46 100644 --- a/MediaBrowser.Model/Services/IHttpResponse.cs +++ b/MediaBrowser.Model/Services/IHttpResponse.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; diff --git a/MediaBrowser.Model/Services/IHttpResult.cs b/MediaBrowser.Model/Services/IHttpResult.cs index fcb137c6b..90afb0f27 100644 --- a/MediaBrowser.Model/Services/IHttpResult.cs +++ b/MediaBrowser.Model/Services/IHttpResult.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index dfea62821..a3e00f587 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Model.Services { @@ -187,7 +188,7 @@ namespace MediaBrowser.Model.Services public override String ToString() { - var vals = this.Select(GetQueryStringValue).ToArray(); + var vals = this.Select(GetQueryStringValue).ToArray(this.Count); return string.Join("&", vals); } diff --git a/MediaBrowser.Model/Social/ISharingRepository.cs b/MediaBrowser.Model/Social/ISharingRepository.cs index 069b6e1fe..1dadd7f71 100644 --- a/MediaBrowser.Model/Social/ISharingRepository.cs +++ b/MediaBrowser.Model/Social/ISharingRepository.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace MediaBrowser.Model.Social { diff --git a/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs b/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs index 66f5294e7..2dec79e93 100644 --- a/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs +++ b/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; - + namespace MediaBrowser.Model.Tasks { /// <summary> @@ -26,12 +24,6 @@ namespace MediaBrowser.Model.Tasks string key = task.ScheduledTask.Key; - var triggers = task.Triggers - .OrderBy(i => i.Type) - .ThenBy(i => i.DayOfWeek ?? DayOfWeek.Sunday) - .ThenBy(i => i.TimeOfDayTicks ?? 0) - .ToList(); - return new TaskInfo { Name = task.Name, @@ -40,7 +32,7 @@ namespace MediaBrowser.Model.Tasks Id = task.Id, LastExecutionResult = task.LastExecutionResult, - Triggers = triggers, + Triggers = task.Triggers, Description = task.Description, Category = task.Category, diff --git a/MediaBrowser.Model/Tasks/TaskInfo.cs b/MediaBrowser.Model/Tasks/TaskInfo.cs index 50276f8eb..8792ce952 100644 --- a/MediaBrowser.Model/Tasks/TaskInfo.cs +++ b/MediaBrowser.Model/Tasks/TaskInfo.cs @@ -41,7 +41,7 @@ namespace MediaBrowser.Model.Tasks /// Gets or sets the triggers. /// </summary> /// <value>The triggers.</value> - public List<TaskTriggerInfo> Triggers { get; set; } + public TaskTriggerInfo[] Triggers { get; set; } /// <summary> /// Gets or sets the description. @@ -72,7 +72,7 @@ namespace MediaBrowser.Model.Tasks /// </summary> public TaskInfo() { - Triggers = new List<TaskTriggerInfo>(); + Triggers = new TaskTriggerInfo[]{}; } } } diff --git a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs index 7cce85105..b9e265d22 100644 --- a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs +++ b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.Books { public class AudioBookMetadataService : MetadataService<AudioBook, SongInfo> { - protected override void MergeData(MetadataResult<AudioBook> source, MetadataResult<AudioBook> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<AudioBook> source, MetadataResult<AudioBook> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs b/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs index 219f95799..2ea0a7ee9 100644 --- a/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs +++ b/MediaBrowser.Providers/Books/AudioPodcastMetadataService.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.Books { public class AudioPodcastMetadataService : MetadataService<AudioPodcast, SongInfo> { - protected override void MergeData(MetadataResult<AudioPodcast> source, MetadataResult<AudioPodcast> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<AudioPodcast> source, MetadataResult<AudioPodcast> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Books/BookMetadataService.cs b/MediaBrowser.Providers/Books/BookMetadataService.cs index ae7e734cc..68b96486a 100644 --- a/MediaBrowser.Providers/Books/BookMetadataService.cs +++ b/MediaBrowser.Providers/Books/BookMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Books { public class BookMetadataService : MetadataService<Book, BookInfo> { - protected override void MergeData(MetadataResult<Book> source, MetadataResult<Book> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Book> source, MetadataResult<Book> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index b391ce4d3..a8400848d 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Providers.BoxSets return updateType; } - protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs index 4ee1826ad..7d1f2779b 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs @@ -123,8 +123,7 @@ namespace MediaBrowser.Providers.BoxSets return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) - .ThenByDescending(i => i.VoteCount ?? 0) - .ToList(); + .ThenByDescending(i => i.VoteCount ?? 0); } /// <summary> @@ -145,8 +144,7 @@ namespace MediaBrowser.Providers.BoxSets private IEnumerable<MovieDbBoxSetProvider.Backdrop> GetBackdrops(MovieDbBoxSetProvider.Images images) { var eligibleBackdrops = images.backdrops == null ? new List<MovieDbBoxSetProvider.Backdrop>() : - images.backdrops - .ToList(); + images.backdrops; return eligibleBackdrops.OrderByDescending(i => i.vote_average) .ThenByDescending(i => i.vote_count); diff --git a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs index 8a3da5bf4..b22db559e 100644 --- a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs +++ b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Channels { public class ChannelMetadataService : MetadataService<Channel, ItemLookupInfo> { - protected override void MergeData(MetadataResult<Channel> source, MetadataResult<Channel> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Channel> source, MetadataResult<Channel> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs index dc0fda72e..1dab08671 100644 --- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Providers.Folders { public class CollectionFolderMetadataService : MetadataService<CollectionFolder, ItemLookupInfo> { - protected override void MergeData(MetadataResult<CollectionFolder> source, MetadataResult<CollectionFolder> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<CollectionFolder> source, MetadataResult<CollectionFolder> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } @@ -27,7 +27,7 @@ namespace MediaBrowser.Providers.Folders public class ManualCollectionsFolderMetadataService : MetadataService<ManualCollectionsFolder, ItemLookupInfo> { - protected override void MergeData(MetadataResult<ManualCollectionsFolder> source, MetadataResult<ManualCollectionsFolder> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<ManualCollectionsFolder> source, MetadataResult<ManualCollectionsFolder> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Folders/FolderMetadataService.cs b/MediaBrowser.Providers/Folders/FolderMetadataService.cs index ff8d87e2b..687dac919 100644 --- a/MediaBrowser.Providers/Folders/FolderMetadataService.cs +++ b/MediaBrowser.Providers/Folders/FolderMetadataService.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Folders } } - protected override void MergeData(MetadataResult<Folder> source, MetadataResult<Folder> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Folder> source, MetadataResult<Folder> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs index 06b62a98b..951794961 100644 --- a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs +++ b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Folders { public class UserViewMetadataService : MetadataService<UserView, ItemLookupInfo> { - protected override void MergeData(MetadataResult<UserView> source, MetadataResult<UserView> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<UserView> source, MetadataResult<UserView> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs index 13d40b4d9..edde0f5e3 100644 --- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs +++ b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.GameGenres { public class GameGenreMetadataService : MetadataService<GameGenre, ItemLookupInfo> { - protected override void MergeData(MetadataResult<GameGenre> source, MetadataResult<GameGenre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<GameGenre> source, MetadataResult<GameGenre> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Games/GameMetadataService.cs b/MediaBrowser.Providers/Games/GameMetadataService.cs index 10f74629f..67becbf58 100644 --- a/MediaBrowser.Providers/Games/GameMetadataService.cs +++ b/MediaBrowser.Providers/Games/GameMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Games { public class GameMetadataService : MetadataService<Game, GameInfo> { - protected override void MergeData(MetadataResult<Game> source, MetadataResult<Game> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Game> source, MetadataResult<Game> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs index ca33563fa..474dd2fcf 100644 --- a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs +++ b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Games { public class GameSystemMetadataService : MetadataService<GameSystem, GameSystemInfo> { - protected override void MergeData(MetadataResult<GameSystem> source, MetadataResult<GameSystem> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<GameSystem> source, MetadataResult<GameSystem> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs index a695fb372..88fba1854 100644 --- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs +++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Genres { public class GenreMetadataService : MetadataService<Genre, ItemLookupInfo> { - protected override void MergeData(MetadataResult<Genre> source, MetadataResult<Genre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Genre> source, MetadataResult<Genre> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs b/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs index f4749c37d..509c91188 100644 --- a/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs +++ b/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.LiveTv { public class AudioRecordingService : MetadataService<LiveTvAudioRecording, ItemLookupInfo> { - protected override void MergeData(MetadataResult<LiveTvAudioRecording> source, MetadataResult<LiveTvAudioRecording> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<LiveTvAudioRecording> source, MetadataResult<LiveTvAudioRecording> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs index 8012021ab..31e3ecaf4 100644 --- a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.LiveTv { public class ChannelMetadataService : MetadataService<LiveTvChannel, ItemLookupInfo> { - protected override void MergeData(MetadataResult<LiveTvChannel> source, MetadataResult<LiveTvChannel> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<LiveTvChannel> source, MetadataResult<LiveTvChannel> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs index f203aa8c6..28a12540b 100644 --- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs +++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.LiveTv { public class ProgramMetadataService : MetadataService<LiveTvProgram, LiveTvProgramLookupInfo> { - protected override void MergeData(MetadataResult<LiveTvProgram> source, MetadataResult<LiveTvProgram> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<LiveTvProgram> source, MetadataResult<LiveTvProgram> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs b/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs index 528e9a5ec..8bfa91655 100644 --- a/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs +++ b/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.LiveTv { public class VideoRecordingService : MetadataService<LiveTvVideoRecording, ItemLookupInfo> { - protected override void MergeData(MetadataResult<LiveTvVideoRecording> source, MetadataResult<LiveTvVideoRecording> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<LiveTvVideoRecording> source, MetadataResult<LiveTvVideoRecording> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 384a911de..7fdbdbcc7 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -17,6 +17,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Manager { @@ -541,7 +542,7 @@ namespace MediaBrowser.Providers.Manager { list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)); } - return list.ToArray(); + return list.ToArray(list.Count); } if (type == ImageType.Primary) diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index b360a2fcd..4864f455a 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -23,6 +23,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Manager { @@ -60,7 +61,7 @@ namespace MediaBrowser.Providers.Manager return hasChanges; } - public async Task<RefreshResult> RefreshImages(IHasMetadata item, LibraryOptions libraryOptions, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken) + public async Task<RefreshResult> RefreshImages(IHasMetadata item, LibraryOptions libraryOptions, List<IImageProvider> providers, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken) { if (refreshOptions.IsReplacingImage(ImageType.Backdrop)) { @@ -73,8 +74,6 @@ namespace MediaBrowser.Providers.Manager var result = new RefreshResult { UpdateType = ItemUpdateType.None }; - var providers = imageProviders.ToList(); - var providerIds = new List<Guid>(); // In order to avoid duplicates, only download these if there are none already @@ -384,10 +383,7 @@ namespace MediaBrowser.Providers.Manager } } - foreach (var image in deletedImages) - { - item.RemoveImage(image); - } + item.RemoveImages(deletedImages); if (deleted) { @@ -461,7 +457,7 @@ namespace MediaBrowser.Providers.Manager var newImageFileInfos = newImages .Select(i => i.FileInfo) - .ToList(); + .ToList(newImages.Count); if (item.AddImages(type, newImageFileInfos)) { diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 01aee9b92..5d531c95c 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Manager { var baseItem = result.Item as BaseItem; - await LibraryManager.UpdatePeople(baseItem, result.People.ToList()); + await LibraryManager.UpdatePeople(baseItem, result.People); await SavePeopleMetadata(result.People, libraryOptions, cancellationToken).ConfigureAwait(false); } await result.Item.UpdateToRepository(reason, cancellationToken).ConfigureAwait(false); @@ -314,7 +314,7 @@ namespace MediaBrowser.Providers.Manager var folder = item as Folder; if (folder != null && folder.SupportsCumulativeRunTimeTicks) { - var items = folder.GetRecursiveChildren(i => !i.IsFolder).ToList(); + var items = folder.GetRecursiveChildren(i => !i.IsFolder); var ticks = items.Select(i => i.RunTimeTicks ?? 0).Sum(); if (!folder.RunTimeTicks.HasValue || folder.RunTimeTicks.Value != ticks) @@ -519,7 +519,7 @@ namespace MediaBrowser.Providers.Manager userDataList.AddRange(localItem.UserDataList); } - MergeData(localItem, temp, new List<MetadataFields>(), !options.ReplaceAllMetadata, true); + MergeData(localItem, temp, new MetadataFields[]{}, !options.ReplaceAllMetadata, true); refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; // Only one local provider allowed per item @@ -567,7 +567,7 @@ namespace MediaBrowser.Providers.Manager else { // TODO: If the new metadata from above has some blank data, this can cause old data to get filled into those empty fields - MergeData(metadata, temp, new List<MetadataFields>(), false, false); + MergeData(metadata, temp, new MetadataFields[]{}, false, false); MergeData(temp, metadata, item.LockedFields, true, false); } } @@ -704,7 +704,7 @@ namespace MediaBrowser.Providers.Manager foreach (var result in results) { - MergeData(result, temp, new List<MetadataFields>(), false, false); + MergeData(result, temp, new MetadataFields[]{}, false, false); } return refreshResult; @@ -736,7 +736,7 @@ namespace MediaBrowser.Providers.Manager protected abstract void MergeData(MetadataResult<TItemType> source, MetadataResult<TItemType> target, - List<MetadataFields> lockedFields, + MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 8cdb9a407..139bd6d58 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -182,9 +182,7 @@ namespace MediaBrowser.Providers.Manager var results = await Task.WhenAll(tasks).ConfigureAwait(false); - var images = results.SelectMany(i => i.ToList()); - - return images; + return results.SelectMany(i => i.ToList()); } /// <summary> diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 93a386ed5..a200b18b5 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Manager { public static void MergeBaseItemData<T>(MetadataResult<T> sourceResult, MetadataResult<T> targetResult, - List<MetadataFields> lockedFields, + MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) where T : BaseItem @@ -150,7 +150,7 @@ namespace MediaBrowser.Providers.Manager if (!lockedFields.Contains(MetadataFields.Studios)) { - if (replaceData || target.Studios.Count == 0) + if (replaceData || target.Studios.Length == 0) { target.Studios = source.Studios; } @@ -158,7 +158,7 @@ namespace MediaBrowser.Providers.Manager if (!lockedFields.Contains(MetadataFields.Tags)) { - if (replaceData || target.Tags.Count == 0) + if (replaceData || target.Tags.Length == 0) { target.Tags = source.Tags; } @@ -166,7 +166,7 @@ namespace MediaBrowser.Providers.Manager if (!lockedFields.Contains(MetadataFields.ProductionLocations)) { - if (replaceData || target.ProductionLocations.Count == 0) + if (replaceData || target.ProductionLocations.Length == 0) { target.ProductionLocations = source.ProductionLocations; } @@ -266,7 +266,7 @@ namespace MediaBrowser.Providers.Manager target.PreferredMetadataLanguage = source.PreferredMetadataLanguage; } - private static void MergeDisplayOrder(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + private static void MergeDisplayOrder(BaseItem source, BaseItem target, MetadataFields[] lockedFields, bool replaceData) { var sourceHasDisplayOrder = source as IHasDisplayOrder; var targetHasDisplayOrder = target as IHasDisplayOrder; @@ -277,7 +277,7 @@ namespace MediaBrowser.Providers.Manager } } - private static void MergeAlbumArtist(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + private static void MergeAlbumArtist(BaseItem source, BaseItem target, MetadataFields[] lockedFields, bool replaceData) { var sourceHasAlbumArtist = source as IHasAlbumArtist; var targetHasAlbumArtist = target as IHasAlbumArtist; @@ -291,7 +291,7 @@ namespace MediaBrowser.Providers.Manager } } - private static void MergeCriticRating(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + private static void MergeCriticRating(BaseItem source, BaseItem target, MetadataFields[] lockedFields, bool replaceData) { if (replaceData || !target.CriticRating.HasValue) { @@ -299,7 +299,7 @@ namespace MediaBrowser.Providers.Manager } } - private static void MergeTrailers(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + private static void MergeTrailers(BaseItem source, BaseItem target, MetadataFields[] lockedFields, bool replaceData) { var sourceCast = source as IHasTrailers; var targetCast = target as IHasTrailers; @@ -313,7 +313,7 @@ namespace MediaBrowser.Providers.Manager } } - private static void MergeVideoInfo(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData) + private static void MergeVideoInfo(BaseItem source, BaseItem target, MetadataFields[] lockedFields, bool replaceData) { var sourceCast = source as Video; var targetCast = target as Video; diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 3e145e1f0..b8b603245 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.MediaInfo { @@ -92,7 +93,7 @@ namespace MediaBrowser.Providers.MediaInfo private string GetAudioImagePath(Audio item) { var filename = item.Album ?? string.Empty; - filename += string.Join(",", item.Artists.ToArray()); + filename += string.Join(",", item.Artists.ToArray(item.Artists.Count)); if (!string.IsNullOrWhiteSpace(item.Album)) { diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 12d1d3468..b0785298b 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -13,6 +13,7 @@ using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; +using System.Linq; namespace MediaBrowser.Providers.MediaInfo { @@ -165,12 +166,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!audio.LockedFields.Contains(MetadataFields.Studios)) { - audio.Studios.Clear(); - - foreach (var studio in data.Studios) - { - audio.AddStudio(studio); - } + audio.SetStudios(data.Studios); } audio.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, data.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist)); diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 143ec8fdd..7956b52af 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -376,14 +376,9 @@ namespace MediaBrowser.Providers.MediaInfo if (!video.IsLocked && !video.LockedFields.Contains(MetadataFields.Studios)) { - if (video.Studios.Count == 0 || isFullRefresh) + if (video.Studios.Length == 0 || isFullRefresh) { - video.Studios.Clear(); - - foreach (var studio in data.Studios) - { - video.AddStudio(studio); - } + video.SetStudios(data.Studios); } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index dfa705126..465c417e7 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -133,7 +133,7 @@ namespace MediaBrowser.Providers.MediaInfo throw new ArgumentException(string.Format("Cannot search for items that don't have a path: {0} {1}", video.Name, video.Id)); } - var files = directoryService.GetFilePaths(containingPath, clearCache); + var files = fileSystem.GetFilePaths(containingPath, clearCache); var videoFileNameWithoutExtension = fileSystem.GetFileNameWithoutExtension(video.Path); diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 200615a5d..61cf1cfc3 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -17,6 +17,7 @@ using System.IO; using MediaBrowser.Controller.Dto; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.MediaInfo { @@ -83,7 +84,7 @@ namespace MediaBrowser.Providers.MediaInfo { MediaTypes = new string[] { MediaType.Video }, IsVirtualItem = false, - IncludeItemTypes = types.ToArray(), + IncludeItemTypes = types.ToArray(types.Count), DtoOptions = new DtoOptions(true) }).OfType<Video>() diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs index db621e554..ddf3e4d1a 100644 --- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs +++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs @@ -146,7 +146,7 @@ namespace MediaBrowser.Providers.Movies movie.ProductionLocations = movieData .production_countries .Select(i => i.name) - .ToList(); + .ToArray(movieData.production_countries.Count); } movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture)); @@ -213,12 +213,7 @@ namespace MediaBrowser.Providers.Movies //studios if (movieData.production_companies != null) { - movie.Studios.Clear(); - - foreach (var studio in movieData.production_companies.Select(c => c.name)) - { - movie.AddStudio(studio); - } + movie.SetStudios(movieData.production_companies.Select(c => c.name)); } // genres diff --git a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs index 257ecadd3..8dc4bd56a 100644 --- a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs @@ -127,8 +127,7 @@ namespace MediaBrowser.Providers.Movies return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) - .ThenByDescending(i => i.VoteCount ?? 0) - .ToList(); + .ThenByDescending(i => i.VoteCount ?? 0); } /// <summary> @@ -149,8 +148,7 @@ namespace MediaBrowser.Providers.Movies private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images) { var eligibleBackdrops = images.backdrops == null ? new List<MovieDbProvider.Backdrop>() : - images.backdrops - .ToList(); + images.backdrops; return eligibleBackdrops.OrderByDescending(i => i.vote_average) .ThenByDescending(i => i.vote_count); diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index fe190afb4..ec1c49742 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -22,6 +22,7 @@ using MediaBrowser.Common; using MediaBrowser.Controller.IO; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Movies { @@ -282,7 +283,7 @@ namespace MediaBrowser.Providers.Movies languages.Add("en"); } - return string.Join(",", languages.ToArray()); + return string.Join(",", languages.ToArray(languages.Count)); } public static string NormalizeLanguage(string language) diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs index 333d289ec..dc3146f2a 100644 --- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.Movies return base.IsFullLocalMetadata(item); } - protected override void MergeData(MetadataResult<Movie> source, MetadataResult<Movie> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Movie> source, MetadataResult<Movie> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); @@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.Movies return base.IsFullLocalMetadata(item); } - protected override void MergeData(MetadataResult<Trailer> source, MetadataResult<Trailer> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Trailer> source, MetadataResult<Trailer> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index bba40608c..839bec604 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs @@ -44,13 +44,13 @@ namespace MediaBrowser.Providers.Music if (!item.LockedFields.Contains(MetadataFields.Studios)) { - var currentList = item.Studios.ToList(); + var currentList = item.Studios; item.Studios = songs.SelectMany(i => i.Studios) .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); + .ToArray(); - if (currentList.Count != item.Studios.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) + if (currentList.Length != item.Studios.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) { updateType = updateType | ItemUpdateType.MetadataEdit; } @@ -151,7 +151,7 @@ namespace MediaBrowser.Providers.Music return updateType; } - protected override void MergeData(MetadataResult<MusicAlbum> source, MetadataResult<MusicAlbum> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<MusicAlbum> source, MetadataResult<MusicAlbum> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index 98fe766e0..c8008ba1e 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Music Recursive = true, IsFolder = false }) : - item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder).ToList(); + item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder); var currentList = item.Genres.ToList(); @@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Music return updateType; } - protected override void MergeData(MetadataResult<MusicArtist> source, MetadataResult<MusicArtist> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<MusicArtist> source, MetadataResult<MusicArtist> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs index 4d791be01..647ac2bff 100644 --- a/MediaBrowser.Providers/Music/AudioMetadataService.cs +++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs @@ -8,12 +8,13 @@ using MediaBrowser.Providers.Manager; using System.Collections.Generic; using System.Linq; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Music { public class AudioMetadataService : MetadataService<Audio, SongInfo> { - protected override void MergeData(MetadataResult<Audio> source, MetadataResult<Audio> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Audio> source, MetadataResult<Audio> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); @@ -22,7 +23,7 @@ namespace MediaBrowser.Providers.Music if (replaceData || targetItem.Artists.Count == 0) { - targetItem.Artists = sourceItem.Artists.ToList(); + targetItem.Artists = sourceItem.Artists.ToList(sourceItem.Artists.Count); } if (replaceData || string.IsNullOrEmpty(targetItem.Album)) diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index b77fcf1b2..17b5196de 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -17,6 +17,7 @@ using System.Threading.Tasks; using System.Xml; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Xml; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Music { @@ -123,7 +124,8 @@ namespace MediaBrowser.Providers.Music } return result; - }).ToList(); + + }).ToList(results.Count); } } } @@ -619,7 +621,7 @@ namespace MediaBrowser.Providers.Music } } - _mbzUrls = list.ToList(); + _mbzUrls = list.ToList(list.Count); return list; } diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 1a2b13e94..06f60c8a3 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Music using (var stream = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) { - var results = GetResultsFromResponse(stream).ToList(); + var results = GetResultsFromResponse(stream); if (results.Count > 0) { @@ -73,7 +73,7 @@ namespace MediaBrowser.Providers.Music return new List<RemoteSearchResult>(); } - private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream) + private List<RemoteSearchResult> GetResultsFromResponse(Stream stream) { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Music } } - private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader) + private List<RemoteSearchResult> ParseArtistList(XmlReader reader) { var list = new List<RemoteSearchResult>(); diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs index b8f6e2c63..40736619a 100644 --- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs +++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs @@ -10,12 +10,13 @@ using System.Linq; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Music { class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo> { - protected override void MergeData(MetadataResult<MusicVideo> source, MetadataResult<MusicVideo> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<MusicVideo> source, MetadataResult<MusicVideo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); @@ -29,7 +30,7 @@ namespace MediaBrowser.Providers.Music if (replaceData || targetItem.Artists.Count == 0) { - targetItem.Artists = sourceItem.Artists.ToList(); + targetItem.Artists = sourceItem.Artists.ToList(sourceItem.Artists.Count); } } diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs index ba059bd7b..3828f8d27 100644 --- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs +++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.MusicGenres { public class MusicGenreMetadataService : MetadataService<MusicGenre, ItemLookupInfo> { - protected override void MergeData(MetadataResult<MusicGenre> source, MetadataResult<MusicGenre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<MusicGenre> source, MetadataResult<MusicGenre> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs index 78cc72836..4dfcdba09 100644 --- a/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs +++ b/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs @@ -116,8 +116,7 @@ namespace MediaBrowser.Providers.People return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) - .ThenByDescending(i => i.VoteCount ?? 0) - .ToList(); + .ThenByDescending(i => i.VoteCount ?? 0); } private string GetLanguage(MovieDbPersonProvider.Profile profile) diff --git a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs index 986c4b4a9..d8c9ce801 100644 --- a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs +++ b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs @@ -184,7 +184,7 @@ namespace MediaBrowser.Providers.People if (!string.IsNullOrWhiteSpace(info.place_of_birth)) { - item.ProductionLocations = new List<string> { info.place_of_birth }; + item.ProductionLocations = new string[] { info.place_of_birth }; } item.Overview = info.biography; diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs index 5d70fc1d6..bbfaa43b4 100644 --- a/MediaBrowser.Providers/People/PersonMetadataService.cs +++ b/MediaBrowser.Providers/People/PersonMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.People { public class PersonMetadataService : MetadataService<Person, PersonLookupInfo> { - protected override void MergeData(MetadataResult<Person> source, MetadataResult<Person> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Person> source, MetadataResult<Person> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs index 85b9dafd4..bff933ccf 100644 --- a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs +++ b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Photos { class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo> { - protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs index 909d359b9..d7f4982e4 100644 --- a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs +++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Providers.Photos { class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo> { - protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index c4ea106fb..dccef3a09 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Providers.Playlists { class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo> { - protected override void MergeData(MetadataResult<Playlist> source, MetadataResult<Playlist> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Playlist> source, MetadataResult<Playlist> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs index ef5ced3e2..23953b0d3 100644 --- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs +++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Providers.Studios { public class StudioMetadataService : MetadataService<Studio, ItemLookupInfo> { - protected override void MergeData(MetadataResult<Studio> source, MetadataResult<Studio> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Studio> source, MetadataResult<Studio> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index f7071f519..fce95364e 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -295,8 +295,7 @@ namespace MediaBrowser.Providers.Subtitles } var providers = _subtitleProviders - .Where(i => i.SupportedMediaTypes.Contains(mediaType)) - .ToList(); + .Where(i => i.SupportedMediaTypes.Contains(mediaType)); return providers.Select(i => new SubtitleProviderInfo { diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs index 8adb6d4d2..29533a46b 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Providers.TV return updateType; } - protected override void MergeData(MetadataResult<Episode> source, MetadataResult<Episode> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Episode> source, MetadataResult<Episode> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs index 1b4e3f44f..c10b0ef9d 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.TV return updateType; } - protected override void MergeData(MetadataResult<Season> source, MetadataResult<Season> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Season> source, MetadataResult<Season> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index 7559a15de..cfea0c315 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Providers.TV return base.IsFullLocalMetadata(item); } - protected override void MergeData(MetadataResult<Series> source, MetadataResult<Series> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Series> source, MetadataResult<Series> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs index 474036e7f..728bbc505 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs @@ -104,8 +104,7 @@ namespace MediaBrowser.Providers.TV return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) - .ThenByDescending(i => i.VoteCount ?? 0) - .ToList(); + .ThenByDescending(i => i.VoteCount ?? 0); } diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs index d4cda15aa..f05f7bb30 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs @@ -118,8 +118,7 @@ namespace MediaBrowser.Providers.TV return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) - .ThenByDescending(i => i.VoteCount ?? 0) - .ToList(); + .ThenByDescending(i => i.VoteCount ?? 0); } /// <summary> @@ -138,8 +137,7 @@ namespace MediaBrowser.Providers.TV private IEnumerable<MovieDbSeriesProvider.Backdrop> GetBackdrops(MovieDbSeriesProvider.Images images) { var eligibleBackdrops = images.backdrops == null ? new List<MovieDbSeriesProvider.Backdrop>() : - images.backdrops - .ToList(); + images.backdrops; return eligibleBackdrops.OrderByDescending(i => i.vote_average) .ThenByDescending(i => i.vote_count); diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs index 0b5708b56..4de1712d2 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.TV { @@ -231,7 +232,7 @@ namespace MediaBrowser.Providers.TV if (seriesInfo.networks != null) { - series.Studios = seriesInfo.networks.Select(i => i.name).ToList(); + series.Studios = seriesInfo.networks.Select(i => i.name).ToArray(seriesInfo.networks.Count); } if (seriesInfo.genres != null) diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs index 9a457ba94..30d2691e3 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -320,9 +320,8 @@ namespace MediaBrowser.Providers.TV /// <param name="progress">The progress.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - private async Task UpdateSeries(IEnumerable<string> seriesIds, string seriesDataPath, long? lastTvDbUpdateTime, IProgress<double> progress, CancellationToken cancellationToken) + private async Task UpdateSeries(List<string> seriesIds, string seriesDataPath, long? lastTvDbUpdateTime, IProgress<double> progress, CancellationToken cancellationToken) { - var list = seriesIds.ToList(); var numComplete = 0; var seriesList = _libraryManager.GetItemList(new InternalItemsQuery() @@ -342,7 +341,7 @@ namespace MediaBrowser.Providers.TV .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb))) .ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb)); - foreach (var seriesId in list) + foreach (var seriesId in seriesIds) { // Find the preferred language(s) for the movie in the library var languages = allSeries[seriesId] @@ -371,7 +370,7 @@ namespace MediaBrowser.Providers.TV numComplete++; double percent = numComplete; - percent /= list.Count; + percent /= seriesIds.Count; percent *= 100; progress.Report(percent); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs index 822664bfd..22dab1cd7 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs @@ -175,8 +175,7 @@ namespace MediaBrowser.Providers.TV return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) - .ThenByDescending(i => i.VoteCount ?? 0) - .ToList(); + .ThenByDescending(i => i.VoteCount ?? 0); } private static void AddImage(XmlReader reader, List<RemoteImageInfo> images, int seasonNumber) diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs index e720827c5..6f3d763e2 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs @@ -169,8 +169,7 @@ namespace MediaBrowser.Providers.TV return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) - .ThenByDescending(i => i.VoteCount ?? 0) - .ToList(); + .ThenByDescending(i => i.VoteCount ?? 0); } private void AddImage(XmlReader reader, List<RemoteImageInfo> images) diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 8a10affb9..78556c5a4 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -482,7 +482,7 @@ namespace MediaBrowser.Providers.TV /// <returns>Task{System.String}.</returns> private async Task<IEnumerable<RemoteSearchResult>> FindSeries(string name, int? year, string language, CancellationToken cancellationToken) { - var results = (await FindSeriesInternal(name, language, cancellationToken).ConfigureAwait(false)).ToList(); + var results = (await FindSeriesInternal(name, language, cancellationToken).ConfigureAwait(false)); if (results.Count == 0) { @@ -491,7 +491,7 @@ namespace MediaBrowser.Providers.TV if (!string.IsNullOrWhiteSpace(nameWithoutYear) && !string.Equals(nameWithoutYear, name, StringComparison.OrdinalIgnoreCase)) { - results = (await FindSeriesInternal(nameWithoutYear, language, cancellationToken).ConfigureAwait(false)).ToList(); + results = (await FindSeriesInternal(nameWithoutYear, language, cancellationToken).ConfigureAwait(false)); } } @@ -507,7 +507,7 @@ namespace MediaBrowser.Providers.TV }); } - private async Task<IEnumerable<RemoteSearchResult>> FindSeriesInternal(string name, string language, CancellationToken cancellationToken) + private async Task<List<RemoteSearchResult>> FindSeriesInternal(string name, string language, CancellationToken cancellationToken) { var url = string.Format(SeriesSearchUrl, WebUtility.UrlEncode(name), NormalizeLanguage(language)); var searchResults = new List<RemoteSearchResult>(); @@ -1273,12 +1273,7 @@ namespace MediaBrowser.Providers.TV if (vals.Count > 0) { - item.Studios.Clear(); - - foreach (var genre in vals) - { - item.AddStudio(genre); - } + item.SetStudios(vals); } } diff --git a/MediaBrowser.Providers/Users/UserMetadataService.cs b/MediaBrowser.Providers/Users/UserMetadataService.cs index 88df704d6..694315c18 100644 --- a/MediaBrowser.Providers/Users/UserMetadataService.cs +++ b/MediaBrowser.Providers/Users/UserMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Users { public class UserMetadataService : MetadataService<User, ItemLookupInfo> { - protected override void MergeData(MetadataResult<User> source, MetadataResult<User> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<User> source, MetadataResult<User> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Videos/VideoMetadataService.cs b/MediaBrowser.Providers/Videos/VideoMetadataService.cs index a07cc5949..f493eb31f 100644 --- a/MediaBrowser.Providers/Videos/VideoMetadataService.cs +++ b/MediaBrowser.Providers/Videos/VideoMetadataService.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Videos } } - protected override void MergeData(MetadataResult<Video> source, MetadataResult<Video> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Video> source, MetadataResult<Video> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Providers/Years/YearMetadataService.cs b/MediaBrowser.Providers/Years/YearMetadataService.cs index 36c2fd1dd..783833656 100644 --- a/MediaBrowser.Providers/Years/YearMetadataService.cs +++ b/MediaBrowser.Providers/Years/YearMetadataService.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Years { public class YearMetadataService : MetadataService<Year, ItemLookupInfo> { - protected override void MergeData(MetadataResult<Year> source, MetadataResult<Year> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) + protected override void MergeData(MetadataResult<Year> source, MetadataResult<Year> target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); } diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index df98c6584..33678d323 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -52,12 +52,10 @@ <HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath> </Reference> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> - <HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Text, Version=4.5.8.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\ServiceStack.Text.4.5.8\lib\net45\ServiceStack.Text.dll</HintPath> - <Private>True</Private> + <Reference Include="ServiceStack.Text, Version=4.5.12.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\ServiceStack.Text.4.5.12\lib\net45\ServiceStack.Text.dll</HintPath> </Reference> <Reference Include="SharpCompress, Version=0.14.0.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll</HintPath> @@ -71,12 +69,10 @@ <Private>True</Private> </Reference> <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL"> - <HintPath>..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath> </Reference> <Reference Include="SQLitePCLRaw.provider.sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62684c7b4f184e3f, processorArchitecture=MSIL"> - <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.8\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="MediaBrowser.IsoMounting.Linux"> @@ -133,10 +129,6 @@ <Project>{89ab4548-770d-41fd-a891-8daff44f452c}</Project> <Name>Emby.Photos</Name> </ProjectReference> - <ProjectReference Include="..\Emby.Server.Core\Emby.Server.Core.csproj"> - <Project>{776b9f0c-5195-45e3-9a36-1cc1f0d8e0b0}</Project> - <Name>Emby.Server.Core</Name> - </ProjectReference> <ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj"> <Project>{e383961b-9356-4d5d-8233-9a1079d03055}</Project> <Name>Emby.Server.Implementations</Name> @@ -169,10 +161,6 @@ <Project>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</Project> <Name>MediaBrowser.Api</Name> </ProjectReference> - <ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj"> - <Project>{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}</Project> - <Name>MediaBrowser.MediaEncoding</Name> - </ProjectReference> <ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj"> <Project>{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}</Project> <Name>MediaBrowser.LocalMetadata</Name> diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index 0a70c446f..aa6a58b48 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -16,8 +16,6 @@ using Emby.Common.Implementations.Logging; using Emby.Common.Implementations.Networking; using Emby.Server.Core.Cryptography; using Emby.Server.Core; -using Emby.Server.Core.IO; -using Emby.Server.Core.Logging; using Emby.Server.Implementations; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Logging; @@ -115,7 +113,7 @@ namespace MediaBrowser.Server.Mono "emby.mono.zip", environmentInfo, imageEncoder, - new Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")), + new SystemEvents(logManager.GetLogger("SystemEvents")), new MemoryStreamProvider(), new NetworkManager(logManager.GetLogger("NetworkManager")), GenerateCertificate, diff --git a/MediaBrowser.Server.Mono/packages.config b/MediaBrowser.Server.Mono/packages.config index dcc477e9a..0cb6cc916 100644 --- a/MediaBrowser.Server.Mono/packages.config +++ b/MediaBrowser.Server.Mono/packages.config @@ -1,11 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" /> - <package id="NLog" version="4.4.11" targetFramework="net46" /> - <package id="ServiceStack.Text" version="4.5.8" targetFramework="net46" /> + <package id="NLog" version="4.4.12" targetFramework="net46" /> + <package id="ServiceStack.Text" version="4.5.12" targetFramework="net46" /> <package id="SharpCompress" version="0.14.0" targetFramework="net46" /> <package id="SimpleInjector" version="4.0.8" targetFramework="net46" /> <package id="SkiaSharp" version="1.58.0" targetFramework="net46" /> - <package id="SQLitePCLRaw.core" version="1.1.7" targetFramework="net46" /> - <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.7" targetFramework="net46" /> + <package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net46" /> + <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.8" targetFramework="net46" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index bc38476ca..e687b34cf 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -24,14 +24,13 @@ using Emby.Common.Implementations.Networking; using Emby.Server.Core.Cryptography; using Emby.Drawing; using Emby.Server.Core; -using Emby.Server.Core.IO; -using Emby.Server.Core.Logging; using Emby.Server.Implementations; using Emby.Server.Implementations.Browser; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Logging; using MediaBrowser.Common.Net; using MediaBrowser.Model.IO; +using SystemEvents = Emby.Server.Implementations.SystemEvents; namespace MediaBrowser.ServerApplication { @@ -320,8 +319,8 @@ namespace MediaBrowser.ServerApplication "emby.windows.zip", environmentInfo, new NullImageEncoder(), - new Server.Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")), - new RecyclableMemoryStreamProvider(), + new SystemEvents(logManager.GetLogger("SystemEvents")), + new MemoryStreamProvider(), new Networking.NetworkManager(logManager.GetLogger("NetworkManager")), GenerateCertificate, () => Environment.UserDomainName); diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 591ac0fba..624822746 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -74,12 +74,10 @@ <HintPath>..\ThirdParty\emby\Emby.Server.Sync.dll</HintPath> </Reference> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> - <HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Text, Version=4.5.8.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\ServiceStack.Text.4.5.8\lib\net45\ServiceStack.Text.dll</HintPath> - <Private>True</Private> + <Reference Include="ServiceStack.Text, Version=4.5.12.0, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\ServiceStack.Text.4.5.12\lib\net45\ServiceStack.Text.dll</HintPath> </Reference> <Reference Include="SharpCompress, Version=0.14.0.0, Culture=neutral, processorArchitecture=MSIL"> <HintPath>..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll</HintPath> @@ -92,12 +90,10 @@ <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath> </Reference> <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL"> - <HintPath>..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll</HintPath> </Reference> <Reference Include="SQLitePCLRaw.provider.sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62684c7b4f184e3f, processorArchitecture=MSIL"> - <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath> - <Private>True</Private> + <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.8\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Configuration" /> @@ -226,10 +222,6 @@ <Project>{89ab4548-770d-41fd-a891-8daff44f452c}</Project> <Name>Emby.Photos</Name> </ProjectReference> - <ProjectReference Include="..\Emby.Server.Core\Emby.Server.Core.csproj"> - <Project>{776b9f0c-5195-45e3-9a36-1cc1f0d8e0b0}</Project> - <Name>Emby.Server.Core</Name> - </ProjectReference> <ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj"> <Project>{e383961b-9356-4d5d-8233-9a1079d03055}</Project> <Name>Emby.Server.Implementations</Name> @@ -250,10 +242,6 @@ <Project>{7ef9f3e0-697d-42f3-a08f-19deb5f84392}</Project> <Name>MediaBrowser.LocalMetadata</Name> </ProjectReference> - <ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj"> - <Project>{0bd82fa6-eb8a-4452-8af5-74f9c3849451}</Project> - <Name>MediaBrowser.MediaEncoding</Name> - </ProjectReference> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> <Name>MediaBrowser.Model</Name> diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index 0fa93db82..e3fea58a6 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="NLog" version="4.4.11" targetFramework="net462" /> - <package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" /> + <package id="NLog" version="4.4.12" targetFramework="net462" /> + <package id="ServiceStack.Text" version="4.5.12" targetFramework="net462" /> <package id="SharpCompress" version="0.14.0" targetFramework="net462" /> <package id="SimpleInjector" version="4.0.8" targetFramework="net462" /> <package id="SkiaSharp" version="1.58.0" targetFramework="net462" /> - <package id="SQLitePCLRaw.core" version="1.1.7" targetFramework="net462" /> - <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.7" targetFramework="net462" /> + <package id="SQLitePCLRaw.core" version="1.1.8" targetFramework="net462" /> + <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.8" targetFramework="net462" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.Tests/MediaBrowser.Tests.csproj b/MediaBrowser.Tests/MediaBrowser.Tests.csproj index a031d47ea..57a645ce0 100644 --- a/MediaBrowser.Tests/MediaBrowser.Tests.csproj +++ b/MediaBrowser.Tests/MediaBrowser.Tests.csproj @@ -37,6 +37,9 @@ <WarningLevel>4</WarningLevel> </PropertyGroup> <ItemGroup> + <Reference Include="Emby.Server.MediaEncoding"> + <HintPath>..\ThirdParty\emby\Emby.Server.MediaEncoding.dll</HintPath> + </Reference> <Reference Include="System" /> <Reference Include="System.XML" /> </ItemGroup> @@ -69,10 +72,6 @@ <Project>{1e37a338-9f57-4b70-bd6d-bb9c591e319b}</Project> <Name>Emby.Common.Implementations</Name> </ProjectReference> - <ProjectReference Include="..\Emby.Server.Core\Emby.Server.Core.csproj"> - <Project>{776b9f0c-5195-45e3-9a36-1cc1f0d8e0b0}</Project> - <Name>Emby.Server.Core</Name> - </ProjectReference> <ProjectReference Include="..\Emby.Server.Implementations\Emby.Server.Implementations.csproj"> <Project>{e383961b-9356-4d5d-8233-9a1079d03055}</Project> <Name>Emby.Server.Implementations</Name> @@ -85,10 +84,6 @@ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project> <Name>MediaBrowser.Controller</Name> </ProjectReference> - <ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj"> - <Project>{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}</Project> - <Name>MediaBrowser.MediaEncoding</Name> - </ProjectReference> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj"> <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project> <Name>MediaBrowser.Model</Name> @@ -108,20 +103,10 @@ </ItemGroup> <ItemGroup> <None Include="app.config" /> - </ItemGroup> - <ItemGroup> - <None Include="MediaEncoding\Subtitles\TestSubtitles\expected.vtt"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </None> - <None Include="MediaEncoding\Subtitles\TestSubtitles\data.ass"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </None> - <None Include="MediaEncoding\Subtitles\TestSubtitles\data2.ass"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </None> - <None Include="MediaEncoding\Subtitles\TestSubtitles\unit.srt"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </None> + <None Include="MediaEncoding\Subtitles\TestSubtitles\data.ass" /> + <None Include="MediaEncoding\Subtitles\TestSubtitles\data2.ass" /> + <None Include="MediaEncoding\Subtitles\TestSubtitles\expected.vtt" /> + <None Include="MediaEncoding\Subtitles\TestSubtitles\unit.srt" /> </ItemGroup> <ItemGroup> <ContentWithTargetPath Include="ConsistencyTests\Resources\StringCheck.xslt"> diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/AssParserTests.cs b/MediaBrowser.Tests/MediaEncoding/Subtitles/AssParserTests.cs index d5a62802d..47f5891c6 100644 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/AssParserTests.cs +++ b/MediaBrowser.Tests/MediaEncoding/Subtitles/AssParserTests.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; using System.IO; using System.Threading; +using Emby.Server.MediaEncoding.Subtitles; namespace MediaBrowser.Tests.MediaEncoding.Subtitles { diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/SrtParserTests.cs b/MediaBrowser.Tests/MediaEncoding/Subtitles/SrtParserTests.cs index af34e13e7..280dc5d78 100644 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/SrtParserTests.cs +++ b/MediaBrowser.Tests/MediaEncoding/Subtitles/SrtParserTests.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; -using MediaBrowser.MediaEncoding.Subtitles; +using Emby.Server.MediaEncoding.Subtitles; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/MediaBrowser.Tests/MediaEncoding/Subtitles/VttWriterTest.cs b/MediaBrowser.Tests/MediaEncoding/Subtitles/VttWriterTest.cs index 924a1736f..f6e2c5298 100644 --- a/MediaBrowser.Tests/MediaEncoding/Subtitles/VttWriterTest.cs +++ b/MediaBrowser.Tests/MediaEncoding/Subtitles/VttWriterTest.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; -using MediaBrowser.MediaEncoding.Subtitles; +using Emby.Server.MediaEncoding.Subtitles; using MediaBrowser.Model.MediaInfo; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index bd9ef3e9f..c855a8d9b 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.WebDashboard.Api { @@ -282,7 +283,7 @@ namespace MediaBrowser.WebDashboard.Api files.Insert(0, "cordova.js"); } - var tags = files.Select(s => string.Format("<script src=\"{0}\" defer></script>", s)).ToArray(); + var tags = files.Select(s => string.Format("<script src=\"{0}\" defer></script>", s)).ToArray(files.Count); builder.Append(string.Join(string.Empty, tags)); diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index 188fc8504..ac5313a29 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.XbmcMetadata PersonIds = new [] { person.Id.ToString("N") }, DtoOptions = new DtoOptions(true) - }).ToList(); + }); foreach (var item in items) { diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index bacba4ea4..bc2fcb054 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -394,13 +394,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers case "lockedfields": { - var fields = new List<MetadataFields>(); - var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val)) { - var list = val.Split('|').Select(i => + item.LockedFields = val.Split('|').Select(i => { MetadataFields field; @@ -411,13 +409,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers return null; - }).Where(i => i.HasValue).Select(i => i.Value); - - fields.AddRange(list); + }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); } - item.LockedFields = fields; - break; } @@ -438,9 +432,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - item.ProductionLocations.AddRange(val.Split('/') + item.ProductionLocations = val.Split('/') .Select(i => i.Trim()) - .Where(i => !string.IsNullOrWhiteSpace(i))); + .Where(i => !string.IsNullOrWhiteSpace(i)) + .ToArray(); } break; } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 8a5b7f5c9..5f14137bd 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -485,9 +485,9 @@ namespace MediaBrowser.XbmcMetadata.Savers writer.WriteElementString("lockdata", item.IsLocked.ToString().ToLower()); - if (item.LockedFields.Count > 0) + if (item.LockedFields.Length > 0) { - writer.WriteElementString("lockedfields", string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray())); + writer.WriteElementString("lockedfields", string.Join("|", item.LockedFields)); } writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat)); diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 1a4638265..382417bb6 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,7 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.7 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F0E0E64C-2A6F-4E35-9533-D53AC07C2CD1}" EndProject @@ -42,8 +42,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Providers", "M EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ServerApplication", "MediaBrowser.ServerApplication\MediaBrowser.ServerApplication.csproj", "{94ADE4D3-B7EC-45CD-A200-CC469433072B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" @@ -70,8 +68,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.ImageMagick", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.Skia", "Emby.Drawing.Skia\Emby.Drawing.Skia.csproj", "{2312DA6D-FF86-4597-9777-BCEEC32D96DD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Server.Core", "Emby.Server.Core\Emby.Server.Core.csproj", "{776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Common.Implementations", "Emby.Common.Implementations\Emby.Common.Implementations.csproj", "{1E37A338-9F57-4B70-BD6D-BB9C591E319B}" @@ -394,37 +390,6 @@ Global {94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|x64.Build.0 = Release|Any CPU {94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|x86.ActiveCfg = Debug|x86 {94ADE4D3-B7EC-45CD-A200-CC469433072B}.Signed|x86.Build.0 = Debug|x86 - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Win32.ActiveCfg = Debug|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|x64.ActiveCfg = Debug|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|x86.ActiveCfg = Debug|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Any CPU.Build.0 = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Win32.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|x64.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|x86.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Any CPU.Build.0 = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Win32.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x64.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x86.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Any CPU.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Any CPU.Build.0 = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Mixed Platforms.Build.0 = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Win32.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|Win32.Build.0 = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|x64.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|x64.Build.0 = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|x86.ActiveCfg = Release|Any CPU - {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Signed|x86.Build.0 = Release|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -905,46 +870,6 @@ Global {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x64.Build.0 = Release|Any CPU {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x86.ActiveCfg = Release|Any CPU {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x86.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|Win32.ActiveCfg = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|Win32.Build.0 = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|x64.ActiveCfg = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|x64.Build.0 = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|x86.ActiveCfg = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Debug|x86.Build.0 = Debug|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|Any CPU.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|Win32.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|Win32.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|x64.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|x64.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|x86.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release Mono|x86.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|Any CPU.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|Win32.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|Win32.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|x64.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|x64.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|x86.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Release|x86.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|Any CPU.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|Any CPU.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|Mixed Platforms.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|Win32.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|Win32.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|x64.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|x64.Build.0 = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|x86.ActiveCfg = Release|Any CPU - {776B9F0C-5195-45E3-9A36-1CC1F0D8E0B0}.Signed|x86.Build.0 = Release|Any CPU {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs index 983e86833..c1983658d 100644 --- a/Mono.Nat/NatUtility.cs +++ b/Mono.Nat/NatUtility.cs @@ -98,16 +98,14 @@ namespace Mono.Nat { try { - var enabledProtocols = EnabledProtocols.ToList(); - - if (enabledProtocols.Contains(PmpSearcher.Instance.Protocol)) + if (EnabledProtocols.Contains(PmpSearcher.Instance.Protocol)) { await Receive(PmpSearcher.Instance, PmpSearcher.sockets).ConfigureAwait(false); } foreach (ISearcher s in controllers) { - if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol)) + if (s.NextSearch < DateTime.Now && EnabledProtocols.Contains(s.Protocol)) { Log("Searching for: {0}", s.GetType().Name); s.Search(); diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs index 10ebbdc2c..fb45b365b 100644 --- a/Mono.Nat/Pmp/PmpNatDevice.cs +++ b/Mono.Nat/Pmp/PmpNatDevice.cs @@ -31,6 +31,7 @@ using System.Net.Sockets; using System.Threading; using System.Collections.Generic; using System.Threading.Tasks; +using MediaBrowser.Model.Extensions; namespace Mono.Nat.Pmp { @@ -86,7 +87,7 @@ namespace Mono.Nat.Pmp try { - byte[] buffer = package.ToArray(); + byte[] buffer = package.ToArray(package.Count); int attempt = 0; int delay = PmpConstants.RetryDelay; diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index c2ad946f4..185fd3301 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.712</version> + <version>3.0.715</version> <title>Emby.Common</title> <authors>Emby Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 19b0342be..417ed348d 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.712</version> + <version>3.0.715</version> <title>Emby.Server.Core</title> <authors>Emby Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Emby Server.</description> <copyright>Copyright © Emby 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.712" /> + <dependency id="MediaBrowser.Common" version="3.0.715" /> </dependencies> </metadata> <files> diff --git a/SocketHttpListener/Net/WebHeaderCollection.cs b/SocketHttpListener/Net/WebHeaderCollection.cs index d20f99b9b..66e159ccb 100644 --- a/SocketHttpListener/Net/WebHeaderCollection.cs +++ b/SocketHttpListener/Net/WebHeaderCollection.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Extensions; namespace SocketHttpListener.Net { @@ -176,7 +177,7 @@ namespace SocketHttpListener.Net } if (separated != null) - return separated.ToArray(); + return separated.ToArray(separated.Count); } return values; |
