Compare commits

...

149 Commits

Author SHA1 Message Date
Joshua M. Boniface
93941f9728 Bump version to 10.8.0 2022-06-10 22:16:13 -04:00
Joshua M. Boniface
aa0f6cb5eb Merge pull request #7868 from cvium/disable_dlna 2022-06-10 21:59:21 -04:00
Cody Robibero
69cc1e0bd8 Merge pull request #7867 from crobibero/name-guid-pair 2022-06-10 10:39:51 -06:00
cvium
007856e61a actually disable DLNA... 2022-06-10 09:04:07 +02:00
cvium
b4954985be chore: disable DLNA by default 2022-06-10 08:56:58 +02:00
Cody Robibero
6b16d90b9b Don't add MigrationOptions to the api spec 2022-06-09 14:26:05 -06:00
Cody Robibero
2a89683e80 Merge pull request #7854 from cvium/enable_mkv_keyframe_extraction 2022-06-06 09:40:38 -06:00
Bond-009
8595a979a8 Merge pull request #7828 from nyanmisaka/fix-dovi-tonemap
Fix Dolby Vision profile 5 and 8 to SDR HW tone-mapping
2022-06-06 17:29:39 +02:00
nyanmisaka
1900096012 Fix the too high default qmin option in amf encoders 2022-06-06 21:46:36 +08:00
nyanmisaka
0e8da3e805 Remove the redundant -sc_threshold from hw encoders 2022-06-06 21:46:36 +08:00
nyanmisaka
84c9e7a22b Fix thumbnail extraction in DoVi 2022-06-06 21:46:36 +08:00
nyanmisaka
be28f940b7 Fix the issue that analyzeduration env is not applied 2022-06-06 21:46:36 +08:00
nyanmisaka
fb95fb1a73 Update DoVi 10bit codec tags and remove extra -strict options 2022-06-06 21:46:36 +08:00
nyanmisaka
910995f922 Fix Dolby Vision profile 5 and 8 to SDR HW tone-mapping 2022-06-06 21:46:36 +08:00
cvium
4f0666ac5c chore: enable on demand keyframe extraction for mkv 2022-06-06 15:41:01 +02:00
Joshua M. Boniface
4bfadbc636 Merge pull request #7852 from nyanmisaka/fix-skia
Fix the PNG image decoding issue in Skia
2022-06-06 09:02:51 -04:00
nyanmisaka
2cc896251f Fix the PNG image decoding issue in Skia
Regression was introduced in 2.88.0: https://github.com/mono/SkiaSharp/issues/2095
2022-06-06 19:47:50 +08:00
Cody Robibero
5e343d30e1 Merge pull request #7810 from Bond-009/unaccpattern 2022-06-04 17:23:40 -06:00
Cody Robibero
df6c5b6d42 Merge pull request #7842 from crobibero/dependency-backport 2022-06-04 17:23:04 -06:00
Bond_009
754bda8f73 IAsyncDisposable is one big pitfall
https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-disposeasync#unacceptable-pattern

Regex used:
```
await using \(.+\)
\W+await using
```
2022-06-04 14:34:59 +02:00
Cody Robibero
3721b5e985 Backport all dependency updates 2022-06-03 18:53:05 -06:00
Cody Robibero
77c73e241f Merge pull request #7781 from crobibero/live-tv-infinite 2022-05-29 08:49:50 -06:00
Cody Robibero
9954cbd550 Merge pull request #7802 from jellyfin/external 2022-05-29 08:49:36 -06:00
Cody Robibero
d8f1a87c85 Update Emby.Server.Implementations/Session/SessionManager.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-05-27 16:25:31 -06:00
Cody Robibero
bf0a7c374c Close live stream on session end 2022-05-27 15:58:31 -06:00
Cody Robibero
8a6b26cd42 initial patch 2022-05-27 15:57:51 -06:00
Joe Rogers
b369194710 Fix tests 2022-05-27 06:12:16 +08:00
Nyanmisaka
293bcfb342 Exclude streams with mismatched types in external files 2022-05-27 06:12:10 +08:00
Cody Robibero
1c5571b24e Remove conditional skia inclusion (#7799) 2022-05-26 16:32:37 +02:00
Cody Robibero
b507d1a780 Merge pull request #7792 from crobibero/skia-native
Conditionally include platform specific Skia assets
2022-05-24 19:40:05 -06:00
Cody Robibero
d471be8d92 Merge pull request #7784 from crobibero/support-transcoding 2022-05-24 14:01:40 -06:00
Cody Robibero
3c5b4b9a27 Conditionally include platform specific Skia assets 2022-05-24 11:19:17 -06:00
Cody Robibero
c9491cf317 Merge pull request #7785 from dmitrylyzo/clear-transcodinginfo
Clear TranscodingInfo if play method changed
2022-05-24 07:59:13 -06:00
Dmitry Lyzo
c5dae18034 Apply suggestions from review 2022-05-23 07:49:54 -06:00
Dmitry Lyzo
ff4f624850 Clear TranscodingInfo if play method changed 2022-05-22 22:17:03 +03:00
Cody Robibero
d29a423475 Enable SupportsTranscoding if device has transcoding profiles 2022-05-22 11:06:38 -06:00
Cody Robibero
4c0510ee6d Merge pull request #7775 from crobibero/openapi-version 2022-05-21 08:59:24 -06:00
Cody Robibero
492c6bbd7e Merge pull request #7780 from 1337joe/fix-tv-guide-search-2 2022-05-21 08:59:13 -06:00
Joe Rogers
84878f537c Support searching with tv program filters 2022-05-21 13:11:26 +02:00
Cody Robibero
825e6460c9 Merge pull request #7774 from crobibero/api-authinfo 2022-05-20 20:49:25 -06:00
Cody Robibero
760b021032 Manually describe Version for openapi 2022-05-20 16:29:43 -06:00
Cody Robibero
a532a866e3 Populate authentication info with server details if using API key 2022-05-19 17:50:29 -06:00
Joshua M. Boniface
71bf567045 Merge pull request #7766 from crobibero/dotnet-6.0.5 2022-05-19 19:06:40 -04:00
Cody Robibero
a82e378da9 Update to dotnet 6.0.5 2022-05-16 14:14:26 -06:00
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
119 changed files with 2595 additions and 508 deletions

View File

@@ -13,7 +13,7 @@ namespace Emby.Dlna.Configuration
public DlnaOptions() public DlnaOptions()
{ {
EnablePlayTo = true; EnablePlayTo = true;
EnableServer = true; EnableServer = false;
BlastAliveMessages = true; BlastAliveMessages = true;
SendOnlyMatchedHost = true; SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60; ClientDiscoveryIntervalSeconds = 60;

View File

@@ -6,8 +6,8 @@ using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using Diacritics.Extensions;
using Emby.Dlna.Didl; using Emby.Dlna.Didl;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@@ -48,7 +48,6 @@ namespace Emby.Naming.Common
".mkv", ".mkv",
".mk3d", ".mk3d",
".mov", ".mov",
".mp2",
".mp4", ".mp4",
".mpe", ".mpe",
".mpeg", ".mpeg",
@@ -315,7 +314,7 @@ namespace Emby.Naming.Common
// This isn't a Kodi naming rule, but the expression below causes false positives, // This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first. // so we make sure this one gets tested first.
// "Foo Bar 889" // "Foo Bar 889"
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$") new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,4})(-(?<endingepnumber>[0-9]{2,4}))*[^\\\/x]*$")
{ {
IsNamed = true IsNamed = true
}, },

View File

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

View File

@@ -11,7 +11,6 @@ using System.Linq;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using Diacritics.Extensions;
using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@@ -5763,7 +5762,7 @@ AND Type = @InternalPersonType)");
{ {
var itemIdBlob = id.ToByteArray(); var itemIdBlob = id.ToByteArray();
// First delete chapters // Delete existing mediastreams
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob); db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
InsertMediaStreams(itemIdBlob, streams, db); InsertMediaStreams(itemIdBlob, streams, db);
@@ -5867,10 +5866,10 @@ AND Type = @InternalPersonType)");
} }
/// <summary> /// <summary>
/// Gets the chapter. /// Gets the media stream.
/// </summary> /// </summary>
/// <param name="reader">The reader.</param> /// <param name="reader">The reader.</param>
/// <returns>ChapterInfo.</returns> /// <returns>MediaStream.</returns>
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader) private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
{ {
var item = new MediaStream var item = new MediaStream

View File

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

View File

@@ -24,15 +24,15 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" /> <PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" /> <PackageReference Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.5" />
<PackageReference Include="Mono.Nat" Version="3.0.2" /> <PackageReference Include="Mono.Nat" Version="3.0.3" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.3" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
<PackageReference Include="sharpcompress" Version="0.30.1" /> <PackageReference Include="sharpcompress" Version="0.31.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.3" /> <PackageReference Include="DotNet.Glob" Version="3.1.3" />
</ItemGroup> </ItemGroup>

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); _logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
result.Exists = false; result.Exists = false;
} }
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fileInfo.FullName);
}
} }
} }

View File

@@ -2840,10 +2840,12 @@ namespace Emby.Server.Implementations.Library
var existingNameCount = 1; // first numbered name will be 2 var existingNameCount = 1; // first numbered name will be 2
var virtualFolderPath = Path.Combine(rootFolderPath, name); var virtualFolderPath = Path.Combine(rootFolderPath, name);
var originalName = name;
while (Directory.Exists(virtualFolderPath)) while (Directory.Exists(virtualFolderPath))
{ {
existingNameCount++; existingNameCount++;
virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount); name = originalName + existingNameCount;
virtualFolderPath = Path.Combine(rootFolderPath, name);
} }
var mediaPathInfos = options.PathInfos; var mediaPathInfos = options.PathInfos;

View File

@@ -151,7 +151,11 @@ namespace Emby.Server.Implementations.Library
{ {
var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user);
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio || i.Type == MediaStreamType.Video)) // If file is strm or main media stream is missing, force a metadata refresh with remote probing
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
&& (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
|| (item.MediaType == MediaType.Video && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Video))
|| (item.MediaType == MediaType.Audio && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio))))
{ {
await item.RefreshMetadata( await item.RefreshMetadata(
new MetadataRefreshOptions(_directoryService) new MetadataRefreshOptions(_directoryService)

View File

@@ -5,9 +5,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Diacritics.Extensions;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;

View File

@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false); await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
_logger.LogInformation("Recording completed to file {0}", targetFile); _logger.LogInformation("Recording completed to file {Path}", targetFile);
} }
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken) private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
@@ -115,7 +115,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
_ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream); _ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath); _logger.LogInformation("ffmpeg recording process started for {Path}", _targetPath);
// Block until ffmpeg exits
await _taskCompletionSource.Task.ConfigureAwait(false);
} }
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile) private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using System.Text;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
@@ -48,12 +49,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle)) if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
{ {
var tmpName = name;
if (addHyphen) if (addHyphen)
{ {
name += " -"; tmpName += " -";
} }
name += " " + info.EpisodeTitle; tmpName += " " + info.EpisodeTitle;
// Since the filename will be used with file ext. (.mp4, .ts, etc)
if (Encoding.UTF8.GetByteCount(tmpName) < 250)
{
name = tmpName;
}
} }
} }
else if (info.IsMovie && info.ProductionYear != null) else if (info.IsMovie && info.ProductionYear != null)

View File

