Compare commits

...

105 Commits

Author SHA1 Message Date
Joshua M. Boniface
884a59da07 Merge pull request #7724 from jtcasper/perms 2022-05-15 20:26:00 -04:00
Joshua M. Boniface
5a9e5e0d5d Merge pull request #7712 from jellyfin/fix-hevc-disable-option 2022-05-15 20:25:01 -04:00
Joshua M. Boniface
85cfea4c50 Merge branch 'release-10.8.z' into fix-hevc-disable-option 2022-05-15 20:24:52 -04:00
Joshua M. Boniface
3ea67374ae Merge pull request #7741 from LewkyB/fix-improperly-labeled-four-digit-episode-numbering
Fix to allow for episode numbering over 999 in certain scenarios
2022-05-15 20:23:59 -04:00
Joshua M. Boniface
5da4bcc782 Merge pull request #7736 from jellyfin/fix-swscale-pgs 2022-05-15 20:22:24 -04:00
Joshua M. Boniface
b46d61dfdf Merge pull request #7699 from Shadowghost/streambuilder-fix 2022-05-15 20:22:13 -04:00
Joshua M. Boniface
8119e4a573 Merge pull request #7749 from cvium/disable_auto_add_collection 2022-05-15 20:21:40 -04:00
Joshua M. Boniface
de3c68d474 Bump version to 10.8.0-beta3 2022-05-15 20:16:25 -04:00
cvium
7aa0db24d8 fix: disable "Automatically add to collection" by default 2022-05-15 14:53:40 +02:00
Luke Brown
be832e82cc change capture groups to take up to 4 digits and adding inline data to test 2022-05-11 22:22:52 -05:00
nyanmisaka
368d10d042 Fix the mismatched resolution in sw PGS burn-in 2022-05-11 19:18:31 +08:00
Nyanmisaka
9523a1682b Apply suggestions from code review
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-05-11 16:38:30 +08:00
Jacob Casper
e67d8ce077 Don't let permission denied kill library scans
I have a single `./Movies` directory that contains symlinks to many
other directories, some of which are in shared paths. Other daemons
sometimes overwrite permissions on these paths and prevent Jellyfin from
being able to scan. Because this exception is unhandled within a LINQ
statement it can randomly kill the metadata refresh for my entire Movies
library.

Running with this patch has allowed me to at least add + scan for
movies in directories that Jellyfin can currently access and gives me
some notification of the symlinks failing due to permissions errors.

