Compare commits

...

298 Commits

Author SHA1 Message Date
Joshua Boniface
c5a2ff8ac4 Bump version to 10.8.3 2022-08-01 20:20:00 -04:00
Joshua Boniface
494ed7e4d2 Revert "Merge pull request #8087 from cvium/generic_subtitleparser"
This PR was causing breakage in installs - ref #8198

This reverts commit 7323ccfc23, reversing
changes made to 77a007a24d.
2022-08-01 20:19:16 -04:00
Joshua M. Boniface
dd97e6bc45 Bump version to 10.8.2 2022-08-01 14:27:30 -04:00
Bond-009
7323ccfc23 Merge pull request #8087 from cvium/generic_subtitleparser 2022-08-01 19:24:45 +02:00
Claus Vium
d258a87fda Update MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2022-08-01 18:30:32 +02:00
Bond-009
77a007a24d Merge pull request #8191 from cvium/remove_not_missing_episodes 2022-08-01 18:19:13 +02:00
cvium
a380153f92 remove redundant null check 2022-07-30 21:54:03 +02:00
cvium
56c81696d3 fix: remove Virtual episodes when their physical counterpart exists 2022-07-30 21:50:53 +02:00
Bond-009
7297431f23 Merge pull request #8174 from nyanmisaka/fixes
Disable auto inserted SW scaler for HW decoders
2022-07-30 15:00:41 +02:00
Claus Vium
f2c7bccb89 Update MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2022-07-30 14:59:28 +02:00
Claus Vium
b0b4068ddf Update MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2022-07-30 14:59:00 +02:00
nyanmisaka
82f362abd9 Fix DV P5 MKV remuxing 2022-07-26 23:06:22 +08:00
nyanmisaka
04b73cace6 Disable auto inserted SW scaler for HW decoders in case of changed resolution 2022-07-26 23:06:11 +08:00
cvium
3b69f38a1f fix tests 2022-07-25 09:51:03 +02:00
cvium
126da94020 use reflection to get all subtitle formats without causing libse configuration loading 2022-07-25 09:47:21 +02:00
Bond-009
f9dffa767f Merge pull request #8167 from crobibero/plugin-library-scan 2022-07-24 22:59:21 +02:00
Claus Vium
444b0ea310 Merge pull request #8163 from daullmer/cover-m3u
Don't refresh playlists on album refresh
2022-07-24 19:38:54 +02:00
Claus Vium
484427b4aa Merge pull request #8127 from jellyfin/wa-i915-hang 2022-07-24 19:18:14 +02:00
David Ullmer
c3f0649fde Update MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-07-24 19:17:21 +02:00
Nyanmisaka
e877486056 Apply suggestions from code review
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-07-25 00:56:40 +08:00
Claus Vium
9854751137 Merge pull request #8061 from jellyfin/fix-long-time-interop
Fix intel dx11-ocl interop taking too long to initialize on windows
2022-07-24 18:54:04 +02:00
Claus Vium
057e8ef240 Merge pull request #8166 from joseph39/people-query-fix-10.8.z
Bind @userid only when it's in the statement
2022-07-24 18:27:59 +02:00
Cody Robibero
205783f46f re-add library scan from library manager 2022-07-23 18:59:30 -06:00
Cody Robibero
b2fb96ffed Merge pull request #8152 from thornbill/fix-rtsp-over-tcp
Fix support for rtsp streams over tcp
2022-07-23 18:24:35 -06:00
Cody Robibero
ee22feb89a Merge pull request #7732 from LewkyB/fix-websockets-close-gracefully-on-shutdown 2022-07-23 18:24:12 -06:00
Joseph
ca5979cd77 Bind @userid only when it's in the statement
This commit fixes a case where SqliteItemRepository attempts to bind @userid even when such parameter may not appear in the query; fixes https://github.com/jellyfin/jellyfin/issues/8141
2022-07-23 16:06:36 -07:00
David Ullmer
d36f49589a Don't refresh playlists on album refresh 2022-07-23 13:51:07 +02:00
luke brown
70f37f0527 Merge branch 'release-10.8.z' of github.com:jellyfin/jellyfin into fix-websockets-close-gracefully-on-shutdown 2022-07-22 18:16:55 -05:00
Bill Thornton
dfe0aef530 Fix support for rtsp streams over tcp 2022-07-20 15:13:11 -04:00
Luke Brown
9e31d5a73f relocated Dispose 2022-07-19 21:49:17 -05:00
Luke Brown
f088ca5555 add Dispose in DisposeAsyncCore 2022-07-19 17:31:00 -05:00
Bond-009
2b46917dcf Merge pull request #8104 from Shadowghost/resolution-fix 2022-07-17 23:52:49 +02:00
nyanmisaka
7bae6eff95 Fix intel dx11-ocl interop taking too long to initialize on windows 2022-07-17 11:47:17 +08:00
Joshua M. Boniface
d0fd23bb4b Merge pull request #8115 from crobibero/dotnet-6.0.7
Update to dotnet 6.0.7
2022-07-16 15:39:11 -04:00
Shadowghost
d694a6c09a Add more MediaStream resolution tests, sort them by width 2022-07-16 10:16:33 +02:00
nyanmisaka
58f61ed118 Workaround for linux 5.18+ i915 hang at cost of performance 2022-07-16 15:18:49 +08:00
Cody Robibero
b9da0e7f83 Update remaining dependencies 2022-07-12 17:41:32 -06:00
Cody Robibero
7eaa0600e0 Update to dotnet 6.0.7 2022-07-12 17:41:22 -06:00
Bond-009
47c2c536e4 Merge pull request #8078 from Andy2244/fix-stream_copy 2022-07-13 00:33:00 +02:00
Bond-009
7ef9e95d75 Merge pull request #8092 from Andy2244/fix-audio_codec_bitrate 2022-07-13 00:29:09 +02:00
Shadowghost
f8ea4577ab Add resolution text output for more resolutions 2022-07-11 15:45:11 +02:00
Andy Walsh
72da42cb0a allow higher opus, vorbis transcode bitrates 2022-07-10 02:14:49 +02:00
cvium
dbfa0f3027 fix unsupported 2022-07-08 20:12:00 +02:00
cvium
78f437401b loop over all compatible SubtitleFormats 2022-07-08 20:11:00 +02:00
cvium
1db748399c feat: make subtitleeditparser generic 2022-07-08 19:44:15 +02:00
Andy Walsh
a41c67d16b fix copy&paste error for requestedRangeTypes preventing stream copy
- add >=0 check to subtitle index check
- fixes #8070, #7880
2022-07-08 13:56:09 +02:00
Bond-009
84a1674f39 Merge pull request #8013 from daullmer/parental_password_fix 2022-07-07 12:42:20 +02:00
David Ullmer
81e535fc62 Rollback changes in IUserManager 2022-07-04 19:19:46 +02:00
David Ullmer
f9d26ea1bc Use IsInRole 2022-07-04 19:08:40 +02:00
David Ullmer
5f3dbd8294 Allow administrator to always change password 2022-07-04 18:16:36 +02:00
Claus Vium
9cebdfdec0 Merge pull request #8038 from Shadowghost/secure-sudoers 2022-06-30 16:40:59 +02:00
Shadowghost
891ccd7bb2 Remove mount and unmount permissions for jellyfin group from sudoers 2022-06-30 14:57:16 +02:00
Joshua M. Boniface
54778d875d Bump version to 10.8.1 2022-06-26 21:00:05 -04:00
Joshua M. Boniface
39d185c7b1 Merge pull request #7964 from jellyfin/dovi-side-data 2022-06-26 20:55:36 -04:00
Joshua M. Boniface
a7d45b5d3a Merge pull request #7994 from nyanmisaka/fix-throttler 2022-06-26 20:53:03 -04:00
David Ullmer
7efa4e38c1 Fix password change during parental schedule 2022-06-26 14:06:40 +02:00
nyanmisaka
506ed6940b Detach TranscodingJob from StreamState 2022-06-26 16:01:46 +08:00
nyanmisaka
5dbe16d3e6 Re-enable throttler for HWA and Copy 2022-06-26 16:01:46 +08:00
nyanmisaka
4c178e9188 Remove 'using' from HLS/Progressive StreamState 2022-06-26 16:01:46 +08:00
nyanmisaka
50bc41d84d Add VideoDoViTitle to display DV compatibility 2022-06-25 18:43:32 +08:00
nyanmisaka
e931f5a32b Add tests 2022-06-25 18:43:32 +08:00
nyanmisaka
d2caed25fb Register DOVI side_data in db 2022-06-25 18:43:32 +08:00
nyanmisaka
3f37ef70e1 Add json parser for DOVI side_data 2022-06-25 18:43:32 +08:00
Claus Vium
d342b79218 Merge pull request #8001 from jellyfin/fix-yuvj420p-hwdec
Fix yuvj420p pixel format hardware decoding
2022-06-24 14:49:27 +02:00
nyanmisaka
c35fc382d4 Fix yuvj420p pixel format hardware decoding 2022-06-24 19:33:53 +08:00
Cody Robibero
cb6e6879e2 Merge pull request #7614 from mihawk90/fedora-spec-rework 2022-06-23 08:04:54 -06:00
Bond-009
a71b190142 Merge pull request #7988 from jellyfin/external-streams-exceptions 2022-06-23 15:45:55 +02:00
Bond-009
910df89cce Merge pull request #7975 from jellyfin/libva-driver-env 2022-06-23 15:33:14 +02:00
Bond-009
5f15339919 Merge pull request #7968 from jellyfin/fix-hwa-bluray 2022-06-23 15:32:35 +02:00
Joshua M. Boniface
56e7b323de Merge pull request #7984 from crobibero/dotnet-6.0.6
Upgrade to dotnet 6.0.6, update remaining dependencies
2022-06-23 09:13:35 -04:00
Cody Robibero
a3a751a4f5 Merge pull request #7985 from jellyfin/revert-7961-next_up_idk
Revert "refactor: use season number and episode number for NextUp ordering instead of SortName"
2022-06-21 06:05:26 -06:00
nyanmisaka
c85255a615 Log external streams exceptions 2022-06-21 19:59:23 +08:00
nyanmisaka
8ea8dcf128 Catch external streams exceptions 2022-06-21 19:56:32 +08:00
Cody Robibero
7884e7e829 Revert "refactor: use season number and episode number for NextUp ordering instead of SortName" 2022-06-20 08:55:19 -06:00
Cody Robibero
3478554249 Fix build 2022-06-20 08:54:46 -06:00
Cody Robibero
9898c10880 Update remaining dependencies 2022-06-20 08:52:30 -06:00
Cody Robibero
ec2ad4ec8c Upgrade to dotnet 6.0.6 2022-06-20 08:48:40 -06:00
Joshua M. Boniface
1ffc77b43d Merge pull request #7961 from cvium/next_up_idk
refactor: use season number and episode number for NextUp ordering instead of SortName
2022-06-20 09:58:27 -04:00
Joshua M. Boniface
ae79bbc34c Merge pull request #7960 from Shadowghost/subrip-encoder-fix 2022-06-20 09:57:57 -04:00
Joshua M. Boniface
52704e8dd0 Merge pull request #7955 from nyanmisaka/fix-ext 2022-06-20 09:57:43 -04:00
Joshua M. Boniface
294ab0757e Merge pull request #7887 from joshuaboniface/fix-restart.sh 2022-06-20 09:57:18 -04:00
nyanmisaka
bdd52df230 Override the VAAPI driver env if i965 device is known 2022-06-20 18:59:54 +08:00
Nyanmisaka
73117b079c Fix HWA decoders are not applied to BluRay folders
fixes #6834
2022-06-19 19:14:55 +08:00
Shadowghost
b60905f991 Add barebone ASS/SSA writers to SubtitleEncoder 2022-06-19 08:59:48 +02:00
Tarulia
6d5c697183 Add make in Fedora Docker install
Fedora 36 doesn't seem to ship make, so add it manually.
2022-06-18 22:35:36 +02:00
Shadowghost
24c56328f2 Add subrip to SubtitleFormat 2022-06-18 01:20:05 +02:00
cvium
ef037ad371 refactor: use season number and episode number for NextUp ordering instead of SortName 2022-06-18 00:07:54 +02:00
Shadowghost
a64e21f57a Fix subtitle encoder if subrip is requested 2022-06-17 23:17:23 +02:00
nyanmisaka
56e135f5e6 Apply suggestions from code review
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-06-18 01:50:08 +08:00
nyanmisaka
ae22d0b7a5 Fix output extension if user has no transcoding permission 2022-06-18 00:25:55 +08:00
Cody Robibero
b36543275f Merge pull request #7950 from nyanmisaka/brighter-vpp-tonemap 2022-06-17 10:01:37 -06:00
Cody Robibero
2c0c3eb3ee Merge pull request #7954 from cvium/fix_7953_dlna_url 2022-06-17 10:01:20 -06:00
Cody Robibero
f1d56aa5ce Merge pull request #7947 from nyanmisaka/video-range-condition 2022-06-17 10:01:06 -06:00
Nyanmisaka
0b6fbebf72 Apply suggestions from code review
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-06-17 16:49:16 +08:00
Claus Vium
db714f967e Merge pull request #7939 from 1337joe/track-selector-fix
Track selector fix
2022-06-17 10:23:53 +02:00
cvium
f020bd6f3b use ignore case for scheme comparison 2022-06-17 09:00:59 +02:00
cvium
3275f83c3b fix: use proper bind address for DLNA location url (#7953) 2022-06-17 08:57:59 +02:00
nyanmisaka
f7813803c2 Brighter VPP tone-mapping on Intel 2022-06-16 23:29:36 +08:00
nyanmisaka
477b922e4a Apply suggestions from code review
Co-authored-by: Cody Robibero <cody@robibe.ro>
2022-06-16 22:11:06 +08:00
nyanmisaka
be72001ff9 Add VideoRangeType to video conditions
This is used to distinguish whether the client supports specific VideoRangeType,
such as SDR, HDR10, HLG and DOVI. Usage is similar to Video Profile condition.
2022-06-16 21:32:54 +08:00
Cody Robibero
6749313249 Merge pull request #7940 from Shadowghost/fix-recommendations 2022-06-16 07:15:39 -06:00
Cody Robibero
4ebe70cf6a Merge pull request #7946 from cvium/svg 2022-06-16 07:14:56 -06:00
cvium
c4051ac16d fix release build 2022-06-16 12:31:50 +02:00
cvium
3491f0968b feat: partially handle SVG files and remove exceptions from blurhash and dimensions 2022-06-16 12:22:46 +02:00
Cody Robibero
fd4ffc6ba3 Merge pull request #7941 from jellyfin/fix-overflow 2022-06-15 15:27:07 -06:00
Tarulia
5912a49d1d Further cleanup of Fedora spec-file
* Replaced shell script used to start JF with a symlink, since it didn't
  do anything else.
* Sorted installs by category/path to make it easier to maintain the
  spec.
* Defined `%defattr` in file section so we don't need to set it on a
  by-file level. This also helps with the messed up default file
  permissions `dotnet publish` produces.
* Removed some duplicate attribute definitions in `%install` and
  `%files` sections. They are now all in the `%install` section because
  of `%defattr` being specified and overriding them otherwise.
2022-06-15 23:12:12 +02:00
Tarulia
f336647d57 Remove env-var after moving web-files on Fedora
* when running Jellyfin as a user from a terminal without passing
  arguments, it would not find the web-files. They were moved in the
  JF-web build for Fedora, so this env-var would point to an invalid
  location. By removing it, JF now checks the default location.
* fixes jellyfin/jellyfin-web#2059
2022-06-15 23:12:12 +02:00
Tarulia
9b805c9e83 Use Fedora 36 image in Fedora Docker builds
* fixes #7504
2022-06-15 23:11:47 +02:00
Tarulia
19ccf414ac Fedora build: Filter dependency to fix F36 build
* fixes #7471
* Filter for F36+ since we don't know when (if ever) this issue will be
  fixed (it's up to .NET SDK). If it's ever fixed we can adjust or
  remove this condition.
2022-06-15 23:09:26 +02:00
Tarulia
0504ed9fe6 Standardise and cleanup Fedora build
* Remove additional dotnet-preview repo from Makefile
  * Repo doesn't seem maintained, and maintainers actively discourage
    the usage in production in its description
  * Considering this, we should build with stable .NET releases, which
    Fedora and RHEL 8+ repos already provide
* Use Fedora-version-specific runtime-identifier for `dotnet publish`
  * This has no actual effect right now judging by the [RID
    catalog](3efd59151a/docs/core/rid-catalog.md),
    so this is just future proofing.
* Remove AutoReqProv
  * There's rarely a reason to use this feature, this is not one of them
  * In the [proposal of this
    feature](https://fedoraproject.org/wiki/AutoReqProv_(draft)#Usage)
    it is stated that this is not to be used on packages with binaries
    in /usr/bin and others, which is the case in this package.
  * also didn't seem to work since we were still having dependency
    issues with implicit dependencies (see jellyfin/jellyfin#7471 )
* Removed DOTNET_SKIP_FIRST_TIME_EXPERIENCE as it is unused in .NET SDK
  * see dotnet/sdk#9945
  * it's already merged for removal in future versions
* Move building process `dotnet publish` to %build section
  * Also removed `--output` from this due to an outstanding bug on SDK's
    side. This also separates building and installing as intended
* define LICENSE as %license, which automatically puts it in a
  standardised directory
2022-06-15 23:09:26 +02:00
Tarulia
c243f588a0 Adjust license in Fedora Spec according to LICENSE 2022-06-15 23:09:26 +02:00
Tarulia
46491d0813 Rewrite Fedora build version detection
Rewrite so we don't need to constantly update with every new Fedora
release. This is especially useful when Fedora and Jellyfin release
cycles don't line up.

Version selection is as follows:
* TARGET environment variable, which is currently used already
* Currently running Fedora version
* Hardcoded Fallback version that can be updated occasionally
2022-06-15 23:09:26 +02:00
nyanmisaka
c7c0cdad95 Fix the map index of externel audio 2022-06-15 23:39:27 +08:00
nyanmisaka
255f5a6707 Fix the int overflow issue in encoder bufsize 2022-06-15 23:27:49 +08:00
Shadowghost
3f497459cb Fix recommendations 2022-06-15 12:34:04 +02:00
Joe Rogers
5d66c84f2d Fix default audio selection ignoring type 2022-06-15 11:58:12 +02:00
Joe Rogers
052a59ac3e Add tests for preferred audio language selection 2022-06-15 11:58:01 +02:00
Claus Vium
1b8a251991 Merge pull request #7934 from Bond-009/xmlasync2
Enable XmlReaderSettings.Async, fixes #7929
2022-06-14 21:24:19 +02:00
Bond-009
1a787e273a Merge pull request #7873 from cvium/fix_nfo_remoteimages 2022-06-14 19:11:37 +02:00
Bond_009
16fba6035c Enable XmlReaderSettings.Async, fixes #7929 2022-06-14 18:16:49 +02:00
Cody Robibero
d73e9f3af5 Fix splashscreen (#7895) 2022-06-14 08:18:35 -06:00
Cody Robibero
2888080098 Merge pull request #7924 from jellyfin/improve-swdec-amf-tonemap 2022-06-14 08:18:22 -06:00
Nyanmisaka
c8282e8441 Apply suggestions from code review
Co-authored-by: Bond-009 <bond.009@outlook.com>
2022-06-14 17:41:16 +08:00
Cody Robibero
b295b0478c Merge pull request #7925 from nyanmisaka/remove-mpeg4-amf 2022-06-13 16:27:45 -06:00
Cody Robibero
42aaea3556 Merge pull request #7913 from cvium/fix_response_logging 2022-06-13 16:27:38 -06:00
Cody Robibero
07b39655eb Merge pull request #7911 from cvium/fix_keyframe_transcode 2022-06-13 16:27:31 -06:00
Cody Robibero
0f75f17736 Merge pull request #7894 from crobibero/search-hints 2022-06-13 16:27:16 -06:00
nyanmisaka
83d8dbf93e Remove MPEG4 hwaccel from AMF 2022-06-14 02:21:00 +08:00
Nyanmisaka
e5aa708cb9 Improve AMF tonemap speed when using sw decoding
Reduce memory copy-back, also set the target device of hwupload explicitly.
2022-06-14 00:48:16 +08:00
cvium
ac760b9c86 fix: read configuration during Invoke instead of during construction 2022-06-13 11:53:19 +02:00
cvium
c07c7b753c fix: only use keyframes when remuxing video 2022-06-13 10:32:34 +02:00
Claus Vium
8b69b0f521 Merge pull request #7885 from iwalton3/fix-navigation-buttons
Prevent 400 error when using navigation buttons.
2022-06-13 09:47:25 +02:00
Cody Robibero
079fac4a54 Switch to FirstOrDefault extension 2022-06-12 09:17:18 -06:00
Cody Robibero
21afec3225 Merge pull request #7892 from cvium/fix_tv_multi 2022-06-12 07:54:32 -06:00
Ian Walton
11c5a0b182 Prevent 400 error when using navigation buttons.
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-06-12 09:00:35 -04:00
cvium
f318417c4f fix: tv shows do not support multi edition 2022-06-12 13:55:17 +02:00
Joshua M. Boniface
874fcaba69 Move service hardening options to override config
Some combination of these options were causing problems with the
functionality of restart.sh as described in the comment and in detail in
issue #7503.

While these seem OK on their face, the implications of this breaking
restart.sh means that they could potentially break other things too.

Thus, we should move these into the optional override file which is in
the administrator's full control, instead of in the default unit, and
leave them off unless a user or package maintainer (e.g. NixOS as
described in the original issue #6952) wants to enable them.

Fixes #7503
2022-06-12 01:05:29 -04:00
cvium
5204863705 fix: respect the image refresh options when parsing remote images from NFO 2022-06-11 15:08:07 +02:00
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
Luke Brown
0f7ba42987 move the Dispose logic into DisposeAsyncCore 2022-06-06 23:59:09 -05: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
Luke Brown
3e8fe1ce11 change placeholder text in LogDebug message 2022-05-31 21:49:01 -05: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
Luke Brown
044ff0542b adding interface changes 2022-05-19 16:40:48 -05:00
Luke Brown
abfbd04782 Moved IAsyncDisposable and IDisposable from WebSocketConnection to it's interface. Added private variable and guard statement to avoid disposing of WebSocketConnection unnecessarily. 2022-05-18 21:04:22 -05:00
Luke Brown
8d0024ec49 change logging back to debug 2022-05-16 22:35:45 -05:00
Luke Brown
8f761a64f5 revert changes to SessionManager 2022-05-16 22:33:04 -05:00
Luke Brown
a64eebe79f changes to use dispose 2022-05-16 22:09:41 -05: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
Luke Brown
8bb4cd017c applied reviewer suggestions: removing unnecessary async and adding necessary async 2022-05-10 08:28:01 -05:00
Luke Brown
c5f09ab650 Fix broken CI by adding await 2022-05-09 23:59:31 -05:00
luke92brown
99df9c8fcd Replace redundant xml comments with inheritdoc. Make async changes to CloseSocket. 2022-05-09 23:44:37 -05:00
luke brown
4b563f4d7e Fix web sockets closing ungracefully 2022-05-09 23:44:24 -05: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
190 changed files with 4078 additions and 923 deletions

View File

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

View File

@@ -221,6 +221,7 @@ namespace Emby.Dlna.Didl
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile,
streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,
@@ -376,6 +377,7 @@ namespace Emby.Dlna.Didl
targetHeight,
streamInfo.TargetVideoBitDepth,
streamInfo.TargetVideoProfile,
streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,

View File

@@ -313,7 +313,7 @@ namespace Emby.Dlna.Main
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, address);
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(false) + descriptorUri);
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(address, false) + descriptorUri);
var device = new SsdpRootDevice
{

View File

@@ -561,6 +561,7 @@ namespace Emby.Dlna.PlayTo
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks ?? 0,
streamInfo.TargetVideoProfile,
streamInfo.TargetVideoRangeType,
streamInfo.TargetVideoLevel,
streamInfo.TargetFramerate ?? 0,
streamInfo.TargetPacketLength,

View File

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

View File

@@ -395,7 +395,13 @@ namespace Emby.Drawing
public string GetImageBlurHash(string path)
{
var size = GetImageDimensions(path);
if (size.Width <= 0 || size.Height <= 0)
return GetImageBlurHash(path, size);
}
/// <inheritdoc />
public string GetImageBlurHash(string path, ImageDimensions imageDimensions)
{
if (imageDimensions.Width <= 0 || imageDimensions.Height <= 0)
{
return string.Empty;
}
@@ -403,8 +409,8 @@ namespace Emby.Drawing
// We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance.
// One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width.
// See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components
float xCompF = MathF.Sqrt(16.0f * size.Width / size.Height);
float yCompF = xCompF * size.Height / size.Width;
float xCompF = MathF.Sqrt(16.0f * imageDimensions.Width / imageDimensions.Height);
float yCompF = xCompF * imageDimensions.Height / imageDimensions.Width;
int xComp = Math.Min((int)xCompF + 1, 9);
int yComp = Math.Min((int)yCompF + 1, 9);
@@ -439,47 +445,46 @@ namespace Emby.Drawing
.ToString("N", CultureInfo.InvariantCulture);
}
private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
private Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{
var inputFormat = Path.GetExtension(originalImagePath)
.TrimStart('.')
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
var inputFormat = Path.GetExtension(originalImagePath.AsSpan()).TrimStart('.').ToString();
// These are just jpg files renamed as tbn
if (string.Equals(inputFormat, "tbn", StringComparison.OrdinalIgnoreCase))
{
return (originalImagePath, dateModified);
return Task.FromResult((originalImagePath, dateModified));
}
if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
{
try
{
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
// TODO _mediaEncoder.ConvertImage is not implemented
// if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat))
// {
// try
// {
// string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
//
// string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
// var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
//
// var file = _fileSystem.GetFileInfo(outputPath);
// if (!file.Exists)
// {
// await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
// dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
// }
// else
// {
// dateModified = file.LastWriteTimeUtc;
// }
//
// originalImagePath = outputPath;
// }
// catch (Exception ex)
// {
// _logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
// }
// }
string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
var file = _fileSystem.GetFileInfo(outputPath);
if (!file.Exists)
{
await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
}
else
{
dateModified = file.LastWriteTimeUtc;
}
originalImagePath = outputPath;
}
catch (Exception ex)
{
_logger.LogError(ex, "Image conversion failed for {Path}", originalImagePath);
}
}
return (originalImagePath, dateModified);
return Task.FromResult((originalImagePath, dateModified));
}
/// <summary>

View File

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

View File

@@ -36,7 +36,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.8.0</VersionPrefix>
<VersionPrefix>10.8.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

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

View File

@@ -111,7 +111,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Class CompositionRoot.
/// </summary>
public abstract class ApplicationHost : IServerApplicationHost, IDisposable
public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable
{
/// <summary>
/// The environment variable prefixes to log at server startup.
@@ -1114,13 +1114,13 @@ namespace Emby.Server.Implementations
}
/// <inheritdoc/>
public string GetApiUrlForLocalAccess(bool allowHttps = true)
public string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true)
{
// With an empty source, the port will be null
string smart = NetManager.GetBindInterface(string.Empty, out _);
var smart = NetManager.GetBindInterface(hostname ?? IPHost.None, out _);
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
int? port = !allowHttps ? HttpPort : null;
return GetLocalApiUrl(smart.Trim('/'), scheme, port);
return GetLocalApiUrl(smart, scheme, port);
}
/// <inheritdoc/>
@@ -1134,11 +1134,13 @@ namespace Emby.Server.Implementations
// NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does
// not. For consistency, always trim the trailing slash.
scheme ??= ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
var isHttps = string.Equals(scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
return new UriBuilder
{
Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp),
Scheme = scheme,
Host = hostname,
Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort),
Port = port ?? (isHttps ? HttpsPort : HttpPort),
Path = ConfigurationManager.GetNetworkConfiguration().BaseUrl
}.ToString().TrimEnd('/');
}
@@ -1230,5 +1232,49 @@ namespace Emby.Server.Implementations
_disposed = true;
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(false);
GC.SuppressFinalize(this);
}
/// <summary>
/// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
/// </summary>
/// <returns>A ValueTask.</returns>
protected virtual async ValueTask DisposeAsyncCore()
{
var type = GetType();
Logger.LogInformation("Disposing {Type}", type.Name);
foreach (var (part, _) in _disposableParts)
{
var partType = part.GetType();
if (partType == type)
{
continue;
}
Logger.LogInformation("Disposing {Type}", partType.Name);
try
{
part.Dispose();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error disposing {Type}", partType.Name);
}
}
// used for closing websockets
foreach (var session in _sessionManager.Sessions)
{
await session.DisposeAsync().ConfigureAwait(false);
}
}
}
}