@@ -8,6 +8,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Security.Cryptography;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@@ -26,6 +27,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{ {
public class XmlTvListingsProvider : IListingsProvider public class XmlTvListingsProvider : IListingsProvider
{ {
private static readonly TimeSpan _maxCacheAge = TimeSpan.FromHours(1);
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger<XmlTvListingsProvider> _logger; private readonly ILogger<XmlTvListingsProvider> _logger;
@@ -69,13 +72,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return UnzipIfNeeded(info.Path, info.Path); return UnzipIfNeeded(info.Path, info.Path);
} }
string cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + "-" + info.Id + ".xml"; string cacheFilename = info.Id + ".xml";
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename); string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
if (File.Exists(cacheFile)) if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
{ {
return UnzipIfNeeded(info.Path, cacheFile); return UnzipIfNeeded(info.Path, cacheFile);
} }
// Must check if file exists as parent directory may not exist.
if (File.Exists(cacheFile))
{
File.Delete(cacheFile);
}
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path); _logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile)); Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
@@ -124,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{ {
using (var stream = File.OpenRead(file)) using (var stream = File.OpenRead(file))
{ {
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); string tempFolder = GetTempFolderPath(stream);
Directory.CreateDirectory(tempFolder); Directory.CreateDirectory(tempFolder);
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml"); _zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
@@ -137,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{ {
using (var stream = File.OpenRead(file)) using (var stream = File.OpenRead(file))
{ {
string tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); string tempFolder = GetTempFolderPath(stream);
Directory.CreateDirectory(tempFolder); Directory.CreateDirectory(tempFolder);
_zipClient.ExtractAllFromGz(stream, tempFolder, true); _zipClient.ExtractAllFromGz(stream, tempFolder, true);
@@ -146,6 +155,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
} }
} }
private string GetTempFolderPath(Stream stream)
{
#pragma warning disable CA5351
using var md5 = MD5.Create();
#pragma warning restore CA5351
var checksum = Convert.ToHexString(md5.ComputeHash(stream));
stream.Position = 0;
return Path.Combine(_config.ApplicationPaths.TempDirectory, checksum);
}
private string FindXmlFile(string directory) private string FindXmlFile(string directory)
{ {
return _fileSystem.GetFiles(directory, true) return _fileSystem.GetFiles(directory, true)

View File

@@ -104,6 +104,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{ {
_logger.LogError(ex, "Error updating {0}", package.Name); _logger.LogError(ex, "Error updating {0}", package.Name);
} }
catch (InvalidDataException ex)
{
_logger.LogError(ex, "Error updating {0}", package.Name);
}
// Update progress // Update progress
lock (progress) lock (progress)

View File

@@ -329,13 +329,17 @@ namespace Emby.Server.Implementations.Session
} }
/// <inheritdoc /> /// <inheritdoc />
public void CloseIfNeeded(SessionInfo session) public async Task CloseIfNeededAsync(SessionInfo session)
{ {
if (!session.SessionControllers.Any(i => i.IsSessionActive)) if (!session.SessionControllers.Any(i => i.IsSessionActive))
{ {
var key = GetSessionKey(session.Client, session.DeviceId); var key = GetSessionKey(session.Client, session.DeviceId);
_activeConnections.TryRemove(key, out _); _activeConnections.TryRemove(key, out _);
if (!string.IsNullOrEmpty(session.PlayState?.LiveStreamId))
{
await _mediaSourceManager.CloseLiveStream(session.PlayState.LiveStreamId).ConfigureAwait(false);
}
OnSessionEnded(session); OnSessionEnded(session);
} }
@@ -413,6 +417,7 @@ namespace Emby.Server.Implementations.Session
session.PlayState.IsPaused = info.IsPaused; session.PlayState.IsPaused = info.IsPaused;
session.PlayState.PositionTicks = info.PositionTicks; session.PlayState.PositionTicks = info.PositionTicks;
session.PlayState.MediaSourceId = info.MediaSourceId; session.PlayState.MediaSourceId = info.MediaSourceId;
session.PlayState.LiveStreamId = info.LiveStreamId;
session.PlayState.CanSeek = info.CanSeek; session.PlayState.CanSeek = info.CanSeek;
session.PlayState.IsMuted = info.IsMuted; session.PlayState.IsMuted = info.IsMuted;
session.PlayState.VolumeLevel = info.VolumeLevel; session.PlayState.VolumeLevel = info.VolumeLevel;
@@ -699,7 +704,9 @@ namespace Emby.Server.Implementations.Session
DeviceName = session.DeviceName, DeviceName = session.DeviceName,
ClientName = session.Client, ClientName = session.Client,
DeviceId = session.DeviceId, DeviceId = session.DeviceId,
Session = session Session = session,
PlaybackPositionTicks = info.PositionTicks,
PlaySessionId = info.PlaySessionId
}; };
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);
@@ -768,6 +775,11 @@ namespace Emby.Server.Implementations.Session
await UpdateNowPlayingItem(session, info, libraryItem, !isAutomated).ConfigureAwait(false); await UpdateNowPlayingItem(session, info, libraryItem, !isAutomated).ConfigureAwait(false);
if (!string.IsNullOrEmpty(session.DeviceId) && info.PlayMethod != PlayMethod.Transcode)
{
ClearTranscodingInfo(session.DeviceId);
}
var users = GetUsers(session); var users = GetUsers(session);
// only update saved user data on actual check-ins, not automated ones // only update saved user data on actual check-ins, not automated ones
@@ -985,7 +997,8 @@ namespace Emby.Server.Implementations.Session
DeviceName = session.DeviceName, DeviceName = session.DeviceName,
ClientName = session.Client, ClientName = session.Client,
DeviceId = session.DeviceId, DeviceId = session.DeviceId,
Session = session Session = session,
PlaySessionId = info.PlaySessionId
}; };
await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false);

View File