(cherry picked from commit 193cc8690a60bc2a432df14d256f00371e055c90)
2022-05-09 10:49:52 -05:00
Cody Robibero
39196bb5e2 Merge pull request #7723 from crobibero/tmdb-lib 2022-05-09 07:32:36 -06:00
Nyanmisaka
5386f06095 Apply suggestions from code review
Co-authored-by: Cody Robibero <cody@robibe.ro>
2022-05-09 17:09:03 +08:00
Cody Robibero
5a9afb0874 Merge pull request #7716 from Shadowghost/opus-fix
Respect limited opus sampling rates when building trancoding command
2022-05-08 15:23:58 -06:00
Shadowghost
029be321d1 Respect limited opus sampling rates when building trancoding command 2022-05-08 22:28:45 +02:00
Cody Robibero
96b7c46df4 Update TMDbLib to 1.9.2 2022-05-08 13:28:57 -06:00
Bond-009
f7ef7d9eda Merge pull request #7718 from jellyfin/dovi-hevc-remux 2022-05-08 21:06:03 +02:00
Nyanmisaka
c69e79f64d Fix the disordered color in Dolby Vision remuxing on Safari 2022-05-07 22:43:32 +08:00
nyanmisaka
4b1256e67b Fix the issue that HEVC transcoding can't be disabled 2022-05-06 02:27:16 +08:00
Bond-009
8d1d973438 Merge pull request #7604 from Jellifi007/fixes-diactritics
Co-authored-by: Cody Robibero <cody@robibe.ro>
2022-05-05 19:59:17 +02:00
Cody Robibero
60affd0965 Merge pull request #7529 from Shadowghost/strm-ffprobe-external-fix 2022-05-04 08:20:48 -06:00
Cody Robibero
8a1eca0913 Merge pull request #7544 from jaantaponen/long-filename-fix 2022-05-04 08:19:43 -06:00
Shadowghost
a4e4b761d5 Apply review suggestions 2022-05-04 16:13:06 +02:00
Shadowghost
2be9a34b26 Fix tests and profiles 2022-05-03 22:22:46 +02:00
Shadowghost
0c8b9091a5 Fix streambuilder reasons for direct playback checks 2022-05-03 15:48:46 +02:00
Shadowghost
128d54622a Fix stream index and subtitle container handling, preserve attachments and nonexternal streams between scans 2022-05-03 11:10:58 +02:00
Shadowghost
21ce0e58c6 Properly handle stream addition and removal for strm use cases 2022-05-03 11:10:58 +02:00
Cody Robibero
3229ba4918 Merge pull request #7693 from crobibero/auth-migrate 2022-05-02 07:41:13 -06:00
Cody Robibero
105f057512 Don't migrate auth token if user doesn't exist 2022-05-01 17:55:20 -06:00
Jaan Taponen
b6a2640f22 Update Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
Co-authored-by: Joe Rogers <1337joe@users.noreply.github.com>
2022-04-30 22:24:11 +03:00
Cody Robibero
a2abae3014 Merge pull request #7654 from Shadowghost/nfo-provider-fix
Prefer MetadataProvider enum as provider id key over arbitrary strings
2022-04-27 18:16:31 -06:00
Shadowghost
7084541508 Prefer MetadataProvider enum as provider id key over arbitrary strings 2022-04-24 21:27:17 +02:00
Joshua M. Boniface
e87240b374 Merge pull request #7648 from jellyfin/libssl3-jammy
Add libssl3 as an alternative dependency for Ubuntu 22.04 LTS
2022-04-23 10:53:25 -04:00
Nyanmisaka
057d5dfc25 Add libssl3 as an alternative dependency for Ubuntu 22.04 LTS 2022-04-23 18:53:59 +08:00
Cody Robibero
12f9132975 Merge pull request #7643 from jellyfin/h264-level 2022-04-22 16:40:53 -06:00
Nyanmisaka
4a1aa619d2 Fix H264 level on safari fmp4 2022-04-22 17:36:36 +08:00
Jellifi007
1002d1d193 Update src/Jellyfin.Extensions/StringExtensions.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2022-04-22 09:01:19 +02:00
Cody Robibero
7c91543694 Merge pull request #7638 from 1337joe/fix-quick-connect-tests 2022-04-21 17:48:29 -06:00
Joe Rogers
b04211a707 Fix quick connect tests 2022-04-21 23:24:45 +02:00
Cody Robibero
fcb65ac38d Merge pull request #7634 from neilsb/patch-1
Correct LocalTrailerCount in API
2022-04-21 14:26:17 -06:00
Jellifi007
727402f7f7 Adding Korean language tests Addresses #6393 2022-04-21 22:17:25 +02:00
Neil Burrows
2a1ca4badc Use already defined variable
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-04-21 11:11:25 +01:00
Neil Burrows
7f52f77ef5 Correct LocalTrailerCount in API
Revert a change which resulted in the `LocalTrailerCount` including the count of Local and Remote trailers which had been introduced in 10.8.
2022-04-21 11:03:33 +01:00
Cody Robibero
1d5961126e Merge pull request #7624 from nielsvanvelzen/quickconnect-enable-d 2022-04-20 17:37:53 -06:00
Cody Robibero
ec6f7bdcff Merge pull request #7625 from crobibero/xml-cache-delete 2022-04-20 17:37:44 -06:00
Cody Robibero
65c47c79da Only delete cache file if exist 2022-04-20 17:37:26 -06:00
Niels van Velzen
5d9df10e27 Enable Quick Connect by default 2022-04-19 21:48:11 +02:00
Joshua M. Boniface
d45d228b36 Bump version to 10.8.0-beta2 2022-04-17 15:52:43 -04:00
JaanTaponen
2b7d139b5b Fix XMLTV edge case where title & sub-title causes a too long filename error
Limit the series title to 241 bytes (so file extensions can be added).
If the end result is longer, the title will be dropped and only series name
with timestamp is used.
2022-04-17 16:46:01 +03:00
Cody Robibero
a280ff603f Merge pull request #7543 from daullmer/nfo-datefix 2022-04-17 06:40:28 -06:00
Cody Robibero
9beb3aff4e Merge pull request #7605 from crobibero/playback-start-stop
Add missing properties to PlaybackStart, PlaybackStop
2022-04-16 06:03:05 -06:00
Cody Robibero
066bdc1e72 Add missing properties to PlaybackStart, PlaybackStop 2022-04-15 17:44:30 -06:00
Cody Robibero
5833c70725 Merge pull request #7537 from dmitrylyzo/fix-streambuilder 2022-04-15 13:29:20 -06:00
Cody Robibero
cd93f49fa8 Merge pull request #7592 from 1337joe/live-tv-fixes 2022-04-15 13:29:10 -06:00
Cody Robibero
93009682b3 Merge pull request #7591 from 1337joe/update-xmltv 2022-04-15 13:29:00 -06:00
Jellifi007
56573f14b0 Fixes diactritics regressions 2022-04-15 20:14:13 +02:00
Joe Rogers
9001eaa67e Check cache file age directly and replace if old 2022-04-13 22:45:17 +02:00
Joe Rogers
76010e80dd Update Jellyfin.XmlTv to 10.8.0 2022-04-13 20:07:14 +02:00
Joshua M. Boniface
5778541d2f Merge pull request #7590 from crobibero/dotnet-6.0.4 2022-04-13 12:06:56 -04:00
Cody Robibero
cb0baddde3 Update to dotnet 6.0.4 2022-04-12 17:34:08 -06:00
Dmitry Lyzo
0ff37413b0 fix: Fix transcode reasons
Don't add codec failure reasons if the codec isn't supported.
2022-04-12 22:56:23 +03:00
Dmitry Lyzo
d5434988d7 test: Add tests for Tizen 3/4 2022-04-12 22:56:23 +03:00
Dmitry Lyzo
847518701d fix: Fix codec conditions
`ApplyConditions` is used to determine the applicability of
the current codec (`Conditions`).
`Conditions` is the actual conditions for the stream.