View File

@@ -11,7 +11,6 @@ using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using Diacritics.Extensions;
using Emby.Server.Implementations.Playlists;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -171,7 +170,15 @@ namespace Emby.Server.Implementations.Data
"CodecTimeBase",
"ColorPrimaries",
"ColorSpace",
"ColorTransfer"
"ColorTransfer",
"DvVersionMajor",
"DvVersionMinor",
"DvProfile",
"DvLevel",
"RpuPresentFlag",
"ElPresentFlag",
"BlPresentFlag",
"DvBlSignalCompatibilityId"
};
private static readonly string _mediaStreamSaveColumnsInsertQuery =
@@ -342,7 +349,7 @@ namespace Emby.Server.Implementations.Data
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
{
const string CreateMediaStreamsTableCommand
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, PRIMARY KEY (ItemId, StreamIndex))";
const string CreateMediaAttachmentsTableCommand
= "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecTag TEXT NULL, Comment TEXT NULL, Filename TEXT NULL, MIMEType TEXT NULL, PRIMARY KEY (ItemId, AttachmentIndex))";
@@ -556,6 +563,15 @@ namespace Emby.Server.Implementations.Data
AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
AddColumn(db, "MediaStreams", "DvVersionMajor", "INT", existingColumnNames);
AddColumn(db, "MediaStreams", "DvVersionMinor", "INT", existingColumnNames);
AddColumn(db, "MediaStreams", "DvProfile", "INT", existingColumnNames);
AddColumn(db, "MediaStreams", "DvLevel", "INT", existingColumnNames);
AddColumn(db, "MediaStreams", "RpuPresentFlag", "INT", existingColumnNames);
AddColumn(db, "MediaStreams", "ElPresentFlag", "INT", existingColumnNames);
AddColumn(db, "MediaStreams", "BlPresentFlag", "INT", existingColumnNames);
AddColumn(db, "MediaStreams", "DvBlSignalCompatibilityId", "INT", existingColumnNames);
},
TransactionMode);
@@ -2404,7 +2420,7 @@ namespace Emby.Server.Implementations.Data
}
// genres, tags, studios, person, year?
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId))");
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from ItemValues where ItemId=@SimilarItemId))");
if (item is MusicArtist)
{
@@ -3059,12 +3075,12 @@ namespace Emby.Server.Implementations.Data
if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
{
return "(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)";
return "(select CleanValue from ItemValues where ItemId=Guid and Type=0 LIMIT 1)";
}
if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
{
return "(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)";
return "(select CleanValue from ItemValues where ItemId=Guid and Type=1 LIMIT 1)";
}
if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
@@ -3074,7 +3090,7 @@ namespace Emby.Server.Implementations.Data
if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
{
return "(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)";
return "(select CleanValue from ItemValues where ItemId=Guid and Type=3 LIMIT 1)";
}
if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
@@ -3147,6 +3163,11 @@ namespace Emby.Server.Implementations.Data
return ItemSortBy.IndexNumber;
}
if (string.Equals(name, ItemSortBy.SimilarityScore, StringComparison.OrdinalIgnoreCase))
{
return ItemSortBy.SimilarityScore;
}
// Unknown SortBy, just sort by the SortName.
return ItemSortBy.SortName;
}
@@ -3847,7 +3868,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@ArtistIds" + index;
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId);
@@ -3868,7 +3889,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@ArtistIds" + index;
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId);
@@ -3889,7 +3910,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@ArtistIds" + index;
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type=1))");
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId);
@@ -3931,7 +3952,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@ExcludeArtistId" + index;
clauses.Add("(guid not in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
if (statement != null)
{
statement.TryBind(paramName, artistId);
@@ -3952,7 +3973,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@GenreId" + index;
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
if (statement != null)
{
statement.TryBind(paramName, genreId);
@@ -3971,7 +3992,7 @@ namespace Emby.Server.Implementations.Data
var index = 0;
foreach (var item in query.Genres)
{
clauses.Add("@Genre" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=2)");
clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)");
if (statement != null)
{
statement.TryBind("@Genre" + index, GetCleanValue(item));
@@ -3990,7 +4011,7 @@ namespace Emby.Server.Implementations.Data
var index = 0;
foreach (var item in tags)
{
clauses.Add("@Tag" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
if (statement != null)
{
statement.TryBind("@Tag" + index, GetCleanValue(item));
@@ -4009,7 +4030,7 @@ namespace Emby.Server.Implementations.Data
var index = 0;
foreach (var item in excludeTags)
{
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
if (statement != null)
{
statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
@@ -4030,7 +4051,7 @@ namespace Emby.Server.Implementations.Data
{
var paramName = "@StudioId" + index;
clauses.Add("(guid in (select itemid from itemvalues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
if (statement != null)
{
@@ -4509,7 +4530,7 @@ namespace Emby.Server.Implementations.Data
{
int index = 0;
string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++));
whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
}
else
{
@@ -4744,11 +4765,11 @@ namespace Emby.Server.Implementations.Data
';',
new string[]
{
"delete from itemvalues where type = 6",
"delete from ItemValues where type = 6",
"insert into itemvalues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
"insert into ItemValues (ItemId, Type, Value, CleanValue) select ItemId, 6, Value, CleanValue from ItemValues where Type=4",
@"insert into itemvalues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
@"insert into ItemValues (ItemId, Type, Value, CleanValue) select AncestorIds.itemid, 6, ItemValues.Value, ItemValues.CleanValue
FROM AncestorIds
LEFT JOIN ItemValues ON (AncestorIds.AncestorId = ItemValues.ItemId)
where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type = 4 "
@@ -4913,6 +4934,7 @@ SELECT key FROM UserDatas WHERE isFavorite=@IsFavorite AND userId=@UserId)
AND Type = @InternalPersonType)");
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
statement?.TryBind("@InternalPersonType", typeof(Person).FullName);
statement?.TryBind("@UserId", query.User.InternalId);
}
if (!query.ItemId.Equals(default))
@@ -4967,11 +4989,6 @@ AND Type = @InternalPersonType)");
statement?.TryBind("@NameContains", "%" + query.NameContains + "%");
}
if (query.User != null)
{
statement?.TryBind("@UserId", query.User.InternalId);
}
return whereClauses;
}
@@ -5763,7 +5780,7 @@ AND Type = @InternalPersonType)");
{
var itemIdBlob = id.ToByteArray();
// First delete chapters
// Delete existing mediastreams
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
InsertMediaStreams(itemIdBlob, streams, db);
@@ -5855,6 +5872,15 @@ AND Type = @InternalPersonType)");
statement.TryBind("@ColorPrimaries" + index, stream.ColorPrimaries);
statement.TryBind("@ColorSpace" + index, stream.ColorSpace);
statement.TryBind("@ColorTransfer" + index, stream.ColorTransfer);
statement.TryBind("@DvVersionMajor" + index, stream.DvVersionMajor);
statement.TryBind("@DvVersionMinor" + index, stream.DvVersionMinor);
statement.TryBind("@DvProfile" + index, stream.DvProfile);
statement.TryBind("@DvLevel" + index, stream.DvLevel);
statement.TryBind("@RpuPresentFlag" + index, stream.RpuPresentFlag);
statement.TryBind("@ElPresentFlag" + index, stream.ElPresentFlag);
statement.TryBind("@BlPresentFlag" + index, stream.BlPresentFlag);
statement.TryBind("@DvBlSignalCompatibilityId" + index, stream.DvBlSignalCompatibilityId);
}
statement.Reset();
@@ -5867,10 +5893,10 @@ AND Type = @InternalPersonType)");
}
/// <summary>
/// Gets the chapter.
/// Gets the media stream.
/// </summary>
/// <param name="reader">The reader.</param>
/// <returns>ChapterInfo.</returns>
/// <returns>MediaStream.</returns>
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
{
var item = new MediaStream
@@ -6026,6 +6052,46 @@ AND Type = @InternalPersonType)");
item.ColorTransfer = colorTransfer;
}
if (reader.TryGetInt32(35, out var dvVersionMajor))
{
item.DvVersionMajor = dvVersionMajor;
}
if (reader.TryGetInt32(36, out var dvVersionMinor))
{
item.DvVersionMinor = dvVersionMinor;
}
if (reader.TryGetInt32(37, out var dvProfile))
{
item.DvProfile = dvProfile;
}
if (reader.TryGetInt32(38, out var dvLevel))
{
item.DvLevel = dvLevel;
}
if (reader.TryGetInt32(39, out var rpuPresentFlag))
{
item.RpuPresentFlag = rpuPresentFlag;
}
if (reader.TryGetInt32(40, out var elPresentFlag))
{
item.ElPresentFlag = elPresentFlag;
}
if (reader.TryGetInt32(41, out var blPresentFlag))
{
item.BlPresentFlag = blPresentFlag;
}
if (reader.TryGetInt32(42, out var dvBlSignalCompatibilityId))
{
item.DvBlSignalCompatibilityId = dvBlSignalCompatibilityId;
}
if (item.Type == MediaStreamType.Subtitle)
{
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Class WebSocketConnection.
/// </summary>
public class WebSocketConnection : IWebSocketConnection, IDisposable
public class WebSocketConnection : IWebSocketConnection
{
/// <summary>
/// The logger.
@@ -36,6 +36,8 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private readonly WebSocket _socket;
private bool _disposed = false;
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
/// </summary>
@@ -244,10 +246,39 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (_disposed)
{
return;
}
if (dispose)
{
_socket.Dispose();
}
_disposed = true;
}
/// <inheritdoc />
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore().ConfigureAwait(false);
Dispose(false);
GC.SuppressFinalize(this);
}
/// <summary>
/// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
/// </summary>
/// <returns>A ValueTask.</returns>
protected virtual async ValueTask DisposeAsyncCore()
{
if (_socket.State == WebSocketState.Open)
{
await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", CancellationToken.None).ConfigureAwait(false);
}
_socket.Dispose();
}
}
}