@@ -53,13 +53,13 @@ namespace Emby.Server.Implementations.Session
connection.Closed += OnConnectionClosed; connection.Closed += OnConnectionClosed;
} }
private void OnConnectionClosed(object? sender, EventArgs e) private async void OnConnectionClosed(object? sender, EventArgs e)
{ {
var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender)); var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender));
_logger.LogDebug("Removing websocket from session {Session}", _session.Id); _logger.LogDebug("Removing websocket from session {Session}", _session.Id);
_sockets.Remove(connection); _sockets.Remove(connection);
connection.Closed -= OnConnectionClosed; connection.Closed -= OnConnectionClosed;
_sessionManager.CloseIfNeeded(_session); await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -34,6 +34,11 @@ namespace Emby.Server.Implementations.Sorting
throw new ArgumentNullException(nameof(y)); throw new ArgumentNullException(nameof(y));
} }
if (!x.IndexNumber.HasValue && !y.IndexNumber.HasValue)
{
return 0;
}
if (!x.IndexNumber.HasValue) if (!x.IndexNumber.HasValue)
{ {
return -1; return -1;

View File

@@ -34,6 +34,11 @@ namespace Emby.Server.Implementations.Sorting
throw new ArgumentNullException(nameof(y)); throw new ArgumentNullException(nameof(y));
} }
if (!x.ParentIndexNumber.HasValue && !y.ParentIndexNumber.HasValue)
{
return 0;
}
if (!x.ParentIndexNumber.HasValue) if (!x.ParentIndexNumber.HasValue)
{ {
return -1; return -1;

View File

@@ -0,0 +1,25 @@
using Emby.Dlna;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Jellyfin.Api.Attributes;
/// <inheritdoc />
public sealed class DlnaEnabledAttribute : ActionFilterAttribute
{
/// <inheritdoc />
public override void OnActionExecuting(ActionExecutingContext context)
{
var serverConfigurationManager = context.HttpContext.RequestServices.GetRequiredService<IServerConfigurationManager>();
var enabled = serverConfigurationManager.GetDlnaConfiguration().EnableServer;
if (!enabled)
{
context.Result = new StatusCodeResult(StatusCodes.Status503ServiceUnavailable);
}
}
}

View File

@@ -86,21 +86,23 @@ namespace Jellyfin.Api.Controllers
/// Updates named configuration. /// Updates named configuration.
/// </summary> /// </summary>
/// <param name="key">Configuration key.</param> /// <param name="key">Configuration key.</param>
/// <param name="configuration">Configuration.</param>
/// <response code="204">Named configuration updated.</response> /// <response code="204">Named configuration updated.</response>
/// <returns>Update status.</returns> /// <returns>Update status.</returns>
[HttpPost("Configuration/{key}")] [HttpPost("Configuration/{key}")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> UpdateNamedConfiguration([FromRoute, Required] string key) public ActionResult UpdateNamedConfiguration([FromRoute, Required] string key, [FromBody, Required] JsonDocument configuration)
{ {
var configurationType = _configurationManager.GetConfigurationType(key); var configurationType = _configurationManager.GetConfigurationType(key);
var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false); var deserializedConfiguration = configuration.Deserialize(configurationType, _serializerOptions);
if (configuration == null)
if (deserializedConfiguration == null)
{ {
throw new ArgumentException("Body doesn't contain a valid configuration"); throw new ArgumentException("Body doesn't contain a valid configuration");
} }
_configurationManager.SaveConfiguration(key, configuration); _configurationManager.SaveConfiguration(key, deserializedConfiguration);
return NoContent(); return NoContent();
} }

View File

@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers
/// Dlna Server Controller. /// Dlna Server Controller.
/// </summary> /// </summary>
[Route("Dlna")] [Route("Dlna")]
[DlnaEnabled]
[Authorize(Policy = Policies.AnonymousLanAccessPolicy)] [Authorize(Policy = Policies.AnonymousLanAccessPolicy)]
public class DlnaServerController : BaseJellyfinApiController public class DlnaServerController : BaseJellyfinApiController
{ {
@@ -55,15 +56,10 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult GetDescriptionXml([FromRoute, Required] string serverId) public ActionResult GetDescriptionXml([FromRoute, Required] string serverId)
{ {
if (DlnaEntryPoint.Enabled) var url = GetAbsoluteUri();
{ var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
var url = GetAbsoluteUri(); var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); return Ok(xml);
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
return Ok(xml);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -83,12 +79,7 @@ namespace Jellyfin.Api.Controllers
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetContentDirectory([FromRoute, Required] string serverId) public ActionResult GetContentDirectory([FromRoute, Required] string serverId)
{ {
if (DlnaEntryPoint.Enabled) return Ok(_contentDirectory.GetServiceXml());
{
return Ok(_contentDirectory.GetServiceXml());
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -108,12 +99,7 @@ namespace Jellyfin.Api.Controllers
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId) public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
{ {
if (DlnaEntryPoint.Enabled) return Ok(_mediaReceiverRegistrar.GetServiceXml());
{
return Ok(_mediaReceiverRegistrar.GetServiceXml());
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -133,12 +119,7 @@ namespace Jellyfin.Api.Controllers
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetConnectionManager([FromRoute, Required] string serverId) public ActionResult GetConnectionManager([FromRoute, Required] string serverId)
{ {
if (DlnaEntryPoint.Enabled) return Ok(_connectionManager.GetServiceXml());
{
return Ok(_connectionManager.GetServiceXml());
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -155,12 +136,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId) public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
{ {
if (DlnaEntryPoint.Enabled) return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -177,12 +153,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId) public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
{ {
if (DlnaEntryPoint.Enabled) return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -199,12 +170,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)]
public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId) public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
{ {
if (DlnaEntryPoint.Enabled) return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -224,12 +190,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId) public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId)
{ {
if (DlnaEntryPoint.Enabled) return ProcessEventRequest(_mediaReceiverRegistrar);
{
return ProcessEventRequest(_mediaReceiverRegistrar);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -249,12 +210,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId) public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId)
{ {
if (DlnaEntryPoint.Enabled) return ProcessEventRequest(_contentDirectory);
{
return ProcessEventRequest(_contentDirectory);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -274,12 +230,7 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Xml)] [ProducesFile(MediaTypeNames.Text.Xml)]
public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId) public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId)
{ {
if (DlnaEntryPoint.Enabled) return ProcessEventRequest(_connectionManager);
{
return ProcessEventRequest(_connectionManager);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -299,12 +250,7 @@ namespace Jellyfin.Api.Controllers
[ProducesImageFile] [ProducesImageFile]
public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName) public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
{ {
if (DlnaEntryPoint.Enabled) return GetIconInternal(fileName);
{
return GetIconInternal(fileName);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
/// <summary> /// <summary>
@@ -322,12 +268,7 @@ namespace Jellyfin.Api.Controllers
[ProducesImageFile] [ProducesImageFile]
public ActionResult GetIcon([FromRoute, Required] string fileName) public ActionResult GetIcon([FromRoute, Required] string fileName)
{ {
if (DlnaEntryPoint.Enabled) return GetIconInternal(fileName);
{
return GetIconInternal(fileName);
}
return StatusCode(StatusCodes.Status503ServiceUnavailable);
} }
private ActionResult GetIconInternal(string fileName) private ActionResult GetIconInternal(string fileName)

View File

@@ -1711,20 +1711,30 @@ namespace Jellyfin.Api.Controllers
return audioTranscodeParams; return audioTranscodeParams;
} }
// flac and opus are experimental in mp4 muxer
var strictArgs = string.Empty;
if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
strictArgs = " -strict -2";
}
if (EncodingHelper.IsCopyCodec(audioCodec)) if (EncodingHelper.IsCopyCodec(audioCodec))
{ {
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs;
if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec))
{ {
return "-codec:a:0 copy -strict -2 -copypriorss:a:0 0" + bitStreamArgs; return copyArgs + " -copypriorss:a:0 0";
} }
return "-codec:a:0 copy -strict -2" + bitStreamArgs; return copyArgs;
} }
var args = "-codec:a:0 " + audioCodec; var args = "-codec:a:0 " + audioCodec + strictArgs;
var channels = state.OutputAudioChannels; var channels = state.OutputAudioChannels;
@@ -1773,13 +1783,24 @@ namespace Jellyfin.Api.Controllers
var args = "-codec:v:0 " + codec; var args = "-codec:v:0 " + codec;
// Prefer hvc1 to hev1.
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase) if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
{ {
args += " -tag:v:0 hvc1"; if (EncodingHelper.IsCopyCodec(codec)
&& (string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|| 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 -strict -2";
}
else
{
// Prefer hvc1 to hev1
args += " -tag:v:0 hvc1";
}
} }
// if (state.EnableMpegtsM2TsMode) // if (state.EnableMpegtsM2TsMode)

View File

@@ -89,6 +89,11 @@ namespace Jellyfin.Api.Controllers
/// <param name="hasImdbId">Optional filter by items that have an imdb id or not.</param> /// <param name="hasImdbId">Optional filter by items that have an imdb id or not.</param>
/// <param name="hasTmdbId">Optional filter by items that have a tmdb id or not.</param> /// <param name="hasTmdbId">Optional filter by items that have a tmdb id or not.</param>
/// <param name="hasTvdbId">Optional filter by items that have a tvdb id or not.</param> /// <param name="hasTvdbId">Optional filter by items that have a tvdb id or not.</param>
/// <param name="isMovie">Optional filter for live tv movies.</param>
/// <param name="isSeries">Optional filter for live tv series.</param>
/// <param name="isNews">Optional filter for live tv news.</param>
/// <param name="isKids">Optional filter for live tv kids.</param>
/// <param name="isSports">Optional filter for live tv sports.</param>
/// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param> /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param>
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param> /// <param name="limit">Optional. The maximum number of records to return.</param>
@@ -173,6 +178,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId, [FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId, [FromQuery] bool? hasTmdbId,
[FromQuery] bool? hasTvdbId, [FromQuery] bool? hasTvdbId,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSeries,
[FromQuery] bool? isNews,
[FromQuery] bool? isKids,
[FromQuery] bool? isSports,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
@@ -316,6 +326,11 @@ namespace Jellyfin.Api.Controllers
Is3D = is3D, Is3D = is3D,
HasTvdbId = hasTvdbId, HasTvdbId = hasTvdbId,
HasTmdbId = hasTmdbId, HasTmdbId = hasTmdbId,
IsMovie = isMovie,
IsSeries = isSeries,
IsNews = isNews,
IsKids = isKids,
IsSports = isSports,
HasOverview = hasOverview, HasOverview = hasOverview,
HasOfficialRating = hasOfficialRating, HasOfficialRating = hasOfficialRating,
HasParentalRating = hasParentalRating, HasParentalRating = hasParentalRating,
@@ -515,8 +530,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param> /// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param>
/// <param name="isHd">Optional filter by items that are HD or not.</param> /// <param name="isHd">Optional filter by items that are HD or not.</param>
/// <param name="is4K">Optional filter by items that are 4K or not.</param> /// <param name="is4K">Optional filter by items that are 4K or not.</param>
/// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.</param> /// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.</param>
/// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimeted.</param> /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.</param>
/// <param name="isMissing">Optional filter by items that are missing episodes or not.</param> /// <param name="isMissing">Optional filter by items that are missing episodes or not.</param>
/// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param> /// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param>
/// <param name="minCommunityRating">Optional filter by minimum community rating.</param> /// <param name="minCommunityRating">Optional filter by minimum community rating.</param>
@@ -529,42 +544,47 @@ namespace Jellyfin.Api.Controllers
/// <param name="hasImdbId">Optional filter by items that have an imdb id or not.</param> /// <param name="hasImdbId">Optional filter by items that have an imdb id or not.</param>
/// <param name="hasTmdbId">Optional filter by items that have a tmdb id or not.</param> /// <param name="hasTmdbId">Optional filter by items that have a tmdb id or not.</param>
/// <param name="hasTvdbId">Optional filter by items that have a tvdb id or not.</param> /// <param name="hasTvdbId">Optional filter by items that have a tvdb id or not.</param>
/// <param name="excludeItemIds">Optional. If specified, results will be filtered by exxcluding item ids. This allows multiple, comma delimeted.</param> /// <param name="isMovie">Optional filter for live tv movies.</param>
/// <param name="isSeries">Optional filter for live tv series.</param>
/// <param name="isNews">Optional filter for live tv news.</param>
/// <param name="isKids">Optional filter for live tv kids.</param>
/// <param name="isSports">Optional filter for live tv sports.</param>
/// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param>
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param> /// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="recursive">When searching within folders, this determines whether or not the search will be recursive. true/false.</param> /// <param name="recursive">When searching within folders, this determines whether or not the search will be recursive. true/false.</param>
/// <param name="searchTerm">Optional. Filter based on a search term.</param> /// <param name="searchTerm">Optional. Filter based on a search term.</param>
/// <param name="sortOrder">Sort Order - Ascending,Descending.</param> /// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param> /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.</param> /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimeted.</param> /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
/// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.</param> /// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.</param>
/// <param name="sortBy">Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param>
/// <param name="isPlayed">Optional filter by items that are played, or not.</param> /// <param name="isPlayed">Optional filter by items that are played, or not.</param>
/// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.</param> /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
/// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.</param> /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.</param>
/// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.</param> /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.</param>
/// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.</param> /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.</param>
/// <param name="enableUserData">Optional, include user data.</param> /// <param name="enableUserData">Optional, include user data.</param>
/// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param> /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param> /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
/// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param> /// <param name="person">Optional. If specified, results will be filtered to include only those containing the specified person.</param>
/// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param> /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the specified person id.</param>
/// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param> /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.</param>
/// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.</param> /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.</param>
/// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimeted.</param> /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.</param>
/// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimeted.</param> /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.</param>
/// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the specified artist id.</param> /// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the specified artist id.</param>
/// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing the specified album artist id.</param> /// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing the specified album artist id.</param>
/// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.</param> /// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.</param>
/// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.</param> /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.</param>
/// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimeted.</param> /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.</param>
/// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.</param> /// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.</param>
/// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.</param> /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.</param>
/// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param> /// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param>
/// <param name="isLocked">Optional filter by items that are locked.</param> /// <param name="isLocked">Optional filter by items that are locked.</param>
/// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param> /// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param>
@@ -575,12 +595,12 @@ namespace Jellyfin.Api.Controllers
/// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param> /// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param>
/// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param> /// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param>
/// <param name="is3D">Optional filter by items that are 3D, or not.</param> /// <param name="is3D">Optional filter by items that are 3D, or not.</param>
/// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimeted.</param> /// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param>
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
/// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimeted.</param> /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.</param>
/// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimeted.</param> /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.</param>
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param> /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional, include image information in output.</param> /// <param name="enableImages">Optional, include image information in output.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
@@ -613,6 +633,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId, [FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId, [FromQuery] bool? hasTmdbId,
[FromQuery] bool? hasTvdbId, [FromQuery] bool? hasTvdbId,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSeries,
[FromQuery] bool? isNews,
[FromQuery] bool? isKids,
[FromQuery] bool? isSports,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
@@ -695,6 +720,11 @@ namespace Jellyfin.Api.Controllers
hasImdbId, hasImdbId,
hasTmdbId, hasTmdbId,
hasTvdbId, hasTvdbId,
isMovie,
isSeries,
isNews,
isKids,
isSports,
excludeItemIds, excludeItemIds,
startIndex, startIndex,
limit, limit,

View File

@@ -155,7 +155,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Package repositories saved.</response> /// <response code="204">Package repositories saved.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Repositories")] [HttpPost("Repositories")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SetRepositories([FromBody, Required] List<RepositoryInfo> repositoryInfos) public ActionResult SetRepositories([FromBody, Required] List<RepositoryInfo> repositoryInfos)
{ {

View File

@@ -57,6 +57,11 @@ namespace Jellyfin.Api.Controllers
/// <param name="hasImdbId">Optional filter by items that have an imdb id or not.</param> /// <param name="hasImdbId">Optional filter by items that have an imdb id or not.</param>
/// <param name="hasTmdbId">Optional filter by items that have a tmdb id or not.</param> /// <param name="hasTmdbId">Optional filter by items that have a tmdb id or not.</param>
/// <param name="hasTvdbId">Optional filter by items that have a tvdb id or not.</param> /// <param name="hasTvdbId">Optional filter by items that have a tvdb id or not.</param>
/// <param name="isMovie">Optional filter for live tv movies.</param>
/// <param name="isSeries">Optional filter for live tv series.</param>
/// <param name="isNews">Optional filter for live tv news.</param>
/// <param name="isKids">Optional filter for live tv kids.</param>
/// <param name="isSports">Optional filter for live tv sports.</param>
/// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param> /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.</param>
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param> /// <param name="limit">Optional. The maximum number of records to return.</param>
@@ -140,6 +145,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId, [FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId, [FromQuery] bool? hasTmdbId,
[FromQuery] bool? hasTvdbId, [FromQuery] bool? hasTvdbId,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSeries,
[FromQuery] bool? isNews,
[FromQuery] bool? isKids,
[FromQuery] bool? isSports,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
@@ -224,6 +234,11 @@ namespace Jellyfin.Api.Controllers
hasImdbId, hasImdbId,
hasTmdbId, hasTmdbId,
hasTvdbId, hasTvdbId,
isMovie,
isSeries,
isNews,
isKids,
isSports,
excludeItemIds, excludeItemIds,
startIndex, startIndex,
limit, limit,

View File

@@ -256,9 +256,17 @@ namespace Jellyfin.Api.Helpers
streamInfo.StartPositionTicks = startTimeTicks; streamInfo.StartPositionTicks = startTimeTicks;
mediaSource.SupportsDirectPlay = streamInfo.PlayMethod == PlayMethod.DirectPlay; mediaSource.SupportsDirectPlay = streamInfo.PlayMethod == PlayMethod.DirectPlay;
// Players do not handle this being set according to PlayMethod // Players do not handle this being set according to PlayMethod
mediaSource.SupportsDirectStream = options.EnableDirectStream ? streamInfo.PlayMethod == PlayMethod.DirectPlay || streamInfo.PlayMethod == PlayMethod.DirectStream : streamInfo.PlayMethod == PlayMethod.DirectPlay; mediaSource.SupportsDirectStream =
mediaSource.SupportsTranscoding = streamInfo.PlayMethod == PlayMethod.DirectStream || mediaSource.TranscodingContainer != null; options.EnableDirectStream
? streamInfo.PlayMethod == PlayMethod.DirectPlay || streamInfo.PlayMethod == PlayMethod.DirectStream
: streamInfo.PlayMethod == PlayMethod.DirectPlay;
mediaSource.SupportsTranscoding =
streamInfo.PlayMethod == PlayMethod.DirectStream
|| mediaSource.TranscodingContainer != null
|| profile.TranscodingProfiles.Any(i => i.Type == streamInfo.MediaType && i.Context == options.Context);
if (item is Audio) if (item is Audio)
{ {
@@ -290,7 +298,7 @@ namespace Jellyfin.Api.Helpers
} }
else else
{ {
if (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream) if (!mediaSource.SupportsDirectPlay && (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream))
{ {
streamInfo.PlayMethod = PlayMethod.Transcode; streamInfo.PlayMethod = PlayMethod.Transcode;
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');

View File

@@ -17,10 +17,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.0" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -362,7 +362,7 @@ namespace Jellyfin.Data.Entities
/// <returns><c>True</c> if the user has the specified permission.</returns> /// <returns><c>True</c> if the user has the specified permission.</returns>
public bool HasPermission(PermissionKind kind) public bool HasPermission(PermissionKind kind)
{ {
return Permissions.First(p => p.Kind == kind).Value; return Permissions.FirstOrDefault(p => p.Kind == kind)?.Value ?? false;
} }
/// <summary> /// <summary>
@@ -372,7 +372,15 @@ namespace Jellyfin.Data.Entities
/// <param name="value">The value to set.</param> /// <param name="value">The value to set.</param>
public void SetPermission(PermissionKind kind, bool value) public void SetPermission(PermissionKind kind, bool value)
{ {
Permissions.First(p => p.Kind == kind).Value = value; var currentPermission = Permissions.FirstOrDefault(p => p.Kind == kind);
if (currentPermission == null)
{
Permissions.Add(new Permission(kind, value));
}
else
{
currentPermission.Value = value;
}
} }
/// <summary> /// <summary>
@@ -382,9 +390,9 @@ namespace Jellyfin.Data.Entities
/// <returns>A string array containing the user's preferences.</returns> /// <returns>A string array containing the user's preferences.</returns>
public string[] GetPreference(PreferenceKind preference) public string[] GetPreference(PreferenceKind preference)
{ {
var val = Preferences.First(p => p.Kind == preference).Value; var val = Preferences.FirstOrDefault(p => p.Kind == preference)?.Value;
return Equals(val, string.Empty) ? Array.Empty<string>() : val.Split(Delimiter); return string.IsNullOrEmpty(val) ? Array.Empty<string>() : val.Split(Delimiter);
} }
/// <summary> /// <summary>
@@ -395,7 +403,7 @@ namespace Jellyfin.Data.Entities
/// <returns>A {T} array containing the user's preference.</returns> /// <returns>A {T} array containing the user's preference.</returns>
public T[] GetPreferenceValues<T>(PreferenceKind preference) public T[] GetPreferenceValues<T>(PreferenceKind preference)
{ {
var val = Preferences.First(p => p.Kind == preference).Value; var val = Preferences.FirstOrDefault(p => p.Kind == preference)?.Value;
if (string.IsNullOrEmpty(val)) if (string.IsNullOrEmpty(val))
{ {
return Array.Empty<T>(); return Array.Empty<T>();
@@ -432,8 +440,16 @@ namespace Jellyfin.Data.Entities
/// <param name="values">The values.</param> /// <param name="values">The values.</param>
public void SetPreference(PreferenceKind preference, string[] values) public void SetPreference(PreferenceKind preference, string[] values)
{ {
Preferences.First(p => p.Kind == preference).Value var value = string.Join(Delimiter, values);
= string.Join(Delimiter, values); var currentPreference = Preferences.FirstOrDefault(p => p.Kind == preference);
if (currentPreference == null)
{
Preferences.Add(new Preference(preference, value));
}
else
{
currentPreference.Value = value;
}
} }
/// <summary> /// <summary>
@@ -444,8 +460,16 @@ namespace Jellyfin.Data.Entities
/// <typeparam name="T">The type of value.</typeparam> /// <typeparam name="T">The type of value.</typeparam>
public void SetPreference<T>(PreferenceKind preference, T[] values) public void SetPreference<T>(PreferenceKind preference, T[] values)
{ {
Preferences.First(p => p.Kind == preference).Value var value = string.Join(Delimiter, values);
= string.Join(Delimiter, values); var currentPreference = Preferences.FirstOrDefault(p => p.Kind == preference);
if (currentPreference == null)
{
Preferences.Add(new Preference(preference, value));
}
else
{
currentPreference.Value = value;
}
} }
/// <summary> /// <summary>

View File

@@ -18,8 +18,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BlurHashSharp" Version="1.2.0" /> <PackageReference Include="BlurHashSharp" Version="1.2.0" />
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" /> <PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" /> <PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.80.3" /> <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using BlurHashSharp.SkiaSharp; using BlurHashSharp.SkiaSharp;
using Diacritics.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;

View File

@@ -463,6 +463,18 @@ namespace Jellyfin.Networking.Manager
/// <inheritdoc/> /// <inheritdoc/>
public bool IsInLocalNetwork(IPObject address) public bool IsInLocalNetwork(IPObject address)
{
return IsInLocalNetwork(address.Address);
}
/// <inheritdoc/>
public bool IsInLocalNetwork(string address)
{
return IPHost.TryParse(address, out IPHost ipHost) && IsInLocalNetwork(ipHost);
}
/// <inheritdoc/>
public bool IsInLocalNetwork(IPAddress address)
{ {
if (address == null) if (address == null)
{ {
@@ -481,36 +493,7 @@ namespace Jellyfin.Networking.Manager
} }
// As private addresses can be redefined by Configuration.LocalNetworkAddresses // As private addresses can be redefined by Configuration.LocalNetworkAddresses
return address.IsLoopback() || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address)); return IPAddress.IsLoopback(address) || (_lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address));
}
/// <inheritdoc/>
public bool IsInLocalNetwork(string address)
{
if (IPHost.TryParse(address, out IPHost ep))
{
return _lanSubnets.ContainsAddress(ep) && !_excludedSubnets.ContainsAddress(ep);
}
return false;
}
/// <inheritdoc/>
public bool IsInLocalNetwork(IPAddress address)
{
if (address == null)
{
throw new ArgumentNullException(nameof(address));
}
// See conversation at https://github.com/jellyfin/jellyfin/pull/3515.
if (TrustAllIP6Interfaces && address.AddressFamily == AddressFamily.InterNetworkV6)
{
return true;
}
// As private addresses can be redefined by Configuration.LocalNetworkAddresses
return _lanSubnets.ContainsAddress(address) && !_excludedSubnets.ContainsAddress(address);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -27,13 +27,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Linq.Async" Version="6.0.1" /> <PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@@ -16,11 +17,16 @@ namespace Jellyfin.Server.Implementations.Security
{ {
private readonly JellyfinDbProvider _jellyfinDbProvider; private readonly JellyfinDbProvider _jellyfinDbProvider;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IServerApplicationHost _serverApplicationHost;
public AuthorizationContext(JellyfinDbProvider jellyfinDb, IUserManager userManager) public AuthorizationContext(
JellyfinDbProvider jellyfinDb,
IUserManager userManager,
IServerApplicationHost serverApplicationHost)
{ {
_jellyfinDbProvider = jellyfinDb; _jellyfinDbProvider = jellyfinDb;
_userManager = userManager; _userManager = userManager;
_serverApplicationHost = serverApplicationHost;
} }
public Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext) public Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext)
@@ -187,17 +193,17 @@ namespace Jellyfin.Server.Implementations.Security
authInfo.Token = key.AccessToken; authInfo.Token = key.AccessToken;
if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
{ {
authInfo.DeviceId = string.Empty; authInfo.DeviceId = _serverApplicationHost.SystemId;
} }
if (string.IsNullOrWhiteSpace(authInfo.Device)) if (string.IsNullOrWhiteSpace(authInfo.Device))
{ {
authInfo.Device = string.Empty; authInfo.Device = _serverApplicationHost.Name;
} }
if (string.IsNullOrWhiteSpace(authInfo.Version)) if (string.IsNullOrWhiteSpace(authInfo.Version))
{ {
authInfo.Version = string.Empty; authInfo.Version = _serverApplicationHost.ApplicationVersionString;
} }
authInfo.IsApiKey = true; authInfo.IsApiKey = true;

View File

@@ -440,6 +440,12 @@ namespace Jellyfin.Server.Extensions
.Cast<IOpenApiAny>() .Cast<IOpenApiAny>()
.ToArray() .ToArray()
}); });
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
options.MapType<Version>(() => new OpenApiSchema
{
Type = "string"
});
} }
} }
} }

View File

@@ -1,4 +1,8 @@
using MediaBrowser.Common.Plugins; using System;
using Jellyfin.Extensions;
using Jellyfin.Server.Migrations;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.ApiClient; using MediaBrowser.Model.ApiClient;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@@ -14,6 +18,19 @@ namespace Jellyfin.Server.Filters
/// </summary> /// </summary>
public class AdditionalModelFilter : IDocumentFilter public class AdditionalModelFilter : IDocumentFilter
{ {
// Array of options that should not be visible in the api spec.
private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions) };
private readonly IServerConfigurationManager _serverConfigurationManager;
/// <summary>
/// Initializes a new instance of the <see cref="AdditionalModelFilter"/> class.
/// </summary>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
public AdditionalModelFilter(IServerConfigurationManager serverConfigurationManager)
{
_serverConfigurationManager = serverConfigurationManager;
}
/// <inheritdoc /> /// <inheritdoc />
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{ {
@@ -29,6 +46,16 @@ namespace Jellyfin.Server.Filters
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository);
foreach (var configuration in _serverConfigurationManager.GetConfigurationStores())
{
if (_ignoredConfigurations.IndexOf(configuration.ConfigurationType) != -1)
{
continue;
}
context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository);
}
} }
} }
} }

View File

@@ -34,11 +34,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.5" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.5" />
<PackageReference Include="prometheus-net" Version="6.0.0" /> <PackageReference Include="prometheus-net" Version="6.0.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" /> <PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" /> <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
@@ -48,7 +48,7 @@
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.3.0" /> <PackageReference Include="Serilog.Sinks.Graylog" Version="2.3.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -5,6 +5,7 @@ using Emby.Server.Implementations.Data;
using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Entities.Security;
using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SQLitePCL.pretty; using SQLitePCL.pretty;
@@ -20,6 +21,7 @@ namespace Jellyfin.Server.Migrations.Routines
private readonly ILogger<MigrateAuthenticationDb> _logger; private readonly ILogger<MigrateAuthenticationDb> _logger;
private readonly JellyfinDbProvider _dbProvider; private readonly JellyfinDbProvider _dbProvider;
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private readonly IUserManager _userManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class. /// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
@@ -27,11 +29,17 @@ namespace Jellyfin.Server.Migrations.Routines
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="dbProvider">The database provider.</param> /// <param name="dbProvider">The database provider.</param>
/// <param name="appPaths">The server application paths.</param> /// <param name="appPaths">The server application paths.</param>
public MigrateAuthenticationDb(ILogger<MigrateAuthenticationDb> logger, JellyfinDbProvider dbProvider, IServerApplicationPaths appPaths) /// <param name="userManager">The user manager.</param>
public MigrateAuthenticationDb(
ILogger<MigrateAuthenticationDb> logger,
JellyfinDbProvider dbProvider,
IServerApplicationPaths appPaths,
IUserManager userManager)
{ {
_logger = logger; _logger = logger;
_dbProvider = dbProvider; _dbProvider = dbProvider;
_appPaths = appPaths; _appPaths = appPaths;
_userManager = userManager;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -74,6 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines
} }
else else
{ {
var userId = new Guid(row[6].ToString());
var user = _userManager.GetUserById(userId);
if (user is null)
{
// User doesn't exist, don't bring over the device.
continue;
}
dbContext.Devices.Add(new Device( dbContext.Devices.Add(new Device(
new Guid(row[6].ToString()), new Guid(row[6].ToString()),
row[3].ToString(), row[3].ToString(),

View File

@@ -545,12 +545,14 @@ namespace Jellyfin.Server
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json"; const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath) Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'"); ?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await using (resource.ConfigureAwait(false)) await using (resource.ConfigureAwait(false))
await using (dst.ConfigureAwait(false))
{ {
// Copy the resource contents to the expected file path for the config file Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await resource.CopyToAsync(dst).ConfigureAwait(false); await using (dst.ConfigureAwait(false))
{
// Copy the resource contents to the expected file path for the config file
await resource.CopyToAsync(dst).ConfigureAwait(false);
}
} }
} }

View File

@@ -60,6 +60,12 @@ namespace MediaBrowser.Common.Configuration
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
object GetConfiguration(string key); object GetConfiguration(string key);
/// <summary>
/// Gets the array of coniguration stores.
/// </summary>
/// <returns>Array of ConfigurationStore.</returns>
ConfigurationStore[] GetConfigurationStores();
/// <summary> /// <summary>
/// Gets the type of the configuration. /// Gets the type of the configuration.
/// </summary> /// </summary>

View File

@@ -65,7 +65,7 @@ namespace MediaBrowser.Common.Net
address = address.MapToIPv4(); address = address.MapToIPv4();
} }
if (IsLoopback(address)) if (IPAddress.IsLoopback(address))
{ {
return (address, prefixLength); return (address, prefixLength);
} }
@@ -102,31 +102,6 @@ namespace MediaBrowser.Common.Net
return (new IPAddress(addressBytes), prefixLength); return (new IPAddress(addressBytes), prefixLength);
} }
/// <summary>
/// Tests to see if the ip address is a Loopback address.
/// </summary>
/// <param name="address">Value to test.</param>
/// <returns>True if it is.</returns>
public static bool IsLoopback(IPAddress address)
{
if (address == null)
{
throw new ArgumentNullException(nameof(address));
}
if (!address.Equals(IPAddress.None))
{
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
return address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback);
}
return false;
}
/// <summary> /// <summary>
/// Tests to see if the ip address is an IP6 address. /// Tests to see if the ip address is an IP6 address.
/// </summary> /// </summary>
@@ -295,7 +270,7 @@ namespace MediaBrowser.Common.Net
/// <returns>True if it is.</returns> /// <returns>True if it is.</returns>
public virtual bool IsLoopback() public virtual bool IsLoopback()
{ {
return IsLoopback(Address); return IPAddress.IsLoopback(Address);
} }
/// <summary> /// <summary>

View File

@@ -8,9 +8,9 @@ using System.Linq;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@@ -5,8 +5,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.Audio namespace MediaBrowser.Controller.Entities.Audio

View File

@@ -11,7 +11,6 @@ using System.Text;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Diacritics.Extensions;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;

View File

@@ -5,8 +5,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Diacritics.Extensions;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities

View File

@@ -5,7 +5,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Diacritics.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@@ -5,7 +5,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Diacritics.Extensions; using Jellyfin.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities

View File

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

View File

@@ -18,7 +18,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Diacritics" Version="3.3.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />

View File

@@ -13,11 +13,13 @@ using System.Threading;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {
@@ -32,6 +34,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder; private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config;
private static readonly string[] _videoProfilesH264 = new[] private static readonly string[] _videoProfilesH264 = new[]
{ {
@@ -54,11 +57,13 @@ namespace MediaBrowser.Controller.MediaEncoding
public EncodingHelper( public EncodingHelper(
IApplicationPaths appPaths, IApplicationPaths appPaths,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder) ISubtitleEncoder subtitleEncoder,
IConfiguration config)
{ {
_appPaths = appPaths; _appPaths = appPaths;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_subtitleEncoder = subtitleEncoder; _subtitleEncoder = subtitleEncoder;
_config = config;
} }
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -144,15 +149,28 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{ {
if (state.VideoStream == null) if (state.VideoStream == null
|| !options.EnableTonemapping
|| GetVideoColorBitDepth(state) != 10)
{ {
return false; return false;
} }
return options.EnableTonemapping if (string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase)
&& (string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) || string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) || string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase))
&& GetVideoColorBitDepth(state) == 10; {
// Only native SW decoder and HW accelerator can parse dovi rpu.
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder;
}
return string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase);
} }
private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options) private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
@@ -516,8 +534,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase)) if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
{ {
// flac is experimental in mp4 muxer return "flac";
return "flac -strict -2";
} }
return codec.ToLowerInvariant(); return codec.ToLowerInvariant();
@@ -1024,7 +1041,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase) if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)) || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
{ {
return FormattableString.Invariant($" -qmin 18 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); // Override the too high default qmin 18 in transcoding preset
return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
} }
if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
@@ -1062,10 +1080,12 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase)) else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{ {
// Clients may direct play higher than level 41, but there's no reason to transcode higher. // Transcode to level 5.1 and lower for maximum compatibility.
if (requestLevel >= 41) // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
// https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
if (requestLevel >= 51)
{ {
return "41"; return "51";
} }
} }
} }
@@ -1220,10 +1240,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// Example: we encoded half of desired length, then codec detected // Example: we encoded half of desired length, then codec detected
// scene cut and inserted a keyframe; next forced keyframe would // scene cut and inserted a keyframe; next forced keyframe would
// be created outside of segment, which breaks seeking. // be created outside of segment, which breaks seeking.
// -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe.
gopArg = string.Format( gopArg = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
" -g:v:0 {0} -keyint_min:v:0 {0} -sc_threshold:v:0 0", " -g:v:0 {0} -keyint_min:v:0 {0}",
Math.Ceiling(segmentLength * framerate.Value)); Math.Ceiling(segmentLength * framerate.Value));
} }
@@ -1243,6 +1262,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
{ {
args += keyFrameArg; args += keyFrameArg;
// prevent the libx264 from post processing to break the set keyframe.
if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase))
{
args += " -sc_threshold:v:0 0";
}
} }
else else
{ {
@@ -2213,13 +2238,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return state.IsInputVideo ? "-sn" : string.Empty; return state.IsInputVideo ? "-sn" : string.Empty;
} }
// We have media info, but we don't know the stream indexes // We have media info, but we don't know the stream index
if (state.VideoStream != null && state.VideoStream.Index == -1) if (state.VideoStream != null && state.VideoStream.Index == -1)
{ {
return "-sn"; return "-sn";
} }
// We have media info, but we don't know the stream indexes // We have media info, but we don't know the stream index
if (state.AudioStream != null && state.AudioStream.Index == -1) if (state.AudioStream != null && state.AudioStream.Index == -1)
{ {
return state.IsInputVideo ? "-sn" : string.Empty; return state.IsInputVideo ? "-sn" : string.Empty;
@@ -2229,10 +2254,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.VideoStream != null) if (state.VideoStream != null)
{ {
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
args += string.Format( args += string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"-map 0:{0}", "-map 0:{0}",
state.VideoStream.Index); videoStreamIndex);
} }
else else
{ {
@@ -2242,23 +2269,24 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream != null) if (state.AudioStream != null)
{ {
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
if (state.AudioStream.IsExternal) if (state.AudioStream.IsExternal)
{ {
int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1; bool hasExternalGraphicsSubs = state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream;
int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream); int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
args += string.Format( args += string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
" -map {0}:{1}", " -map {0}:{1}",
externalAudioMapIndex, externalAudioMapIndex,
externalAudioStream); audioStreamIndex);
} }
else else
{ {
args += string.Format( args += string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
" -map 0:{0}", " -map 0:{0}",
state.AudioStream.Index); audioStreamIndex);
} }
} }
else else
@@ -2273,14 +2301,21 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else if (subtitleMethod == SubtitleDeliveryMethod.Embed) else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
{ {
int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format( args += string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
" -map 0:{0}", " -map 0:{0}",
state.SubtitleStream.Index); subtitleStreamIndex);
} }
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{ {
args += " -map 1:0 -sn"; int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 1:{0} -sn",
externalSubtitleStreamIndex);
} }
return args; return args;
@@ -2509,7 +2544,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", "scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
maxWidthParam, maxWidthParam,
maxHeightParam, maxHeightParam,
scaleVal); scaleVal);
@@ -2553,7 +2588,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2", "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
maxWidthParam, maxWidthParam,
scaleVal); scaleVal);
} }
@@ -2565,7 +2600,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})", "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
maxHeightParam, maxHeightParam,
scaleVal); scaleVal);
} }
@@ -2614,7 +2649,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else else
{ {
filter = "scale={0}:trunc({0}/dar/2)*2"; filter = "scale={0}:trunc({0}/a/2)*2";
} }
} }
@@ -2768,8 +2803,8 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else if (hasGraphicalSubs) else if (hasGraphicalSubs)
{ {
// [0:s]scale=s=1280x720 // [0:s]scale=expr
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -2955,7 +2990,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3153,7 +3190,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3381,7 +3420,8 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
// qsv requires a fixed pool size. // qsv requires a fixed pool size.
subFilters.Add("hwupload=extra_hw_frames=32"); // default to 64 otherwise it will fail on certain iGPU.
subFilters.Add("hwupload=extra_hw_frames=64");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue) var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3398,7 +3438,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3589,7 +3631,8 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
// qsv requires a fixed pool size. // qsv requires a fixed pool size.
subFilters.Add("hwupload=extra_hw_frames=32"); // default to 64 otherwise it will fail on certain iGPU.
subFilters.Add("hwupload=extra_hw_frames=64");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue) var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3606,7 +3649,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
} }
@@ -3853,7 +3898,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4028,7 +4075,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4124,9 +4173,8 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Join(',', overlayFilters)); string.Join(',', overlayFilters));
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal); var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
var subtitleStreamIndex = state.SubtitleStream.IsExternal var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
? 0 var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
: state.SubtitleStream.Index;
if (hasSubs) if (hasSubs)
{ {
@@ -4147,7 +4195,7 @@ namespace MediaBrowser.Controller.MediaEncoding
filterStr, filterStr,
mapPrefix, mapPrefix,
subtitleStreamIndex, subtitleStreamIndex,
state.VideoStream.Index, videoStreamIndex,
mainStr, mainStr,
subStr, subStr,
overlayStr); overlayStr);
@@ -4840,22 +4888,21 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer) public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
{ {
var inputModifier = string.Empty; var inputModifier = string.Empty;
var probeSizeArgument = string.Empty; var analyzeDurationArgument = string.Empty;
string analyzeDurationArgument; // Apply -analyzeduration as per the environment variable,
if (state.MediaSource.AnalyzeDurationMs.HasValue) // otherwise ffmpeg will break on certain files due to default value is 0.
// The default value of -probesize is more than enough, so leave it as is.
var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
{
analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
}
else if (state.MediaSource.AnalyzeDurationMs.HasValue)
{ {
analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToString(CultureInfo.InvariantCulture); analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToString(CultureInfo.InvariantCulture);
} }
else
{
analyzeDurationArgument = string.Empty;
}
if (!string.IsNullOrEmpty(probeSizeArgument))
{
inputModifier += " " + probeSizeArgument;
}
if (!string.IsNullOrEmpty(analyzeDurationArgument)) if (!string.IsNullOrEmpty(analyzeDurationArgument))
{ {
@@ -5357,12 +5404,22 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
} }
// opus will fail on 44100
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{ {
if (state.OutputAudioSampleRate.HasValue) // opus only supports specific sampling rates
var sampleRate = state.OutputAudioSampleRate;
if (sampleRate.HasValue)
{ {
audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); var sampleRateValue = sampleRate.Value switch
{
<= 8000 => 8000,
<= 12000 => 12000,
<= 16000 => 16000,
<= 24000 => 24000,
_ => 48000
};
audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
} }
} }
@@ -5384,6 +5441,28 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Empty).Trim(); string.Empty).Trim();
} }
public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
{
var index = 0;
var length = mediaStreams.Count;
for (var i = 0; i < length; i++)
{
var currentMediaStream = mediaStreams[i];
if (currentMediaStream == streamToFind)
{
return index;
}
if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
{
index++;
}
}
return -1;
}
public static bool IsCopyCodec(string codec) public static bool IsCopyCodec(string codec)
{ {
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);

View File

@@ -141,6 +141,13 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
string GetInputArgument(string inputFile, MediaSourceInfo mediaSource); string GetInputArgument(string inputFile, MediaSourceInfo mediaSource);
/// <summary>
/// Gets the input argument for an external subtitle file.
/// </summary>
/// <param name="inputFile">The input file.</param>
/// <returns>System.String.</returns>
string GetExternalSubtitleInputArgument(string inputFile);
/// <summary> /// <summary>
/// Gets the time parameter. /// Gets the time parameter.
/// </summary> /// </summary>

View File

@@ -5,6 +5,11 @@ namespace MediaBrowser.Controller.Resolvers
/// </summary> /// </summary>
public enum ResolverPriority public enum ResolverPriority
{ {
/// <summary>
/// The highest priority. Used by plugins to bypass the default server resolvers.
/// </summary>
Plugin = 0,
/// <summary> /// <summary>
/// The first. /// The first.
/// </summary> /// </summary>

View File

@@ -352,6 +352,6 @@ namespace MediaBrowser.Controller.Session
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task RevokeUserTokens(Guid userId, string currentAccessToken); Task RevokeUserTokens(Guid userId, string currentAccessToken);
void CloseIfNeeded(SessionInfo session); Task CloseIfNeededAsync(SessionInfo session);
} }
} }

