mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-12 20:13:01 +03:00
Compare commits
105 Commits
master
...
v10.8.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
884a59da07 | ||
|
|
5a9e5e0d5d | ||
|
|
85cfea4c50 | ||
|
|
3ea67374ae | ||
|
|
5da4bcc782 | ||
|
|
b46d61dfdf | ||
|
|
8119e4a573 | ||
|
|
de3c68d474 | ||
|
|
7aa0db24d8 | ||
|
|
be832e82cc | ||
|
|
368d10d042 | ||
|
|
9523a1682b | ||
|
|
e67d8ce077 | ||
|
|
39196bb5e2 | ||
|
|
5386f06095 | ||
|
|
5a9afb0874 | ||
|
|
029be321d1 | ||
|
|
96b7c46df4 | ||
|
|
f7ef7d9eda | ||
|
|
c69e79f64d | ||
|
|
4b1256e67b | ||
|
|
8d1d973438 | ||
|
|
60affd0965 | ||
|
|
8a1eca0913 | ||
|
|
a4e4b761d5 | ||
|
|
2be9a34b26 | ||
|
|
0c8b9091a5 | ||
|
|
128d54622a | ||
|
|
21ce0e58c6 | ||
|
|
3229ba4918 | ||
|
|
105f057512 | ||
|
|
b6a2640f22 | ||
|
|
a2abae3014 | ||
|
|
7084541508 | ||
|
|
e87240b374 | ||
|
|
057d5dfc25 | ||
|
|
12f9132975 | ||
|
|
4a1aa619d2 | ||
|
|
1002d1d193 | ||
|
|
7c91543694 | ||
|
|
b04211a707 | ||
|
|
fcb65ac38d | ||
|
|
727402f7f7 | ||
|
|
2a1ca4badc | ||
|
|
7f52f77ef5 | ||
|
|
1d5961126e | ||
|
|
ec6f7bdcff | ||
|
|
65c47c79da | ||
|
|
5d9df10e27 | ||
|
|
d45d228b36 | ||
|
|
2b7d139b5b | ||
|
|
a280ff603f | ||
|
|
9beb3aff4e | ||
|
|
066bdc1e72 | ||
|
|
5833c70725 | ||
|
|
cd93f49fa8 | ||
|
|
93009682b3 | ||
|
|
56573f14b0 | ||
|
|
9001eaa67e | ||
|
|
76010e80dd | ||
|
|
5778541d2f | ||
|
|
cb0baddde3 | ||
|
|
0ff37413b0 | ||
|
|
d5434988d7 | ||
|
|
847518701d | ||
|
|
c5212a20a3 | ||
|
|
385a0b9437 | ||
|
|
884ba4f3ed | ||
|
|
cba6a4e3f3 | ||
|
|
d5f44f7a5c | ||
|
|
bf1ccf7493 | ||
|
|
d7c548f3db | ||
|
|
a7abdca47a | ||
|
|
7a8eaa8811 | ||
|
|
045dca49d0 | ||
|
|
e9ce82445e | ||
|
|
2133c6e348 | ||
|
|
5de2db9f52 | ||
|
|
620625c4c1 | ||
|
|
e0b035e34e | ||
|
|
1946414e14 | ||
|
|
132c85e554 | ||
|
|
9d1049e83f | ||
|
|
72aca15191 | ||
|
|
577325b788 | ||
|
|
fec2cf5060 | ||
|
|
53b06ce4e3 | ||
|
|
bdb85aeecf | ||
|
|
e299adc819 | ||
|
|
0674f84e9e | ||
|
|
b6086398d3 | ||
|
|
1d585146d6 | ||
|
|
aa1b1c6bbb | ||
|
|
bebe1808ce | ||
|
|
fb2e4c2e1a | ||
|
|
784ed796ce | ||
|
|
579155a571 | ||
|
|
ca16a55a47 | ||
|
|
e2ffd41141 | ||
|
|
ca67a48140 | ||
|
|
d2ce315c1d | ||
|
|
6a6874aa16 | ||
|
|
6f45848b51 | ||
|
|
23ba15ccfb | ||
|
|
5376c37d42 |
@@ -6,8 +6,8 @@ using System.IO;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Diacritics.Extensions;
|
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ namespace Emby.Naming.Common
|
|||||||
".mkv",
|
".mkv",
|
||||||
".mk3d",
|
".mk3d",
|
||||||
".mov",
|
".mov",
|
||||||
".mp2",
|
|
||||||
".mp4",
|
".mp4",
|
||||||
".mpe",
|
".mpe",
|
||||||
".mpeg",
|
".mpeg",
|
||||||
@@ -315,7 +314,7 @@ namespace Emby.Naming.Common
|
|||||||
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
||||||
// so we make sure this one gets tested first.
|
// so we make sure this one gets tested first.
|
||||||
// "Foo Bar 889"
|
// "Foo Bar 889"
|
||||||
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$")
|
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,4})(-(?<endingepnumber>[0-9]{2,4}))*[^\\\/x]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -398,6 +398,12 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ConfigurationStore[] GetConfigurationStores()
|
||||||
|
{
|
||||||
|
return _configurationStores;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Type GetConfigurationType(string key)
|
public Type GetConfigurationType(string key)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Diacritics.Extensions;
|
|
||||||
using Emby.Server.Implementations.Playlists;
|
using Emby.Server.Implementations.Playlists;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
@@ -5763,7 +5762,7 @@ AND Type = @InternalPersonType)");
|
|||||||
{
|
{
|
||||||
var itemIdBlob = id.ToByteArray();
|
var itemIdBlob = id.ToByteArray();
|
||||||
|
|
||||||
// First delete chapters
|
// Delete existing mediastreams
|
||||||
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
|
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
|
||||||
|
|
||||||
InsertMediaStreams(itemIdBlob, streams, db);
|
InsertMediaStreams(itemIdBlob, streams, db);
|
||||||
@@ -5867,10 +5866,10 @@ AND Type = @InternalPersonType)");
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the chapter.
|
/// Gets the media stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reader">The reader.</param>
|
/// <param name="reader">The reader.</param>
|
||||||
/// <returns>ChapterInfo.</returns>
|
/// <returns>MediaStream.</returns>
|
||||||
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
|
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
|
||||||
{
|
{
|
||||||
var item = new MediaStream
|
var item = new MediaStream
|
||||||
|
|||||||
@@ -1094,7 +1094,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
{
|
{
|
||||||
if (item is IHasTrailers hasTrailers)
|
if (item is IHasTrailers hasTrailers)
|
||||||
{
|
{
|
||||||
dto.LocalTrailerCount = hasTrailers.GetTrailerCount();
|
dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,12 +24,12 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
||||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
<PackageReference Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.4" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.2" />
|
<PackageReference Include="Mono.Nat" Version="3.0.2" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.3" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.3" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.30.1" />
|
<PackageReference Include="sharpcompress" Version="0.30.1" />
|
||||||
|
|||||||
@@ -262,6 +262,10 @@ namespace Emby.Server.Implementations.IO
|
|||||||
_logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
|
_logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
|
||||||
result.Exists = false;
|
result.Exists = false;
|
||||||
}
|
}
|
||||||
|
catch (UnauthorizedAccessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fileInfo.FullName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2840,10 +2840,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var existingNameCount = 1; // first numbered name will be 2
|
var existingNameCount = 1; // first numbered name will be 2
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||||
|
var originalName = name;
|
||||||
while (Directory.Exists(virtualFolderPath))
|
while (Directory.Exists(virtualFolderPath))
|
||||||
{
|
{
|
||||||
existingNameCount++;
|
existingNameCount++;
|
||||||
virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
|
name = originalName + existingNameCount;
|
||||||
|
virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaPathInfos = options.PathInfos;
|
var mediaPathInfos = options.PathInfos;
|
||||||
|
|||||||
@@ -151,7 +151,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
|
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
|
||||||
|
|
||||||
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video))
|
// If file is strm or main media stream is missing, force a metadata refresh with remote probing
|
||||||
|
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
|
||||||
|
&& (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| (item.MediaType == MediaType.Video && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Video))
|
||||||
|
|| (item.MediaType == MediaType.Audio && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio))))
|
||||||
{
|
{
|
||||||
await item.RefreshMetadata(
|
await item.RefreshMetadata(
|
||||||
new MetadataRefreshOptions(_directoryService)
|
new MetadataRefreshOptions(_directoryService)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Diacritics.Extensions;
|
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
|
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
_logger.LogInformation("Recording completed to file {Path}", targetFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
|
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
|
||||||
@@ -115,7 +115,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||||
_ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
_ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||||
|
|
||||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
_logger.LogInformation("ffmpeg recording process started for {Path}", _targetPath);
|
||||||
|
|
||||||
|
// Block until ffmpeg exits
|
||||||
|
await _taskCompletionSource.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)
|
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
@@ -48,12 +49,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
|
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
|
||||||
{
|
{
|
||||||
|
var tmpName = name;
|
||||||
if (addHyphen)
|
if (addHyphen)
|
||||||
{
|
{
|
||||||
name += " -";
|
tmpName += " -";
|
||||||
}
|
}
|
||||||
|
|
||||||
name += " " + info.EpisodeTitle;
|
tmpName += " " + info.EpisodeTitle;
|
||||||
|
// Since the filename will be used with file ext. (.mp4, .ts, etc)
|
||||||
|
if (Encoding.UTF8.GetByteCount(tmpName) < 250)
|
||||||
|
{
|
||||||
|
name = tmpName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (info.IsMovie && info.ProductionYear != null)
|
else if (info.IsMovie && info.ProductionYear != null)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
@@ -26,6 +27,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
{
|
{
|
||||||
public class XmlTvListingsProvider : IListingsProvider
|
public class XmlTvListingsProvider : IListingsProvider
|
||||||
{
|
{
|
||||||
|
private static readonly TimeSpan _maxCacheAge = TimeSpan.FromHours(1);
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly ILogger<XmlTvListingsProvider> _logger;
|
private readonly ILogger<XmlTvListingsProvider> _logger;
|
||||||
@@ -69,13 +72,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
return UnzipIfNeeded(info.Path, info.Path);
|
return UnzipIfNeeded(info.Path, info.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + "-" + info.Id + ".xml";
|
string cacheFilename = info.Id + ".xml";
|
||||||
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
|
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
|
||||||
if (File.Exists(cacheFile))
|
if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
|
||||||
{
|
{
|
||||||
return UnzipIfNeeded(info.Path, cacheFile);
|
return UnzipIfNeeded(info.Path, cacheFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must check if file exists as parent directory may not exist.
|
||||||
|
if (File.Exists(cacheFile))
|
||||||
|
{
|
||||||
|
File.Delete(cacheFile);
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
|
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
||||||
@@ -124,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
{
|
{
|
||||||
using (var stream = File.OpenRead(file))
|
using (var stream = File.OpenRead(file))
|
||||||
{
|
{
|
||||||
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
|
string tempFolder = GetTempFolderPath(stream);
|
||||||
Directory.CreateDirectory(tempFolder);
|
Directory.CreateDirectory(tempFolder);
|
||||||
|
|
||||||
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
|
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
|
||||||
@@ -137,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
{
|
{
|
||||||
using (var stream = File.OpenRead(file))
|
using (var stream = File.OpenRead(file))
|
||||||
{
|
{
|
||||||
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
|
string tempFolder = GetTempFolderPath(stream);
|
||||||
Directory.CreateDirectory(tempFolder);
|
Directory.CreateDirectory(tempFolder);
|
||||||
|
|
||||||
_zipClient.ExtractAllFromGz(stream, tempFolder, true);
|
_zipClient.ExtractAllFromGz(stream, tempFolder, true);
|
||||||
@@ -146,6 +155,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetTempFolderPath(Stream stream)
|
||||||
|
{
|
||||||
|
#pragma warning disable CA5351
|
||||||
|
using var md5 = MD5.Create();
|
||||||
|
#pragma warning restore CA5351
|
||||||
|
var checksum = Convert.ToHexString(md5.ComputeHash(stream));
|
||||||
|
stream.Position = 0;
|
||||||
|
return Path.Combine(_config.ApplicationPaths.TempDirectory, checksum);
|
||||||
|
}
|
||||||
|
|
||||||
private string FindXmlFile(string directory)
|
private string FindXmlFile(string directory)
|
||||||
{
|
{
|
||||||
return _fileSystem.GetFiles(directory, true)
|
return _fileSystem.GetFiles(directory, true)
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
|||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error updating {0}", package.Name);
|
_logger.LogError(ex, "Error updating {0}", package.Name);
|
||||||
}
|
}
|
||||||
|
catch (InvalidDataException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error updating {0}", package.Name);
|
||||||
|
}
|
||||||
|
|
||||||
// Update progress
|
// Update progress
|
||||||
lock (progress)
|
lock (progress)
|
||||||
|
|||||||
@@ -699,7 +699,9 @@ namespace Emby.Server.Implementations.Session
|
|||||||
DeviceName = session.DeviceName,
|
DeviceName = session.DeviceName,
|
||||||
ClientName = session.Client,
|
ClientName = session.Client,
|
||||||
DeviceId = session.DeviceId,
|
DeviceId = session.DeviceId,
|
||||||
Session = session
|
Session = session,
|
||||||
|
PlaybackPositionTicks = info.PositionTicks,
|
||||||
|
PlaySessionId = info.PlaySessionId
|
||||||
};
|
};
|
||||||
|
|
||||||
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
|
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
|
||||||
@@ -985,7 +987,8 @@ namespace Emby.Server.Implementations.Session
|
|||||||
DeviceName = session.DeviceName,
|
DeviceName = session.DeviceName,
|
||||||
ClientName = session.Client,
|
ClientName = session.Client,
|
||||||
DeviceId = session.DeviceId,
|
DeviceId = session.DeviceId,
|
||||||
Session = session
|
Session = session,
|
||||||
|
PlaySessionId = info.PlaySessionId
|
||||||
};
|
};
|
||||||
|
|
||||||
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
|
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ namespace Emby.Server.Implementations.Sorting
|
|||||||
throw new ArgumentNullException(nameof(y));
|
throw new ArgumentNullException(nameof(y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!x.IndexNumber.HasValue && !y.IndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!x.IndexNumber.HasValue)
|
if (!x.IndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ namespace Emby.Server.Implementations.Sorting
|
|||||||
throw new ArgumentNullException(nameof(y));
|
throw new ArgumentNullException(nameof(y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!x.ParentIndexNumber.HasValue && !y.ParentIndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!x.ParentIndexNumber.HasValue)
|
if (!x.ParentIndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -86,21 +86,23 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// Updates named configuration.
|
/// Updates named configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">Configuration key.</param>
|
/// <param name="key">Configuration key.</param>
|
||||||
|
/// <param name="configuration">Configuration.</param>
|
||||||
/// <response code="204">Named configuration updated.</response>
|
/// <response code="204">Named configuration updated.</response>
|
||||||
/// <returns>Update status.</returns>
|
/// <returns>Update status.</returns>
|
||||||
[HttpPost("Configuration/{key}")]
|
[HttpPost("Configuration/{key}")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
public async Task<ActionResult> UpdateNamedConfiguration([FromRoute, Required] string key)
|
public ActionResult UpdateNamedConfiguration([FromRoute, Required] string key, [FromBody, Required] JsonDocument configuration)
|
||||||
{
|
{
|
||||||
var configurationType = _configurationManager.GetConfigurationType(key);
|
var configurationType = _configurationManager.GetConfigurationType(key);
|
||||||
var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);
|
var deserializedConfiguration = configuration.Deserialize(configurationType, _serializerOptions);
|
||||||
if (configuration == null)
|
|
||||||
|
if (deserializedConfiguration == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Body doesn't contain a valid configuration");
|
throw new ArgumentException("Body doesn't contain a valid configuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
_configurationManager.SaveConfiguration(key, configuration);
|
_configurationManager.SaveConfiguration(key, deserializedConfiguration);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1773,13 +1773,23 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
var args = "-codec:v:0 " + codec;
|
var args = "-codec:v:0 " + codec;
|
||||||
|
|
||||||
// Prefer hvc1 to hev1.
|
|
||||||
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
args += " -tag:v:0 hvc1";
|
if (EncodingHelper.IsCopyCodec(codec)
|
||||||
|
&& (string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
// Prefer dvh1 to dvhe
|
||||||
|
args += " -tag:v:0 dvh1";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Prefer hvc1 to hev1
|
||||||
|
args += " -tag:v:0 hvc1";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (state.EnableMpegtsM2TsMode)
|
// if (state.EnableMpegtsM2TsMode)
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <response code="204">Package repositories saved.</response>
|
/// <response code="204">Package repositories saved.</response>
|
||||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||||
[HttpPost("Repositories")]
|
[HttpPost("Repositories")]
|
||||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
public ActionResult SetRepositories([FromBody, Required] List<RepositoryInfo> repositoryInfos)
|
public ActionResult SetRepositories([FromBody, Required] List<RepositoryInfo> repositoryInfos)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.0" />
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ namespace Jellyfin.Data.Entities
|
|||||||
/// <returns><c>True</c> if the user has the specified permission.</returns>
|
/// <returns><c>True</c> if the user has the specified permission.</returns>
|
||||||
public bool HasPermission(PermissionKind kind)
|
public bool HasPermission(PermissionKind kind)
|
||||||
{
|
{
|
||||||
return Permissions.First(p => p.Kind == kind).Value;
|
return Permissions.FirstOrDefault(p => p.Kind == kind)?.Value ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -372,7 +372,15 @@ namespace Jellyfin.Data.Entities
|
|||||||
/// <param name="value">The value to set.</param>
|
/// <param name="value">The value to set.</param>
|
||||||
public void SetPermission(PermissionKind kind, bool value)
|
public void SetPermission(PermissionKind kind, bool value)
|
||||||
{
|
{
|
||||||
Permissions.First(p => p.Kind == kind).Value = value;
|
var currentPermission = Permissions.FirstOrDefault(p => p.Kind == kind);
|
||||||
|
if (currentPermission == null)
|
||||||
|
{
|
||||||
|
Permissions.Add(new Permission(kind, value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentPermission.Value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -382,9 +390,9 @@ namespace Jellyfin.Data.Entities
|
|||||||
/// <returns>A string array containing the user's preferences.</returns>
|
/// <returns>A string array containing the user's preferences.</returns>
|
||||||
public string[] GetPreference(PreferenceKind preference)
|
public string[] GetPreference(PreferenceKind preference)
|
||||||
{
|
{
|
||||||
var val = Preferences.First(p => p.Kind == preference).Value;
|
var val = Preferences.FirstOrDefault(p => p.Kind == preference)?.Value;
|
||||||
|
|
||||||
return Equals(val, string.Empty) ? Array.Empty<string>() : val.Split(Delimiter);
|
return string.IsNullOrEmpty(val) ? Array.Empty<string>() : val.Split(Delimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -395,7 +403,7 @@ namespace Jellyfin.Data.Entities
|
|||||||
/// <returns>A {T} array containing the user's preference.</returns>
|
/// <returns>A {T} array containing the user's preference.</returns>
|
||||||
public T[] GetPreferenceValues<T>(PreferenceKind preference)
|
public T[] GetPreferenceValues<T>(PreferenceKind preference)
|
||||||
{
|
{
|
||||||
var val = Preferences.First(p => p.Kind == preference).Value;
|
var val = Preferences.FirstOrDefault(p => p.Kind == preference)?.Value;
|
||||||
if (string.IsNullOrEmpty(val))
|
if (string.IsNullOrEmpty(val))
|
||||||
{
|
{
|
||||||
return Array.Empty<T>();
|
return Array.Empty<T>();
|
||||||
@@ -432,8 +440,16 @@ namespace Jellyfin.Data.Entities
|
|||||||
/// <param name="values">The values.</param>
|
/// <param name="values">The values.</param>
|
||||||
public void SetPreference(PreferenceKind preference, string[] values)
|
public void SetPreference(PreferenceKind preference, string[] values)
|
||||||
{
|
{
|
||||||
Preferences.First(p => p.Kind == preference).Value
|
var value = string.Join(Delimiter, values);
|
||||||
= string.Join(Delimiter, values);
|
var currentPreference = Preferences.FirstOrDefault(p => p.Kind == preference);
|
||||||
|
if (currentPreference == null)
|
||||||
|
{
|
||||||
|
Preferences.Add(new Preference(preference, value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentPreference.Value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -444,8 +460,16 @@ namespace Jellyfin.Data.Entities
|
|||||||
/// <typeparam name="T">The type of value.</typeparam>
|
/// <typeparam name="T">The type of value.</typeparam>
|
||||||
public void SetPreference<T>(PreferenceKind preference, T[] values)
|
public void SetPreference<T>(PreferenceKind preference, T[] values)
|
||||||
{
|
{
|
||||||
Preferences.First(p => p.Kind == preference).Value
|
var value = string.Join(Delimiter, values);
|
||||||
= string.Join(Delimiter, values);
|
var currentPreference = Preferences.FirstOrDefault(p => p.Kind == preference);
|
||||||
|
if (currentPreference == null)
|
||||||
|
{
|
||||||
|
Preferences.Add(new Preference(preference, value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentPreference.Value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using BlurHashSharp.SkiaSharp;
|
using BlurHashSharp.SkiaSharp;
|
||||||
using Diacritics.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
|||||||
@@ -463,6 +463,18 @@ namespace Jellyfin.Networking.Manager
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsInLocalNetwork(IPObject address)
|
public bool IsInLocalNetwork(IPObject address)
|
||||||
|
{
|
||||||
|
return IsInLocalNetwork(address.Address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsInLocalNetwork(string address)
|
||||||
|
{
|
||||||
|
return IPHost.TryParse(address, out IPHost ipHost) && IsInLocalNetwork(ipHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsInLocalNetwork(IPAddress address)
|
||||||
{
|
{
|
||||||
if (address == null)
|
if (address == null)
|
||||||
{
|
{
|
||||||
@@ -481,36 +493,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
|
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
|
||||||
return address.IsLoopback() || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address));
|
return IPAddress.IsLoopback(address) || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address));
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool IsInLocalNetwork(string address)
|
|
||||||
{
|
|
||||||
if (IPHost.TryParse(address, out IPHost ep))
|
|
||||||
{
|
|
||||||
return _lanSubnets.ContainsAddress(ep) && !_excludedSubnets.ContainsAddress(ep);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public bool IsInLocalNetwork(IPAddress address)
|
|
||||||
{
|
|
||||||
if (address == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(address));
|
|
||||||
}
|
|
||||||
|
|
||||||
// See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
|
|
||||||
if (TrustAllIP6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
|
|
||||||
return _lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -27,13 +27,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Model.ApiClient;
|
using MediaBrowser.Model.ApiClient;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@@ -14,6 +15,17 @@ namespace Jellyfin.Server.Filters
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AdditionalModelFilter : IDocumentFilter
|
public class AdditionalModelFilter : IDocumentFilter
|
||||||
{
|
{
|
||||||
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AdditionalModelFilter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
|
public AdditionalModelFilter(IServerConfigurationManager serverConfigurationManager)
|
||||||
|
{
|
||||||
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||||
{
|
{
|
||||||
@@ -29,6 +41,11 @@ namespace Jellyfin.Server.Filters
|
|||||||
|
|
||||||
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
|
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
|
||||||
context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository);
|
context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository);
|
||||||
|
|
||||||
|
foreach (var configuration in _serverConfigurationManager.GetConfigurationStores())
|
||||||
|
{
|
||||||
|
context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@
|
|||||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.4" />
|
||||||
<PackageReference Include="prometheus-net" Version="6.0.0" />
|
<PackageReference Include="prometheus-net" Version="6.0.0" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Emby.Server.Implementations.Data;
|
|||||||
using Jellyfin.Data.Entities.Security;
|
using Jellyfin.Data.Entities.Security;
|
||||||
using Jellyfin.Server.Implementations;
|
using Jellyfin.Server.Implementations;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SQLitePCL.pretty;
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
private readonly ILogger<MigrateAuthenticationDb> _logger;
|
private readonly ILogger<MigrateAuthenticationDb> _logger;
|
||||||
private readonly JellyfinDbProvider _dbProvider;
|
private readonly JellyfinDbProvider _dbProvider;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
|
/// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
|
||||||
@@ -27,11 +29,17 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="dbProvider">The database provider.</param>
|
/// <param name="dbProvider">The database provider.</param>
|
||||||
/// <param name="appPaths">The server application paths.</param>
|
/// <param name="appPaths">The server application paths.</param>
|
||||||
public MigrateAuthenticationDb(ILogger<MigrateAuthenticationDb> logger, JellyfinDbProvider dbProvider, IServerApplicationPaths appPaths)
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
public MigrateAuthenticationDb(
|
||||||
|
ILogger<MigrateAuthenticationDb> logger,
|
||||||
|
JellyfinDbProvider dbProvider,
|
||||||
|
IServerApplicationPaths appPaths,
|
||||||
|
IUserManager userManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_dbProvider = dbProvider;
|
_dbProvider = dbProvider;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -74,6 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var userId = new Guid(row[6].ToString());
|
||||||
|
var user = _userManager.GetUserById(userId);
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
// User doesn't exist, don't bring over the device.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
dbContext.Devices.Add(new Device(
|
dbContext.Devices.Add(new Device(
|
||||||
new Guid(row[6].ToString()),
|
new Guid(row[6].ToString()),
|
||||||
row[3].ToString(),
|
row[3].ToString(),
|
||||||
|
|||||||
@@ -60,6 +60,12 @@ namespace MediaBrowser.Common.Configuration
|
|||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
object GetConfiguration(string key);
|
object GetConfiguration(string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the array of coniguration stores.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Array of ConfigurationStore.</returns>
|
||||||
|
ConfigurationStore[] GetConfigurationStores();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the type of the configuration.
|
/// Gets the type of the configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace MediaBrowser.Common.Net
|
|||||||
address = address.MapToIPv4();
|
address = address.MapToIPv4();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsLoopback(address))
|
if (IPAddress.IsLoopback(address))
|
||||||
{
|
{
|
||||||
return (address, prefixLength);
|
return (address, prefixLength);
|
||||||
}
|
}
|
||||||
@@ -102,31 +102,6 @@ namespace MediaBrowser.Common.Net
|
|||||||
return (new IPAddress(addressBytes), prefixLength);
|
return (new IPAddress(addressBytes), prefixLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tests to see if the ip address is a Loopback address.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Value to test.</param>
|
|
||||||
/// <returns>True if it is.</returns>
|
|
||||||
public static bool IsLoopback(IPAddress address)
|
|
||||||
{
|
|
||||||
if (address == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(address));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!address.Equals(IPAddress.None))
|
|
||||||
{
|
|
||||||
if (address.IsIPv4MappedToIPv6)
|
|
||||||
{
|
|
||||||
address = address.MapToIPv4();
|
|
||||||
}
|
|
||||||
|
|
||||||
return address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests to see if the ip address is an IP6 address.
|
/// Tests to see if the ip address is an IP6 address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -295,7 +270,7 @@ namespace MediaBrowser.Common.Net
|
|||||||
/// <returns>True if it is.</returns>
|
/// <returns>True if it is.</returns>
|
||||||
public virtual bool IsLoopback()
|
public virtual bool IsLoopback()
|
||||||
{
|
{
|
||||||
return IsLoopback(Address);
|
return IPAddress.IsLoopback(Address);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ using System.Linq;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Diacritics.Extensions;
|
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Diacritics.Extensions;
|
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities.Audio
|
namespace MediaBrowser.Controller.Entities.Audio
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using System.Text;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Diacritics.Extensions;
|
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Diacritics.Extensions;
|
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Diacritics.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Diacritics.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Diacritics.Extensions;
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Library
|
namespace MediaBrowser.Controller.Library
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Diacritics" Version="3.3.10" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
|
|||||||
@@ -1062,10 +1062,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Clients may direct play higher than level 41, but there's no reason to transcode higher.
|
// Transcode to level 5.1 and lower for maximum compatibility.
|
||||||
if (requestLevel >= 41)
|
// h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
|
||||||
|
// https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
|
||||||
|
if (requestLevel >= 51)
|
||||||
{
|
{
|
||||||
return "41";
|
return "51";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2213,13 +2215,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
return state.IsInputVideo ? "-sn" : string.Empty;
|
return state.IsInputVideo ? "-sn" : string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have media info, but we don't know the stream indexes
|
// We have media info, but we don't know the stream index
|
||||||
if (state.VideoStream != null && state.VideoStream.Index == -1)
|
if (state.VideoStream != null && state.VideoStream.Index == -1)
|
||||||
{
|
{
|
||||||
return "-sn";
|
return "-sn";
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have media info, but we don't know the stream indexes
|
// We have media info, but we don't know the stream index
|
||||||
if (state.AudioStream != null && state.AudioStream.Index == -1)
|
if (state.AudioStream != null && state.AudioStream.Index == -1)
|
||||||
{
|
{
|
||||||
return state.IsInputVideo ? "-sn" : string.Empty;
|
return state.IsInputVideo ? "-sn" : string.Empty;
|
||||||
@@ -2229,10 +2231,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (state.VideoStream != null)
|
if (state.VideoStream != null)
|
||||||
{
|
{
|
||||||
|
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
|
||||||
|
|
||||||
args += string.Format(
|
args += string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"-map 0:{0}",
|
"-map 0:{0}",
|
||||||
state.VideoStream.Index);
|
videoStreamIndex);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2242,23 +2246,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (state.AudioStream != null)
|
if (state.AudioStream != null)
|
||||||
{
|
{
|
||||||
|
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
|
||||||
if (state.AudioStream.IsExternal)
|
if (state.AudioStream.IsExternal)
|
||||||
{
|
{
|
||||||
int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1;
|
bool hasExternalGraphicsSubs = state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream;
|
||||||
int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
|
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
|
||||||
|
|
||||||
args += string.Format(
|
args += string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
" -map {0}:{1}",
|
" -map {0}:{1}",
|
||||||
externalAudioMapIndex,
|
externalAudioMapIndex,
|
||||||
externalAudioStream);
|
audioStreamIndex);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
args += string.Format(
|
args += string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
" -map 0:{0}",
|
" -map 0:{0}",
|
||||||
state.AudioStream.Index);
|
audioStreamIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -2273,14 +2278,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
|
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
|
||||||
{
|
{
|
||||||
|
int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
|
||||||
|
|
||||||
args += string.Format(
|
args += string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
" -map 0:{0}",
|
" -map 0:{0}",
|
||||||
state.SubtitleStream.Index);
|
subtitleStreamIndex);
|
||||||
}
|
}
|
||||||
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
|
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
|
||||||
{
|
{
|
||||||
args += " -map 1:0 -sn";
|
int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
|
||||||
|
|
||||||
|
args += string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
" -map 1:{0} -sn",
|
||||||
|
externalSubtitleStreamIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
@@ -2509,7 +2521,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
return string.Format(
|
return string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2",
|
"scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
|
||||||
maxWidthParam,
|
maxWidthParam,
|
||||||
maxHeightParam,
|
maxHeightParam,
|
||||||
scaleVal);
|
scaleVal);
|
||||||
@@ -2553,7 +2565,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
return string.Format(
|
return string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2",
|
"scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
|
||||||
maxWidthParam,
|
maxWidthParam,
|
||||||
scaleVal);
|
scaleVal);
|
||||||
}
|
}
|
||||||
@@ -2565,7 +2577,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
return string.Format(
|
return string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})",
|
"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
|
||||||
maxHeightParam,
|
maxHeightParam,
|
||||||
scaleVal);
|
scaleVal);
|
||||||
}
|
}
|
||||||
@@ -2614,7 +2626,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
filter = "scale={0}:trunc({0}/dar/2)*2";
|
filter = "scale={0}:trunc({0}/a/2)*2";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2768,8 +2780,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
else if (hasGraphicalSubs)
|
else if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
// [0:s]scale=s=1280x720
|
// [0:s]scale=expr
|
||||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
subFilters.Add(subSwScaleFilter);
|
subFilters.Add(subSwScaleFilter);
|
||||||
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
||||||
}
|
}
|
||||||
@@ -2955,7 +2967,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var subSwScaleFilter = isSwDecoder
|
||||||
|
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
|
||||||
|
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
subFilters.Add(subSwScaleFilter);
|
subFilters.Add(subSwScaleFilter);
|
||||||
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
||||||
}
|
}
|
||||||
@@ -3153,7 +3167,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var subSwScaleFilter = isSwDecoder
|
||||||
|
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
|
||||||
|
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
subFilters.Add(subSwScaleFilter);
|
subFilters.Add(subSwScaleFilter);
|
||||||
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
||||||
}
|
}
|
||||||
@@ -3381,7 +3397,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
// qsv requires a fixed pool size.
|
// qsv requires a fixed pool size.
|
||||||
subFilters.Add("hwupload=extra_hw_frames=32");
|
// default to 64 otherwise it will fail on certain iGPU.
|
||||||
|
subFilters.Add("hwupload=extra_hw_frames=64");
|
||||||
|
|
||||||
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
|
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
|
||||||
@@ -3398,7 +3415,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var subSwScaleFilter = isSwDecoder
|
||||||
|
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
|
||||||
|
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
subFilters.Add(subSwScaleFilter);
|
subFilters.Add(subSwScaleFilter);
|
||||||
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
||||||
}
|
}
|
||||||
@@ -3589,7 +3608,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
// qsv requires a fixed pool size.
|
// qsv requires a fixed pool size.
|
||||||
subFilters.Add("hwupload=extra_hw_frames=32");
|
// default to 64 otherwise it will fail on certain iGPU.
|
||||||
|
subFilters.Add("hwupload=extra_hw_frames=64");
|
||||||
|
|
||||||
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
|
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
|
||||||
@@ -3606,7 +3626,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var subSwScaleFilter = isSwDecoder
|
||||||
|
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
|
||||||
|
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
subFilters.Add(subSwScaleFilter);
|
subFilters.Add(subSwScaleFilter);
|
||||||
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
||||||
}
|
}
|
||||||
@@ -3853,7 +3875,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var subSwScaleFilter = isSwDecoder
|
||||||
|
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
|
||||||
|
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
subFilters.Add(subSwScaleFilter);
|
subFilters.Add(subSwScaleFilter);
|
||||||
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
||||||
|
|
||||||
@@ -4028,7 +4052,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
if (hasGraphicalSubs)
|
if (hasGraphicalSubs)
|
||||||
{
|
{
|
||||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
var subSwScaleFilter = isSwDecoder
|
||||||
|
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
|
||||||
|
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||||
subFilters.Add(subSwScaleFilter);
|
subFilters.Add(subSwScaleFilter);
|
||||||
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
||||||
|
|
||||||
@@ -4124,9 +4150,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
string.Join(',', overlayFilters));
|
string.Join(',', overlayFilters));
|
||||||
|
|
||||||
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
|
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
|
||||||
var subtitleStreamIndex = state.SubtitleStream.IsExternal
|
var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
|
||||||
? 0
|
var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
|
||||||
: state.SubtitleStream.Index;
|
|
||||||
|
|
||||||
if (hasSubs)
|
if (hasSubs)
|
||||||
{
|
{
|
||||||
@@ -4147,7 +4172,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
filterStr,
|
filterStr,
|
||||||
mapPrefix,
|
mapPrefix,
|
||||||
subtitleStreamIndex,
|
subtitleStreamIndex,
|
||||||
state.VideoStream.Index,
|
videoStreamIndex,
|
||||||
mainStr,
|
mainStr,
|
||||||
subStr,
|
subStr,
|
||||||
overlayStr);
|
overlayStr);
|
||||||
@@ -5357,12 +5382,22 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
|
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
// opus will fail on 44100
|
|
||||||
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (state.OutputAudioSampleRate.HasValue)
|
// opus only supports specific sampling rates
|
||||||
|
var sampleRate = state.OutputAudioSampleRate;
|
||||||
|
if (sampleRate.HasValue)
|
||||||
{
|
{
|
||||||
audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
|
var sampleRateValue = sampleRate.Value switch
|
||||||
|
{
|
||||||
|
<= 8000 => 8000,
|
||||||
|
<= 12000 => 12000,
|
||||||
|
<= 16000 => 16000,
|
||||||
|
<= 24000 => 24000,
|
||||||
|
_ => 48000
|
||||||
|
};
|
||||||
|
|
||||||
|
audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5384,6 +5419,28 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
string.Empty).Trim();
|
string.Empty).Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
var length = mediaStreams.Count;
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
var currentMediaStream = mediaStreams[i];
|
||||||
|
if (currentMediaStream == streamToFind)
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsCopyCodec(string codec)
|
public static bool IsCopyCodec(string codec)
|
||||||
{
|
{
|
||||||
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
|
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|||||||
@@ -141,6 +141,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
string GetInputArgument(string inputFile, MediaSourceInfo mediaSource);
|
string GetInputArgument(string inputFile, MediaSourceInfo mediaSource);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the input argument for an external subtitle file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputFile">The input file.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
string GetExternalSubtitleInputArgument(string inputFile);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the time parameter.
|
/// Gets the time parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ namespace MediaBrowser.Controller.Resolvers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum ResolverPriority
|
public enum ResolverPriority
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The highest priority. Used by plugins to bypass the default server resolvers.
|
||||||
|
/// </summary>
|
||||||
|
Plugin = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The first.
|
/// The first.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -411,6 +411,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol);
|
return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the input argument for an external subtitle file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputFile">The input file.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Unrecognized InputType.</exception>
|
||||||
|
public string GetExternalSubtitleInputArgument(string inputFile)
|
||||||
|
{
|
||||||
|
const string Prefix = "file";
|
||||||
|
|
||||||
|
return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the media info internal.
|
/// Gets the media info internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
MediaStream subtitleStream,
|
MediaStream subtitleStream,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!subtitleStream.IsExternal)
|
if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
string outputFormat;
|
string outputFormat;
|
||||||
string outputCodec;
|
string outputCodec;
|
||||||
@@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
// Extract
|
// Extract
|
||||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
|
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
|
||||||
|
|
||||||
await ExtractTextSubtitle(mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
|
await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
|
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
|
||||||
@@ -494,7 +494,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
/// Extracts the text subtitle.
|
/// Extracts the text subtitle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mediaSource">The mediaSource.</param>
|
/// <param name="mediaSource">The mediaSource.</param>
|
||||||
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
|
/// <param name="subtitleStream">The subtitle stream.</param>
|
||||||
/// <param name="outputCodec">The output codec.</param>
|
/// <param name="outputCodec">The output codec.</param>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
@@ -502,7 +502,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
|
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
|
||||||
private async Task ExtractTextSubtitle(
|
private async Task ExtractTextSubtitle(
|
||||||
MediaSourceInfo mediaSource,
|
MediaSourceInfo mediaSource,
|
||||||
int subtitleStreamIndex,
|
MediaStream subtitleStream,
|
||||||
string outputCodec,
|
string outputCodec,
|
||||||
string outputPath,
|
string outputPath,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
@@ -511,12 +511,21 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
|
|
||||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!File.Exists(outputPath))
|
if (!File.Exists(outputPath))
|
||||||
{
|
{
|
||||||
|
var args = _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource);
|
||||||
|
|
||||||
|
if (subtitleStream.IsExternal)
|
||||||
|
{
|
||||||
|
args = _mediaEncoder.GetExternalSubtitleInputArgument(subtitleStream.Path);
|
||||||
|
}
|
||||||
|
|
||||||
await ExtractTextSubtitleInternal(
|
await ExtractTextSubtitleInternal(
|
||||||
_mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource),
|
args,
|
||||||
subtitleStreamIndex,
|
subtitleStreamIndex,
|
||||||
outputCodec,
|
outputCodec,
|
||||||
outputPath,
|
outputPath,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
RequirePerfectSubtitleMatch = true;
|
RequirePerfectSubtitleMatch = true;
|
||||||
AllowEmbeddedSubtitles = EmbeddedSubtitleOptions.AllowAll;
|
AllowEmbeddedSubtitles = EmbeddedSubtitleOptions.AllowAll;
|
||||||
|
|
||||||
AutomaticallyAddToCollection = true;
|
AutomaticallyAddToCollection = false;
|
||||||
EnablePhotos = true;
|
EnablePhotos = true;
|
||||||
SaveSubtitlesWithMedia = true;
|
SaveSubtitlesWithMedia = true;
|
||||||
EnableRealtimeMonitor = true;
|
EnableRealtimeMonitor = true;
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether quick connect is available for use on this server.
|
/// Gets or sets a value indicating whether quick connect is available for use on this server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool QuickConnectAvailable { get; set; } = false;
|
public bool QuickConnectAvailable { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [enable case sensitive item ids].
|
/// Gets or sets a value indicating whether [enable case sensitive item ids].
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
// If device requirements are satisfied then allow both direct stream and direct play
|
// If device requirements are satisfied then allow both direct stream and direct play
|
||||||
if (item.SupportsDirectPlay)
|
if (item.SupportsDirectPlay)
|
||||||
{
|
{
|
||||||
if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
|
if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
|
||||||
{
|
{
|
||||||
if (options.EnableDirectPlay)
|
if (options.EnableDirectPlay)
|
||||||
{
|
{
|
||||||
@@ -401,7 +401,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
// While options takes the network and other factors into account. Only applies to direct stream
|
// While options takes the network and other factors into account. Only applies to direct stream
|
||||||
if (item.SupportsDirectStream)
|
if (item.SupportsDirectStream)
|
||||||
{
|
{
|
||||||
if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
|
if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
|
||||||
{
|
{
|
||||||
if (options.EnableDirectStream)
|
if (options.EnableDirectStream)
|
||||||
{
|
{
|
||||||
@@ -604,11 +604,11 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
var videoStream = item.VideoStream;
|
var videoStream = item.VideoStream;
|
||||||
|
|
||||||
var directPlayEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
|
var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
|
||||||
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
|
var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
|
||||||
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == 0);
|
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0);
|
||||||
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == 0);
|
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0);
|
||||||
var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult;
|
var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility;
|
||||||
|
|
||||||
_logger.LogDebug(
|
_logger.LogDebug(
|
||||||
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
|
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
|
||||||
@@ -625,7 +625,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
var directPlay = directPlayInfo.PlayMethod;
|
var directPlay = directPlayInfo.PlayMethod;
|
||||||
transcodeReasons |= directPlayInfo.TranscodeReasons;
|
transcodeReasons |= directPlayInfo.TranscodeReasons;
|
||||||
|
|
||||||
if (directPlay != null)
|
if (directPlay.HasValue)
|
||||||
{
|
{
|
||||||
directPlayProfile = directPlayInfo.Profile;
|
directPlayProfile = directPlayInfo.Profile;
|
||||||
playlistItem.PlayMethod = directPlay.Value;
|
playlistItem.PlayMethod = directPlay.Value;
|
||||||
@@ -676,7 +676,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
playlistItem.TranscodeReasons = transcodeReasons;
|
playlistItem.TranscodeReasons = transcodeReasons;
|
||||||
|
|
||||||
if (playlistItem.PlayMethod != PlayMethod.DirectStream || !options.EnableDirectStream)
|
if (playlistItem.PlayMethod != PlayMethod.DirectStream && playlistItem.PlayMethod != PlayMethod.DirectPlay)
|
||||||
{
|
{
|
||||||
// Can't direct play, find the transcoding profile
|
// Can't direct play, find the transcoding profile
|
||||||
// If we do this for direct-stream we will overwrite the info
|
// If we do this for direct-stream we will overwrite the info
|
||||||
@@ -687,6 +687,8 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec);
|
BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec);
|
||||||
|
|
||||||
|
playlistItem.PlayMethod = PlayMethod.Transcode;
|
||||||
|
|
||||||
if (subtitleStream != null)
|
if (subtitleStream != null)
|
||||||
{
|
{
|
||||||
var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
|
var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
|
||||||
@@ -696,14 +698,9 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
|
playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playlistItem.PlayMethod != PlayMethod.DirectPlay)
|
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
|
||||||
{
|
{
|
||||||
playlistItem.PlayMethod = PlayMethod.Transcode;
|
ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
|
||||||
|
|
||||||
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
|
|
||||||
{
|
|
||||||
ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,16 +741,19 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
|
var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
|
||||||
|
|
||||||
if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec))
|
if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec))
|
||||||
{
|
{
|
||||||
var videoCodec = transcodingProfile.VideoCodec;
|
var videoCodec = transcodingProfile.VideoCodec;
|
||||||
var container = transcodingProfile.Container;
|
var container = transcodingProfile.Container;
|
||||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||||
.Where(i => i.Type == CodecType.Video &&
|
.Where(i => i.Type == CodecType.Video &&
|
||||||
i.ContainsAnyCodec(videoCodec, container))
|
i.ContainsAnyCodec(videoCodec, container) &&
|
||||||
|
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
|
i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
|
||||||
var conditionsSatisfied = !appliedVideoConditions.Any() || !appliedVideoConditions.Any(satisfied => !satisfied);
|
|
||||||
|
// An empty appliedVideoConditions means that the codec has no conditions for the current video stream
|
||||||
|
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
|
||||||
return conditionsSatisfied ? 1 : 2;
|
return conditionsSatisfied ? 1 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,35 +768,42 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec)
|
private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec)
|
||||||
{
|
{
|
||||||
// prefer matching video codecs
|
// Prefer matching video codecs
|
||||||
var videoCodecs = ContainerProfile.SplitValue(videoCodec);
|
var videoCodecs = ContainerProfile.SplitValue(videoCodec);
|
||||||
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream.Codec) ? videoStream.Codec : null;
|
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
|
||||||
playlistItem.VideoCodecs = directVideoCodec != null ? new[] { directVideoCodec } : videoCodecs;
|
if (directVideoCodec != null)
|
||||||
|
{
|
||||||
|
// merge directVideoCodec to videoCodecs
|
||||||
|
Array.Resize(ref videoCodecs, videoCodecs.Length + 1);
|
||||||
|
videoCodecs[^1] = directVideoCodec;
|
||||||
|
}
|
||||||
|
|
||||||
// copy video codec options as a starting point, this applies to transcode and direct-stream
|
playlistItem.VideoCodecs = videoCodecs;
|
||||||
playlistItem.MaxFramerate = videoStream.AverageFrameRate;
|
|
||||||
var qualifier = videoStream.Codec;
|
// Copy video codec options as a starting point, this applies to transcode and direct-stream
|
||||||
if (videoStream.Level.HasValue)
|
playlistItem.MaxFramerate = videoStream?.AverageFrameRate;
|
||||||
|
var qualifier = videoStream?.Codec;
|
||||||
|
if (videoStream?.Level != null)
|
||||||
{
|
{
|
||||||
playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture));
|
playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoStream.BitDepth.HasValue)
|
if (videoStream?.BitDepth != null)
|
||||||
{
|
{
|
||||||
playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture));
|
playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(videoStream.Profile))
|
if (!string.IsNullOrEmpty(videoStream?.Profile))
|
||||||
{
|
{
|
||||||
playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant());
|
playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoStream.Level != 0)
|
if (videoStream != null && videoStream.Level != 0)
|
||||||
{
|
{
|
||||||
playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString());
|
playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// prefer matching audio codecs, could do better here
|
// Prefer matching audio codecs, could do better here
|
||||||
var audioCodecs = ContainerProfile.SplitValue(audioCodec);
|
var audioCodecs = ContainerProfile.SplitValue(audioCodec);
|
||||||
var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
|
var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
|
||||||
playlistItem.AudioCodecs = audioCodecs;
|
playlistItem.AudioCodecs = audioCodecs;
|
||||||
@@ -806,7 +813,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
playlistItem.AudioStreamIndex = audioStream.Index;
|
playlistItem.AudioStreamIndex = audioStream.Index;
|
||||||
playlistItem.AudioCodecs = new[] { audioStream.Codec };
|
playlistItem.AudioCodecs = new[] { audioStream.Codec };
|
||||||
|
|
||||||
// copy matching audio codec options
|
// Copy matching audio codec options
|
||||||
playlistItem.AudioSampleRate = audioStream.SampleRate;
|
playlistItem.AudioSampleRate = audioStream.SampleRate;
|
||||||
playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString());
|
playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString());
|
||||||
|
|
||||||
@@ -843,7 +850,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||||
.Where(i => i.Type == CodecType.Video &&
|
.Where(i => i.Type == CodecType.Video &&
|
||||||
i.ContainsAnyCodec(videoCodec, container) &&
|
i.ContainsAnyCodec(videoCodec, container) &&
|
||||||
i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
|
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
|
||||||
var isFirstAppliedCodecProfile = true;
|
var isFirstAppliedCodecProfile = true;
|
||||||
foreach (var i in appliedVideoConditions)
|
foreach (var i in appliedVideoConditions)
|
||||||
{
|
{
|
||||||
@@ -873,9 +880,9 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
|
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
|
||||||
|
|
||||||
var appliedAudioConditions = options.Profile.CodecProfiles
|
var appliedAudioConditions = options.Profile.CodecProfiles
|
||||||
.Where(i => i.Type == CodecType.Video &&
|
.Where(i => i.Type == CodecType.VideoAudio &&
|
||||||
i.ContainsAnyCodec(audioCodec, container) &&
|
i.ContainsAnyCodec(audioCodec, container) &&
|
||||||
i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
|
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
|
||||||
isFirstAppliedCodecProfile = true;
|
isFirstAppliedCodecProfile = true;
|
||||||
foreach (var i in appliedAudioConditions)
|
foreach (var i in appliedAudioConditions)
|
||||||
{
|
{
|
||||||
@@ -1067,7 +1074,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
DeviceProfile profile = options.Profile;
|
DeviceProfile profile = options.Profile;
|
||||||
string container = mediaSource.Container;
|
string container = mediaSource.Container;
|
||||||
|
|
||||||
// video
|
// Video
|
||||||
int? width = videoStream?.Width;
|
int? width = videoStream?.Width;
|
||||||
int? height = videoStream?.Height;
|
int? height = videoStream?.Height;
|
||||||
int? bitDepth = videoStream?.BitDepth;
|
int? bitDepth = videoStream?.BitDepth;
|
||||||
@@ -1079,7 +1086,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
bool? isInterlaced = videoStream?.IsInterlaced;
|
bool? isInterlaced = videoStream?.IsInterlaced;
|
||||||
string videoCodecTag = videoStream?.CodecTag;
|
string videoCodecTag = videoStream?.CodecTag;
|
||||||
bool? isAvc = videoStream?.IsAVC;
|
bool? isAvc = videoStream?.IsAVC;
|
||||||
// audio
|
// Audio
|
||||||
var defaultLanguage = audioStream?.Language ?? string.Empty;
|
var defaultLanguage = audioStream?.Language ?? string.Empty;
|
||||||
var defaultMarked = audioStream?.IsDefault ?? false;
|
var defaultMarked = audioStream?.IsDefault ?? false;
|
||||||
|
|
||||||
@@ -1108,18 +1115,9 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
profile,
|
profile,
|
||||||
"VideoCodecProfile",
|
"VideoCodecProfile",
|
||||||
profile.CodecProfiles
|
profile.CodecProfiles
|
||||||
.Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container))
|
.Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container) &&
|
||||||
.SelectMany(codecProfile =>
|
!checkVideoConditions(codecProfile.ApplyConditions).Any())
|
||||||
{
|
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
|
||||||
var failedApplyConditions = checkVideoConditions(codecProfile.ApplyConditions);
|
|
||||||
if (!failedApplyConditions.Any())
|
|
||||||
{
|
|
||||||
return Array.Empty<ProfileCondition>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var failedConditions = checkVideoConditions(codecProfile.Conditions);
|
|
||||||
return failedApplyConditions.Concat(failedConditions);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Check audiocandidates profile conditions
|
// Check audiocandidates profile conditions
|
||||||
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked));
|
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked));
|
||||||
@@ -1189,7 +1187,18 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
|
audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
var failureReasons = directPlayProfileReasons | containerProfileReasons | videoCodecProfileReasons | audioCodecProfileReasons | subtitleProfileReasons;
|
var failureReasons = directPlayProfileReasons | containerProfileReasons | subtitleProfileReasons;
|
||||||
|
|
||||||
|
if ((failureReasons & TranscodeReason.VideoCodecNotSupported) == 0)
|
||||||
|
{
|
||||||
|
failureReasons |= videoCodecProfileReasons;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((failureReasons & TranscodeReason.AudioCodecNotSupported) == 0)
|
||||||
|
{
|
||||||
|
failureReasons |= audioCodecProfileReasons;
|
||||||
|
}
|
||||||
|
|
||||||
var directStreamFailureReasons = failureReasons & (~DirectStreamReasons);
|
var directStreamFailureReasons = failureReasons & (~DirectStreamReasons);
|
||||||
|
|
||||||
PlayMethod? playMethod = null;
|
PlayMethod? playMethod = null;
|
||||||
@@ -1206,6 +1215,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
|
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
|
||||||
})
|
})
|
||||||
.OrderByDescending(analysis => analysis.Result.PlayMethod)
|
.OrderByDescending(analysis => analysis.Result.PlayMethod)
|
||||||
|
.ThenByDescending(analysis => analysis.Rank)
|
||||||
.ThenBy(analysis => analysis.Order)
|
.ThenBy(analysis => analysis.Order)
|
||||||
.ToArray()
|
.ToArray()
|
||||||
.ToLookup(analysis => analysis.Result.PlayMethod != null);
|
.ToLookup(analysis => analysis.Result.PlayMethod != null);
|
||||||
@@ -1218,7 +1228,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return profileMatch;
|
return profileMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason;
|
var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason;
|
||||||
if (failureReasons == 0)
|
if (failureReasons == 0)
|
||||||
{
|
{
|
||||||
failureReasons = TranscodeReason.DirectPlayError;
|
failureReasons = TranscodeReason.DirectPlayError;
|
||||||
@@ -1264,13 +1274,13 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
mediaSource.Path ?? "Unknown path");
|
mediaSource.Path ?? "Unknown path");
|
||||||
}
|
}
|
||||||
|
|
||||||
private TranscodeReason IsEligibleForDirectPlay(
|
private TranscodeReason IsBitrateEligibleForDirectPlayback(
|
||||||
MediaSourceInfo item,
|
MediaSourceInfo item,
|
||||||
long maxBitrate,
|
long maxBitrate,
|
||||||
VideoOptions options,
|
VideoOptions options,
|
||||||
PlayMethod playMethod)
|
PlayMethod playMethod)
|
||||||
{
|
{
|
||||||
bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod);
|
bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod);
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
{
|
||||||
return TranscodeReason.ContainerBitrateExceedsLimit;
|
return TranscodeReason.ContainerBitrateExceedsLimit;
|
||||||
@@ -1438,7 +1448,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsItemBitrateEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
|
private bool IsItemBitrateEligibleForDirectPlayback(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
|
||||||
{
|
{
|
||||||
// Don't restrict by bitrate if coming from an external domain
|
// Don't restrict by bitrate if coming from an external domain
|
||||||
if (item.IsRemote)
|
if (item.IsRemote)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Entities
|
namespace MediaBrowser.Model.Entities
|
||||||
{
|
{
|
||||||
@@ -9,6 +10,16 @@ namespace MediaBrowser.Model.Entities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ProviderIdsExtensions
|
public static class ProviderIdsExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Case insensitive dictionary of <see cref="MetadataProvider"/> string representation.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<string, string> _metadataProviderEnumDictionary =
|
||||||
|
Enum.GetValues<MetadataProvider>()
|
||||||
|
.ToDictionary(
|
||||||
|
enumValue => enumValue.ToString(),
|
||||||
|
enumValue => enumValue.ToString(),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if this instance has an id for the given provider.
|
/// Checks if this instance has an id for the given provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -108,7 +119,7 @@ namespace MediaBrowser.Model.Entities
|
|||||||
/// <param name="instance">The instance.</param>
|
/// <param name="instance">The instance.</param>
|
||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <param name="value">The value.</param>
|
/// <param name="value">The value.</param>
|
||||||
public static void SetProviderId(this IHasProviderIds instance, string name, string value)
|
public static void SetProviderId(this IHasProviderIds instance, string name, string? value)
|
||||||
{
|
{
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
{
|
{
|
||||||
@@ -125,7 +136,15 @@ namespace MediaBrowser.Model.Entities
|
|||||||
// Ensure it exists
|
// Ensure it exists
|
||||||
instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
instance.ProviderIds[name] = value;
|
// Match on internal MetadataProvider enum string values before adding arbitrary providers
|
||||||
|
if (_metadataProviderEnumDictionary.TryGetValue(name, out var enumValue))
|
||||||
|
{
|
||||||
|
instance.ProviderIds[enumValue] = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
instance.ProviderIds[name] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="6.0.2" />
|
<PackageReference Include="System.Text.Json" Version="6.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Diacritics.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ using MediaBrowser.Model.Configuration;
|
|||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Priority_Queue;
|
using Priority_Queue;
|
||||||
@@ -188,6 +189,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound);
|
throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// some iptv/epg providers don't correctly report media type, extract from url if no extension found
|
||||||
|
if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType)))
|
||||||
|
{
|
||||||
|
contentType = MimeTypes.GetMimeType(url);
|
||||||
|
}
|
||||||
|
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await SaveImage(
|
await SaveImage(
|
||||||
item,
|
item,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
|
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
|
||||||
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
|
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
|
||||||
<PackageReference Include="TMDbLib" Version="1.9.1" />
|
<PackageReference Include="TMDbLib" Version="1.9.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -173,16 +173,30 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
IReadOnlyList<MediaAttachment> mediaAttachments;
|
IReadOnlyList<MediaAttachment> mediaAttachments;
|
||||||
ChapterInfo[] chapters;
|
ChapterInfo[] chapters;
|
||||||
|
|
||||||
|
mediaStreams = new List<MediaStream>();
|
||||||
|
|
||||||
|
// Add external streams before adding the streams from the file to preserve stream IDs on remote videos
|
||||||
|
await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var startIndex = mediaStreams.Count == 0 ? 0 : (mediaStreams.Max(i => i.Index) + 1);
|
||||||
|
|
||||||
if (mediaInfo != null)
|
if (mediaInfo != null)
|
||||||
{
|
{
|
||||||
mediaStreams = mediaInfo.MediaStreams.ToList();
|
foreach (var mediaStream in mediaInfo.MediaStreams)
|
||||||
|
{
|
||||||
|
mediaStream.Index = startIndex++;
|
||||||
|
mediaStreams.Add(mediaStream);
|
||||||
|
}
|
||||||
|
|
||||||
mediaAttachments = mediaInfo.MediaAttachments;
|
mediaAttachments = mediaInfo.MediaAttachments;
|
||||||
|
|
||||||
video.TotalBitrate = mediaInfo.Bitrate;
|
video.TotalBitrate = mediaInfo.Bitrate;
|
||||||
// video.FormatName = (mediaInfo.Container ?? string.Empty)
|
// video.FormatName = (mediaInfo.Container ?? string.Empty)
|
||||||
// .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
|
// .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// For dvd's this may not always be accurate, so don't set the runtime if the item already has one
|
// For DVDs this may not always be accurate, so don't set the runtime if the item already has one
|
||||||
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
|
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
|
||||||
|
|
||||||
if (needToSetRuntime)
|
if (needToSetRuntime)
|
||||||
@@ -213,15 +227,20 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mediaStreams = new List<MediaStream>();
|
var currentMediaStreams = video.GetMediaStreams();
|
||||||
|
foreach (var mediaStream in currentMediaStreams)
|
||||||
|
{
|
||||||
|
if (!mediaStream.IsExternal)
|
||||||
|
{
|
||||||
|
mediaStream.Index = startIndex++;
|
||||||
|
mediaStreams.Add(mediaStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mediaAttachments = Array.Empty<MediaAttachment>();
|
mediaAttachments = Array.Empty<MediaAttachment>();
|
||||||
chapters = Array.Empty<ChapterInfo>();
|
chapters = Array.Empty<ChapterInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var libraryOptions = _libraryManager.GetLibraryOptions(video);
|
var libraryOptions = _libraryManager.GetLibraryOptions(video);
|
||||||
|
|
||||||
if (mediaInfo != null)
|
if (mediaInfo != null)
|
||||||
@@ -254,7 +273,11 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
|
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
|
||||||
|
|
||||||
_itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
|
_itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
|
||||||
_itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
|
|
||||||
|
if (mediaAttachments.Any())
|
||||||
|
{
|
||||||
|
_itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
|
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
|
||||||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
|
options.MetadataRefreshMode == MetadataRefreshMode.Default)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -153,6 +152,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
var files = directoryService.GetFilePaths(folder, clearCache).ToList();
|
var files = directoryService.GetFilePaths(folder, clearCache).ToList();
|
||||||
|
files.Remove(video.Path);
|
||||||
var internalMetadataPath = video.GetInternalMetadataPath();
|
var internalMetadataPath = video.GetInternalMetadataPath();
|
||||||
if (_fileSystem.DirectoryExists(internalMetadataPath))
|
if (_fileSystem.DirectoryExists(internalMetadataPath))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Diacritics.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.StudioImages
|
namespace MediaBrowser.Providers.Plugins.StudioImages.Configuration
|
||||||
{
|
{
|
||||||
public class PluginConfiguration : BasePluginConfiguration
|
public class PluginConfiguration : BasePluginConfiguration
|
||||||
{
|
{
|
||||||
@@ -12,12 +12,19 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(_repository))
|
||||||
|
{
|
||||||
|
_repository = Plugin.DefaultServer;
|
||||||
|
}
|
||||||
|
|
||||||
return _repository;
|
return _repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_repository = value.TrimEnd('/');
|
_repository = string.IsNullOrEmpty(value)
|
||||||
|
? Plugin.DefaultServer
|
||||||
|
: value.TrimEnd('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
<form class="configForm">
|
<form class="configForm">
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<input is="emby-input" type="text" id="repository" required label="Repository" />
|
<input is="emby-input" type="text" id="repository" label="Repository" />
|
||||||
<div class="fieldDescription">This can be any Jellyfin-compatible artwork repository.</div>
|
<div class="fieldDescription">This can be any Jellyfin-compatible artwork repository. Leave blank to use default repository.</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
|
|
||||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||||
config.RepositoryUrl = document.querySelector('#server').value;
|
config.RepositoryUrl = document.querySelector('#repository').value;
|
||||||
|
|
||||||
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using MediaBrowser.Common.Configuration;
|
|||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using MediaBrowser.Providers.Plugins.StudioImages.Configuration;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.StudioImages
|
namespace MediaBrowser.Providers.Plugins.StudioImages
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,29 +18,24 @@ using MediaBrowser.Controller.Providers;
|
|||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.StudioImages;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Studios
|
namespace MediaBrowser.Providers.Plugins.StudioImages
|
||||||
{
|
{
|
||||||
public class StudiosImageProvider : IRemoteImageProvider
|
public class StudiosImageProvider : IRemoteImageProvider
|
||||||
{
|
{
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly string repositoryUrl;
|
|
||||||
|
|
||||||
public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
|
public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
repositoryUrl = Plugin.Instance.Configuration.RepositoryUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Artwork Repository";
|
public string Name => "Artwork Repository";
|
||||||
|
|
||||||
public int Order => 0;
|
|
||||||
|
|
||||||
public bool Supports(BaseItem item)
|
public bool Supports(BaseItem item)
|
||||||
{
|
{
|
||||||
return item is Studio;
|
return item is Studio;
|
||||||
@@ -98,12 +93,12 @@ namespace MediaBrowser.Providers.Studios
|
|||||||
|
|
||||||
private string GetUrl(string image, string filename)
|
private string GetUrl(string image, string filename)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", repositoryUrl, image, filename);
|
return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", GetRepositoryUrl(), image, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken)
|
private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", repositoryUrl);
|
string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", GetRepositoryUrl());
|
||||||
|
|
||||||
return EnsureList(url, file, _fileSystem, cancellationToken);
|
return EnsureList(url, file, _fileSystem, cancellationToken);
|
||||||
}
|
}
|
||||||
@@ -169,5 +164,8 @@ namespace MediaBrowser.Providers.Studios
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetRepositoryUrl()
|
||||||
|
=> Plugin.Instance.Configuration.RepositoryUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
public static bool IsTrailerType(Video video)
|
public static bool IsTrailerType(Video video)
|
||||||
{
|
{
|
||||||
return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase)
|
return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase)
|
||||||
&& (!video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
|
&& (video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
|
||||||
|| !video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
|
|| video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
{
|
{
|
||||||
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
|
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
|
||||||
|
|
||||||
writer.WriteElementString("disbanded", artist.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
writer.WriteElementString("disbanded", artist.EndDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
var albums = artist
|
var albums = artist
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
writer.WriteElementString("lockedfields", string.Join('|', item.LockedFields));
|
writer.WriteElementString("lockedfields", string.Join('|', item.LockedFields));
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat, CultureInfo.InvariantCulture));
|
writer.WriteElementString("dateadded", item.DateCreated.ToString(DateAddedFormat, CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
writer.WriteElementString("title", item.Name ?? string.Empty);
|
writer.WriteElementString("title", item.Name ?? string.Empty);
|
||||||
|
|
||||||
@@ -601,16 +601,16 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
{
|
{
|
||||||
writer.WriteElementString(
|
writer.WriteElementString(
|
||||||
"formed",
|
"formed",
|
||||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
writer.WriteElementString(
|
writer.WriteElementString(
|
||||||
"premiered",
|
"premiered",
|
||||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
|
||||||
writer.WriteElementString(
|
writer.WriteElementString(
|
||||||
"releasedate",
|
"releasedate",
|
||||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,7 +622,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
|
|
||||||
writer.WriteElementString(
|
writer.WriteElementString(
|
||||||
"enddate",
|
"enddate",
|
||||||
item.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
item.EndDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -891,7 +891,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
{
|
{
|
||||||
writer.WriteElementString(
|
writer.WriteElementString(
|
||||||
"lastplayed",
|
"lastplayed",
|
||||||
userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLowerInvariant());
|
userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLowerInvariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteStartElement("resume");
|
writer.WriteStartElement("resume");
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
{
|
{
|
||||||
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
|
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
|
||||||
|
|
||||||
writer.WriteElementString("aired", episode.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
writer.WriteElementString("aired", episode.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!episode.ParentIndexNumber.HasValue || episode.ParentIndexNumber.Value == 0)
|
if (!episode.ParentIndexNumber.HasValue || episode.ParentIndexNumber.Value == 0)
|
||||||
|
|||||||
18
debian/changelog
vendored
18
debian/changelog
vendored
@@ -1,8 +1,20 @@
|
|||||||
jellyfin-server (10.8.0-1) unstable; urgency=medium
|
jellyfin-server (10.8.0~beta3) unstable; urgency=medium
|
||||||
|
|
||||||
* Forthcoming stable release
|
* New upstream version 10.8.0-beta3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta3
|
||||||
|
|
||||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 04 Dec 2020 21:55:12 -0500
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 15 May 2022 20:15:43 -0400
|
||||||
|
|
||||||
|
jellyfin-server (10.8.0~beta2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.0-beta2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta2
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 17 Apr 2022 15:51:43 -0400
|
||||||
|
|
||||||
|
jellyfin-server (10.8.0~beta1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.0-beta1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta1
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 25 Mar 2022 22:22:51 -0400
|
||||||
|
|
||||||
jellyfin-server (10.7.0-1) unstable; urgency=medium
|
jellyfin-server (10.7.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
|||||||
2
debian/control
vendored
2
debian/control
vendored
@@ -22,7 +22,7 @@ Depends: at,
|
|||||||
libsqlite3-0,
|
libsqlite3-0,
|
||||||
libfontconfig1,
|
libfontconfig1,
|
||||||
libfreetype6,
|
libfreetype6,
|
||||||
libssl1.1
|
libssl1.1 | libssl3
|
||||||
Recommends: jellyfin-web, sudo
|
Recommends: jellyfin-web, sudo
|
||||||
Description: Jellyfin is the Free Software Media System.
|
Description: Jellyfin is the Free Software Media System.
|
||||||
This package provides the Jellyfin server backend and API.
|
This package provides the Jellyfin server backend and API.
|
||||||
|
|||||||
2
debian/metapackage/jellyfin
vendored
2
debian/metapackage/jellyfin
vendored
@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
|
|||||||
Standards-Version: 3.9.2
|
Standards-Version: 3.9.2
|
||||||
|
|
||||||
Package: jellyfin
|
Package: jellyfin
|
||||||
Version: 10.8.0
|
Version: 10.8.0~beta3
|
||||||
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
|
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
Depends: jellyfin-server, jellyfin-web
|
Depends: jellyfin-server, jellyfin-web
|
||||||
Description: Provides the Jellyfin Free Software Media System
|
Description: Provides the Jellyfin Free Software Media System
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ RUN yum update -yq \
|
|||||||
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
|
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
|
||||||
|
|
||||||
# Install DotNET SDK
|
# Install DotNET SDK
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/9d8c7137-2091-4fc6-a419-60ba59c8b9de/db0c5cda94f31d2260d369123de32d59/dotnet-sdk-6.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ RUN dnf update -yq \
|
|||||||
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget
|
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget
|
||||||
|
|
||||||
# Install DotNET SDK
|
# Install DotNET SDK
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/9d8c7137-2091-4fc6-a419-60ba59c8b9de/db0c5cda94f31d2260d369123de32d59/dotnet-sdk-6.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ RUN apt-get update -yqq \
|
|||||||
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/9d8c7137-2091-4fc6-a419-60ba59c8b9de/db0c5cda94f31d2260d369123de32d59/dotnet-sdk-6.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
|
|||||||
mmv build-essential lsb-release
|
mmv build-essential lsb-release
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/9d8c7137-2091-4fc6-a419-60ba59c8b9de/db0c5cda94f31d2260d369123de32d59/dotnet-sdk-6.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
|
|||||||
mmv build-essential lsb-release
|
mmv build-essential lsb-release
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/9d8c7137-2091-4fc6-a419-60ba59c8b9de/db0c5cda94f31d2260d369123de32d59/dotnet-sdk-6.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
Name: jellyfin
|
Name: jellyfin
|
||||||
Version: 10.8.0
|
Version: 10.8.0~beta3
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: The Free Software Media System
|
Summary: The Free Software Media System
|
||||||
License: GPLv3
|
License: GPLv3
|
||||||
@@ -153,6 +153,12 @@ fi
|
|||||||
%systemd_postun_with_restart jellyfin.service
|
%systemd_postun_with_restart jellyfin.service
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sun May 15 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.0-beta3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta3
|
||||||
|
* Sun Apr 17 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.0-beta2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta2
|
||||||
|
* Fri Mar 25 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.0-beta1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0-beta1
|
||||||
* Mon Nov 29 2021 Brian J. Murrell <brian@interlinx.bc.ca>
|
* Mon Nov 29 2021 Brian J. Murrell <brian@interlinx.bc.ca>
|
||||||
- Add jellyfin-server-lowports.service drop-in in a server-lowports
|
- Add jellyfin-server-lowports.service drop-in in a server-lowports
|
||||||
subpackage to allow binding to low ports
|
subpackage to allow binding to low ports
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Jellyfin.Extensions
|
namespace Jellyfin.Extensions
|
||||||
{
|
{
|
||||||
@@ -7,6 +11,44 @@ namespace Jellyfin.Extensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StringExtensions
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
|
// Matches non-conforming unicode chars
|
||||||
|
// https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
|
||||||
|
private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the diacritics character from the strings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The string to act on.</param>
|
||||||
|
/// <returns>The string without diacritics character.</returns>
|
||||||
|
public static string RemoveDiacritics(this string text)
|
||||||
|
{
|
||||||
|
string withDiactritics = _nonConformingUnicode
|
||||||
|
.Replace(text, string.Empty)
|
||||||
|
.Normalize(NormalizationForm.FormD);
|
||||||
|
|
||||||
|
var withoutDiactritics = new StringBuilder();
|
||||||
|
foreach (char c in withDiactritics)
|
||||||
|
{
|
||||||
|
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
|
||||||
|
if (uc != UnicodeCategory.NonSpacingMark)
|
||||||
|
{
|
||||||
|
withoutDiactritics.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return withoutDiactritics.ToString().Normalize(NormalizationForm.FormC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks wether or not the specified string has diacritics in it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The string to check.</param>
|
||||||
|
/// <returns>True if the string has diacritics, false otherwise.</returns>
|
||||||
|
public static bool HasDiacritics(this string text)
|
||||||
|
{
|
||||||
|
return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Counts the number of occurrences of [needle] in the string.
|
/// Counts the number of occurrences of [needle] in the string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
|||||||
@@ -5,6 +5,38 @@ namespace Jellyfin.Extensions.Tests
|
|||||||
{
|
{
|
||||||
public class StringExtensionsTests
|
public class StringExtensionsTests
|
||||||
{
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("", "")] // Identity edge-case (no diactritics)
|
||||||
|
[InlineData("Indiana Jones", "Indiana Jones")] // Identity (no diactritics)
|
||||||
|
[InlineData("a\ud800b", "ab")] // Invalid UTF-16 char stripping
|
||||||
|
[InlineData("Jön", "Jon")] // Issue #7484
|
||||||
|
[InlineData("Jönssonligan", "Jonssonligan")] // Issue #7484
|
||||||
|
[InlineData("Kieślowski", "Kieslowski")] // Issue #7450
|
||||||
|
[InlineData("Cidadão Kane", "Cidadao Kane")] // Issue #7560
|
||||||
|
[InlineData("운명처럼 널 사랑해", "운명처럼 널 사랑해")] // Issue #6393 (Korean language support)
|
||||||
|
[InlineData("애타는 로맨스", "애타는 로맨스")] // Issue #6393
|
||||||
|
public void RemoveDiacritics_ValidInput_Corrects(string input, string expectedResult)
|
||||||
|
{
|
||||||
|
string result = input.RemoveDiacritics();
|
||||||
|
Assert.Equal(expectedResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("", false)] // Identity edge-case (no diactritics)
|
||||||
|
[InlineData("Indiana Jones", false)] // Identity (no diactritics)
|
||||||
|
[InlineData("a\ud800b", true)] // Invalid UTF-16 char stripping
|
||||||
|
[InlineData("Jön", true)] // Issue #7484
|
||||||
|
[InlineData("Jönssonligan", true)] // Issue #7484
|
||||||
|
[InlineData("Kieślowski", true)] // Issue #7450
|
||||||
|
[InlineData("Cidadão Kane", true)] // Issue #7560
|
||||||
|
[InlineData("운명처럼 널 사랑해", false)] // Issue #6393 (Korean language support)
|
||||||
|
[InlineData("애타는 로맨스", false)] // Issue #6393
|
||||||
|
public void HasDiacritics_ValidInput_Corrects(string input, bool expectedResult)
|
||||||
|
{
|
||||||
|
bool result = input.HasDiacritics();
|
||||||
|
Assert.Equal(expectedResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("", '_', 0)]
|
[InlineData("", '_', 0)]
|
||||||
[InlineData("___", '_', 3)]
|
[InlineData("___", '_', 3)]
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Jellyfin.Model.Tests
|
|||||||
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
||||||
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
||||||
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
|
||||||
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||||
// Firefox
|
// Firefox
|
||||||
@@ -38,7 +38,7 @@ namespace Jellyfin.Model.Tests
|
|||||||
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
||||||
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
||||||
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
|
||||||
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||||
// Safari
|
// Safari
|
||||||
@@ -89,7 +89,7 @@ namespace Jellyfin.Model.Tests
|
|||||||
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
|
[InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
|
||||||
[InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
|
[InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
|
||||||
[InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
|
||||||
[InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
[InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||||
// TranscodeMedia
|
// TranscodeMedia
|
||||||
@@ -131,6 +131,37 @@ namespace Jellyfin.Model.Tests
|
|||||||
[InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
|
[InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
|
||||||
[InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
|
[InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
|
||||||
[InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
|
[InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
|
||||||
|
// AndroidTV
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
|
||||||
|
// Tizen 3 Stereo
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-dts-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
|
||||||
|
[InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
// Tizen 4 4K 5.1
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-dts-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
|
public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
|
||||||
{
|
{
|
||||||
var options = await GetVideoOptions(deviceName, mediaSource);
|
var options = await GetVideoOptions(deviceName, mediaSource);
|
||||||
@@ -146,7 +177,7 @@ namespace Jellyfin.Model.Tests
|
|||||||
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
||||||
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
||||||
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
|
||||||
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||||
// Firefox
|
// Firefox
|
||||||
@@ -156,7 +187,7 @@ namespace Jellyfin.Model.Tests
|
|||||||
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
||||||
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
|
||||||
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
|
||||||
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
|
||||||
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||||
// Safari
|
// Safari
|
||||||
@@ -198,6 +229,37 @@ namespace Jellyfin.Model.Tests
|
|||||||
[InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
|
[InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
|
||||||
[InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
|
[InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
|
||||||
[InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
|
[InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
|
||||||
|
// AndroidTV
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
|
||||||
|
// Tizen 3 Stereo
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-dts-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
|
||||||
|
[InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen3-stereo", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
// Tizen 4 4K 5.1
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-dts-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
|
||||||
public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
|
public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
|
||||||
{
|
{
|
||||||
var options = await GetVideoOptions(deviceName, mediaSource);
|
var options = await GetVideoOptions(deviceName, mediaSource);
|
||||||
@@ -223,12 +285,26 @@ namespace Jellyfin.Model.Tests
|
|||||||
// RokuSSPlus
|
// RokuSSPlus
|
||||||
[InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
[InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||||
[InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
[InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||||
|
// no streams
|
||||||
|
[InlineData("Chrome", "no-streams", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450
|
||||||
|
// AndroidTV
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
|
||||||
|
[InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
|
||||||
|
// Tizen 3 Stereo
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
|
||||||
|
[InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
|
||||||
|
// Tizen 4 4K 5.1
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
|
||||||
|
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
|
||||||
public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
|
public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
|
||||||
{
|
{
|
||||||
var options = await GetVideoOptions(deviceName, mediaSource);
|
var options = await GetVideoOptions(deviceName, mediaSource);
|
||||||
var streamCount = options.MediaSources[0].MediaStreams.Count;
|
var streamCount = options.MediaSources[0].MediaStreams.Count;
|
||||||
options.AudioStreamIndex = streamCount - 2;
|
if (streamCount > 0)
|
||||||
options.SubtitleStreamIndex = streamCount - 1;
|
{
|
||||||
|
options.AudioStreamIndex = streamCount - 2;
|
||||||
|
options.SubtitleStreamIndex = streamCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
|
var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
|
||||||
Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
|
Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
|
||||||
@@ -262,23 +338,23 @@ namespace Jellyfin.Model.Tests
|
|||||||
Assert.NotNull(mediaSource);
|
Assert.NotNull(mediaSource);
|
||||||
var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
|
var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
|
||||||
var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
|
var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
|
||||||
// TODO: check AudioStreamIndex vs options.AudioStreamIndex
|
// TODO: Check AudioStreamIndex vs options.AudioStreamIndex
|
||||||
var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
|
var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
|
||||||
|
|
||||||
var uri = ParseUri(val);
|
var uri = ParseUri(val);
|
||||||
|
|
||||||
if (playMethod == PlayMethod.DirectPlay)
|
if (playMethod == PlayMethod.DirectPlay)
|
||||||
{
|
{
|
||||||
// check expected container
|
// Check expected container
|
||||||
var containers = ContainerProfile.SplitValue(mediaSource.Container);
|
var containers = ContainerProfile.SplitValue(mediaSource.Container);
|
||||||
// TODO: test transcode too
|
// TODO: Test transcode too
|
||||||
// Assert.Contains(uri.Extension, containers);
|
// Assert.Contains(uri.Extension, containers);
|
||||||
|
|
||||||
// check expected video codec (1)
|
// Check expected video codec (1)
|
||||||
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
|
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
|
||||||
Assert.Single(val.TargetVideoCodec);
|
Assert.Single(val.TargetVideoCodec);
|
||||||
|
|
||||||
// check expected audio codecs (1)
|
// Check expected audio codecs (1)
|
||||||
Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
|
Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
|
||||||
Assert.Single(val.TargetAudioCodec);
|
Assert.Single(val.TargetAudioCodec);
|
||||||
// Assert.Single(val.AudioCodecs);
|
// Assert.Single(val.AudioCodecs);
|
||||||
@@ -294,7 +370,7 @@ namespace Jellyfin.Model.Tests
|
|||||||
Assert.NotEmpty(val.VideoCodecs);
|
Assert.NotEmpty(val.VideoCodecs);
|
||||||
Assert.NotEmpty(val.AudioCodecs);
|
Assert.NotEmpty(val.AudioCodecs);
|
||||||
|
|
||||||
// check expected container (todo: this could be a test param)
|
// Check expected container (todo: this could be a test param)
|
||||||
if (transcodeProtocol == "http")
|
if (transcodeProtocol == "http")
|
||||||
{
|
{
|
||||||
// Assert.Equal("webm", val.Container);
|
// Assert.Equal("webm", val.Container);
|
||||||
@@ -327,32 +403,39 @@ namespace Jellyfin.Model.Tests
|
|||||||
stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
|
stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: fill out tests here
|
// TODO: Fill out tests here
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirectStream and Remux
|
// DirectStream and Remux
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// check expected video codec (1)
|
// Check expected video codec (1)
|
||||||
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
|
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
|
||||||
Assert.Single(val.TargetVideoCodec);
|
Assert.Single(val.TargetVideoCodec);
|
||||||
|
|
||||||
if (transcodeMode == "DirectStream")
|
if (transcodeMode == "DirectStream")
|
||||||
{
|
{
|
||||||
|
// Check expected audio codecs (1)
|
||||||
if (!targetAudioStream.IsExternal)
|
if (!targetAudioStream.IsExternal)
|
||||||
{
|
{
|
||||||
// check expected audio codecs (1)
|
if (val.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
|
||||||
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
|
{
|
||||||
|
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (transcodeMode == "Remux")
|
else if (transcodeMode == "Remux")
|
||||||
{
|
{
|
||||||
// check expected audio codecs (1)
|
// Check expected audio codecs (1)
|
||||||
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
|
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
|
||||||
Assert.Single(val.AudioCodecs);
|
Assert.Single(val.AudioCodecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// video details
|
// Video details
|
||||||
var videoStream = targetVideoStream;
|
var videoStream = targetVideoStream;
|
||||||
Assert.False(val.EstimateContentLength);
|
Assert.False(val.EstimateContentLength);
|
||||||
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
|
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
|
||||||
@@ -361,10 +444,10 @@ namespace Jellyfin.Model.Tests
|
|||||||
Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
|
Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
|
||||||
Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
|
Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
|
||||||
|
|
||||||
// audio codec not supported
|
// Audio codec not supported
|
||||||
if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
|
if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
|
||||||
{
|
{
|
||||||
// audio stream specified
|
// Audio stream specified
|
||||||
if (options.AudioStreamIndex >= 0)
|
if (options.AudioStreamIndex >= 0)
|
||||||
{
|
{
|
||||||
// TODO:fixme
|
// TODO:fixme
|
||||||
@@ -374,10 +457,10 @@ namespace Jellyfin.Model.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// audio stream not specified
|
// Audio stream not specified
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO:fixme
|
// TODO: Fixme
|
||||||
Assert.All(audioStreams, stream =>
|
Assert.All(audioStreams, stream =>
|
||||||
{
|
{
|
||||||
if (!stream.IsExternal)
|
if (!stream.IsExternal)
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
{
|
||||||
|
"Name": "Jellyfin AndroidTV-ExoPlayer",
|
||||||
|
"EnableAlbumArtInDidl": false,
|
||||||
|
"EnableSingleAlbumArtLimit": false,
|
||||||
|
"EnableSingleSubtitleLimit": false,
|
||||||
|
"SupportedMediaTypes": "Audio,Photo,Video",
|
||||||
|
"MaxAlbumArtWidth": 0,
|
||||||
|
"MaxAlbumArtHeight": 0,
|
||||||
|
"MaxStreamingBitrate": 120000000,
|
||||||
|
"MaxStaticBitrate": 100000000,
|
||||||
|
"MusicStreamingTranscodingBitrate": 192000,
|
||||||
|
"TimelineOffsetSeconds": 0,
|
||||||
|
"RequiresPlainVideoItems": false,
|
||||||
|
"RequiresPlainFolders": false,
|
||||||
|
"EnableMSMediaReceiverRegistrar": false,
|
||||||
|
"IgnoreTranscodeByteRangeRequests": false,
|
||||||
|
"DirectPlayProfiles": [
|
||||||
|
{
|
||||||
|
"Container": "m4v,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,mp4,webm",
|
||||||
|
"AudioCodec": "aac,mp3,mp2,aac_latm,alac,ac3,eac3,dca,dts,mlp,truehd,pcm_alaw,pcm_mulaw",
|
||||||
|
"VideoCodec": "h264,hevc,vp8,vp9,mpeg,mpeg2video",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "aac,mp3,mp2,aac_latm,alac,ac3,eac3,dca,dts,mlp,truehd,pcm_alaw,pcm_mulaw,,pa,flac,wav,wma,ogg,oga,webma,ape,opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "jpg,jpeg,png,gif,web",
|
||||||
|
"Type": "Photo",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CodecProfiles": [
|
||||||
|
{
|
||||||
|
"Type": "Video",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "EqualsAny",
|
||||||
|
"Property": "VideoProfile",
|
||||||
|
"Value": "high|main|baseline|constrained baseline",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "VideoLevel",
|
||||||
|
"Value": "51",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Codec": "h264",
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": "Video",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "RefFrames",
|
||||||
|
"Value": "12",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ApplyConditions": [
|
||||||
|
{
|
||||||
|
"Condition": "GreaterThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "1200",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Codec": "h264",
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": "Video",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "RefFrames",
|
||||||
|
"Value": "4",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ApplyConditions": [
|
||||||
|
{
|
||||||
|
"Condition": "GreaterThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "1900",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Codec": "h264",
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": "VideoAudio",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "AudioChannels",
|
||||||
|
"Value": "6",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TranscodingProfiles": [
|
||||||
|
{
|
||||||
|
"Container": "ts",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "h264",
|
||||||
|
"AudioCodec": "aac,mp3",
|
||||||
|
"Protocol": "hls",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp3",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "mp3",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SubtitleProfiles": [
|
||||||
|
{
|
||||||
|
"Format": "srt",
|
||||||
|
"Method": "Embed",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "srt",
|
||||||
|
"Method": "External",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "subrip",
|
||||||
|
"Method": "Embed",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "subrip",
|
||||||
|
"Method": "External",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "ass",
|
||||||
|
"Method": "Encode",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "ssa",
|
||||||
|
"Method": "Encode",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "pgs",
|
||||||
|
"Method": "Encode",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "pgssub",
|
||||||
|
"Method": "Encode",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "dvdsub",
|
||||||
|
"Method": "Encode",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "vtt",
|
||||||
|
"Method": "Embed",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "sub",
|
||||||
|
"Method": "Embed",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "idx",
|
||||||
|
"Method": "Embed",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "DeviceProfile"
|
||||||
|
}
|
||||||
@@ -0,0 +1,526 @@
|
|||||||
|
{
|
||||||
|
"Name": "Jellyfin Tizen 3 Stereo",
|
||||||
|
"EnableAlbumArtInDidl": false,
|
||||||
|
"EnableSingleAlbumArtLimit": false,
|
||||||
|
"EnableSingleSubtitleLimit": false,
|
||||||
|
"SupportedMediaTypes": "Audio,Photo,Video",
|
||||||
|
"MaxAlbumArtWidth": 0,
|
||||||
|
"MaxAlbumArtHeight": 0,
|
||||||
|
"MaxStreamingBitrate": 120000000,
|
||||||
|
"MaxStaticBitrate": 100000000,
|
||||||
|
"MusicStreamingTranscodingBitrate": 384000,
|
||||||
|
"TimelineOffsetSeconds": 0,
|
||||||
|
"RequiresPlainVideoItems": false,
|
||||||
|
"RequiresPlainFolders": false,
|
||||||
|
"EnableMSMediaReceiverRegistrar": false,
|
||||||
|
"IgnoreTranscodeByteRangeRequests": false,
|
||||||
|
"DirectPlayProfiles": [
|
||||||
|
{
|
||||||
|
"Container": "webm",
|
||||||
|
"AudioCodec": "vorbis,opus",
|
||||||
|
"VideoCodec": "vp8,vp9",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp4,m4v",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mkv",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "m2ts",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,vc1,mpeg2video",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wmv",
|
||||||
|
"AudioCodec": "wma",
|
||||||
|
"VideoCodec": "wmv,vc1",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "ts,mpegts",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,hevc,vc1,mpeg2video",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "asf",
|
||||||
|
"AudioCodec": "wma",
|
||||||
|
"VideoCodec": "wmv,vc1",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "avi",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mpg,mpeg,flv,3gp,mts,trp,vob,vro",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mov",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "webm",
|
||||||
|
"AudioCodec": "opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp3",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "m4a",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "m4b",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "flac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "webma",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "webm",
|
||||||
|
"AudioCodec": "webma",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wma",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wav",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "ogg",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TranscodingProfiles": [
|
||||||
|
{
|
||||||
|
"Container": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Protocol": "hls",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 1,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": true,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp3",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "mp3",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "opus",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wav",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "wav",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "opus",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp3",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "mp3",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wav",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "wav",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mkv",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"Protocol": "",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": true,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "1920",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "ts",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "h264,hevc",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,opus",
|
||||||
|
"Protocol": "hls",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 1,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "1920",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "webm",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "vp8,vp9,vpx",
|
||||||
|
"AudioCodec": "vorbis,opus",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "2",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "1920",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp4",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "h264",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,dca,dts,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "1920",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CodecProfiles": [
|
||||||
|
{
|
||||||
|
"Type": "Video",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "NotEquals",
|
||||||
|
"Property": "IsAnamorphic",
|
||||||
|
"Value": "true",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "EqualsAny",
|
||||||
|
"Property": "VideoProfile",
|
||||||
|
"Value": "high|main|baseline|constrained baseline|high 10",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "VideoLevel",
|
||||||
|
"Value": "52",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "VideoBitrate",
|
||||||
|
"Value": "20000000",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Codec": "h264",
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": "Video",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "NotEquals",
|
||||||
|
"Property": "IsAnamorphic",
|
||||||
|
"Value": "true",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "EqualsAny",
|
||||||
|
"Property": "VideoProfile",
|
||||||
|
"Value": "main|main 10",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "VideoLevel",
|
||||||
|
"Value": "183",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "VideoBitrate",
|
||||||
|
"Value": "20000000",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Codec": "hevc",
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": "Video",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "VideoBitrate",
|
||||||
|
"Value": "20000000",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ResponseProfiles": [
|
||||||
|
{
|
||||||
|
"Container": "m4v",
|
||||||
|
"Type": "Video",
|
||||||
|
"MimeType": "video/mp4",
|
||||||
|
"$type": "ResponseProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SubtitleProfiles": [
|
||||||
|
{
|
||||||
|
"Format": "vtt",
|
||||||
|
"Method": "External",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "ass",
|
||||||
|
"Method": "External",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "ssa",
|
||||||
|
"Method": "External",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "DeviceProfile"
|
||||||
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
{
|
||||||
|
"Name": "Jellyfin Tizen 4 4K 5.1",
|
||||||
|
"EnableAlbumArtInDidl": false,
|
||||||
|
"EnableSingleAlbumArtLimit": false,
|
||||||
|
"EnableSingleSubtitleLimit": false,
|
||||||
|
"SupportedMediaTypes": "Audio,Photo,Video",
|
||||||
|
"MaxAlbumArtWidth": 0,
|
||||||
|
"MaxAlbumArtHeight": 0,
|
||||||
|
"MaxStreamingBitrate": 120000000,
|
||||||
|
"MaxStaticBitrate": 100000000,
|
||||||
|
"MusicStreamingTranscodingBitrate": 384000,
|
||||||
|
"TimelineOffsetSeconds": 0,
|
||||||
|
"RequiresPlainVideoItems": false,
|
||||||
|
"RequiresPlainFolders": false,
|
||||||
|
"EnableMSMediaReceiverRegistrar": false,
|
||||||
|
"IgnoreTranscodeByteRangeRequests": false,
|
||||||
|
"DirectPlayProfiles": [
|
||||||
|
{
|
||||||
|
"Container": "webm",
|
||||||
|
"AudioCodec": "vorbis,opus",
|
||||||
|
"VideoCodec": "vp8,vp9",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp4,m4v",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mkv",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "m2ts",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,vc1,mpeg2video",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wmv",
|
||||||
|
"AudioCodec": "wma",
|
||||||
|
"VideoCodec": "wmv,vc1",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "ts,mpegts",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,hevc,vc1,mpeg2video",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "asf",
|
||||||
|
"AudioCodec": "wma",
|
||||||
|
"VideoCodec": "wmv,vc1",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "avi",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264,hevc",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mpg,mpeg,flv,3gp,mts,trp,vob,vro",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mov",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"VideoCodec": "h264",
|
||||||
|
"Type": "Video",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "webm",
|
||||||
|
"AudioCodec": "opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp3",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "m4a",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "m4b",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "flac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "webma",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "webm",
|
||||||
|
"AudioCodec": "webma",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wma",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wav",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "ogg",
|
||||||
|
"Type": "Audio",
|
||||||
|
"$type": "DirectPlayProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"TranscodingProfiles": [
|
||||||
|
{
|
||||||
|
"Container": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Protocol": "hls",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 1,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": true,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp3",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "mp3",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "opus",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wav",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "wav",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "opus",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "opus",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp3",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "mp3",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "aac",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "aac",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "wav",
|
||||||
|
"Type": "Audio",
|
||||||
|
"AudioCodec": "wav",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mkv",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "h264,hevc,mpeg2video,vc1,msmpeg4v2,vp8,vp9",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"Protocol": "",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": true,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "3840",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "ts",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "h264,hevc",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,opus",
|
||||||
|
"Protocol": "hls",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 1,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "3840",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "webm",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "vp8,vp9,vpx",
|
||||||
|
"AudioCodec": "vorbis,opus",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Streaming",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MaxAudioChannels": "6",
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "3840",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Container": "mp4",
|
||||||
|
"Type": "Video",
|
||||||
|
"VideoCodec": "h264",
|
||||||
|
"AudioCodec": "aac,mp3,ac3,eac3,mp2,pcm_s16le,pcm_s24le,aac_latm,opus,flac,vorbis",
|
||||||
|
"Protocol": "http",
|
||||||
|
"EstimateContentLength": false,
|
||||||
|
"EnableMpegtsM2TsMode": false,
|
||||||
|
"TranscodeSeekInfo": "Auto",
|
||||||
|
"CopyTimestamps": false,
|
||||||
|
"Context": "Static",
|
||||||
|
"EnableSubtitlesInManifest": false,
|
||||||
|
"MinSegments": 0,
|
||||||
|
"SegmentLength": 0,
|
||||||
|
"BreakOnNonKeyFrames": false,
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "Width",
|
||||||
|
"Value": "3840",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "TranscodingProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"CodecProfiles": [
|
||||||
|
{
|
||||||
|
"Type": "Video",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "NotEquals",
|
||||||
|
"Property": "IsAnamorphic",
|
||||||
|
"Value": "true",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "EqualsAny",
|
||||||
|
"Property": "VideoProfile",
|
||||||
|
"Value": "high|main|baseline|constrained baseline|high 10",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "VideoLevel",
|
||||||
|
"Value": "52",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Codec": "h264",
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Type": "Video",
|
||||||
|
"Conditions": [
|
||||||
|
{
|
||||||
|
"Condition": "NotEquals",
|
||||||
|
"Property": "IsAnamorphic",
|
||||||
|
"Value": "true",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "EqualsAny",
|
||||||
|
"Property": "VideoProfile",
|
||||||
|
"Value": "main|main 10",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Condition": "LessThanEqual",
|
||||||
|
"Property": "VideoLevel",
|
||||||
|
"Value": "183",
|
||||||
|
"IsRequired": false,
|
||||||
|
"$type": "ProfileCondition"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Codec": "hevc",
|
||||||
|
"$type": "CodecProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ResponseProfiles": [
|
||||||
|
{
|
||||||
|
"Container": "m4v",
|
||||||
|
"Type": "Video",
|
||||||
|
"MimeType": "video/mp4",
|
||||||
|
"$type": "ResponseProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"SubtitleProfiles": [
|
||||||
|
{
|
||||||
|
"Format": "vtt",
|
||||||
|
"Method": "External",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "ass",
|
||||||
|
"Method": "External",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Format": "ssa",
|
||||||
|
"Method": "External",
|
||||||
|
"$type": "SubtitleProfile"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"$type": "DeviceProfile"
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"Id": "a766d122b58e45d9492d17af77748bf5",
|
||||||
|
"Path": "/Media/MyVideo-720p.mp4",
|
||||||
|
"Container": "mov,mp4,m4a,3gp,3g2,mj2",
|
||||||
|
"Size": 835317696,
|
||||||
|
"Name": "MyVideo-720p",
|
||||||
|
"ETag": "579a34c6d5dfb21d81539a51220b6a23",
|
||||||
|
"RunTimeTicks": 25801230336,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "h264",
|
||||||
|
"CodecTag": "avc1",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/11988",
|
||||||
|
"VideoRange": "SDR",
|
||||||
|
"DisplayTitle": "720p H264 SDR",
|
||||||
|
"NalLengthSize": "0",
|
||||||
|
"BitRate": 2032876,
|
||||||
|
"BitDepth": 8,
|
||||||
|
"RefFrames": 1,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Height": 720,
|
||||||
|
"Width": 1280,
|
||||||
|
"AverageFrameRate": 23.976,
|
||||||
|
"RealFrameRate": 23.976,
|
||||||
|
"Profile": "High",
|
||||||
|
"Type": 1,
|
||||||
|
"AspectRatio": "16:9",
|
||||||
|
"PixelFormat": "yuv420p",
|
||||||
|
"Level": 41
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "dts",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/48000",
|
||||||
|
"DisplayTitle": "En - DTS - 5.1 - Default",
|
||||||
|
"ChannelLayout": "5.1",
|
||||||
|
"BitRate": 384000,
|
||||||
|
"Channels": 6,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Index": 1,
|
||||||
|
"Score": 202
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "srt",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/1000000",
|
||||||
|
"localizedUndefined": "Undefined",
|
||||||
|
"localizedDefault": "Default",
|
||||||
|
"localizedForced": "Forced",
|
||||||
|
"DisplayTitle": "En - Default",
|
||||||
|
"BitRate": 92,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Type": 2,
|
||||||
|
"Index": 2,
|
||||||
|
"Score": 6421,
|
||||||
|
"IsExternal": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Bitrate": 2590008,
|
||||||
|
"DefaultAudioStreamIndex": 1,
|
||||||
|
"DefaultSubtitleStreamIndex": 2
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"Id": "f6eab7118618ab26e61e495a1853481a",
|
||||||
|
"Path": "/Media/MyVideo-WEBDL-2160p.mp4",
|
||||||
|
"Container": "mov,mp4,m4a,3gp,3g2,mj2",
|
||||||
|
"Size": 6521110016,
|
||||||
|
"Name": "MyVideo WEBDL-2160p",
|
||||||
|
"ETag": "a2fb84b618ba2467fe377543f879e9bf",
|
||||||
|
"RunTimeTicks": 34318510080,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"MediaStreams": [
|
||||||
|
{
|
||||||
|
"Codec": "hevc",
|
||||||
|
"CodecTag": "hev1",
|
||||||
|
"Language": "eng",
|
||||||
|
"ColorSpace": "bt2020nc",
|
||||||
|
"ColorTransfer": "smpte2084",
|
||||||
|
"ColorPrimaries": "bt2020",
|
||||||
|
"TimeBase": "1/16000",
|
||||||
|
"VideoRange": "HDR",
|
||||||
|
"DisplayTitle": "4K HEVC HDR",
|
||||||
|
"BitRate": 14715079,
|
||||||
|
"BitDepth": 8,
|
||||||
|
"RefFrames": 1,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Height": 2160,
|
||||||
|
"Width": 3840,
|
||||||
|
"AverageFrameRate": 23.976,
|
||||||
|
"RealFrameRate": 23.976,
|
||||||
|
"Profile": "Main 10",
|
||||||
|
"Type": 1,
|
||||||
|
"AspectRatio": "16:9",
|
||||||
|
"PixelFormat": "yuv420p10le",
|
||||||
|
"Level": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "truehd",
|
||||||
|
"CodecTag": "AC-3",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/48000",
|
||||||
|
"DisplayTitle": "TRUEHD - 7.1",
|
||||||
|
"ChannelLayout": "7.1",
|
||||||
|
"BitRate": 384000,
|
||||||
|
"Channels": 8,
|
||||||
|
"SampleRate": 48000,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Index": 1,
|
||||||
|
"Score": 203
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Codec": "srt",
|
||||||
|
"Language": "eng",
|
||||||
|
"TimeBase": "1/1000000",
|
||||||
|
"localizedUndefined": "Undefined",
|
||||||
|
"localizedDefault": "Default",
|
||||||
|
"localizedForced": "Forced",
|
||||||
|
"DisplayTitle": "En - Default",
|
||||||
|
"BitRate": 92,
|
||||||
|
"IsDefault": true,
|
||||||
|
"Type": 2,
|
||||||
|
"Index": 2,
|
||||||
|
"Score": 6421,
|
||||||
|
"IsExternal": true,
|
||||||
|
"IsTextSubtitleStream": true,
|
||||||
|
"SupportsExternalStream": true,
|
||||||
|
"Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Bitrate": 15201382,
|
||||||
|
"DefaultAudioStreamIndex": 1,
|
||||||
|
"DefaultSubtitleStreamIndex": 2
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"Id": "f6eab7118618ab26e61e495a1853481a",
|
||||||
|
"Path": "/Media/MyVideo-WEBDL-2160p.mp4",
|
||||||
|
"Container": "mov,mp4,m4a,3gp,3g2,mj2",
|
||||||
|
"Size": 6521110016,
|
||||||
|
"Name": "MyVideo WEBDL-2160p",
|
||||||
|
"ETag": "a2fb84b618ba2467fe377543f879e9bf",
|
||||||
|
"RunTimeTicks": 34318510080,
|
||||||
|
"SupportsTranscoding": true,
|
||||||
|
"SupportsDirectStream": true,
|
||||||
|
"SupportsDirectPlay": true,
|
||||||
|
"SupportsProbing": true,
|
||||||
|
"MediaStreams": [],
|
||||||
|
"Bitrate": 15201382,
|
||||||
|
"DefaultAudioStreamIndex": null,
|
||||||
|
"DefaultSubtitleStreamIndex": null
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.TV;
|
using Emby.Naming.TV;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ namespace Jellyfin.Naming.Tests.TV
|
|||||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
[InlineData("Season 21/One Piece 1001", 1001)]
|
||||||
[InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", 3)]
|
[InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", 3)]
|
||||||
[InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 22)]
|
[InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 22)]
|
||||||
[InlineData("Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", 1)]
|
[InlineData("Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", 1)]
|
||||||
|
|||||||
@@ -85,6 +85,17 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
|
|||||||
EpisodeTitle = "The VCR Illumination"
|
EpisodeTitle = "The VCR Illumination"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
data.Add(
|
||||||
|
"Lorem ipsum dolor sit amet: consect 2018_12_06_21_06_00",
|
||||||
|
new TimerInfo
|
||||||
|
{
|
||||||
|
Name = "Lorem ipsum dolor sit amet: consect",
|
||||||
|
IsProgramSeries = true,
|
||||||
|
StartDate = new DateTime(2018, 12, 6, 21, 6, 0, DateTimeKind.Local),
|
||||||
|
OriginalAirDate = new DateTime(2018, 12, 6),
|
||||||
|
EpisodeTitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor"
|
||||||
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsEnabled_QuickConnectUnavailable_False()
|
public void IsEnabled_QuickConnectUnavailable_False()
|
||||||
=> Assert.False(_quickConnectManager.IsEnabled);
|
{
|
||||||
|
_config.QuickConnectAvailable = false;
|
||||||
|
Assert.False(_quickConnectManager.IsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("", "DeviceId", "Client", "1.0.0")]
|
[InlineData("", "DeviceId", "Client", "1.0.0")]
|
||||||
@@ -69,19 +72,31 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryConnect_QuickConnectUnavailable_ThrowsAuthenticationException()
|
public void TryConnect_QuickConnectUnavailable_ThrowsAuthenticationException()
|
||||||
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
|
{
|
||||||
|
_config.QuickConnectAvailable = false;
|
||||||
|
Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CheckRequestStatus_QuickConnectUnavailable_ThrowsAuthenticationException()
|
public void CheckRequestStatus_QuickConnectUnavailable_ThrowsAuthenticationException()
|
||||||
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
|
{
|
||||||
|
_config.QuickConnectAvailable = false;
|
||||||
|
Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
|
public void AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
|
||||||
=> Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty));
|
{
|
||||||
|
_config.QuickConnectAvailable = false;
|
||||||
|
Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetAuthorizedRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
|
public void GetAuthorizedRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
|
||||||
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
|
{
|
||||||
|
_config.QuickConnectAvailable = false;
|
||||||
|
Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void IsEnabled_QuickConnectAvailable_True()
|
public void IsEnabled_QuickConnectAvailable_True()
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using Emby.Server.Implementations.Sorting;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
using MediaBrowser.Controller.Sorting;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Tests.Sorting;
|
||||||
|
|
||||||
|
public class IndexNumberComparerTests
|
||||||
|
{
|
||||||
|
private readonly IBaseItemComparer _cmp = new IndexNumberComparer();
|
||||||
|
|
||||||
|
private static TheoryData<BaseItem?, BaseItem?> Compare_GivenNull_ThrowsArgumentNullException_TestData()
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
{ null, new Audio() },
|
||||||
|
{ new Audio(), null }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Compare_GivenNull_ThrowsArgumentNullException_TestData))]
|
||||||
|
public void Compare_GivenNull_ThrowsArgumentNullException(BaseItem? x, BaseItem? y)
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => _cmp.Compare(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(null, null, 0)]
|
||||||
|
[InlineData(0, null, 1)]
|
||||||
|
[InlineData(null, 0, -1)]
|
||||||
|
[InlineData(1, 1, 0)]
|
||||||
|
[InlineData(0, 1, -1)]
|
||||||
|
[InlineData(1, 0, 1)]
|
||||||
|
public void Compare_ValidIndices_SortsExpected(int? index1, int? index2, int expected)
|
||||||
|
{
|
||||||
|
BaseItem x = new Audio
|
||||||
|
{
|
||||||
|
IndexNumber = index1
|
||||||
|
};
|
||||||
|
BaseItem y = new Audio
|
||||||
|
{
|
||||||
|
IndexNumber = index2
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.Equal(expected, _cmp.Compare(x, y));
|
||||||
|
Assert.Equal(-expected, _cmp.Compare(y, x));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Emby.Server.Implementations.Sorting;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
using MediaBrowser.Controller.Sorting;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Tests.Sorting;
|
||||||
|
|
||||||
|
public class ParentIndexNumberComparerTests
|
||||||
|
{
|
||||||
|
private readonly IBaseItemComparer _cmp = new ParentIndexNumberComparer();
|
||||||
|
|
||||||
|
private static TheoryData<BaseItem?, BaseItem?> Compare_GivenNull_ThrowsArgumentNullException_TestData()
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
{ null, new Audio() },
|
||||||
|
{ new Audio(), null }
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Compare_GivenNull_ThrowsArgumentNullException_TestData))]
|
||||||
|
public void Compare_GivenNull_ThrowsArgumentNullException(BaseItem? x, BaseItem? y)
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => _cmp.Compare(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(null, null, 0)]
|
||||||
|
[InlineData(0, null, 1)]
|
||||||
|
[InlineData(null, 0, -1)]
|
||||||
|
[InlineData(1, 1, 0)]
|
||||||
|
[InlineData(0, 1, -1)]
|
||||||
|
[InlineData(1, 0, 1)]
|
||||||
|
public void Compare_ValidIndices_SortsExpected(int? parentIndex1, int? parentIndex2, int expected)
|
||||||
|
{
|
||||||
|
BaseItem x = new Audio
|
||||||
|
{
|
||||||
|
ParentIndexNumber = parentIndex1
|
||||||
|
};
|
||||||
|
BaseItem y = new Audio
|
||||||
|
{
|
||||||
|
ParentIndexNumber = parentIndex2
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.Equal(expected, _cmp.Compare(x, y));
|
||||||
|
Assert.Equal(-expected, _cmp.Compare(y, x));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
|||||||
Reference in New Issue
Block a user