View File

@@ -262,6 +262,10 @@ namespace Emby.Server.Implementations.IO
_logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
result.Exists = false;
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, "Reading the file at {Path} failed due to a permissions exception.", fileInfo.FullName);
}
}
}

View File

@@ -1860,7 +1860,9 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(item));
}
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
var outdated = forceUpdate
? item.ImageInfos.Where(i => i.Path != null).ToArray()
: item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
// Skip image processing if current or live tv source
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
{
@@ -1883,7 +1885,7 @@ namespace Emby.Server.Implementations.Library
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
continue;
}
catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
catch (Exception ex) when (ex is InvalidOperationException or IOException)
{
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
continue;
@@ -1895,23 +1897,24 @@ namespace Emby.Server.Implementations.Library
}
}
ImageDimensions size;
try
{
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
size = _imageProcessor.GetImageDimensions(item, image);
image.Width = size.Width;
image.Height = size.Height;
}
catch (Exception ex)
{
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
size = new ImageDimensions(0, 0);
image.Width = 0;
image.Height = 0;
continue;
}
try
{
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path);
image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path, size);
}
catch (Exception ex)
{
@@ -2450,6 +2453,12 @@ namespace Emby.Server.Implementations.Library
return RootFolder;
}
/// <inheritdoc />
public void QueueLibraryScan()
{
_taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
}
/// <inheritdoc />
public int? GetSeasonNumberFromPath(string path)
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
@@ -2840,10 +2849,12 @@ namespace Emby.Server.Implementations.Library
var existingNameCount = 1; // first numbered name will be 2
var virtualFolderPath = Path.Combine(rootFolderPath, name);
var originalName = name;
while (Directory.Exists(virtualFolderPath))
{
existingNameCount++;
virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
name = originalName + existingNameCount;
virtualFolderPath = Path.Combine(rootFolderPath, name);
}
var mediaPathInfos = options.PathInfos;

