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.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Diacritics.Extensions;
|
||||
using Emby.Dlna.Didl;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ namespace Emby.Naming.Common
|
||||
".mkv",
|
||||
".mk3d",
|
||||
".mov",
|
||||
".mp2",
|
||||
".mp4",
|
||||
".mpe",
|
||||
".mpeg",
|
||||
@@ -315,7 +314,7 @@ namespace Emby.Naming.Common
|
||||
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
||||
// so we make sure this one gets tested first.
|
||||
// "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
|
||||
},
|
||||
|
||||
@@ -398,6 +398,12 @@ namespace Emby.Server.Implementations.AppBase
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ConfigurationStore[] GetConfigurationStores()
|
||||
{
|
||||
return _configurationStores;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Type GetConfigurationType(string key)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Diacritics.Extensions;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
@@ -5763,7 +5762,7 @@ AND Type = @InternalPersonType)");
|
||||
{
|
||||
var itemIdBlob = id.ToByteArray();
|
||||
|
||||
// First delete chapters
|
||||
// Delete existing mediastreams
|
||||
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
|
||||
|
||||
InsertMediaStreams(itemIdBlob, streams, db);
|
||||
@@ -5867,10 +5866,10 @@ AND Type = @InternalPersonType)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chapter.
|
||||
/// Gets the media stream.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader.</param>
|
||||
/// <returns>ChapterInfo.</returns>
|
||||
/// <returns>MediaStream.</returns>
|
||||
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
|
||||
{
|
||||
var item = new MediaStream
|
||||
|
||||
@@ -1094,7 +1094,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
if (item is IHasTrailers hasTrailers)
|
||||
{
|
||||
dto.LocalTrailerCount = hasTrailers.GetTrailerCount();
|
||||
dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<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.Caching.Memory" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.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="prometheus-net.DotNetRuntime" Version="4.2.3" />
|
||||
<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);
|
||||
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 virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||
var originalName = name;
|
||||
while (Directory.Exists(virtualFolderPath))
|
||||
{
|
||||
existingNameCount++;
|
||||
virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
|
||||
name = originalName + existingNameCount;
|
||||
virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||
}
|
||||
|
||||
var mediaPathInfos = options.PathInfos;
|
||||
|
||||
@@ -151,7 +151,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
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(
|
||||
new MetadataRefreshOptions(_directoryService)
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
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);
|
||||
|
||||
_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)
|
||||
@@ -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
|
||||
_ = 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)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using System.Text;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
@@ -48,12 +49,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
|
||||
{
|
||||
var tmpName = name;
|
||||
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)
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
@@ -26,6 +27,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
public class XmlTvListingsProvider : IListingsProvider
|
||||
{
|
||||
private static readonly TimeSpan _maxCacheAge = TimeSpan.FromHours(1);
|
||||
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<XmlTvListingsProvider> _logger;
|
||||
@@ -69,13 +72,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
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);
|
||||
if (File.Exists(cacheFile))
|
||||
if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
|
||||
{
|
||||
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);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
||||
@@ -124,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
string tempFolder = GetTempFolderPath(stream);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
|
||||
@@ -137,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
string tempFolder = GetTempFolderPath(stream);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
_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)
|
||||
{
|
||||
return _fileSystem.GetFiles(directory, true)
|
||||
|
||||
@@ -104,6 +104,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
{
|
||||
_logger.LogError(ex, "Error updating {0}", package.Name);
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating {0}", package.Name);
|
||||
}
|
||||
|
||||
// Update progress
|
||||
lock (progress)
|
||||
|
||||
@@ -699,7 +699,9 @@ namespace Emby.Server.Implementations.Session
|
||||
DeviceName = session.DeviceName,
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
Session = session
|
||||
Session = session,
|
||||
PlaybackPositionTicks = info.PositionTicks,
|
||||
PlaySessionId = info.PlaySessionId
|
||||
};
|
||||
|
||||
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
|
||||
@@ -985,7 +987,8 @@ namespace Emby.Server.Implementations.Session
|
||||
DeviceName = session.DeviceName,
|
||||
ClientName = session.Client,
|
||||
DeviceId = session.DeviceId,
|
||||
Session = session
|
||||
Session = session,
|
||||
PlaySessionId = info.PlaySessionId
|
||||
};
|
||||
|
||||
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
|
||||
|
||||
@@ -34,6 +34,11 @@ namespace Emby.Server.Implementations.Sorting
|
||||
throw new ArgumentNullException(nameof(y));
|
||||
}
|
||||
|
||||
if (!x.IndexNumber.HasValue && !y.IndexNumber.HasValue)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!x.IndexNumber.HasValue)
|
||||
{
|
||||
return -1;
|
||||
|
||||
@@ -34,6 +34,11 @@ namespace Emby.Server.Implementations.Sorting
|
||||
throw new ArgumentNullException(nameof(y));
|
||||
}
|
||||
|
||||
if (!x.ParentIndexNumber.HasValue && !y.ParentIndexNumber.HasValue)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!x.ParentIndexNumber.HasValue)
|
||||
{
|
||||
return -1;
|
||||
|
||||
@@ -86,21 +86,23 @@ namespace Jellyfin.Api.Controllers
|
||||
/// Updates named configuration.
|
||||
/// </summary>
|
||||
/// <param name="key">Configuration key.</param>
|
||||
/// <param name="configuration">Configuration.</param>
|
||||
/// <response code="204">Named configuration updated.</response>
|
||||
/// <returns>Update status.</returns>
|
||||
[HttpPost("Configuration/{key}")]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[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 configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);
|
||||
if (configuration == null)
|
||||
var deserializedConfiguration = configuration.Deserialize(configurationType, _serializerOptions);
|
||||
|
||||
if (deserializedConfiguration == null)
|
||||
{
|
||||
throw new ArgumentException("Body doesn't contain a valid configuration");
|
||||
}
|
||||
|
||||
_configurationManager.SaveConfiguration(key, configuration);
|
||||
_configurationManager.SaveConfiguration(key, deserializedConfiguration);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
@@ -1773,13 +1773,23 @@ namespace Jellyfin.Api.Controllers
|
||||
|
||||
var args = "-codec:v:0 " + codec;
|
||||
|
||||
// Prefer hvc1 to hev1.
|
||||
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h265", 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)
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace Jellyfin.Api.Controllers
|
||||
/// <response code="204">Package repositories saved.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||
[HttpPost("Repositories")]
|
||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult SetRepositories([FromBody, Required] List<RepositoryInfo> repositoryInfos)
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<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>
|
||||
public bool HasPermission(PermissionKind kind)
|
||||
{
|
||||
return Permissions.First(p => p.Kind == kind).Value;
|
||||
return Permissions.FirstOrDefault(p => p.Kind == kind)?.Value ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -372,7 +372,15 @@ namespace Jellyfin.Data.Entities
|
||||
/// <param name="value">The value to set.</param>
|
||||
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>
|
||||
@@ -382,9 +390,9 @@ namespace Jellyfin.Data.Entities
|
||||
/// <returns>A string array containing the user's preferences.</returns>
|
||||
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>
|
||||
@@ -395,7 +403,7 @@ namespace Jellyfin.Data.Entities
|
||||
/// <returns>A {T} array containing the user's preference.</returns>
|
||||
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))
|
||||
{
|
||||
return Array.Empty<T>();
|
||||
@@ -432,8 +440,16 @@ namespace Jellyfin.Data.Entities
|
||||
/// <param name="values">The values.</param>
|
||||
public void SetPreference(PreferenceKind preference, string[] values)
|
||||
{
|
||||
Preferences.First(p => p.Kind == preference).Value
|
||||
= string.Join(Delimiter, values);
|
||||
var value = 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>
|
||||
@@ -444,8 +460,16 @@ namespace Jellyfin.Data.Entities
|
||||
/// <typeparam name="T">The type of value.</typeparam>
|
||||
public void SetPreference<T>(PreferenceKind preference, T[] values)
|
||||
{
|
||||
Preferences.First(p => p.Kind == preference).Value
|
||||
= string.Join(Delimiter, values);
|
||||
var value = 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>
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using BlurHashSharp.SkiaSharp;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
|
||||
@@ -463,6 +463,18 @@ namespace Jellyfin.Networking.Manager
|
||||
|
||||
/// <inheritdoc/>
|
||||
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)
|
||||
{
|
||||
@@ -481,36 +493,7 @@ namespace Jellyfin.Networking.Manager
|
||||
}
|
||||
|
||||
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
|
||||
return address.IsLoopback() || (_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);
|
||||
return IPAddress.IsLoopback(address) || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.ApiClient;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -14,6 +15,17 @@ namespace Jellyfin.Server.Filters
|
||||
/// </summary>
|
||||
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 />
|
||||
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(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="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
||||
<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.EntityFrameworkCore" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.4" />
|
||||
<PackageReference Include="prometheus-net" Version="6.0.0" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.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.Server.Implementations;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
@@ -20,6 +21,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
private readonly ILogger<MigrateAuthenticationDb> _logger;
|
||||
private readonly JellyfinDbProvider _dbProvider;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
/// <summary>
|
||||
/// 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="dbProvider">The database provider.</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;
|
||||
_dbProvider = dbProvider;
|
||||
_appPaths = appPaths;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -74,6 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
}
|
||||
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(
|
||||
new Guid(row[6].ToString()),
|
||||
row[3].ToString(),
|
||||
|
||||
@@ -60,6 +60,12 @@ namespace MediaBrowser.Common.Configuration
|
||||
/// <returns>System.Object.</returns>
|
||||
object GetConfiguration(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the array of coniguration stores.
|
||||
/// </summary>
|
||||
/// <returns>Array of ConfigurationStore.</returns>
|
||||
ConfigurationStore[] GetConfigurationStores();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the configuration.
|
||||
/// </summary>
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace MediaBrowser.Common.Net
|
||||
address = address.MapToIPv4();
|
||||
}
|
||||
|
||||
if (IsLoopback(address))
|
||||
if (IPAddress.IsLoopback(address))
|
||||
{
|
||||
return (address, prefixLength);
|
||||
}
|
||||
@@ -102,31 +102,6 @@ namespace MediaBrowser.Common.Net
|
||||
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>
|
||||
/// Tests to see if the ip address is an IP6 address.
|
||||
/// </summary>
|
||||
@@ -295,7 +270,7 @@ namespace MediaBrowser.Common.Net
|
||||
/// <returns>True if it is.</returns>
|
||||
public virtual bool IsLoopback()
|
||||
{
|
||||
return IsLoopback(Address);
|
||||
return IPAddress.IsLoopback(Address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,9 +8,9 @@ using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities.Audio
|
||||
|
||||
@@ -11,7 +11,6 @@ using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace MediaBrowser.Controller.Library
|
||||
{
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Diacritics" Version="3.3.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" 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" />
|
||||
|
||||
@@ -1062,10 +1062,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
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.
|
||||
if (requestLevel >= 41)
|
||||
// Transcode to level 5.1 and lower for maximum compatibility.
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return state.IsInputVideo ? "-sn" : string.Empty;
|
||||
@@ -2229,10 +2231,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
if (state.VideoStream != null)
|
||||
{
|
||||
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
|
||||
|
||||
args += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"-map 0:{0}",
|
||||
state.VideoStream.Index);
|
||||
videoStreamIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2242,23 +2246,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
if (state.AudioStream != null)
|
||||
{
|
||||
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
|
||||
if (state.AudioStream.IsExternal)
|
||||
{
|
||||
int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1;
|
||||
int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
|
||||
bool hasExternalGraphicsSubs = state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream;
|
||||
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
|
||||
|
||||
args += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" -map {0}:{1}",
|
||||
externalAudioMapIndex,
|
||||
externalAudioStream);
|
||||
audioStreamIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
args += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" -map 0:{0}",
|
||||
state.AudioStream.Index);
|
||||
audioStreamIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -2273,14 +2278,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
|
||||
{
|
||||
int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
|
||||
|
||||
args += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" -map 0:{0}",
|
||||
state.SubtitleStream.Index);
|
||||
subtitleStreamIndex);
|
||||
}
|
||||
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;
|
||||
@@ -2509,7 +2521,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
return string.Format(
|
||||
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,
|
||||
maxHeightParam,
|
||||
scaleVal);
|
||||
@@ -2553,7 +2565,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
return string.Format(
|
||||
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,
|
||||
scaleVal);
|
||||
}
|
||||
@@ -2565,7 +2577,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
return string.Format(
|
||||
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,
|
||||
scaleVal);
|
||||
}
|
||||
@@ -2614,7 +2626,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
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)
|
||||
{
|
||||
// [0:s]scale=s=1280x720
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
// [0:s]scale=expr
|
||||
var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
||||
}
|
||||
@@ -2955,7 +2967,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
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);
|
||||
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
||||
}
|
||||
@@ -3153,7 +3167,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
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);
|
||||
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
||||
}
|
||||
@@ -3381,7 +3397,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
|
||||
// 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 overlaySize = (overlayW.HasValue && overlayH.HasValue)
|
||||
@@ -3398,7 +3415,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
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);
|
||||
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
|
||||
}
|
||||
@@ -3589,7 +3608,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
|
||||
// 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 overlaySize = (overlayW.HasValue && overlayH.HasValue)
|
||||
@@ -3606,7 +3626,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
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);
|
||||
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
||||
}
|
||||
@@ -3853,7 +3875,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
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);
|
||||
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
||||
|
||||
@@ -4028,7 +4052,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
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);
|
||||
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
|
||||
|
||||
@@ -4124,9 +4150,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
string.Join(',', overlayFilters));
|
||||
|
||||
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
|
||||
var subtitleStreamIndex = state.SubtitleStream.IsExternal
|
||||
? 0
|
||||
: state.SubtitleStream.Index;
|
||||
var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
|
||||
var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
|
||||
|
||||
if (hasSubs)
|
||||
{
|
||||
@@ -4147,7 +4172,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
filterStr,
|
||||
mapPrefix,
|
||||
subtitleStreamIndex,
|
||||
state.VideoStream.Index,
|
||||
videoStreamIndex,
|
||||
mainStr,
|
||||
subStr,
|
||||
overlayStr);
|
||||
@@ -5357,12 +5382,22 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
// opus will fail on 44100
|
||||
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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -141,6 +141,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
/// <returns>System.String.</returns>
|
||||
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>
|
||||
/// Gets the time parameter.
|
||||
/// </summary>
|
||||
|
||||
@@ -5,6 +5,11 @@ namespace MediaBrowser.Controller.Resolvers
|
||||
/// </summary>
|
||||
public enum ResolverPriority
|
||||
{
|
||||
/// <summary>
|
||||
/// The highest priority. Used by plugins to bypass the default server resolvers.
|
||||
/// </summary>
|
||||
Plugin = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The first.
|
||||
/// </summary>
|
||||
|
||||
@@ -411,6 +411,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
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>
|
||||
/// Gets the media info internal.
|
||||
/// </summary>
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
MediaStream subtitleStream,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!subtitleStream.IsExternal)
|
||||
if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string outputFormat;
|
||||
string outputCodec;
|
||||
@@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
// Extract
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
|
||||
|
||||
await ExtractTextSubtitle(mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
|
||||
await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
|
||||
@@ -494,7 +494,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
/// Extracts the text subtitle.
|
||||
/// </summary>
|
||||
/// <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="outputPath">The output path.</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>
|
||||
private async Task ExtractTextSubtitle(
|
||||
MediaSourceInfo mediaSource,
|
||||
int subtitleStreamIndex,
|
||||
MediaStream subtitleStream,
|
||||
string outputCodec,
|
||||
string outputPath,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -511,12 +511,21 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(outputPath))
|
||||
{
|
||||
var args = _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource);
|
||||
|
||||
if (subtitleStream.IsExternal)
|
||||
{
|
||||
args = _mediaEncoder.GetExternalSubtitleInputArgument(subtitleStream.Path);
|
||||
}
|
||||
|
||||
await ExtractTextSubtitleInternal(
|
||||
_mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource),
|
||||
args,
|
||||
subtitleStreamIndex,
|
||||
outputCodec,
|
||||
outputPath,
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Configuration
|
||||
RequirePerfectSubtitleMatch = true;
|
||||
AllowEmbeddedSubtitles = EmbeddedSubtitleOptions.AllowAll;
|
||||
|
||||
AutomaticallyAddToCollection = true;
|
||||
AutomaticallyAddToCollection = false;
|
||||
EnablePhotos = true;
|
||||
SaveSubtitlesWithMedia = true;
|
||||
EnableRealtimeMonitor = true;
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace MediaBrowser.Model.Configuration
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether quick connect is available for use on this server.
|
||||
/// </summary>
|
||||
public bool QuickConnectAvailable { get; set; } = false;
|
||||
public bool QuickConnectAvailable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 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 (item.SupportsDirectPlay)
|
||||
{
|
||||
if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
|
||||
if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
|
||||
{
|
||||
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
|
||||
if (item.SupportsDirectStream)
|
||||
{
|
||||
if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
|
||||
if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
|
||||
{
|
||||
if (options.EnableDirectStream)
|
||||
{
|
||||
@@ -604,11 +604,11 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
var videoStream = item.VideoStream;
|
||||
|
||||
var directPlayEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
|
||||
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
|
||||
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == 0);
|
||||
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == 0);
|
||||
var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult;
|
||||
var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
|
||||
var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
|
||||
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0);
|
||||
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0);
|
||||
var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility;
|
||||
|
||||
_logger.LogDebug(
|
||||
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
|
||||
@@ -625,7 +625,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
var directPlay = directPlayInfo.PlayMethod;
|
||||
transcodeReasons |= directPlayInfo.TranscodeReasons;
|
||||
|
||||
if (directPlay != null)
|
||||
if (directPlay.HasValue)
|
||||
{
|
||||
directPlayProfile = directPlayInfo.Profile;
|
||||
playlistItem.PlayMethod = directPlay.Value;
|
||||
@@ -676,7 +676,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
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
|
||||
// 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);
|
||||
|
||||
playlistItem.PlayMethod = PlayMethod.Transcode;
|
||||
|
||||
if (subtitleStream != null)
|
||||
{
|
||||
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 };
|
||||
}
|
||||
|
||||
if (playlistItem.PlayMethod != PlayMethod.DirectPlay)
|
||||
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
|
||||
{
|
||||
playlistItem.PlayMethod = PlayMethod.Transcode;
|
||||
|
||||
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
|
||||
{
|
||||
ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
|
||||
}
|
||||
ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -744,16 +741,19 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
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 container = transcodingProfile.Container;
|
||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||
.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 =>
|
||||
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)));
|
||||
var conditionsSatisfied = !appliedVideoConditions.Any() || !appliedVideoConditions.Any(satisfied => !satisfied);
|
||||
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)));
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
// prefer matching video codecs
|
||||
// Prefer matching video codecs
|
||||
var videoCodecs = ContainerProfile.SplitValue(videoCodec);
|
||||
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream.Codec) ? videoStream.Codec : null;
|
||||
playlistItem.VideoCodecs = directVideoCodec != null ? new[] { directVideoCodec } : videoCodecs;
|
||||
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
|
||||
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.MaxFramerate = videoStream.AverageFrameRate;
|
||||
var qualifier = videoStream.Codec;
|
||||
if (videoStream.Level.HasValue)
|
||||
playlistItem.VideoCodecs = videoCodecs;
|
||||
|
||||
// Copy video codec options as a starting point, this applies to transcode and direct-stream
|
||||
playlistItem.MaxFramerate = videoStream?.AverageFrameRate;
|
||||
var qualifier = videoStream?.Codec;
|
||||
if (videoStream?.Level != null)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(videoStream.Profile))
|
||||
if (!string.IsNullOrEmpty(videoStream?.Profile))
|
||||
{
|
||||
playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (videoStream.Level != 0)
|
||||
if (videoStream != null && videoStream.Level != 0)
|
||||
{
|
||||
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 directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
|
||||
playlistItem.AudioCodecs = audioCodecs;
|
||||
@@ -806,7 +813,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
playlistItem.AudioStreamIndex = audioStream.Index;
|
||||
playlistItem.AudioCodecs = new[] { audioStream.Codec };
|
||||
|
||||
// copy matching audio codec options
|
||||
// Copy matching audio codec options
|
||||
playlistItem.AudioSampleRate = audioStream.SampleRate;
|
||||
playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString());
|
||||
|
||||
@@ -843,7 +850,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
var appliedVideoConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Video &&
|
||||
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;
|
||||
foreach (var i in appliedVideoConditions)
|
||||
{
|
||||
@@ -873,9 +880,9 @@ namespace MediaBrowser.Model.Dlna
|
||||
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
|
||||
|
||||
var appliedAudioConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Video &&
|
||||
.Where(i => i.Type == CodecType.VideoAudio &&
|
||||
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;
|
||||
foreach (var i in appliedAudioConditions)
|
||||
{
|
||||
@@ -1067,7 +1074,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
DeviceProfile profile = options.Profile;
|
||||
string container = mediaSource.Container;
|
||||
|
||||
// video
|
||||
// Video
|
||||
int? width = videoStream?.Width;
|
||||
int? height = videoStream?.Height;
|
||||
int? bitDepth = videoStream?.BitDepth;
|
||||
@@ -1079,7 +1086,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
bool? isInterlaced = videoStream?.IsInterlaced;
|
||||
string videoCodecTag = videoStream?.CodecTag;
|
||||
bool? isAvc = videoStream?.IsAVC;
|
||||
// audio
|
||||
// Audio
|
||||
var defaultLanguage = audioStream?.Language ?? string.Empty;
|
||||
var defaultMarked = audioStream?.IsDefault ?? false;
|
||||
|
||||
@@ -1108,18 +1115,9 @@ namespace MediaBrowser.Model.Dlna
|
||||
profile,
|
||||
"VideoCodecProfile",
|
||||
profile.CodecProfiles
|
||||
.Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container))
|
||||
.SelectMany(codecProfile =>
|
||||
{
|
||||
var failedApplyConditions = checkVideoConditions(codecProfile.ApplyConditions);
|
||||
if (!failedApplyConditions.Any())
|
||||
{
|
||||
return Array.Empty<ProfileCondition>();
|
||||
}
|
||||
|
||||
var failedConditions = checkVideoConditions(codecProfile.Conditions);
|
||||
return failedApplyConditions.Concat(failedConditions);
|
||||
}));
|
||||
.Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container) &&
|
||||
!checkVideoConditions(codecProfile.ApplyConditions).Any())
|
||||
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
|
||||
|
||||
// Check audiocandidates profile conditions
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
})
|
||||
.OrderByDescending(analysis => analysis.Result.PlayMethod)
|
||||
.ThenByDescending(analysis => analysis.Rank)
|
||||
.ThenBy(analysis => analysis.Order)
|
||||
.ToArray()
|
||||
.ToLookup(analysis => analysis.Result.PlayMethod != null);
|
||||
@@ -1218,7 +1228,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
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)
|
||||
{
|
||||
failureReasons = TranscodeReason.DirectPlayError;
|
||||
@@ -1264,13 +1274,13 @@ namespace MediaBrowser.Model.Dlna
|
||||
mediaSource.Path ?? "Unknown path");
|
||||
}
|
||||
|
||||
private TranscodeReason IsEligibleForDirectPlay(
|
||||
private TranscodeReason IsBitrateEligibleForDirectPlayback(
|
||||
MediaSourceInfo item,
|
||||
long maxBitrate,
|
||||
VideoOptions options,
|
||||
PlayMethod playMethod)
|
||||
{
|
||||
bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod);
|
||||
bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod);
|
||||
if (!result)
|
||||
{
|
||||
return TranscodeReason.ContainerBitrateExceedsLimit;
|
||||
@@ -1438,7 +1448,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
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
|
||||
if (item.IsRemote)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Model.Entities
|
||||
{
|
||||
@@ -9,6 +10,16 @@ namespace MediaBrowser.Model.Entities
|
||||
/// </summary>
|
||||
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>
|
||||
/// Checks if this instance has an id for the given provider.
|
||||
/// </summary>
|
||||
@@ -108,7 +119,7 @@ namespace MediaBrowser.Model.Entities
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="name">The name.</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)
|
||||
{
|
||||
@@ -125,7 +136,15 @@ namespace MediaBrowser.Model.Entities
|
||||
// Ensure it exists
|
||||
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>
|
||||
</PackageReference>
|
||||
<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>
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
|
||||
@@ -30,6 +30,7 @@ using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Priority_Queue;
|
||||
@@ -188,6 +189,12 @@ namespace MediaBrowser.Providers.Manager
|
||||
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 SaveImage(
|
||||
item,
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
|
||||
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
|
||||
<PackageReference Include="TMDbLib" Version="1.9.1" />
|
||||
<PackageReference Include="TMDbLib" Version="1.9.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -173,16 +173,30 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
IReadOnlyList<MediaAttachment> mediaAttachments;
|
||||
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)
|
||||
{
|
||||
mediaStreams = mediaInfo.MediaStreams.ToList();
|
||||
foreach (var mediaStream in mediaInfo.MediaStreams)
|
||||
{
|
||||
mediaStream.Index = startIndex++;
|
||||
mediaStreams.Add(mediaStream);
|
||||
}
|
||||
|
||||
mediaAttachments = mediaInfo.MediaAttachments;
|
||||
|
||||
video.TotalBitrate = mediaInfo.Bitrate;
|
||||
// video.FormatName = (mediaInfo.Container ?? string.Empty)
|
||||
// .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;
|
||||
|
||||
if (needToSetRuntime)
|
||||
@@ -213,15 +227,20 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
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>();
|
||||
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);
|
||||
|
||||
if (mediaInfo != null)
|
||||
@@ -254,7 +273,11 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
|
||||
|
||||
_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 ||
|
||||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -153,6 +152,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
|
||||
var files = directoryService.GetFilePaths(folder, clearCache).ToList();
|
||||
files.Remove(video.Path);
|
||||
var internalMetadataPath = video.GetInternalMetadataPath();
|
||||
if (_fileSystem.DirectoryExists(internalMetadataPath))
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Diacritics.Extensions;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using MediaBrowser.Model.Plugins;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.StudioImages
|
||||
namespace MediaBrowser.Providers.Plugins.StudioImages.Configuration
|
||||
{
|
||||
public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
@@ -12,12 +12,19 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_repository))
|
||||
{
|
||||
_repository = Plugin.DefaultServer;
|
||||
}
|
||||
|
||||
return _repository;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_repository = value.TrimEnd('/');
|
||||
_repository = string.IsNullOrEmpty(value)
|
||||
? Plugin.DefaultServer
|
||||
: value.TrimEnd('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<div class="content-primary">
|
||||
<form class="configForm">
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="repository" required label="Repository" />
|
||||
<div class="fieldDescription">This can be any Jellyfin-compatible artwork repository.</div>
|
||||
<input is="emby-input" type="text" id="repository" label="Repository" />
|
||||
<div class="fieldDescription">This can be any Jellyfin-compatible artwork repository. Leave blank to use default repository.</div>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
@@ -44,7 +44,7 @@
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.Plugins;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Providers.Plugins.StudioImages.Configuration;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.StudioImages
|
||||
{
|
||||
|
||||
@@ -18,29 +18,24 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Providers.Plugins.StudioImages;
|
||||
|
||||
namespace MediaBrowser.Providers.Studios
|
||||
namespace MediaBrowser.Providers.Plugins.StudioImages
|
||||
{
|
||||
public class StudiosImageProvider : IRemoteImageProvider
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly string repositoryUrl;
|
||||
|
||||
public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
|
||||
{
|
||||
_config = config;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_fileSystem = fileSystem;
|
||||
repositoryUrl = Plugin.Instance.Configuration.RepositoryUrl;
|
||||
}
|
||||
|
||||
public string Name => "Artwork Repository";
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public bool Supports(BaseItem item)
|
||||
{
|
||||
return item is Studio;
|
||||
@@ -98,12 +93,12 @@ namespace MediaBrowser.Providers.Studios
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase)
|
||||
&& (!video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
|
||||
|| !video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
|
||||
&& (video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
|
||||
|| video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
||||
{
|
||||
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
|
||||
|
||||
@@ -473,7 +473,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
||||
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);
|
||||
|
||||
@@ -601,16 +601,16 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
||||
{
|
||||
writer.WriteElementString(
|
||||
"formed",
|
||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
||||
item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteElementString(
|
||||
"premiered",
|
||||
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
|
||||
item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
|
||||
writer.WriteElementString(
|
||||
"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(
|
||||
"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(
|
||||
"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");
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
||||
{
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -22,7 +22,7 @@ Depends: at,
|
||||
libsqlite3-0,
|
||||
libfontconfig1,
|
||||
libfreetype6,
|
||||
libssl1.1
|
||||
libssl1.1 | libssl3
|
||||
Recommends: jellyfin-web, sudo
|
||||
Description: Jellyfin is the Free Software Media System.
|
||||
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
|
||||
|
||||
Package: jellyfin
|
||||
Version: 10.8.0
|
||||
Version: 10.8.0~beta3
|
||||
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
Depends: jellyfin-server, jellyfin-web
|
||||
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
|
||||
|
||||
# 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 \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& 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
|
||||
|
||||
# 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 \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& 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
|
||||
|
||||
# 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 \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
|
||||
mmv build-essential lsb-release
|
||||
|
||||
# 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 \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
|
||||
mmv build-essential lsb-release
|
||||
|
||||
# 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 \
|
||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
%endif
|
||||
|
||||
Name: jellyfin
|
||||
Version: 10.8.0
|
||||
Version: 10.8.0~beta3
|
||||
Release: 1%{?dist}
|
||||
Summary: The Free Software Media System
|
||||
License: GPLv3
|
||||
@@ -153,6 +153,12 @@ fi
|
||||
%systemd_postun_with_restart jellyfin.service
|
||||
|
||||
%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>
|
||||
- Add jellyfin-server-lowports.service drop-in in a server-lowports
|
||||
subpackage to allow binding to low ports
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Jellyfin.Extensions
|
||||
{
|
||||
@@ -7,6 +11,44 @@ namespace Jellyfin.Extensions
|
||||
/// </summary>
|
||||
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>
|
||||
/// Counts the number of occurrences of [needle] in the string.
|
||||
/// </summary>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" 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.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
|
||||
@@ -5,6 +5,38 @@ namespace Jellyfin.Extensions.Tests
|
||||
{
|
||||
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]
|
||||
[InlineData("", '_', 0)]
|
||||
[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-hevc-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-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||
// Firefox
|
||||
@@ -38,7 +38,7 @@ namespace Jellyfin.Model.Tests
|
||||
[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-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-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||
// 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-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", "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-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||
// TranscodeMedia
|
||||
@@ -131,6 +131,37 @@ namespace Jellyfin.Model.Tests
|
||||
[InlineData("Null", "mkv-vp9-aac-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)]
|
||||
// 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 = "")
|
||||
{
|
||||
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-hevc-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-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||
// Firefox
|
||||
@@ -156,7 +187,7 @@ namespace Jellyfin.Model.Tests
|
||||
[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-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-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
|
||||
// Safari
|
||||
@@ -198,6 +229,37 @@ namespace Jellyfin.Model.Tests
|
||||
[InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-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
|
||||
// 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 = "")
|
||||
{
|
||||
var options = await GetVideoOptions(deviceName, mediaSource);
|
||||
@@ -223,12 +285,26 @@ namespace Jellyfin.Model.Tests
|
||||
// RokuSSPlus
|
||||
[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
|
||||
// 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 = "")
|
||||
{
|
||||
var options = await GetVideoOptions(deviceName, mediaSource);
|
||||
var streamCount = options.MediaSources[0].MediaStreams.Count;
|
||||
options.AudioStreamIndex = streamCount - 2;
|
||||
options.SubtitleStreamIndex = streamCount - 1;
|
||||
if (streamCount > 0)
|
||||
{
|
||||
options.AudioStreamIndex = streamCount - 2;
|
||||
options.SubtitleStreamIndex = streamCount - 1;
|
||||
}
|
||||
|
||||
var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
|
||||
Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
|
||||
@@ -262,23 +338,23 @@ namespace Jellyfin.Model.Tests
|
||||
Assert.NotNull(mediaSource);
|
||||
var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
|
||||
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 uri = ParseUri(val);
|
||||
|
||||
if (playMethod == PlayMethod.DirectPlay)
|
||||
{
|
||||
// check expected container
|
||||
// Check expected container
|
||||
var containers = ContainerProfile.SplitValue(mediaSource.Container);
|
||||
// TODO: test transcode too
|
||||
// TODO: Test transcode too
|
||||
// Assert.Contains(uri.Extension, containers);
|
||||
|
||||
// check expected video codec (1)
|
||||
// Check expected video codec (1)
|
||||
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
|
||||
Assert.Single(val.TargetVideoCodec);
|
||||
|
||||
// check expected audio codecs (1)
|
||||
// Check expected audio codecs (1)
|
||||
Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
|
||||
Assert.Single(val.TargetAudioCodec);
|
||||
// Assert.Single(val.AudioCodecs);
|
||||
@@ -294,7 +370,7 @@ namespace Jellyfin.Model.Tests
|
||||
Assert.NotEmpty(val.VideoCodecs);
|
||||
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")
|
||||
{
|
||||
// Assert.Equal("webm", val.Container);
|
||||
@@ -327,32 +403,39 @@ namespace Jellyfin.Model.Tests
|
||||
stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
|
||||
}
|
||||
|
||||
// todo: fill out tests here
|
||||
// TODO: Fill out tests here
|
||||
}
|
||||
|
||||
// DirectStream and Remux
|
||||
else
|
||||
{
|
||||
// check expected video codec (1)
|
||||
// Check expected video codec (1)
|
||||
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
|
||||
Assert.Single(val.TargetVideoCodec);
|
||||
|
||||
if (transcodeMode == "DirectStream")
|
||||
{
|
||||
// Check expected audio codecs (1)
|
||||
if (!targetAudioStream.IsExternal)
|
||||
{
|
||||
// check expected audio codecs (1)
|
||||
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
|
||||
if (val.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
|
||||
{
|
||||
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (transcodeMode == "Remux")
|
||||
{
|
||||
// check expected audio codecs (1)
|
||||
// Check expected audio codecs (1)
|
||||
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
|
||||
Assert.Single(val.AudioCodecs);
|
||||
}
|
||||
|
||||
// video details
|
||||
// Video details
|
||||
var videoStream = targetVideoStream;
|
||||
Assert.False(val.EstimateContentLength);
|
||||
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
|
||||
@@ -361,10 +444,10 @@ namespace Jellyfin.Model.Tests
|
||||
Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
|
||||
Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
|
||||
|
||||
// audio codec not supported
|
||||
// Audio codec not supported
|
||||
if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
|
||||
{
|
||||
// audio stream specified
|
||||
// Audio stream specified
|
||||
if (options.AudioStreamIndex >= 0)
|
||||
{
|
||||
// TODO:fixme
|
||||
@@ -374,10 +457,10 @@ namespace Jellyfin.Model.Tests
|
||||
}
|
||||
}
|
||||
|
||||
// audio stream not specified
|
||||
// Audio stream not specified
|
||||
else
|
||||
{
|
||||
// TODO:fixme
|
||||
// TODO: Fixme
|
||||
Assert.All(audioStreams, stream =>
|
||||
{
|
||||
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 Xunit;
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Jellyfin.Naming.Tests.TV
|
||||
private readonly NamingOptions _namingOptions = new NamingOptions();
|
||||
|
||||
[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("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)]
|
||||
|
||||
@@ -85,6 +85,17 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,10 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect
|
||||
|
||||
[Fact]
|
||||
public void IsEnabled_QuickConnectUnavailable_False()
|
||||
=> Assert.False(_quickConnectManager.IsEnabled);
|
||||
{
|
||||
_config.QuickConnectAvailable = false;
|
||||
Assert.False(_quickConnectManager.IsEnabled);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "DeviceId", "Client", "1.0.0")]
|
||||
@@ -69,19 +72,31 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect
|
||||
|
||||
[Fact]
|
||||
public void TryConnect_QuickConnectUnavailable_ThrowsAuthenticationException()
|
||||
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
|
||||
{
|
||||
_config.QuickConnectAvailable = false;
|
||||
Assert.Throws<AuthenticationException>(() => _quickConnectManager.TryConnect(_quickConnectAuthInfo));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckRequestStatus_QuickConnectUnavailable_ThrowsAuthenticationException()
|
||||
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
|
||||
{
|
||||
_config.QuickConnectAvailable = false;
|
||||
Assert.Throws<AuthenticationException>(() => _quickConnectManager.CheckRequestStatus(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
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]
|
||||
public void GetAuthorizedRequest_QuickConnectUnavailable_ThrowsAuthenticationException()
|
||||
=> Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
|
||||
{
|
||||
_config.QuickConnectAvailable = false;
|
||||
Assert.Throws<AuthenticationException>(() => _quickConnectManager.GetAuthorizedRequest(string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
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.AutoMoq" 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.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" 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.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
|
||||
Reference in New Issue
Block a user