`CodecType.VideoAudio` (not `CodecType.Video`) must be used
for the audio tracks in the video.
2022-04-12 22:56:23 +03:00
Cody Robibero
c5212a20a3 Merge pull request #7580 from jellyfin/external-audio-map 2022-04-12 13:41:21 -06:00
Cody Robibero
385a0b9437 Merge pull request #7567 from cvium/fix_xmltv_caching 2022-04-12 13:40:05 -06:00
Nyanmisaka
884ba4f3ed Fix the wrong external audio map index if text subtitle exists 2022-04-10 22:49:40 +08:00
Cody Robibero
cba6a4e3f3 Merge pull request #7578 from Shadowghost/extension-parser-fix
Remove mp2 from video file extensions
2022-04-10 06:24:50 -06:00
Shadowghost
d5f44f7a5c Remove mp2 from video file extensions 2022-04-10 14:02:17 +02:00
Cody Robibero
bf1ccf7493 Merge pull request #7521 from 1337joe/image-mime-fallback
Add fallback for image downloads with bad reported MediaType
2022-04-09 08:46:06 -06:00
Cody Robibero
d7c548f3db Merge pull request #7561 from DMouse10462/named-config-api-fix
Fix NamedConfiguration API Generation
2022-04-09 08:45:13 -06:00
Joshua M. Boniface
a7abdca47a Merge pull request #7569 from crobibero/repo-auth 2022-04-08 11:23:30 -04:00
Cody Robibero
7a8eaa8811 Require elevation to save the list of plugin repositories 2022-04-08 08:46:55 -06:00
cvium
045dca49d0 fix: use the checksum as temp folder name when extracting xmltv listings 2022-04-07 22:12:12 +02:00
David Mouse
e9ce82445e Add ConfigurationType Types to generated OpenAPI
This allows clients to see and work with the associated types for each NamedConfiguration key, even if the UpdateNamedConfiguration endpoint doesn't properly advertise its types.
2022-04-05 23:19:27 -04:00
David Mouse
2133c6e348 Parameterize request body of UpdateNamedConfiguration
This gives OpenAPI clients the knowledge that they should provide a body of some kind.
2022-04-05 23:19:27 -04:00
Cody Robibero
5de2db9f52 Merge pull request #7507 from crobibero/studio-image-plugin
Fix StudioImageProvider
2022-04-05 05:52:48 -06:00
Cody Robibero
620625c4c1 Merge pull request #7557 from jellyfin/pgs-qsv-iris655 2022-04-04 16:05:50 -06:00
Nyanmisaka
e0b035e34e Apply suggestions from code review
Co-authored-by: Shadowghost <Shadowghost@users.noreply.github.com>
2022-04-05 00:02:13 +08:00
Nyanmisaka
1946414e14 Fix PGS burn-in on certain iGPU such as Iris Plus 655 2022-04-04 23:48:12 +08:00
Cody Robibero
132c85e554 Merge pull request #7542 from 1337joe/make-recording-stop
Make recording stop at scheduled stop time
2022-04-04 07:20:20 -06:00
Joe Rogers
9d1049e83f Make Record call block until finished as expected 2022-04-04 14:43:14 +02:00
Cody Robibero
72aca15191 Merge pull request #7548 from 1337joe/comparer-null-fix 2022-04-04 06:30:24 -06:00
Cody Robibero
577325b788 Merge pull request #7523 from crobibero/null-stream
Allow media without streams to playback
2022-04-04 05:45:45 -06:00
Cody Robibero
fec2cf5060 Merge pull request #7551 from cvium/fix_trailertype
fix: remove (incorrect) negation of bool expression
2022-04-04 05:02:58 -06:00
cvium
53b06ce4e3 fix: remove (incorrect) negation of bool expression 2022-04-04 08:35:41 +02:00
Cody Robibero
bdb85aeecf Merge pull request #7549 from cvium/fix_isinlocalnetwork 2022-04-03 16:38:04 -06:00
cvium
e299adc819 fix: use stdlib IPAddress.IsLoopback 2022-04-04 00:03:54 +02:00
Joe Rogers
0674f84e9e Add check so nulls will result in valid sort 2022-04-03 22:50:32 +02:00
David Ullmer
b6086398d3 Write Global Date to nfo files 2022-04-02 19:46:15 +02:00
Cody Robibero
1d585146d6 Merge pull request #7519 from nielsvanvelzen/resolverpriority-plugin2 2022-03-30 20:11:46 -06:00
Cody Robibero
aa1b1c6bbb Merge pull request #7527 from Shadowghost/mediaresolver-fix 2022-03-30 20:10:36 -06:00
Cody Robibero
bebe1808ce Merge pull request #7525 from 1337joe/fix-duplicate-library-media-paths 2022-03-30 20:10:12 -06:00
Shadowghost
fb2e4c2e1a Remove video file from file list before processing external files 2022-03-30 22:48:48 +02:00
Joe Rogers
784ed796ce Update name so media paths store to correct library 2022-03-30 15:20:57 +02:00
Cody Robibero
579155a571 Allow media without streams to playback 2022-03-29 20:17:12 -06:00
Joe Rogers
ca16a55a47 Add fallback for image downloads with bad MediaType 2022-03-29 21:42:54 +02:00
Niels van Velzen
e2ffd41141 Add new priority level to ResolverPriority for plugins 2022-03-29 20:00:50 +02:00
Cody Robibero
ca67a48140 Merge pull request #7512 from crobibero/update-plugin 2022-03-28 16:34:46 -06:00
Claus Vium
d2ce315c1d Merge pull request #7506 from crobibero/set-permissions
Safely get/set User permission/preference
2022-03-28 23:02:42 +02:00
Cody Robibero
6a6874aa16 Catch checksum mismatch when updating plugins 2022-03-28 06:48:21 -06:00
Cody Robibero
6f45848b51 Safely set/get user permission 2022-03-28 06:45:47 -06:00
Cody Robibero
23ba15ccfb Fix StudiosImageProvider 2022-03-27 21:42:35 -06:00
Joshua M. Boniface
5376c37d42 Bump packaging version to 10.8.0~beta1 2022-03-27 12:12:24 -04:00
88 changed files with 2239 additions and 289 deletions