View File

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

View File

@@ -13,11 +13,11 @@ namespace Emby.Server.Implementations.Library
{
public static int? GetDefaultAudioStreamIndex(IReadOnlyList<MediaStream> streams, IReadOnlyList<string> preferredLanguages, bool preferDefaultTrack)
{
var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages);
var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages).ToList();
if (preferDefaultTrack)
{
var defaultStream = streams.FirstOrDefault(i => i.IsDefault);
var defaultStream = sortedStreams.FirstOrDefault(i => i.IsDefault);
if (defaultStream != null)
{

View File

@@ -225,7 +225,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<Episode>(parent, files, true, collectionType, true);
return ResolveVideos<Episode>(parent, files, false, collectionType, true);
}
return null;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
{
public sealed class WebSocketController : ISessionController, IDisposable
public sealed class WebSocketController : ISessionController, IAsyncDisposable, IDisposable
{
private readonly ILogger<WebSocketController> _logger;
private readonly ISessionManager _sessionManager;
@@ -53,13 +53,13 @@ namespace Emby.Server.Implementations.Session
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));
_logger.LogDebug("Removing websocket from session {Session}", _session.Id);
_sockets.Remove(connection);
connection.Closed -= OnConnectionClosed;
_sessionManager.CloseIfNeeded(_session);
await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false);
}
/// <inheritdoc />
@@ -99,6 +99,23 @@ namespace Emby.Server.Implementations.Session
foreach (var socket in _sockets)
{
socket.Closed -= OnConnectionClosed;
socket.Dispose();
}
_disposed = true;
}
public async ValueTask DisposeAsync()
{
if (_disposed)
{
return;
}
foreach (var socket in _sockets)
{
socket.Closed -= OnConnectionClosed;
await socket.DisposeAsync().ConfigureAwait(false);
}
_disposed = true;

View File

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

View File

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

View File

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

View File

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

View File

@@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers
// Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token
// since it gets disposed when ffmpeg exits
var cancellationToken = cancellationTokenSource.Token;
using var state = await StreamingHelpers.GetStreamingState(
var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
Request,
_authContext,
@@ -1414,7 +1414,8 @@ namespace Jellyfin.Api.Controllers
state.RunTimeTicks ?? 0,
state.Request.SegmentContainer ?? string.Empty,
"hls1/main/",
Request.QueryString.ToString());
Request.QueryString.ToString(),
EncodingHelper.IsCopyCodec(state.OutputVideoCodec));
var playlist = _dynamicHlsPlaylistGenerator.CreateMainPlaylist(request);
return new FileContentResult(Encoding.UTF8.GetBytes(playlist), MimeTypes.GetMimeType("playlist.m3u8"));
@@ -1431,7 +1432,7 @@ namespace Jellyfin.Api.Controllers
var cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = cancellationTokenSource.Token;
using var state = await StreamingHelpers.GetStreamingState(
var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
Request,
_authContext,
@@ -1711,20 +1712,30 @@ namespace Jellyfin.Api.Controllers
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))
{
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
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))
{
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;
@@ -1773,13 +1784,25 @@ namespace Jellyfin.Api.Controllers
var args = "-codec:v:0 " + codec;
// Prefer hvc1 to hev1.
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
{
args += " -tag:v:0 hvc1";
if (EncodingHelper.IsCopyCodec(codec)
&& (string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase)
|| 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)

View File

@@ -1724,6 +1724,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery, Range(0, 100)] int quality = 90)
{
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
if (!brandingOptions.SplashscreenEnabled)
{
return NotFound();
}
string splashscreenPath;
if (!string.IsNullOrWhiteSpace(brandingOptions.SplashscreenLocation)
@@ -1776,6 +1781,7 @@ namespace Jellyfin.Api.Controllers
/// <summary>
/// Uploads a custom splashscreen.
/// The body is expected to the image contents base64 encoded.
/// </summary>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
/// <response code="204">Successfully uploaded new splashscreen.</response>
@@ -1799,7 +1805,13 @@ namespace Jellyfin.Api.Controllers
return BadRequest("Error reading mimetype from uploaded image");
}
var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + MimeTypes.ToExtension(mimeType.Value));
var extension = MimeTypes.ToExtension(mimeType.Value);
if (string.IsNullOrEmpty(extension))
{
return BadRequest("Error converting mimetype to an image extension");
}
var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + extension);
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
brandingOptions.SplashscreenLocation = filePath;
_serverConfigurationManager.SaveConfiguration("branding", brandingOptions);
@@ -1812,6 +1824,29 @@ namespace Jellyfin.Api.Controllers
return NoContent();
}
/// <summary>
/// Delete a custom splashscreen.
/// </summary>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
/// <response code="204">Successfully deleted the custom splashscreen.</response>
/// <response code="403">User does not have permission to delete splashscreen..</response>
[HttpDelete("Branding/Splashscreen")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteCustomSplashscreen()
{
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
if (!string.IsNullOrEmpty(brandingOptions.SplashscreenLocation)
&& System.IO.File.Exists(brandingOptions.SplashscreenLocation))
{
System.IO.File.Delete(brandingOptions.SplashscreenLocation);
brandingOptions.SplashscreenLocation = null;
_serverConfigurationManager.SaveConfiguration("branding", brandingOptions);
}
return NoContent();
}
private static async Task<MemoryStream> GetMemoryStream(Stream inputStream)
{
using var reader = new StreamReader(inputStream);

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="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="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="limit">Optional. The maximum number of records to return.</param>
@@ -173,6 +178,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId,
[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] int? startIndex,
[FromQuery] int? limit,
@@ -316,6 +326,11 @@ namespace Jellyfin.Api.Controllers
Is3D = is3D,
HasTvdbId = hasTvdbId,
HasTmdbId = hasTmdbId,
IsMovie = isMovie,
IsSeries = isSeries,
IsNews = isNews,
IsKids = isKids,
IsSports = isSports,
HasOverview = hasOverview,
HasOfficialRating = hasOfficialRating,
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="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="locationTypes">Optional. If specified, results will be filtered based on 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 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 delimited.</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="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="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="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="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="searchTerm">Optional. Filter based on a search term.</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="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="excludeItemTypes">Optional. If specified, results will be filtered based on 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 delimeted.</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="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 delimited.</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 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="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="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="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.</param>
/// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.</param>
/// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.</param>
/// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multiple, comma 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 delimited.</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 delimited.</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="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="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="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.</param>
/// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimeted.</param>
/// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. 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 delimited.</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="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="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.</param>
/// <param name="albumIds">Optional. If specified, results will be filtered based on album id. 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 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="isLocked">Optional filter by items that are locked.</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="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="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="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="studioIds">Optional. If specified, results will be filtered based on studio 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 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 delimited.</param>
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional, include image information in output.</param>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
@@ -613,6 +633,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId,
[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] int? startIndex,
[FromQuery] int? limit,
@@ -695,6 +720,11 @@ namespace Jellyfin.Api.Controllers
hasImdbId,
hasTmdbId,
hasTvdbId,
isMovie,
isSeries,
isNews,
isKids,
isSports,
excludeItemIds,
startIndex,
limit,

View File

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

View File

@@ -6,6 +6,7 @@ using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -187,7 +188,7 @@ namespace Jellyfin.Api.Controllers
result.AlbumArtist = album.AlbumArtist;
break;
case Audio song:
result.AlbumArtist = song.AlbumArtists?[0];
result.AlbumArtist = song.AlbumArtists?.FirstOrDefault();
result.Artists = song.Artists;
MusicAlbum musicAlbum = song.AlbumEntity;

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="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="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="limit">Optional. The maximum number of records to return.</param>
@@ -140,6 +145,11 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? hasImdbId,
[FromQuery] bool? hasTmdbId,
[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] int? startIndex,
[FromQuery] int? limit,
@@ -224,6 +234,11 @@ namespace Jellyfin.Api.Controllers
hasImdbId,
hasTmdbId,
hasTvdbId,
isMovie,
isSeries,
isNews,
isKids,
isSports,
excludeItemIds,
startIndex,
limit,

View File

@@ -282,16 +282,19 @@ namespace Jellyfin.Api.Controllers
}
else
{
var success = await _userManager.AuthenticateUser(
user.Username,
request.CurrentPw,
request.CurrentPw,
HttpContext.GetNormalizedRemoteIp().ToString(),
false).ConfigureAwait(false);
if (success == null)
if (!HttpContext.User.IsInRole(UserRoles.Administrator))
{
return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
var success = await _userManager.AuthenticateUser(
user.Username,
request.CurrentPw,
request.CurrentPw,
HttpContext.GetNormalizedRemoteIp().ToString(),
false).ConfigureAwait(false);
if (success == null)
{
return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
}
}
await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false);

View File

@@ -427,7 +427,7 @@ namespace Jellyfin.Api.Controllers
StreamOptions = streamOptions
};
using var state = await StreamingHelpers.GetStreamingState(
var state = await StreamingHelpers.GetStreamingState(
streamingRequest,
Request,
_authContext,

View File

@@ -216,7 +216,7 @@ namespace Jellyfin.Api.Helpers
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
sdrVideoUrl += "&AllowVideoStreamCopy=false";
var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec) ?? 0;
var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0;
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;

View File

@@ -256,9 +256,17 @@ namespace Jellyfin.Api.Helpers
streamInfo.StartPositionTicks = startTimeTicks;
mediaSource.SupportsDirectPlay = streamInfo.PlayMethod == PlayMethod.DirectPlay;
// 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.SupportsTranscoding = streamInfo.PlayMethod == PlayMethod.DirectStream || mediaSource.TranscodingContainer != null;
mediaSource.SupportsDirectStream =
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)
{
@@ -290,7 +298,7 @@ namespace Jellyfin.Api.Helpers
}
else
{
if (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream)
if (!mediaSource.SupportsDirectPlay && (mediaSource.SupportsTranscoding || mediaSource.SupportsDirectStream))
{
streamInfo.PlayMethod = PlayMethod.Transcode;
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');

View File

@@ -179,7 +179,7 @@ namespace Jellyfin.Api.Helpers
{
containerInternal = streamingRequest.Static ?
StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio)
: GetOutputFileExtension(state);
: GetOutputFileExtension(state, mediaSource);
}
state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
@@ -235,7 +235,7 @@ namespace Jellyfin.Api.Helpers
ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static);
var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
? GetOutputFileExtension(state)
? GetOutputFileExtension(state, mediaSource)
: ("." + state.OutputContainer);
state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);
@@ -312,7 +312,7 @@ namespace Jellyfin.Api.Helpers
responseHeaders.Add(
"contentFeatures.dlna.org",
ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoRangeType, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
}
}
@@ -409,8 +409,9 @@ namespace Jellyfin.Api.Helpers
/// Gets the output file extension.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="mediaSource">The mediaSource.</param>
/// <returns>System.String.</returns>
private static string? GetOutputFileExtension(StreamState state)
private static string? GetOutputFileExtension(StreamState state, MediaSourceInfo? mediaSource)
{
var ext = Path.GetExtension(state.RequestedUrl);
@@ -425,7 +426,7 @@ namespace Jellyfin.Api.Helpers
var videoCodec = state.Request.VideoCodec;
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase))
string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
{
return ".ts";
}
@@ -474,6 +475,13 @@ namespace Jellyfin.Api.Helpers
}
}
// Fallback to the container of mediaSource
if (!string.IsNullOrEmpty(mediaSource?.Container))
{
var idx = mediaSource.Container.IndexOf(',', StringComparison.OrdinalIgnoreCase);
return '.' + (idx == -1 ? mediaSource.Container : mediaSource.Container[..idx]).Trim();
}
return null;
}
@@ -533,6 +541,7 @@ namespace Jellyfin.Api.Helpers
state.TargetVideoBitDepth,
state.OutputVideoBitrate,
state.TargetVideoProfile,
state.TargetVideoRangeType,
state.TargetVideoLevel,
state.TargetFramerate,
state.TargetPacketLength,