View File

@@ -16,6 +16,7 @@ using MediaBrowser.Common;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -49,6 +50,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IConfiguration _config;
private readonly string _startupOptionFFmpegPath; private readonly string _startupOptionFFmpegPath;
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2); private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
@@ -85,6 +87,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_configurationManager = configurationManager; _configurationManager = configurationManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_localization = localization; _localization = localization;
_config = config;
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty; _startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
_jsonSerializerOptions = JsonDefaults.Options; _jsonSerializerOptions = JsonDefaults.Options;
} }
@@ -371,8 +374,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
var inputFile = request.MediaSource.Path; var inputFile = request.MediaSource.Path;
string analyzeDuration = string.Empty; string analyzeDuration = string.Empty;
string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (request.MediaSource.AnalyzeDurationMs > 0) if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
{
analyzeDuration = "-analyzeduration " + ffmpegAnalyzeDuration;
}
else if (request.MediaSource.AnalyzeDurationMs > 0)
{ {
analyzeDuration = "-analyzeduration " + analyzeDuration = "-analyzeduration " +
(request.MediaSource.AnalyzeDurationMs * 1000).ToString(); (request.MediaSource.AnalyzeDurationMs * 1000).ToString();
@@ -411,6 +419,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol); return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol);
} }
/// <summary>
/// Gets the input argument for an external subtitle file.
/// </summary>
/// <param name="inputFile">The input file.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentException">Unrecognized InputType.</exception>
public string GetExternalSubtitleInputArgument(string inputFile)
{
const string Prefix = "file";
return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File);
}
/// <summary> /// <summary>
/// Gets the media info internal. /// Gets the media info internal.
/// </summary> /// </summary>
@@ -616,10 +637,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24")); filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24"));
} }
// Use SW tonemap on HDR video stream only when the zscale filter is available. // Use SW tonemap on HDR10/HLG video stream only when the zscale filter is available.
var enableHdrExtraction = string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) && SupportsFilter("zscale"); var enableHdrExtraction = false;
if (enableHdrExtraction)
if ((string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
&& SupportsFilter("zscale"))
{ {
enableHdrExtraction = true;
filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p"); filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p");
} }