View File

@@ -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;

View File

@@ -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
},

View File

@@ -398,6 +398,12 @@ namespace Emby.Server.Implementations.AppBase
});
}
/// <inheritdoc />
public ConfigurationStore[] GetConfigurationStores()
{
return _configurationStores;
}
/// <inheritdoc />
public Type GetConfigurationType(string key)
{

View File

@@ -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

View File

@@ -1094,7 +1094,7 @@ namespace Emby.Server.Implementations.Dto
{
if (item is IHasTrailers hasTrailers)
{
dto.LocalTrailerCount = hasTrailers.GetTrailerCount();
dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
}
else
{

View File

@@ -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" />

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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)

View File

@@ -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)
{

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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;

View File

@@ -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/>

View File

@@ -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>

View File

@@ -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);
}
}
}
}

View File

@@ -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" />

View File

@@ -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(),

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Diacritics.Extensions;
using Jellyfin.Extensions;
namespace MediaBrowser.Controller.Library
{

View File

@@ -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" />

View File

@@ -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);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -17,7 +17,7 @@ namespace MediaBrowser.Model.Configuration
RequirePerfectSubtitleMatch = true;
AllowEmbeddedSubtitles = EmbeddedSubtitleOptions.AllowAll;
AutomaticallyAddToCollection = true;
AutomaticallyAddToCollection = false;
EnablePhotos = true;
SaveSubtitlesWithMedia = true;
EnableRealtimeMonitor = true;

View File

@@ -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].

View File

@@ -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)

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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,

View File

@@ -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>

View File

@@ -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)

View File

@@ -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))
{

View File

@@ -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;

View File

@@ -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('/');
}
}
}

View File

@@ -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);
});

View File

@@ -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
{

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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");

View File

@@ -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
View File

@@ -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
View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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)]

View File

@@ -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)

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)]

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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" />

View File

@@ -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" />