View File

@@ -654,8 +654,8 @@ namespace Jellyfin.Api.Helpers
{
if (EnableThrottling(state))
{
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem);
state.TranscodingThrottler.Start();
transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem);
transcodingJob.TranscodingThrottler.Start();
}
}
@@ -663,18 +663,11 @@ namespace Jellyfin.Api.Helpers
{
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
// enable throttling when NOT using hardware acceleration
if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
{
return state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
state.IsInputVideo &&
state.VideoType == VideoType.VideoFile &&
!EncodingHelper.IsCopyCodec(state.OutputVideoCodec);
}
return false;
return state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
state.IsInputVideo &&
state.VideoType == VideoType.VideoFile;
}
/// <summary>

View File

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

View File

@@ -47,11 +47,6 @@ namespace Jellyfin.Api.Models.StreamingDtos
}
}
/// <summary>
/// Gets or sets the transcoding throttler.
/// </summary>
public TranscodingThrottler? TranscodingThrottler { get; set; }
/// <summary>
/// Gets the video request.
/// </summary>
@@ -191,11 +186,8 @@ namespace Jellyfin.Api.Models.StreamingDtos
{
_mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
}
TranscodingThrottler?.Dispose();
}
TranscodingThrottler = null;
TranscodingJob = null;
_disposed = true;

View File

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

View File

@@ -18,7 +18,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.8.0</VersionPrefix>
<VersionPrefix>10.8.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

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

View File

@@ -3,14 +3,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using BlurHashSharp.SkiaSharp;
using Diacritics.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Drawing;
using Microsoft.Extensions.Logging;
using SkiaSharp;
using static Jellyfin.Drawing.Skia.SkiaHelper;
using SKSvg = SkiaSharp.Extended.Svg.SKSvg;
namespace Jellyfin.Drawing.Skia
{
@@ -19,8 +19,7 @@ namespace Jellyfin.Drawing.Skia
/// </summary>
public class SkiaEncoder : IImageEncoder
{
private static readonly HashSet<string> _transparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private static readonly HashSet<string> _transparentImageTypes = new(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
private readonly ILogger<SkiaEncoder> _logger;
private readonly IApplicationPaths _appPaths;
@@ -71,7 +70,7 @@ namespace Jellyfin.Drawing.Skia
/// <inheritdoc/>
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
=> new HashSet<ImageFormat> { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
/// <summary>
/// Check if the native lib is available.
@@ -109,9 +108,7 @@ namespace Jellyfin.Drawing.Skia
}
/// <inheritdoc />
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="FileNotFoundException">The path is not valid.</exception>
/// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
public ImageDimensions GetImageSize(string path)
{
if (!File.Exists(path))
@@ -119,12 +116,27 @@ namespace Jellyfin.Drawing.Skia
throw new FileNotFoundException("File not found", path);
}
var extension = Path.GetExtension(path.AsSpan());
if (extension.Equals(".svg", StringComparison.OrdinalIgnoreCase))
{
var svg = new SKSvg();
svg.Load(path);
return new ImageDimensions(Convert.ToInt32(svg.Picture.CullRect.Width), Convert.ToInt32(svg.Picture.CullRect.Height));
}
using var codec = SKCodec.Create(path, out SKCodecResult result);
EnsureSuccess(result);
var info = codec.Info;
return new ImageDimensions(info.Width, info.Height);
switch (result)
{
case SKCodecResult.Success:
var info = codec.Info;
return new ImageDimensions(info.Width, info.Height);
case SKCodecResult.Unimplemented:
_logger.LogDebug("Image format not supported: {FilePath}", path);
return new ImageDimensions(0, 0);
default:
_logger.LogError("Unable to determine image dimensions for {FilePath}: {SkCodecResult}", path, result);
return new ImageDimensions(0, 0);
}
}
/// <inheritdoc />
@@ -138,6 +150,13 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentNullException(nameof(path));
}
var extension = Path.GetExtension(path.AsSpan()).TrimStart('.');
if (!SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase))
{
_logger.LogDebug("Unable to compute blur hash due to unsupported format: {ImagePath}", path);
return string.Empty;
}
// Any larger than 128x128 is too slow and there's no visually discernible difference
return BlurHashEncoder.Encode(xComp, yComp, path, 128, 128);
}
@@ -378,6 +397,13 @@ namespace Jellyfin.Drawing.Skia
throw new ArgumentException("String can't be empty.", nameof(outputPath));
}
var inputFormat = Path.GetExtension(inputPath.AsSpan()).TrimStart('.');
if (!SupportedInputFormats.Contains(inputFormat, StringComparison.OrdinalIgnoreCase))
{
_logger.LogDebug("Unable to encode image due to unsupported format: {ImagePath}", inputPath);
return inputPath;
}
var skiaOutputFormat = GetImageFormat(outputFormat);
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);

View File