View File

@@ -195,7 +195,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
MediaStream subtitleStream, MediaStream subtitleStream,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (!subtitleStream.IsExternal) if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
{ {
string outputFormat; string outputFormat;
string outputCodec; string outputCodec;
@@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
// Extract // Extract
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat); var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
await ExtractTextSubtitle(mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken) await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false); return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
@@ -494,7 +494,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Extracts the text subtitle. /// Extracts the text subtitle.
/// </summary> /// </summary>
/// <param name="mediaSource">The mediaSource.</param> /// <param name="mediaSource">The mediaSource.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param> /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="outputCodec">The output codec.</param> /// <param name="outputCodec">The output codec.</param>
/// <param name="outputPath">The output path.</param> /// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
@@ -502,7 +502,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception> /// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
private async Task ExtractTextSubtitle( private async Task ExtractTextSubtitle(
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
int subtitleStreamIndex, MediaStream subtitleStream,
string outputCodec, string outputCodec,
string outputPath, string outputPath,
CancellationToken cancellationToken) CancellationToken cancellationToken)
@@ -511,12 +511,21 @@ namespace MediaBrowser.MediaEncoding.Subtitles
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
try try
{ {
if (!File.Exists(outputPath)) if (!File.Exists(outputPath))
{ {
var args = _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource);
if (subtitleStream.IsExternal)
{
args = _mediaEncoder.GetExternalSubtitleInputArgument(subtitleStream.Path);
}
await ExtractTextSubtitleInternal( await ExtractTextSubtitleInternal(
_mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource), args,
subtitleStreamIndex, subtitleStreamIndex,
outputCodec, outputCodec,
outputPath, outputPath,
@@ -672,11 +681,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (!string.Equals(text, newText, StringComparison.Ordinal)) if (!string.Equals(text, newText, StringComparison.Ordinal))
{ {
var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
var writer = new StreamWriter(fileStream, encoding);
await using (fileStream.ConfigureAwait(false)) await using (fileStream.ConfigureAwait(false))
await using (writer.ConfigureAwait(false))
{ {
await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false); var writer = new StreamWriter(fileStream, encoding);
await using (writer.ConfigureAwait(false))
{
await writer.WriteAsync(newText.AsMemory(), cancellationToken).ConfigureAwait(false);
}
} }
} }
} }

View File

@@ -39,7 +39,7 @@ namespace MediaBrowser.Model.Configuration
EnableHardwareEncoding = true; EnableHardwareEncoding = true;
AllowHevcEncoding = false; AllowHevcEncoding = false;
EnableSubtitleExtraction = true; EnableSubtitleExtraction = true;
AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = Array.Empty<string>(); AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" };
HardwareDecodingCodecs = new string[] { "h264", "vc1" }; HardwareDecodingCodecs = new string[] { "h264", "vc1" };
} }

View File

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

View File