@@ -8,19 +8,6 @@ namespace Jellyfin.Drawing.Skia
/// </summary>
public static class SkiaHelper
{
/// <summary>
/// Ensures the result is a success
/// by throwing an exception when that's not the case.
/// </summary>
/// <param name="result">The result returned by Skia.</param>
public static void EnsureSuccess(SKCodecResult result)
{
if (result != SKCodecResult.Success)
{
throw new SkiaCodecException(result);
}
}
/// <summary>
/// Gets the next valid image as a bitmap.
/// </summary>

View File

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

View File

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

View File

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

View File

@@ -440,6 +440,12 @@ namespace Jellyfin.Server.Extensions
.Cast<IOpenApiAny>()
.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.Model.ApiClient;
using MediaBrowser.Model.Entities;
@@ -14,6 +18,19 @@ namespace Jellyfin.Server.Filters
/// </summary>
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 />
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(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>
<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.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.3" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.7" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.7" />
<PackageReference Include="prometheus-net" Version="6.0.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
@@ -48,7 +48,7 @@
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.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>

View File

@@ -19,41 +19,44 @@ namespace Jellyfin.Server.Middleware
private readonly RequestDelegate _next;
private readonly ILogger<ResponseTimeMiddleware> _logger;
private readonly bool _enableWarning;
private readonly long _warningThreshold;
/// <summary>
/// Initializes a new instance of the <see cref="ResponseTimeMiddleware"/> class.
/// </summary>
/// <param name="next">Next request delegate.</param>
/// <param name="logger">Instance of the <see cref="ILogger{ExceptionMiddleware}"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
public ResponseTimeMiddleware(
RequestDelegate next,
ILogger<ResponseTimeMiddleware> logger,
IServerConfigurationManager serverConfigurationManager)
ILogger<ResponseTimeMiddleware> logger)
{
_next = next;
_logger = logger;
_enableWarning = serverConfigurationManager.Configuration.EnableSlowResponseWarning;
_warningThreshold = serverConfigurationManager.Configuration.SlowResponseThresholdMs;
}
/// <summary>
/// Invoke request.
/// </summary>
/// <param name="context">Request context.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <returns>Task.</returns>
public async Task Invoke(HttpContext context)
public async Task Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager)
{
var watch = new Stopwatch();
watch.Start();
var enableWarning = serverConfigurationManager.Configuration.EnableSlowResponseWarning;
var warningThreshold = serverConfigurationManager.Configuration.SlowResponseThresholdMs;
context.Response.OnStarting(() =>
{
watch.Stop();
LogWarning(context, watch);
if (enableWarning && watch.ElapsedMilliseconds > warningThreshold)
{
_logger.LogWarning(
"Slow HTTP Response from {Url} to {RemoteIp} in {Elapsed:g} with Status Code {StatusCode}",
context.Request.GetDisplayUrl(),
context.GetNormalizedRemoteIp(),
watch.Elapsed,
context.Response.StatusCode);
}
var responseTimeForCompleteRequest = watch.ElapsedMilliseconds;
context.Response.Headers[ResponseHeaderResponseTime] = responseTimeForCompleteRequest.ToString(CultureInfo.InvariantCulture);
return Task.CompletedTask;
@@ -62,18 +65,5 @@ namespace Jellyfin.Server.Middleware
// Call the next delegate/middleware in the pipeline
await this._next(context).ConfigureAwait(false);
}
private void LogWarning(HttpContext context, Stopwatch watch)
{
if (_enableWarning && watch.ElapsedMilliseconds > _warningThreshold)
{
_logger.LogWarning(
"Slow HTTP Response from {Url} to {RemoteIp} in {Elapsed:g} with Status Code {StatusCode}",
context.Request.GetDisplayUrl(),
context.GetNormalizedRemoteIp(),
watch.Elapsed,
context.Response.StatusCode);
}
}
}
}

View File

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

View File

@@ -243,7 +243,7 @@ namespace Jellyfin.Server
}
}
appHost.Dispose();
await appHost.DisposeAsync().ConfigureAwait(false);
}
if (_restartOnShutdown)
@@ -545,12 +545,14 @@ namespace Jellyfin.Server
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
Stream resource = typeof(Program).Assembly.GetManifestResourceStream(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 (dst.ConfigureAwait(false))
{
// Copy the resource contents to the expected file path for the config file
await resource.CopyToAsync(dst).ConfigureAwait(false);
Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
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>
object GetConfiguration(string key);
/// <summary>
/// Gets the array of coniguration stores.
/// </summary>
/// <returns>Array of ConfigurationStore.</returns>
ConfigurationStore[] GetConfigurationStores();
/// <summary>
/// Gets the type of the configuration.
/// </summary>

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.8.0</VersionPrefix>
<VersionPrefix>10.8.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

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

View File

@@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>BlurHash.</returns>
string GetImageBlurHash(string path);
/// <summary>
/// Gets the blurhash of the image.
/// </summary>
/// <param name="path">Path to the image file.</param>
/// <param name="imageDimensions">The image dimensions.</param>
/// <returns>BlurHash.</returns>
string GetImageBlurHash(string path, ImageDimensions imageDimensions);
/// <summary>
/// Gets the image cache tag.
/// </summary>

View File

@@ -169,8 +169,8 @@ namespace MediaBrowser.Controller.Entities.Audio
var childUpdateType = ItemUpdateType.None;
// Refresh songs
foreach (var item in items)
// Refresh songs only and not m3u files in album folder
foreach (var item in items.OfType<Audio>())
{
cancellationToken.ThrowIfCancellationRequested();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -258,14 +258,10 @@ namespace MediaBrowser.Controller.Entities.TV
SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
DtoOptions = options
DtoOptions = options,
IsMissing = user?.DisplayMissingEpisodes
};
if (!user.DisplayMissingEpisodes)
{
query.IsMissing = false;
}
var allItems = LibraryManager.GetItemList(query);
var allSeriesEpisodes = allItems.OfType<Episode>().ToList();

View File

@@ -4,6 +4,7 @@
using System.Net;
using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.System;
using Microsoft.AspNetCore.Http;
@@ -74,9 +75,10 @@ namespace MediaBrowser.Controller
/// <summary>
/// Gets an URL that can be used to access the API over LAN.
/// </summary>
/// <param name="hostname">An optional hostname to use.</param>
/// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
/// <returns>The API URL.</returns>
string GetApiUrlForLocalAccess(bool allowHttps = true);
string GetApiUrlForLocalAccess(IPObject hostname = null, bool allowHttps = true);
/// <summary>
/// Gets a local (LAN) URL that can be used to access the API.

View File

@@ -570,5 +570,13 @@ namespace MediaBrowser.Controller.Library
Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason);
BaseItem GetParentItem(Guid? parentId, Guid? userId);
/// <summary>
/// Queue a library scan.
/// </summary>
/// <remarks>
/// This exists so plugins can trigger a library scan.
/// </remarks>
void QueueLibraryScan();
}
}

View File

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

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.8.0</VersionPrefix>
<VersionPrefix>10.8.3</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -18,7 +18,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Diacritics" Version="3.3.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />

View File

@@ -75,6 +75,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <value>The profile.</value>
public string Profile { get; set; }
/// <summary>
/// Gets or sets the video range type.
/// </summary>
/// <value>The video range type.</value>
public string VideoRangeType { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>

View File

@@ -13,11 +13,13 @@ using System.Threading;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -32,6 +34,8 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IApplicationPaths _appPaths;
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config;
private readonly Version _minKernelVersioni915Hang = new Version(5, 18);
private static readonly string[] _videoProfilesH264 = new[]
{
@@ -54,11 +58,13 @@ namespace MediaBrowser.Controller.MediaEncoding
public EncodingHelper(
IApplicationPaths appPaths,
IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder)
ISubtitleEncoder subtitleEncoder,
IConfiguration config)
{
_appPaths = appPaths;
_mediaEncoder = mediaEncoder;
_subtitleEncoder = subtitleEncoder;
_config = config;
}
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -120,6 +126,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& _mediaEncoder.SupportsFilter("scale_vaapi")
&& _mediaEncoder.SupportsFilter("deinterlace_vaapi")
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
&& _mediaEncoder.SupportsFilter("procamp_vaapi")
&& _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
&& _mediaEncoder.SupportsFilter("hwupload_vaapi");
}
@@ -144,29 +151,44 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream == null)
if (state.VideoStream == null
|| !options.EnableTonemapping
|| GetVideoColorBitDepth(state) != 10)
{
return false;
}
return options.EnableTonemapping
&& (string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
&& GetVideoColorBitDepth(state) == 10;
if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase))
{
// 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.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
&& (string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.VideoStream.VideoRangeType, "HLG", StringComparison.OrdinalIgnoreCase));
}
private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream == null)
if (state.VideoStream == null
|| !options.EnableVppTonemapping
|| GetVideoColorBitDepth(state) != 10)
{
return false;
}
// Native VPP tonemapping may come to QSV in the future.
return options.EnableVppTonemapping
&& string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
&& GetVideoColorBitDepth(state) == 10;
return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
@@ -516,8 +538,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase))
{
// flac is experimental in mp4 muxer
return "flac -strict -2";
return "flac";
}
return codec.ToLowerInvariant();
@@ -696,6 +717,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (_mediaEncoder.IsVaapiDeviceInteli965)
{
// Only override i965 since it has lower priority than iHD in libva lookup.
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
args.Append(GetVaapiDeviceArgs(null, "i965", null, VaapiAlias));
}
else
@@ -907,6 +931,13 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
}
// Disable auto inserted SW scaler for HW decoders in case of changed resolution.
var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
if (!isSwDecoder)
{
arg.Append(" -autoscale 0");
}
return arg.ToString();
}
@@ -1024,7 +1055,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoCodec, "h264_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)
@@ -1062,10 +1094,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
// Clients may direct play higher than level 41, but there's no reason to transcode higher.
if (requestLevel >= 41)
// Transcode to level 5.1 and lower for maximum compatibility.
// h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
// https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
if (requestLevel >= 51)
{
return "41";
return "51";
}
}
}
@@ -1220,10 +1254,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// Example: we encoded half of desired length, then codec detected
// scene cut and inserted a keyframe; next forced keyframe would
// 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(
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));
}
@@ -1243,6 +1276,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
{
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
{
@@ -1271,6 +1310,10 @@ namespace MediaBrowser.Controller.MediaEncoding
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
var intelLowPowerHwEncoding = false;
// Workaround for linux 5.18+ i915 hang at cost of performance.
// https://github.com/intel/media-driver/issues/1456
var enableWaFori915Hang = false;
if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
@@ -1286,6 +1329,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
if (OperatingSystem.IsLinux() && Environment.OSVersion.Version >= _minKernelVersioni915Hang)
{
var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
|| vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
&& IsVaapiSupported(state)
&& IsOpenclFullSupported()
&& !IsVaapiVppTonemapAvailable(state, encodingOptions)
&& IsHwTonemapAvailable(state, encodingOptions);
enableWaFori915Hang = isIntelDecoder && doOclTonemap;
}
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
@@ -1294,6 +1351,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
}
else
{
enableWaFori915Hang = false;
}
}
if (intelLowPowerHwEncoding)
@@ -1301,6 +1362,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -low_power 1";
}
if (enableWaFori915Hang)
{
param += " -async_depth 1";
}
var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
@@ -1682,6 +1748,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Can't stream copy if we're burning in subtitles
if (request.SubtitleStreamIndex.HasValue
&& request.SubtitleStreamIndex.Value >= 0
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
{
return false;
@@ -1728,6 +1795,20 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
if (requestedRangeTypes.Length > 0)
{
if (string.IsNullOrEmpty(videoStream.VideoRangeType))
{
return false;
}
if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
// Video width must fall within requested value
if (request.MaxWidth.HasValue
&& (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value))
@@ -1868,7 +1949,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return request.EnableAutoStreamCopy;
}
public int? GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
public int GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
{
var bitrate = request.VideoBitRate;
@@ -1900,7 +1981,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
return bitrate;
// Cap the max target bitrate to intMax/2 to satisify the bufsize=bitrate*2.
return Math.Min(bitrate ?? 0, int.MaxValue / 2);
}
private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
@@ -1980,6 +2062,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
{
@@ -2213,13 +2297,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return state.IsInputVideo ? "-sn" : string.Empty;
}
// We have media info, but we don't know the stream indexes
// We have media info, but we don't know the stream index
if (state.VideoStream != null && state.VideoStream.Index == -1)
{
return "-sn";
}
// We have media info, but we don't know the stream indexes
// We have media info, but we don't know the stream index
if (state.AudioStream != null && state.AudioStream.Index == -1)
{
return state.IsInputVideo ? "-sn" : string.Empty;
@@ -2229,10 +2313,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.VideoStream != null)
{
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
args += string.Format(
CultureInfo.InvariantCulture,
"-map 0:{0}",
state.VideoStream.Index);
videoStreamIndex);
}
else
{
@@ -2242,23 +2328,27 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream != null)
{
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
if (state.AudioStream.IsExternal)
{
int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1;
int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
bool hasExternalGraphicsSubs = state.SubtitleStream != null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& state.SubtitleStream.IsExternal
&& !state.SubtitleStream.IsTextSubtitleStream;
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
args += string.Format(
CultureInfo.InvariantCulture,
" -map {0}:{1}",
externalAudioMapIndex,
externalAudioStream);
audioStreamIndex);
}
else
{
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
state.AudioStream.Index);
audioStreamIndex);
}
}
else
@@ -2273,14 +2363,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
{
int subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0}",
state.SubtitleStream.Index);
subtitleStreamIndex);
}
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
args += " -map 1:0 -sn";
int externalSubtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 1:{0} -sn",
externalSubtitleStreamIndex);
}
return args;
@@ -2509,7 +2606,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2",
"scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
maxWidthParam,
maxHeightParam,
scaleVal);
@@ -2553,7 +2650,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2",
"scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
maxWidthParam,
scaleVal);
}
@@ -2565,7 +2662,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
"scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})",
"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
maxHeightParam,
scaleVal);
}
@@ -2614,7 +2711,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
filter = "scale={0}:trunc({0}/dar/2)*2";
filter = "scale={0}:trunc({0}/a/2)*2";
}
}
@@ -2665,7 +2762,18 @@ namespace MediaBrowser.Controller.MediaEncoding
var args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709";
if (!hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
if (hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
{
args += ",procamp_vaapi=b={2}:c={3}:extra_hw_frames=16";
return string.Format(
CultureInfo.InvariantCulture,
args,
hwTonemapSuffix,
videoFormat ?? "nv12",
options.VppTonemappingBrightness,
options.VppTonemappingContrast);
}
else
{
args += ":tonemap={2}:peak={3}:desat={4}";
@@ -2768,8 +2876,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (hasGraphicalSubs)
{
// [0:s]scale=s=1280x720
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
// [0:s]scale=expr
var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -2867,7 +2975,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doCuTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=cuda");
}
}
@@ -2947,7 +3055,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
subFilters.Add("hwupload");
subFilters.Add("hwupload=derive_device=cuda");
overlayFilters.Add("overlay_cuda=eof_action=endall:shortest=1:repeatlast=0");
}
}
@@ -2955,7 +3063,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3057,7 +3167,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=16");
mainFilters.Add("format=d3d11");
mainFilters.Add("hwmap=derive_device=opencl");
}
}
@@ -3084,7 +3196,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var memoryOutput = false;
var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
if ((isD3d11vaDecoder && isSwEncoder) || isUploadForOclTonemap)
if (isD3d11vaDecoder && isSwEncoder)
{
memoryOutput = true;
@@ -3096,7 +3208,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// OUTPUT yuv420p surface
if (isSwDecoder && isAmfEncoder)
if (isSwDecoder && isAmfEncoder && !isUploadForOclTonemap)
{
memoryOutput = true;
}
@@ -3111,7 +3223,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
if (isDxInDxOut && !hasSubs)
if ((isDxInDxOut || isUploadForOclTonemap) && !hasSubs)
{
// OUTPUT d3d11(nv12) surface(vram)
// reverse-mapping via d3d11-opencl interop.
@@ -3122,7 +3234,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/* Make sub and overlay filters for subtitle stream */
var subFilters = new List<string>();
var overlayFilters = new List<string>();
if (isDxInDxOut)
if (isDxInDxOut || isUploadForOclTonemap)
{
if (hasSubs)
{
@@ -3143,7 +3255,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
subFilters.Add("hwupload");
subFilters.Add("hwupload=derive_device=opencl");
overlayFilters.Add("overlay_opencl=eof_action=endall:shortest=1:repeatlast=0");
overlayFilters.Add("hwmap=derive_device=d3d11va:reverse=1");
overlayFilters.Add("format=d3d11");
@@ -3153,7 +3265,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3275,7 +3389,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isD3d11vaDecoder || isQsvDecoder)
@@ -3381,7 +3495,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// qsv requires a fixed pool size.
subFilters.Add("hwupload=extra_hw_frames=32");
// default to 64 otherwise it will fail on certain iGPU.
subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3398,7 +3513,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3469,7 +3586,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder || isQsvDecoder)
@@ -3589,7 +3706,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// qsv requires a fixed pool size.
subFilters.Add("hwupload=extra_hw_frames=32");
// default to 64 otherwise it will fail on certain iGPU.
subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3606,7 +3724,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
}
@@ -3650,7 +3770,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var newfilters = new List<string>();
var noOverlay = swFilterChain.OverlayFilters.Count == 0;
newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
newfilters.Add("hwupload");
newfilters.Add("hwupload=derive_device=vaapi");
var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
@@ -3731,7 +3851,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder)
@@ -3836,7 +3956,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add(subTextSubtitlesFilter);
}
subFilters.Add("hwupload");
subFilters.Add("hwupload=derive_device=vaapi");
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var overlaySize = (overlayW.HasValue && overlayH.HasValue)
@@ -3853,7 +3973,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -3925,7 +4047,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw
if (doOclTonemap)
{
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
else if (isVaapiDecoder)
@@ -3955,7 +4077,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
mainFilters.Add("hwdownload");
mainFilters.Add("format=p010le");
mainFilters.Add("hwupload");
mainFilters.Add("hwupload=derive_device=opencl");
}
}
@@ -4028,7 +4150,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
var subSwScaleFilter = isSwDecoder
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4124,9 +4248,8 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Join(',', overlayFilters));
var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
var subtitleStreamIndex = state.SubtitleStream.IsExternal
? 0
: state.SubtitleStream.Index;
var subtitleStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.SubtitleStream);
var videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
if (hasSubs)
{
@@ -4147,7 +4270,7 @@ namespace MediaBrowser.Controller.MediaEncoding
filterStr,
mapPrefix,
subtitleStreamIndex,
state.VideoStream.Index,
videoStreamIndex,
mainStr,
subStr,
overlayStr);
@@ -4205,6 +4328,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return videoStream.BitDepth.Value;
}
else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
{
return 8;
@@ -4237,14 +4361,18 @@ namespace MediaBrowser.Controller.MediaEncoding
protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
{
var videoStream = state.VideoStream;
if (videoStream == null)
var mediaSource = state.MediaSource;
if (videoStream == null || mediaSource == null)
{
return null;
}
// Only use alternative encoders for video files.
var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
if (videoType != VideoType.VideoFile)
// HWA decoders can handle both video files and video folders.
var videoType = mediaSource.VideoType;
if (videoType != VideoType.VideoFile
&& videoType != VideoType.Iso
&& videoType != VideoType.Dvd
&& videoType != VideoType.BluRay)
{
return null;
}
@@ -4413,7 +4541,9 @@ namespace MediaBrowser.Controller.MediaEncoding
if (isD3d11Supported && isCodecAvailable)
{
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
// set -threads 3 to intel d3d11va decoder explicitly. Lower threads may result in dead lock.
// on newer devices such as Xe, the larger the init_pool_size, the longer the initialization time for opencl to derive from d3d11.
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
}
}
else
@@ -4491,7 +4621,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
&& _mediaEncoder.SupportsFilter("alphasrc");
var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
@@ -4550,7 +4681,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var hwSurface = IsCudaFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
@@ -4616,7 +4748,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
&& IsOpenclFullSupported()
&& _mediaEncoder.SupportsFilter("alphasrc");
var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsAmf)
@@ -4636,11 +4769,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
}
if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
}
}
if (is8_10bitSwFormatsAmf)
@@ -4677,7 +4805,8 @@ namespace MediaBrowser.Controller.MediaEncoding
&& IsVaapiFullSupported()
&& IsOpenclFullSupported()
&& _mediaEncoder.SupportsFilter("alphasrc");
var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsVaapi)
@@ -4734,7 +4863,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsVt)
@@ -4840,22 +4970,21 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions, string segmentContainer)
{
var inputModifier = string.Empty;
var probeSizeArgument = string.Empty;
var analyzeDurationArgument = string.Empty;
string analyzeDurationArgument;
if (state.MediaSource.AnalyzeDurationMs.HasValue)
// Apply -analyzeduration as per the environment variable,
// 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);
}
else
{
analyzeDurationArgument = string.Empty;
}
if (!string.IsNullOrEmpty(probeSizeArgument))
{
inputModifier += " " + probeSizeArgument;
}
if (!string.IsNullOrEmpty(analyzeDurationArgument))
{
@@ -4878,7 +5007,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.InputProtocol == MediaProtocol.Rtsp)
{
inputModifier += " -rtsp_transport tcp -rtsp_transport udp -rtsp_flags prefer_tcp";
inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
}
if (!string.IsNullOrEmpty(state.InputAudioSync))
@@ -5357,12 +5486,22 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
// opus will fail on 44100
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
if (state.OutputAudioSampleRate.HasValue)
// opus only supports specific sampling rates
var sampleRate = state.OutputAudioSampleRate;
if (sampleRate.HasValue)
{
audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
var sampleRateValue = sampleRate.Value switch
{
<= 8000 => 8000,
<= 12000 => 12000,
<= 16000 => 16000,
<= 24000 => 24000,
_ => 48000
};
audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
}
}
@@ -5384,6 +5523,28 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Empty).Trim();
}
public static int FindIndex(IReadOnlyList<MediaStream> mediaStreams, MediaStream streamToFind)
{
var index = 0;
var length = mediaStreams.Count;
for (var i = 0; i < length; i++)
{
var currentMediaStream = mediaStreams[i];
if (currentMediaStream == streamToFind)
{
return index;
}
if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
{
index++;
}
}
return -1;
}
public static bool IsCopyCodec(string codec)
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);

View File

@@ -366,6 +366,28 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
/// <summary>
/// Gets the target video range type.
/// </summary>
public string TargetVideoRangeType
{
get
{
if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec))
{
return VideoStream?.VideoRangeType;
}
var requestedRangeType = GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault();
if (!string.IsNullOrEmpty(requestedRangeType))
{
return requestedRangeType;
}
return null;
}
}
public string TargetVideoCodecTag
{
get
@@ -579,6 +601,26 @@ namespace MediaBrowser.Controller.MediaEncoding
return Array.Empty<string>();
}
public string[] GetRequestedRangeTypes(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.VideoRangeType))
{
return BaseRequest.VideoRangeType.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
}
if (!string.IsNullOrEmpty(codec))
{
var rangetype = BaseRequest.GetOption(codec, "rangetype");
if (!string.IsNullOrEmpty(rangetype))
{
return rangetype.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries);
}
}
return Array.Empty<string>();
}
public string GetRequestedLevel(string codec)
{
if (!string.IsNullOrEmpty(BaseRequest.Level))

View File

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

View File

@@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.MediaEncoding
percent = 100.0 * currentMs / totalMs;
transcodingPosition = val;
transcodingPosition = TimeSpan.FromMilliseconds(currentMs);
}
}
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))

View File