@@ -79,7 +79,7 @@ namespace MediaBrowser.Model.Configuration
/// <summary> /// <summary>
/// Gets or sets a value indicating whether quick connect is available for use on this server. /// Gets or sets a value indicating whether quick connect is available for use on this server.
/// </summary> /// </summary>
public bool QuickConnectAvailable { get; set; } = false; public bool QuickConnectAvailable { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [enable case sensitive item ids]. /// Gets or sets a value indicating whether [enable case sensitive item ids].

View File

@@ -385,7 +385,7 @@ namespace MediaBrowser.Model.Dlna
// If device requirements are satisfied then allow both direct stream and direct play // If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay) if (item.SupportsDirectPlay)
{ {
if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay)) if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectPlay))
{ {
if (options.EnableDirectPlay) if (options.EnableDirectPlay)
{ {
@@ -401,7 +401,7 @@ namespace MediaBrowser.Model.Dlna
// While options takes the network and other factors into account. Only applies to direct stream // While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream) if (item.SupportsDirectStream)
{ {
if (IsItemBitrateEligibleForDirectPlay(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream)) if (IsItemBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(true) ?? 0, PlayMethod.DirectStream))
{ {
if (options.EnableDirectStream) if (options.EnableDirectStream)
{ {
@@ -604,11 +604,11 @@ namespace MediaBrowser.Model.Dlna
var videoStream = item.VideoStream; var videoStream = item.VideoStream;
var directPlayEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay); var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream); var directStreamBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectStream);
bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult == 0); bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayBitrateEligibility == 0);
bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directPlayEligibilityResult == 0); bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamBitrateEligibility == 0);
var transcodeReasons = directPlayEligibilityResult | directStreamEligibilityResult; var transcodeReasons = directPlayBitrateEligibility | directStreamBitrateEligibility;
_logger.LogDebug( _logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}", "Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@@ -625,7 +625,7 @@ namespace MediaBrowser.Model.Dlna
var directPlay = directPlayInfo.PlayMethod; var directPlay = directPlayInfo.PlayMethod;
transcodeReasons |= directPlayInfo.TranscodeReasons; transcodeReasons |= directPlayInfo.TranscodeReasons;
if (directPlay != null) if (directPlay.HasValue)
{ {
directPlayProfile = directPlayInfo.Profile; directPlayProfile = directPlayInfo.Profile;
playlistItem.PlayMethod = directPlay.Value; playlistItem.PlayMethod = directPlay.Value;
@@ -676,7 +676,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.TranscodeReasons = transcodeReasons; playlistItem.TranscodeReasons = transcodeReasons;
if (playlistItem.PlayMethod != PlayMethod.DirectStream || !options.EnableDirectStream) if (playlistItem.PlayMethod != PlayMethod.DirectStream && playlistItem.PlayMethod != PlayMethod.DirectPlay)
{ {
// Can't direct play, find the transcoding profile // Can't direct play, find the transcoding profile
// If we do this for direct-stream we will overwrite the info // If we do this for direct-stream we will overwrite the info
@@ -687,6 +687,8 @@ namespace MediaBrowser.Model.Dlna
BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec); BuildStreamVideoItem(playlistItem, options, item, videoStream, audioStream, candidateAudioStreams, transcodingProfile.Container, transcodingProfile.VideoCodec, transcodingProfile.AudioCodec);
playlistItem.PlayMethod = PlayMethod.Transcode;
if (subtitleStream != null) if (subtitleStream != null)
{ {
var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol); var subtitleProfile = GetSubtitleProfile(item, subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, _transcoderSupport, transcodingProfile.Container, transcodingProfile.Protocol);
@@ -696,14 +698,9 @@ namespace MediaBrowser.Model.Dlna
playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format }; playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
} }
if (playlistItem.PlayMethod != PlayMethod.DirectPlay) if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
{ {
playlistItem.PlayMethod = PlayMethod.Transcode; ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
if ((playlistItem.TranscodeReasons & (VideoReasons | TranscodeReason.ContainerBitrateExceedsLimit)) != 0)
{
ApplyTranscodingConditions(playlistItem, transcodingProfile.Conditions, null, true, true);
}
} }
} }
} }
@@ -744,16 +741,19 @@ namespace MediaBrowser.Model.Dlna
{ {
var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec); var videoCodecs = ContainerProfile.SplitValue(transcodingProfile.VideoCodec);
if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream.Codec)) if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec))
{ {
var videoCodec = transcodingProfile.VideoCodec; var videoCodec = transcodingProfile.VideoCodec;
var container = transcodingProfile.Container; var container = transcodingProfile.Container;
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodec, container)) i.ContainsAnyCodec(videoCodec, container) &&
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)))
.Select(i => .Select(i =>
i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC))); i.Conditions.All(condition => ConditionProcessor.IsVideoConditionSatisfied(condition, videoStream?.Width, videoStream?.Height, videoStream?.BitDepth, videoStream?.BitRate, videoStream?.Profile, videoStream?.Level, videoFramerate, videoStream?.PacketLength, timestamp, videoStream?.IsAnamorphic, videoStream?.IsInterlaced, videoStream?.RefFrames, numVideoStreams, numAudioStreams, videoStream?.CodecTag, videoStream?.IsAVC)));
var conditionsSatisfied = !appliedVideoConditions.Any() || !appliedVideoConditions.Any(satisfied => !satisfied);
// An empty appliedVideoConditions means that the codec has no conditions for the current video stream
var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied);
return conditionsSatisfied ? 1 : 2; return conditionsSatisfied ? 1 : 2;
} }
@@ -768,35 +768,42 @@ namespace MediaBrowser.Model.Dlna
private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec) private void BuildStreamVideoItem(StreamInfo playlistItem, VideoOptions options, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream, IEnumerable<MediaStream> candidateAudioStreams, string container, string videoCodec, string audioCodec)
{ {
// prefer matching video codecs // Prefer matching video codecs
var videoCodecs = ContainerProfile.SplitValue(videoCodec); var videoCodecs = ContainerProfile.SplitValue(videoCodec);
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream.Codec) ? videoStream.Codec : null; var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
playlistItem.VideoCodecs = directVideoCodec != null ? new[] { directVideoCodec } : videoCodecs; if (directVideoCodec != null)
{
// merge directVideoCodec to videoCodecs
Array.Resize(ref videoCodecs, videoCodecs.Length + 1);
videoCodecs[^1] = directVideoCodec;
}
// copy video codec options as a starting point, this applies to transcode and direct-stream playlistItem.VideoCodecs = videoCodecs;
playlistItem.MaxFramerate = videoStream.AverageFrameRate;
var qualifier = videoStream.Codec; // Copy video codec options as a starting point, this applies to transcode and direct-stream
if (videoStream.Level.HasValue) playlistItem.MaxFramerate = videoStream?.AverageFrameRate;
var qualifier = videoStream?.Codec;
if (videoStream?.Level != null)
{ {
playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture)); playlistItem.SetOption(qualifier, "level", videoStream.Level.Value.ToString(CultureInfo.InvariantCulture));
} }
if (videoStream.BitDepth.HasValue) if (videoStream?.BitDepth != null)
{ {
playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture)); playlistItem.SetOption(qualifier, "videobitdepth", videoStream.BitDepth.Value.ToString(CultureInfo.InvariantCulture));
} }
if (!string.IsNullOrEmpty(videoStream.Profile)) if (!string.IsNullOrEmpty(videoStream?.Profile))
{ {
playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant()); playlistItem.SetOption(qualifier, "profile", videoStream.Profile.ToLowerInvariant());
} }
if (videoStream.Level != 0) if (videoStream != null && videoStream.Level != 0)
{ {
playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString()); playlistItem.SetOption(qualifier, "level", videoStream.Level.ToString());
} }
// prefer matching audio codecs, could do better here // Prefer matching audio codecs, could do better here
var audioCodecs = ContainerProfile.SplitValue(audioCodec); var audioCodecs = ContainerProfile.SplitValue(audioCodec);
var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)); var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
playlistItem.AudioCodecs = audioCodecs; playlistItem.AudioCodecs = audioCodecs;
@@ -806,7 +813,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioStreamIndex = audioStream.Index; playlistItem.AudioStreamIndex = audioStream.Index;
playlistItem.AudioCodecs = new[] { audioStream.Codec }; playlistItem.AudioCodecs = new[] { audioStream.Codec };
// copy matching audio codec options // Copy matching audio codec options
playlistItem.AudioSampleRate = audioStream.SampleRate; playlistItem.AudioSampleRate = audioStream.SampleRate;
playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString()); playlistItem.SetOption(qualifier, "audiochannels", audioStream.Channels.ToString());
@@ -843,7 +850,7 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodec, container) && i.ContainsAnyCodec(videoCodec, container) &&
i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))); i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
var isFirstAppliedCodecProfile = true; var isFirstAppliedCodecProfile = true;
foreach (var i in appliedVideoConditions) foreach (var i in appliedVideoConditions)
{ {
@@ -873,9 +880,9 @@ namespace MediaBrowser.Model.Dlna
int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth; int? inputAudioBitDepth = audioStream == null ? null : audioStream.BitDepth;
var appliedAudioConditions = options.Profile.CodecProfiles var appliedAudioConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.VideoAudio &&
i.ContainsAnyCodec(audioCodec, container) && i.ContainsAnyCodec(audioCodec, container) &&
i.ApplyConditions.Any(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))); i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
isFirstAppliedCodecProfile = true; isFirstAppliedCodecProfile = true;
foreach (var i in appliedAudioConditions) foreach (var i in appliedAudioConditions)
{ {
@@ -1067,7 +1074,7 @@ namespace MediaBrowser.Model.Dlna
DeviceProfile profile = options.Profile; DeviceProfile profile = options.Profile;
string container = mediaSource.Container; string container = mediaSource.Container;
// video // Video
int? width = videoStream?.Width; int? width = videoStream?.Width;
int? height = videoStream?.Height; int? height = videoStream?.Height;
int? bitDepth = videoStream?.BitDepth; int? bitDepth = videoStream?.BitDepth;
@@ -1079,7 +1086,7 @@ namespace MediaBrowser.Model.Dlna
bool? isInterlaced = videoStream?.IsInterlaced; bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag; string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = videoStream?.IsAVC; bool? isAvc = videoStream?.IsAVC;
// audio // Audio
var defaultLanguage = audioStream?.Language ?? string.Empty; var defaultLanguage = audioStream?.Language ?? string.Empty;
var defaultMarked = audioStream?.IsDefault ?? false; var defaultMarked = audioStream?.IsDefault ?? false;
@@ -1108,18 +1115,9 @@ namespace MediaBrowser.Model.Dlna
profile, profile,
"VideoCodecProfile", "VideoCodecProfile",
profile.CodecProfiles profile.CodecProfiles
.Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container)) .Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container) &&
.SelectMany(codecProfile => !checkVideoConditions(codecProfile.ApplyConditions).Any())
{ .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
var failedApplyConditions = checkVideoConditions(codecProfile.ApplyConditions);
if (!failedApplyConditions.Any())
{
return Array.Empty<ProfileCondition>();
}
var failedConditions = checkVideoConditions(codecProfile.Conditions);
return failedApplyConditions.Concat(failedConditions);
}));
// Check audiocandidates profile conditions // Check audiocandidates profile conditions
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked)); var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked));
@@ -1189,7 +1187,18 @@ namespace MediaBrowser.Model.Dlna
audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream); audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
} }
var failureReasons = directPlayProfileReasons | containerProfileReasons | videoCodecProfileReasons | audioCodecProfileReasons | subtitleProfileReasons; var failureReasons = directPlayProfileReasons | containerProfileReasons | subtitleProfileReasons;
if ((failureReasons & TranscodeReason.VideoCodecNotSupported) == 0)
{
failureReasons |= videoCodecProfileReasons;
}
if ((failureReasons & TranscodeReason.AudioCodecNotSupported) == 0)
{
failureReasons |= audioCodecProfileReasons;
}
var directStreamFailureReasons = failureReasons & (~DirectStreamReasons); var directStreamFailureReasons = failureReasons & (~DirectStreamReasons);
PlayMethod? playMethod = null; PlayMethod? playMethod = null;
@@ -1206,6 +1215,7 @@ namespace MediaBrowser.Model.Dlna
return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked); return (Result: (Profile: directPlayProfile, PlayMethod: playMethod, AudioStreamIndex: selectedAudioStream?.Index, TranscodeReason: failureReasons), Order: order, Rank: ranked);
}) })
.OrderByDescending(analysis => analysis.Result.PlayMethod) .OrderByDescending(analysis => analysis.Result.PlayMethod)
.ThenByDescending(analysis => analysis.Rank)
.ThenBy(analysis => analysis.Order) .ThenBy(analysis => analysis.Order)
.ToArray() .ToArray()
.ToLookup(analysis => analysis.Result.PlayMethod != null); .ToLookup(analysis => analysis.Result.PlayMethod != null);
@@ -1218,7 +1228,7 @@ namespace MediaBrowser.Model.Dlna
return profileMatch; return profileMatch;
} }
var failureReasons = analyzedProfiles[false].OrderBy(a => a.Result.TranscodeReason).ThenBy(analysis => analysis.Order).FirstOrDefault().Result.TranscodeReason; var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason;
if (failureReasons == 0) if (failureReasons == 0)
{ {
failureReasons = TranscodeReason.DirectPlayError; failureReasons = TranscodeReason.DirectPlayError;
@@ -1264,13 +1274,13 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path"); mediaSource.Path ?? "Unknown path");
} }
private TranscodeReason IsEligibleForDirectPlay( private TranscodeReason IsBitrateEligibleForDirectPlayback(
MediaSourceInfo item, MediaSourceInfo item,
long maxBitrate, long maxBitrate,
VideoOptions options, VideoOptions options,
PlayMethod playMethod) PlayMethod playMethod)
{ {
bool result = IsItemBitrateEligibleForDirectPlay(item, maxBitrate, playMethod); bool result = IsItemBitrateEligibleForDirectPlayback(item, maxBitrate, playMethod);
if (!result) if (!result)
{ {
return TranscodeReason.ContainerBitrateExceedsLimit; return TranscodeReason.ContainerBitrateExceedsLimit;
@@ -1438,7 +1448,7 @@ namespace MediaBrowser.Model.Dlna
return null; return null;
} }
private bool IsItemBitrateEligibleForDirectPlay(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod) private bool IsItemBitrateEligibleForDirectPlayback(MediaSourceInfo item, long maxBitrate, PlayMethod playMethod)
{ {
// Don't restrict by bitrate if coming from an external domain // Don't restrict by bitrate if coming from an external domain
if (item.IsRemote) if (item.IsRemote)

View File

@@ -121,8 +121,7 @@ namespace MediaBrowser.Model.Entities
var codecTag = CodecTag; var codecTag = CodecTag;
if (string.Equals(codecTag, "dva1", StringComparison.OrdinalIgnoreCase) if (string.Equals(codecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codecTag, "dvav", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase) || string.Equals(codecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase) || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase)) || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase))

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace MediaBrowser.Model.Entities namespace MediaBrowser.Model.Entities
{ {
@@ -9,6 +10,16 @@ namespace MediaBrowser.Model.Entities
/// </summary> /// </summary>
public static class ProviderIdsExtensions public static class ProviderIdsExtensions
{ {
/// <summary>
/// Case insensitive dictionary of <see cref="MetadataProvider"/> string representation.
/// </summary>
private static readonly Dictionary<string, string> _metadataProviderEnumDictionary =
Enum.GetValues<MetadataProvider>()
.ToDictionary(
enumValue => enumValue.ToString(),
enumValue => enumValue.ToString(),
StringComparer.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// Checks if this instance has an id for the given provider. /// Checks if this instance has an id for the given provider.
/// </summary> /// </summary>
@@ -108,7 +119,7 @@ namespace MediaBrowser.Model.Entities
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public static void SetProviderId(this IHasProviderIds instance, string name, string value) public static void SetProviderId(this IHasProviderIds instance, string name, string? value)
{ {
if (instance == null) if (instance == null)
{ {
@@ -125,7 +136,15 @@ namespace MediaBrowser.Model.Entities
// Ensure it exists // Ensure it exists
instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
instance.ProviderIds[name] = value; // Match on internal MetadataProvider enum string values before adding arbitrary providers
if (_metadataProviderEnumDictionary.TryGetValue(name, out var enumValue))
{
instance.ProviderIds[enumValue] = value;
}
else
{
instance.ProviderIds[name] = value;
}
} }
} }

View File

@@ -35,12 +35,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="MimeTypes" Version="2.3.0"> <PackageReference Include="MimeTypes" Version="2.4.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="System.Globalization" Version="4.3.0" /> <PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="6.0.2" /> <PackageReference Include="System.Text.Json" Version="6.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -64,5 +64,11 @@ namespace MediaBrowser.Model.Session
/// </summary> /// </summary>
/// <value>The repeat mode.</value> /// <value>The repeat mode.</value>
public RepeatMode RepeatMode { get; set; } public RepeatMode RepeatMode { get; set; }
/// <summary>
/// Gets or sets the now playing live stream identifier.
/// </summary>
/// <value>The live stream identifier.</value>
public string LiveStreamId { get; set; }
} }
} }

View File

@@ -8,7 +8,7 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Diacritics.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;

View File

@@ -30,6 +30,7 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Priority_Queue; using Priority_Queue;
@@ -188,6 +189,12 @@ namespace MediaBrowser.Providers.Manager
throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound); throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound);
} }
// some iptv/epg providers don't correctly report media type, extract from url if no extension found
if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType)))
{
contentType = MimeTypes.GetMimeType(url);
}
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await SaveImage( await SaveImage(
item, item,

View File

@@ -20,9 +20,9 @@
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" /> <PackageReference Include="OptimizedPriorityQueue" Version="5.1.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.3" /> <PackageReference Include="PlaylistsNET" Version="1.2.1" />
<PackageReference Include="TMDbLib" Version="1.9.1" /> <PackageReference Include="TMDbLib" Version="1.9.2" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -173,16 +173,30 @@ namespace MediaBrowser.Providers.MediaInfo
IReadOnlyList<MediaAttachment> mediaAttachments; IReadOnlyList<MediaAttachment> mediaAttachments;
ChapterInfo[] chapters; ChapterInfo[] chapters;
mediaStreams = new List<MediaStream>();
// Add external streams before adding the streams from the file to preserve stream IDs on remote videos
await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
var startIndex = mediaStreams.Count == 0 ? 0 : (mediaStreams.Max(i => i.Index) + 1);
if (mediaInfo != null) if (mediaInfo != null)
{ {
mediaStreams = mediaInfo.MediaStreams.ToList(); foreach (var mediaStream in mediaInfo.MediaStreams)
{
mediaStream.Index = startIndex++;
mediaStreams.Add(mediaStream);
}
mediaAttachments = mediaInfo.MediaAttachments; mediaAttachments = mediaInfo.MediaAttachments;
video.TotalBitrate = mediaInfo.Bitrate; video.TotalBitrate = mediaInfo.Bitrate;
// video.FormatName = (mediaInfo.Container ?? string.Empty) // video.FormatName = (mediaInfo.Container ?? string.Empty)
// .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); // .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
// For dvd's this may not always be accurate, so don't set the runtime if the item already has one // For DVDs this may not always be accurate, so don't set the runtime if the item already has one
var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0; var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
if (needToSetRuntime) if (needToSetRuntime)
@@ -213,15 +227,20 @@ namespace MediaBrowser.Providers.MediaInfo
} }
else else
{ {
mediaStreams = new List<MediaStream>(); var currentMediaStreams = video.GetMediaStreams();
foreach (var mediaStream in currentMediaStreams)
{
if (!mediaStream.IsExternal)
{
mediaStream.Index = startIndex++;
mediaStreams.Add(mediaStream);
}
}
mediaAttachments = Array.Empty<MediaAttachment>(); mediaAttachments = Array.Empty<MediaAttachment>();
chapters = Array.Empty<ChapterInfo>(); chapters = Array.Empty<ChapterInfo>();
} }
await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
var libraryOptions = _libraryManager.GetLibraryOptions(video); var libraryOptions = _libraryManager.GetLibraryOptions(video);
if (mediaInfo != null) if (mediaInfo != null)
@@ -254,7 +273,11 @@ namespace MediaBrowser.Providers.MediaInfo
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
_itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken); _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken);
_itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
if (mediaAttachments.Any())
{
_itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken);
}
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
options.MetadataRefreshMode == MetadataRefreshMode.Default) options.MetadataRefreshMode == MetadataRefreshMode.Default)

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -107,19 +106,28 @@ namespace MediaBrowser.Providers.MediaInfo
if (mediaInfo.MediaStreams.Count == 1) if (mediaInfo.MediaStreams.Count == 1)
{ {
MediaStream mediaStream = mediaInfo.MediaStreams[0]; MediaStream mediaStream = mediaInfo.MediaStreams[0];
mediaStream.Index = startIndex++;
mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo)); if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
|| (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
{
mediaStream.Index = startIndex++;
mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
}
} }
else else
{ {
foreach (MediaStream mediaStream in mediaInfo.MediaStreams) foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
{ {
mediaStream.Index = startIndex++; if ((mediaStream.Type == MediaStreamType.Audio && _type == DlnaProfileType.Audio)
|| (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
{
mediaStream.Index = startIndex++;
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo)); mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
}
} }
} }
} }
@@ -153,6 +161,7 @@ namespace MediaBrowser.Providers.MediaInfo
} }
var files = directoryService.GetFilePaths(folder, clearCache).ToList(); var files = directoryService.GetFilePaths(folder, clearCache).ToList();
files.Remove(video.Path);
var internalMetadataPath = video.GetInternalMetadataPath(); var internalMetadataPath = video.GetInternalMetadataPath();
if (_fileSystem.DirectoryExists(internalMetadataPath)) if (_fileSystem.DirectoryExists(internalMetadataPath))
{ {
@@ -222,13 +231,6 @@ namespace MediaBrowser.Providers.MediaInfo
mediaStream.Title = string.IsNullOrEmpty(mediaStream.Title) ? (string.IsNullOrEmpty(pathInfo.Title) ? null : pathInfo.Title) : mediaStream.Title; mediaStream.Title = string.IsNullOrEmpty(mediaStream.Title) ? (string.IsNullOrEmpty(pathInfo.Title) ? null : pathInfo.Title) : mediaStream.Title;
mediaStream.Language = string.IsNullOrEmpty(mediaStream.Language) ? (string.IsNullOrEmpty(pathInfo.Language) ? null : pathInfo.Language) : mediaStream.Language; mediaStream.Language = string.IsNullOrEmpty(mediaStream.Language) ? (string.IsNullOrEmpty(pathInfo.Language) ? null : pathInfo.Language) : mediaStream.Language;
mediaStream.Type = _type switch
{
DlnaProfileType.Audio => MediaStreamType.Audio,
DlnaProfileType.Subtitle => MediaStreamType.Subtitle,
_ => mediaStream.Type
};
return mediaStream; return mediaStream;
} }
} }

View File

@@ -13,7 +13,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using Diacritics.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;

View File

@@ -2,7 +2,7 @@
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
namespace MediaBrowser.Providers.Plugins.StudioImages namespace MediaBrowser.Providers.Plugins.StudioImages.Configuration
{ {
public class PluginConfiguration : BasePluginConfiguration public class PluginConfiguration : BasePluginConfiguration
{ {
@@ -12,12 +12,19 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
{ {
get get
{ {
if (string.IsNullOrEmpty(_repository))
{
_repository = Plugin.DefaultServer;
}
return _repository; return _repository;
} }
set set
{ {
_repository = value.TrimEnd('/'); _repository = string.IsNullOrEmpty(value)
? Plugin.DefaultServer
: value.TrimEnd('/');
} }
} }
} }

View File

@@ -9,8 +9,8 @@
<div class="content-primary"> <div class="content-primary">
<form class="configForm"> <form class="configForm">
<div class="inputContainer"> <div class="inputContainer">
<input is="emby-input" type="text" id="repository" required label="Repository" /> <input is="emby-input" type="text" id="repository" label="Repository" />
<div class="fieldDescription">This can be any Jellyfin-compatible artwork repository.</div> <div class="fieldDescription">This can be any Jellyfin-compatible artwork repository. Leave blank to use default repository.</div>
</div> </div>
<br /> <br />
<div> <div>
@@ -44,7 +44,7 @@
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
config.RepositoryUrl = document.querySelector('#server').value; config.RepositoryUrl = document.querySelector('#repository').value;
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
}); });

View File

@@ -7,6 +7,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Plugins.StudioImages.Configuration;
namespace MediaBrowser.Providers.Plugins.StudioImages namespace MediaBrowser.Providers.Plugins.StudioImages
{ {

View File

@@ -18,29 +18,24 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using MediaBrowser.Providers.Plugins.StudioImages;
namespace MediaBrowser.Providers.Studios namespace MediaBrowser.Providers.Plugins.StudioImages
{ {
public class StudiosImageProvider : IRemoteImageProvider public class StudiosImageProvider : IRemoteImageProvider
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly string repositoryUrl;
public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem) public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
{ {
_config = config; _config = config;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_fileSystem = fileSystem; _fileSystem = fileSystem;
repositoryUrl = Plugin.Instance.Configuration.RepositoryUrl;
} }
public string Name => "Artwork Repository"; public string Name => "Artwork Repository";
public int Order => 0;
public bool Supports(BaseItem item) public bool Supports(BaseItem item)
{ {
return item is Studio; return item is Studio;
@@ -98,12 +93,12 @@ namespace MediaBrowser.Providers.Studios
private string GetUrl(string image, string filename) private string GetUrl(string image, string filename)
{ {
return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", repositoryUrl, image, filename); return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", GetRepositoryUrl(), image, filename);
} }
private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken) private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken)
{ {
string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", repositoryUrl); string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", GetRepositoryUrl());
return EnsureList(url, file, _fileSystem, cancellationToken); return EnsureList(url, file, _fileSystem, cancellationToken);
} }
@@ -169,5 +164,8 @@ namespace MediaBrowser.Providers.Studios
} }
} }
} }
private string GetRepositoryUrl()
=> Plugin.Instance.Configuration.RepositoryUrl;
} }
} }

View File

@@ -84,8 +84,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public static bool IsTrailerType(Video video) public static bool IsTrailerType(Video video)
{ {
return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase) return video.Site.Equals("youtube", StringComparison.OrdinalIgnoreCase)
&& (!video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase) && (video.Type.Equals("trailer", StringComparison.OrdinalIgnoreCase)
|| !video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase)); || video.Type.Equals("teaser", StringComparison.OrdinalIgnoreCase));
} }
/// <summary> /// <summary>

View File

@@ -58,7 +58,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{ {
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat; var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
writer.WriteElementString("disbanded", artist.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); writer.WriteElementString("disbanded", artist.EndDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
} }
var albums = artist var albums = artist

View File

@@ -473,7 +473,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("lockedfields", string.Join('|', item.LockedFields)); writer.WriteElementString("lockedfields", string.Join('|', item.LockedFields));
} }
writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat, CultureInfo.InvariantCulture)); writer.WriteElementString("dateadded", item.DateCreated.ToString(DateAddedFormat, CultureInfo.InvariantCulture));
writer.WriteElementString("title", item.Name ?? string.Empty); writer.WriteElementString("title", item.Name ?? string.Empty);
@@ -601,16 +601,16 @@ namespace MediaBrowser.XbmcMetadata.Savers
{ {
writer.WriteElementString( writer.WriteElementString(
"formed", "formed",
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
} }
else else
{ {
writer.WriteElementString( writer.WriteElementString(
"premiered", "premiered",
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
writer.WriteElementString( writer.WriteElementString(
"releasedate", "releasedate",
item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); item.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
} }
} }
@@ -622,7 +622,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString( writer.WriteElementString(
"enddate", "enddate",
item.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); item.EndDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
} }
} }
@@ -891,7 +891,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{ {
writer.WriteElementString( writer.WriteElementString(
"lastplayed", "lastplayed",
userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLowerInvariant()); userdata.LastPlayedDate.Value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLowerInvariant());
} }
writer.WriteStartElement("resume"); writer.WriteStartElement("resume");

View File

@@ -75,7 +75,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{ {
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat; var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
writer.WriteElementString("aired", episode.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture)); writer.WriteElementString("aired", episode.PremiereDate.Value.ToString(formatString, CultureInfo.InvariantCulture));
} }
if (!episode.ParentIndexNumber.HasValue || episode.ParentIndexNumber.Value == 0) if (!episode.ParentIndexNumber.HasValue || episode.ParentIndexNumber.Value == 0)

4
debian/changelog vendored
View File

@@ -1,8 +1,8 @@
jellyfin-server (10.8.0-1) unstable; urgency=medium jellyfin-server (10.8.0-1) unstable; urgency=medium
* Forthcoming stable release * New upstream version 10.8.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 04 Dec 2020 21:55:12 -0500 -- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 10 Jun 2022 22:15:12 -0400
jellyfin-server (10.7.0-1) unstable; urgency=medium jellyfin-server (10.7.0-1) unstable; urgency=medium

2
debian/control vendored
View File

@@ -22,7 +22,7 @@ Depends: at,
libsqlite3-0, libsqlite3-0,
libfontconfig1, libfontconfig1,
libfreetype6, libfreetype6,
libssl1.1 libssl1.1 | libssl3
Recommends: jellyfin-web, sudo Recommends: jellyfin-web, sudo
Description: Jellyfin is the Free Software Media System. Description: Jellyfin is the Free Software Media System.
This package provides the Jellyfin server backend and API. This package provides the Jellyfin server backend and API.

View File