@@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
public interface IWebSocketConnection
public interface IWebSocketConnection : IAsyncDisposable, IDisposable
{
/// <summary>
/// Occurs when [closed].

View File

@@ -34,8 +34,8 @@ namespace MediaBrowser.Controller.Providers
public bool IsReplacingImage(ImageType type)
{
return ImageRefreshMode == MetadataRefreshMode.FullRefresh &&
(ReplaceAllImages || ReplaceImages.Contains(type));
return ImageRefreshMode == MetadataRefreshMode.FullRefresh
&& (ReplaceAllImages || ReplaceImages.Contains(type));
}
}
}

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
@@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Session
/// <summary>
/// Class SessionInfo.
/// </summary>
public sealed class SessionInfo : IDisposable
public sealed class SessionInfo : IAsyncDisposable, IDisposable
{
// 1 second
private const long ProgressIncrement = 10000000;
@@ -380,10 +381,28 @@ namespace MediaBrowser.Controller.Session
{
if (controller is IDisposable disposable)
{
_logger.LogDebug("Disposing session controller {0}", disposable.GetType().Name);
_logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name);
disposable.Dispose();
}
}
}
public async ValueTask DisposeAsync()
{
_disposed = true;
StopAutomaticProgress();
var controllers = SessionControllers.ToList();
foreach (var controller in controllers)
{
if (controller is IAsyncDisposable disposableAsync)
{
_logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
await disposableAsync.DisposeAsync().ConfigureAwait(false);
}
}
}
}
}

View File

@@ -100,6 +100,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"scale_vaapi",
"deinterlace_vaapi",
"tonemap_vaapi",
"procamp_vaapi",
"overlay_vaapi",
"hwupload_vaapi"
};

View File

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

View File

@@ -30,7 +30,7 @@
<PackageReference Include="libse" Version="3.6.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
<PackageReference Include="UTF.Unknown" Version="2.5.0" />
<PackageReference Include="UTF.Unknown" Version="2.5.1" />
</ItemGroup>
<!-- Code Analyzers-->

View File

@@ -310,5 +310,12 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <value>The color primaries.</value>
[JsonPropertyName("color_primaries")]
public string ColorPrimaries { get; set; }
/// <summary>
/// Gets or sets the side_data_list.
/// </summary>
/// <value>The side_data_list.</value>
[JsonPropertyName("side_data_list")]
public IReadOnlyList<MediaStreamInfoSideData> SideDataList { get; set; }
}
}

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace MediaBrowser.MediaEncoding.Probing
{
/// <summary>
/// Class MediaStreamInfoSideData.
/// </summary>
public class MediaStreamInfoSideData
{
/// <summary>
/// Gets or sets the SideDataType.
/// </summary>
/// <value>The SideDataType.</value>
[JsonPropertyName("side_data_type")]
public string? SideDataType { get; set; }
/// <summary>
/// Gets or sets the DvVersionMajor.
/// </summary>
/// <value>The DvVersionMajor.</value>
[JsonPropertyName("dv_version_major")]
public int? DvVersionMajor { get; set; }
/// <summary>
/// Gets or sets the DvVersionMinor.
/// </summary>
/// <value>The DvVersionMinor.</value>
[JsonPropertyName("dv_version_minor")]
public int? DvVersionMinor { get; set; }
/// <summary>
/// Gets or sets the DvProfile.
/// </summary>
/// <value>The DvProfile.</value>
[JsonPropertyName("dv_profile")]
public int? DvProfile { get; set; }
/// <summary>
/// Gets or sets the DvLevel.
/// </summary>
/// <value>The DvLevel.</value>
[JsonPropertyName("dv_level")]
public int? DvLevel { get; set; }
/// <summary>
/// Gets or sets the RpuPresentFlag.
/// </summary>
/// <value>The RpuPresentFlag.</value>
[JsonPropertyName("rpu_present_flag")]
public int? RpuPresentFlag { get; set; }
/// <summary>
/// Gets or sets the ElPresentFlag.
/// </summary>
/// <value>The ElPresentFlag.</value>
[JsonPropertyName("el_present_flag")]
public int? ElPresentFlag { get; set; }
/// <summary>
/// Gets or sets the BlPresentFlag.
/// </summary>
/// <value>The BlPresentFlag.</value>
[JsonPropertyName("bl_present_flag")]
public int? BlPresentFlag { get; set; }
/// <summary>
/// Gets or sets the DvBlSignalCompatibilityId.
/// </summary>
/// <value>The DvBlSignalCompatibilityId.</value>
[JsonPropertyName("dv_bl_signal_compatibility_id")]
public int? DvBlSignalCompatibilityId { get; set; }
}
}

View File

@@ -841,6 +841,27 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.ColorPrimaries = streamInfo.ColorPrimaries;
}
if (streamInfo.SideDataList != null)
{
foreach (var data in streamInfo.SideDataList)
{
// Parse Dolby Vision metadata from side_data
if (string.Equals(data.SideDataType, "DOVI configuration record", StringComparison.OrdinalIgnoreCase))
{
stream.DvVersionMajor = data.DvVersionMajor;
stream.DvVersionMinor = data.DvVersionMinor;
stream.DvProfile = data.DvProfile;
stream.DvLevel = data.DvLevel;
stream.RpuPresentFlag = data.RpuPresentFlag;
stream.ElPresentFlag = data.ElPresentFlag;
stream.BlPresentFlag = data.BlPresentFlag;
stream.DvBlSignalCompatibilityId = data.DvBlSignalCompatibilityId;
break;
}
}
}
}
else
{

View File

@@ -0,0 +1,54 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// ASS subtitle writer.
/// </summary>
public class AssWriter : ISubtitleWriter
{
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{
var trackEvents = info.TrackEvents;
var timeFormat = @"hh\:mm\:ss\.ff";
// Write ASS header
writer.WriteLine("[Script Info]");
writer.WriteLine("Title: Jellyfin transcoded ASS subtitle");
writer.WriteLine("ScriptType: v4.00+");
writer.WriteLine();
writer.WriteLine("[V4+ Styles]");
writer.WriteLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding");
writer.WriteLine("Style: Default,Arial,20,&H00FFFFFF,&H00FFFFFF,&H19333333,&H910E0807,0,0,0,0,100,100,0,0,0,1,0,2,10,10,10,1");
writer.WriteLine();
writer.WriteLine("[Events]");
writer.WriteLine("Format: Layer, Start, End, Style, Text");
for (int i = 0; i < trackEvents.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var trackEvent = trackEvents[i];
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase);
writer.WriteLine(
"Dialogue: 0,{0},{1},Default,{2}",
startTime,
endTime,
text);
}
}
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// SSA subtitle writer.
/// </summary>
public class SsaWriter : ISubtitleWriter
{
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{
var trackEvents = info.TrackEvents;
var timeFormat = @"hh\:mm\:ss\.ff";
// Write SSA header
writer.WriteLine("[Script Info]");
writer.WriteLine("Title: Jellyfin transcoded SSA subtitle");
writer.WriteLine("ScriptType: v4.00");
writer.WriteLine();
writer.WriteLine("[V4 Styles]");
writer.WriteLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding");
writer.WriteLine("Style: Default,Arial,20,&H00FFFFFF,&H00FFFFFF,&H19333333,&H19333333,0,0,0,1,0,2,10,10,10,0,1");
writer.WriteLine();
writer.WriteLine("[Events]");
writer.WriteLine("Format: Layer, Start, End, Style, Text");
for (int i = 0; i < trackEvents.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var trackEvent = trackEvents[i];
var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks).ToString(timeFormat, CultureInfo.InvariantCulture);
var text = Regex.Replace(trackEvent.Text, @"\n", "\\n", RegexOptions.IgnoreCase);
writer.WriteLine(
"Dialogue: 0,{0},{1},Default,{2}",
startTime,
endTime,
text);
}
}
}
}
}

View File

@@ -195,7 +195,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
MediaStream subtitleStream,
CancellationToken cancellationToken)
{
if (!subtitleStream.IsExternal)
if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
{
string outputFormat;
string outputCodec;
@@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
// Extract
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
await ExtractTextSubtitle(mediaSource, subtitleStream.Index, outputCodec, outputPath, cancellationToken)
await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, outputFormat, false);
@@ -283,6 +283,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value)
{
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{
value = new AssWriter();
return true;
}
if (string.IsNullOrEmpty(format))
{
throw new ArgumentNullException(nameof(format));
@@ -294,12 +300,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return true;
}
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase) || string.Equals(format, SubtitleFormat.SUBRIP, StringComparison.OrdinalIgnoreCase))
{
value = new SrtWriter();
return true;
}
if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{
value = new SsaWriter();
return true;
}
if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase))
{
value = new VttWriter();
@@ -494,7 +506,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Extracts the text subtitle.
/// </summary>
/// <param name="mediaSource">The mediaSource.</param>
/// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="outputCodec">The output codec.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -502,7 +514,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
private async Task ExtractTextSubtitle(
MediaSourceInfo mediaSource,
int subtitleStreamIndex,
MediaStream subtitleStream,
string outputCodec,
string outputPath,
CancellationToken cancellationToken)
@@ -511,12 +523,21 @@ namespace MediaBrowser.MediaEncoding.Subtitles
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
var subtitleStreamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
try
{
if (!File.Exists(outputPath))
{
var args = _mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource);
if (subtitleStream.IsExternal)
{
args = _mediaEncoder.GetExternalSubtitleInputArgument(subtitleStream.Path);
}
await ExtractTextSubtitleInternal(
_mediaEncoder.GetInputArgument(mediaSource.Path, mediaSource),
args,
subtitleStreamIndex,
outputCodec,
outputPath,
@@ -672,11 +693,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (!string.Equals(text, newText, StringComparison.Ordinal))
{
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 (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

@@ -20,6 +20,11 @@ public class BrandingOptions
/// <value>The custom CSS.</value>
public string? CustomCss { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enable the splashscreen.
/// </summary>
public bool SplashscreenEnabled { get; set; } = true;
/// <summary>
/// Gets or sets the splashscreen location on disk.
/// </summary>

View File

@@ -26,6 +26,8 @@ namespace MediaBrowser.Model.Configuration
TonemappingThreshold = 0.8;
TonemappingPeak = 100;
TonemappingParam = 0;
VppTonemappingBrightness = 0;
VppTonemappingContrast = 1.2;
H264Crf = 23;
H265Crf = 28;
DeinterlaceDoubleRate = false;
@@ -39,7 +41,7 @@ namespace MediaBrowser.Model.Configuration
EnableHardwareEncoding = true;
AllowHevcEncoding = false;
EnableSubtitleExtraction = true;
AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = Array.Empty<string>();
AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" };
HardwareDecodingCodecs = new string[] { "h264", "vc1" };
}
@@ -89,6 +91,10 @@ namespace MediaBrowser.Model.Configuration
public double TonemappingParam { get; set; }
public double VppTonemappingBrightness { get; set; }
public double VppTonemappingContrast { get; set; }
public int H264Crf { get; set; }
public int H265Crf { get; set; }

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ namespace MediaBrowser.Model.Dlna
int? videoBitDepth,
int? videoBitrate,
string? videoProfile,
string? videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -42,6 +43,8 @@ namespace MediaBrowser.Model.Dlna
return IsConditionSatisfied(condition, videoLevel);
case ProfileConditionValue.VideoProfile:
return IsConditionSatisfied(condition, videoProfile);
case ProfileConditionValue.VideoRangeType:
return IsConditionSatisfied(condition, videoRangeType);
case ProfileConditionValue.VideoCodecTag:
return IsConditionSatisfied(condition, videoCodecTag);
case ProfileConditionValue.PacketLength:

View File

@@ -128,6 +128,7 @@ namespace MediaBrowser.Model.Dlna
bool isDirectStream,
long? runtimeTicks,
string videoProfile,
string videoRangeType,
double? videoLevel,
float? videoFramerate,
int? packetLength,
@@ -176,6 +177,7 @@ namespace MediaBrowser.Model.Dlna
bitDepth,
videoBitrate,
videoProfile,
videoRangeType,
videoLevel,
videoFramerate,
packetLength,

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