@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
Standards-Version: 3.9.2 Standards-Version: 3.9.2
Package: jellyfin Package: jellyfin
Version: 10.8.0 Version: 10.8.0~beta3
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org> Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
Depends: jellyfin-server, jellyfin-web Depends: jellyfin-server, jellyfin-web
Description: Provides the Jellyfin Free Software Media System Description: Provides the Jellyfin Free Software Media System

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 && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
# Install DotNET SDK # Install DotNET SDK
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

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 && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget
# Install DotNET SDK # Install DotNET SDK
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -17,7 +17,7 @@ RUN apt-get update -yqq \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
# Install dotnet repository # Install dotnet repository
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release mmv build-essential lsb-release
# Install dotnet repository # Install dotnet repository
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release mmv build-essential lsb-release
# Install dotnet repository # Install dotnet repository
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/c505a449-9ecf-4352-8629-56216f521616/bd6807340faae05b61de340c8bf161e8/dotnet-sdk-6.0.201-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/dc930bff-ef3d-4f6f-8799-6eb60390f5b4/1efee2a8ea0180c94aff8f15eb3af981/dotnet-sdk-6.0.300-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -153,6 +153,8 @@ fi
%systemd_postun_with_restart jellyfin.service %systemd_postun_with_restart jellyfin.service
%changelog %changelog
* Fri Jun 10 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.0
* Mon Nov 29 2021 Brian J. Murrell <brian@interlinx.bc.ca> * Mon Nov 29 2021 Brian J. Murrell <brian@interlinx.bc.ca>
- Add jellyfin-server-lowports.service drop-in in a server-lowports - Add jellyfin-server-lowports.service drop-in in a server-lowports
subpackage to allow binding to low ports subpackage to allow binding to low ports

View File

@@ -1,4 +1,8 @@
using System; using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace Jellyfin.Extensions namespace Jellyfin.Extensions
{ {
@@ -7,6 +11,44 @@ namespace Jellyfin.Extensions
/// </summary> /// </summary>
public static class StringExtensions public static class StringExtensions
{ {
// Matches non-conforming unicode chars
// https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)");
/// <summary>
/// Removes the diacritics character from the strings.
/// </summary>
/// <param name="text">The string to act on.</param>
/// <returns>The string without diacritics character.</returns>
public static string RemoveDiacritics(this string text)
{
string withDiactritics = _nonConformingUnicode
.Replace(text, string.Empty)
.Normalize(NormalizationForm.FormD);
var withoutDiactritics = new StringBuilder();
foreach (char c in withDiactritics)
{
UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);
if (uc != UnicodeCategory.NonSpacingMark)
{
withoutDiactritics.Append(c);
}
}
return withoutDiactritics.ToString().Normalize(NormalizationForm.FormC);
}
/// <summary>
/// Checks wether or not the specified string has diacritics in it.
/// </summary>
/// <param name="text">The string to check.</param>
/// <returns>True if the string has diacritics, false otherwise.</returns>
public static bool HasDiacritics(this string text)
{
return !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal);
}
/// <summary> /// <summary>
/// Counts the number of occurrences of [needle] in the string. /// Counts the number of occurrences of [needle] in the string.
/// </summary> /// </summary>

View File

@@ -15,13 +15,16 @@
<PackageReference Include="AutoFixture" Version="4.17.0" /> <PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.4" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="Moq" Version="4.18.1" />
</ItemGroup> </ItemGroup>
<!-- Code Analyzers --> <!-- Code Analyzers -->

View File

@@ -12,11 +12,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.5" />
</ItemGroup> </ItemGroup>
<!-- Code Analyzers --> <!-- Code Analyzers -->

View File

@@ -12,10 +12,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup> </ItemGroup>

View File

@@ -7,10 +7,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup> </ItemGroup>

View File

@@ -7,9 +7,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
@@ -17,7 +17,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.5" />
</ItemGroup> </ItemGroup>
<!-- Code Analyzers --> <!-- Code Analyzers -->

View File

@@ -5,6 +5,38 @@ namespace Jellyfin.Extensions.Tests
{ {
public class StringExtensionsTests public class StringExtensionsTests
{ {
[Theory]
[InlineData("", "")] // Identity edge-case (no diactritics)
[InlineData("Indiana Jones", "Indiana Jones")] // Identity (no diactritics)
[InlineData("a\ud800b", "ab")] // Invalid UTF-16 char stripping
[InlineData("Jön", "Jon")] // Issue #7484
[InlineData("Jönssonligan", "Jonssonligan")] // Issue #7484
[InlineData("Kieślowski", "Kieslowski")] // Issue #7450
[InlineData("Cidadão Kane", "Cidadao Kane")] // Issue #7560
[InlineData("운명처럼 널 사랑해", "운명처럼 널 사랑해")] // Issue #6393 (Korean language support)
[InlineData("애타는 로맨스", "애타는 로맨스")] // Issue #6393
public void RemoveDiacritics_ValidInput_Corrects(string input, string expectedResult)
{
string result = input.RemoveDiacritics();
Assert.Equal(expectedResult, result);
}
[Theory]
[InlineData("", false)] // Identity edge-case (no diactritics)
[InlineData("Indiana Jones", false)] // Identity (no diactritics)
[InlineData("a\ud800b", true)] // Invalid UTF-16 char stripping
[InlineData("Jön", true)] // Issue #7484
[InlineData("Jönssonligan", true)] // Issue #7484
[InlineData("Kieślowski", true)] // Issue #7450
[InlineData("Cidadão Kane", true)] // Issue #7560
[InlineData("운명처럼 널 사랑해", false)] // Issue #6393 (Korean language support)
[InlineData("애타는 로맨스", false)] // Issue #6393
public void HasDiacritics_ValidInput_Corrects(string input, bool expectedResult)
{
bool result = input.HasDiacritics();
Assert.Equal(expectedResult, result);
}
[Theory] [Theory]
[InlineData("", '_', 0)] [InlineData("", '_', 0)]
[InlineData("___", '_', 3)] [InlineData("___", '_', 3)]

View File

@@ -7,9 +7,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@@ -8,9 +8,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@@ -22,10 +22,13 @@
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" /> <PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" /> <PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<!-- Code Analyzers --> <!-- Code Analyzers -->

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-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox // Firefox
@@ -38,7 +38,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Safari // Safari
@@ -89,7 +89,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] [InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
[InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] [InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
[InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// TranscodeMedia // TranscodeMedia
@@ -131,6 +131,37 @@ namespace Jellyfin.Model.Tests
[InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] [InlineData("Null", "mkv-vp9-aac-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
[InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] [InlineData("Null", "mkv-vp9-ac3-srt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
[InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)] [InlineData("Null", "mkv-vp9-vorbis-vtt-2600k", null, TranscodeReason.ContainerBitrateExceedsLimit)]
// AndroidTV
[InlineData("AndroidTVExoPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
// Tizen 3 Stereo
[InlineData("Tizen3-stereo", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-h264-dts-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
[InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
// Tizen 4 4K 5.1
[InlineData("Tizen4-4K-5.1", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-h264-dts-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
[InlineData("Tizen4-4K-5.1", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
[InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") public async Task BuildVideoItemSimple(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{ {
var options = await GetVideoOptions(deviceName, mediaSource); var options = await GetVideoOptions(deviceName, mediaSource);
@@ -146,7 +177,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox // Firefox
@@ -156,7 +187,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Safari // Safari
@@ -198,6 +229,37 @@ namespace Jellyfin.Model.Tests
[InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("JellyfinMediaPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450
[InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("JellyfinMediaPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)] // #6450
[InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
// AndroidTV
[InlineData("AndroidTVExoPlayer", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("AndroidTVExoPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
// Tizen 3 Stereo
[InlineData("Tizen3-stereo", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-h264-dts-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
[InlineData("Tizen3-stereo", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen3-stereo", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
// Tizen 4 4K 5.1
[InlineData("Tizen4-4K-5.1", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-h264-dts-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
[InlineData("Tizen4-4K-5.1", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mp4-hevc-truehd-srt-15200k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
[InlineData("Tizen4-4K-5.1", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectPlay)]
[InlineData("Tizen4-4K-5.1", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)]
public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") public async Task BuildVideoItemWithFirstExplicitStream(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{ {
var options = await GetVideoOptions(deviceName, mediaSource); var options = await GetVideoOptions(deviceName, mediaSource);
@@ -223,12 +285,26 @@ namespace Jellyfin.Model.Tests
// RokuSSPlus // RokuSSPlus
[InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
[InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// no streams
[InlineData("Chrome", "no-streams", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] // #6450
// AndroidTV
[InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
[InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
// Tizen 3 Stereo
[InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
[InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
// Tizen 4 4K 5.1
[InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
[InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")]
public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "") public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = (TranscodeReason)0, string transcodeMode = "DirectStream", string transcodeProtocol = "")
{ {
var options = await GetVideoOptions(deviceName, mediaSource); var options = await GetVideoOptions(deviceName, mediaSource);
var streamCount = options.MediaSources[0].MediaStreams.Count; var streamCount = options.MediaSources[0].MediaStreams.Count;
options.AudioStreamIndex = streamCount - 2; if (streamCount > 0)
options.SubtitleStreamIndex = streamCount - 1; {
options.AudioStreamIndex = streamCount - 2;
options.SubtitleStreamIndex = streamCount - 1;
}
var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol); var streamInfo = BuildVideoItemSimpleTest(options, playMethod, why, transcodeMode, transcodeProtocol);
Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex); Assert.Equal(streamInfo?.AudioStreamIndex, options.AudioStreamIndex);
@@ -262,23 +338,23 @@ namespace Jellyfin.Model.Tests
Assert.NotNull(mediaSource); Assert.NotNull(mediaSource);
var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video); var videoStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Video);
var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio); var audioStreams = mediaSource.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio);
// TODO: check AudioStreamIndex vs options.AudioStreamIndex // TODO: Check AudioStreamIndex vs options.AudioStreamIndex
var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex); var inputAudioStream = mediaSource.GetDefaultAudioStream(audioStreamIndexInput ?? mediaSource.DefaultAudioStreamIndex);
var uri = ParseUri(val); var uri = ParseUri(val);
if (playMethod == PlayMethod.DirectPlay) if (playMethod == PlayMethod.DirectPlay)
{ {
// check expected container // Check expected container
var containers = ContainerProfile.SplitValue(mediaSource.Container); var containers = ContainerProfile.SplitValue(mediaSource.Container);
// TODO: test transcode too // TODO: Test transcode too
// Assert.Contains(uri.Extension, containers); // Assert.Contains(uri.Extension, containers);
// check expected video codec (1) // Check expected video codec (1)
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
Assert.Single(val.TargetVideoCodec); Assert.Single(val.TargetVideoCodec);
// check expected audio codecs (1) // Check expected audio codecs (1)
Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec); Assert.Contains(targetAudioStream.Codec, val.TargetAudioCodec);
Assert.Single(val.TargetAudioCodec); Assert.Single(val.TargetAudioCodec);
// Assert.Single(val.AudioCodecs); // Assert.Single(val.AudioCodecs);
@@ -294,7 +370,7 @@ namespace Jellyfin.Model.Tests
Assert.NotEmpty(val.VideoCodecs); Assert.NotEmpty(val.VideoCodecs);
Assert.NotEmpty(val.AudioCodecs); Assert.NotEmpty(val.AudioCodecs);
// check expected container (todo: this could be a test param) // Check expected container (todo: this could be a test param)
if (transcodeProtocol == "http") if (transcodeProtocol == "http")
{ {
// Assert.Equal("webm", val.Container); // Assert.Equal("webm", val.Container);
@@ -327,32 +403,39 @@ namespace Jellyfin.Model.Tests
stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs)); stream => Assert.DoesNotContain(stream.Codec, val.VideoCodecs));
} }
// todo: fill out tests here // TODO: Fill out tests here
} }
// DirectStream and Remux // DirectStream and Remux
else else
{ {
// check expected video codec (1) // Check expected video codec (1)
Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec); Assert.Contains(targetVideoStream.Codec, val.TargetVideoCodec);
Assert.Single(val.TargetVideoCodec); Assert.Single(val.TargetVideoCodec);
if (transcodeMode == "DirectStream") if (transcodeMode == "DirectStream")
{ {
// Check expected audio codecs (1)
if (!targetAudioStream.IsExternal) if (!targetAudioStream.IsExternal)
{ {
// check expected audio codecs (1) if (val.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported))
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs); {
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
}
else
{
Assert.DoesNotContain(targetAudioStream.Codec, val.AudioCodecs);
}
} }
} }
else if (transcodeMode == "Remux") else if (transcodeMode == "Remux")
{ {
// check expected audio codecs (1) // Check expected audio codecs (1)
Assert.Contains(targetAudioStream.Codec, val.AudioCodecs); Assert.Contains(targetAudioStream.Codec, val.AudioCodecs);
Assert.Single(val.AudioCodecs); Assert.Single(val.AudioCodecs);
} }
// video details // Video details
var videoStream = targetVideoStream; var videoStream = targetVideoStream;
Assert.False(val.EstimateContentLength); Assert.False(val.EstimateContentLength);
Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo); Assert.Equal(TranscodeSeekInfo.Auto, val.TranscodeSeekInfo);
@@ -361,10 +444,10 @@ namespace Jellyfin.Model.Tests
Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth); Assert.Equal(videoStream.BitDepth, val.TargetVideoBitDepth);
Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue); Assert.InRange(val.VideoBitrate.GetValueOrDefault(), videoStream.BitRate.GetValueOrDefault(), int.MaxValue);
// audio codec not supported // Audio codec not supported
if ((why & TranscodeReason.AudioCodecNotSupported) != 0) if ((why & TranscodeReason.AudioCodecNotSupported) != 0)
{ {
// audio stream specified // Audio stream specified
if (options.AudioStreamIndex >= 0) if (options.AudioStreamIndex >= 0)
{ {
// TODO:fixme // TODO:fixme
@@ -374,10 +457,10 @@ namespace Jellyfin.Model.Tests
} }
} }
// audio stream not specified // Audio stream not specified
else else
{ {
// TODO:fixme // TODO: Fixme
Assert.All(audioStreams, stream => Assert.All(audioStreams, stream =>
{ {
if (!stream.IsExternal) if (!stream.IsExternal)

View File

@@ -7,12 +7,15 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Moq" Version="4.17.2" /> <PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2" /> <PackageReference Include="coverlet.collector" Version="3.1.2" />
<PackageReference Include="FsCheck.Xunit" Version="2.16.4" /> <PackageReference Include="FsCheck.Xunit" Version="2.16.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

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

Some files were not shown because too many files have changed in this diff Show More