Compare commits

...

200 Commits

Author SHA1 Message Date
Joshua M. Boniface
e93d03d8cb Bump version to 10.8.13 2023-11-28 22:21:57 -05:00
Joshua M. Boniface
a656799dc8 Merge pull request from GHSA-866x-wj5j-2vf4
Validate codec and container
2023-11-28 22:20:32 -05:00
Joshua M. Boniface
83d2c69516 Merge pull request from GHSA-rr9h-w522-cvmr
Remove the functionality of /System/MediaEncoder/Path
2023-11-28 22:19:55 -05:00
Cody Robibero
204fdeb035 Validate codec and container 2023-11-28 15:21:32 -07:00
Joshua M. Boniface
c4cdcb73fc Merge pull request #10625 from ilovepilav/fix-alerts-missing-from-admin-dashboard
Fix: Plugin Installed Alerts missing from Admin Dashboard #10620
(cherry picked from commit 2f6536e34b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2023-11-27 00:19:32 -05:00
Joshua M. Boniface
1e0bd32358 Set Path endpoint obsolete 2023-11-26 18:33:13 -05:00
Joshua M. Boniface
bf5f00a383 Restore original flag behaviour 2023-11-23 13:25:36 -05:00
Joshua M. Boniface
0430ffecb6 Restore ordering 2023-11-23 01:44:35 -05:00
Joshua M. Boniface
85cfd080f1 Remove the functionality of /System/MediaEncoder/Path
Eliminate this endpoint by having it perform no action, pending total
removal in the next major version (10.9.0).

Further, adjust the MediaEncoder startup options to completely ignore
what is in the XML currently, and respect only the "--ffmpeg" arg, which
is set on most of our platforms, falling back to system "ffmpeg" in
$PATH if not found. This ensures that, should the "--ffmpeg" arg be
changed by an administrator wishing to alter the default FFmpeg binary,
this change will be reflected properly on next startup.
2023-11-22 21:29:32 -05:00
Joshua M. Boniface
a173d01139 Revert "Merge pull request #10514 from Bond-009/skia3"
This reverts commit 0bac1eab98, reversing
changes made to 4df1003029.

Causes segfaults on startup.
2023-11-05 11:47:36 -05:00
Joshua M. Boniface
3e7fad55de Bump version to 10.8.12 2023-11-04 14:42:59 -04:00
Joshua M. Boniface
8cd685a4e9 Merge pull request #10528 from nyanmisaka/backport-10451
Fix mismatched intel VAAPI UMD/KMD - Backport #10451
2023-11-04 14:36:05 -04:00
Joshua M. Boniface
9d565bbb83 Merge pull request #10454 from Shadowghost/env
Add MALLOC_TRIM_THRESHOLD_ to default ENV
2023-11-04 14:16:10 -04:00
nyanmisaka
ab855af95e Fix mismatched intel VAAPI UMD/KMD introduced by pull request #10451
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-11-05 01:09:07 +08:00
Claus Vium
0bac1eab98 Merge pull request #10514 from Bond-009/skia3
Update SkiaSharp (v2.88.6) and BlurHashSharp (v1.3.1)
2023-11-02 08:58:34 +01:00
Bond_009
97c2ba0115 Update SkiaSharp (v2.88.6) and BlurHashSharp (v1.3.1) 2023-11-01 23:47:04 +01:00
Cody Robibero
4df1003029 Merge pull request #10487 from thornbill/backport-9485
Fix scaleFactor limit - Backport #9738
2023-10-28 08:44:56 -06:00
Oliver Weyhmüller
3269ce56ca Fix scaleFactor limitation to 1 introduced by pull request #9485 2023-10-28 01:02:27 -04:00
Claus Vium
ce8eddd484 Merge pull request #10406 from thornbill/fix-playlists
Fix playlist listings
2023-10-27 09:47:11 +02:00
Shadowghost
5e2872509a Add MALLOC_TRIM_THRESHOLD_=131072 to default ENV 2023-10-22 17:55:36 +02:00
Bill Thornton
d3f4dcf6f6 Revert "Don't ignore parentId for playlists"
This reverts commit 98c6c34fbb.
2023-10-15 02:16:27 -04:00
Joshua M. Boniface
be5e10ac37 Bump version to 10.8.11 2023-09-23 21:40:40 -04:00
Cody Robibero
b85a0288a7 Merge pull request #10265 from Shadowghost/backport-10173 2023-09-23 09:14:30 -06:00
Shadowghost
f8fd851961 Backport #10173 2023-09-23 13:46:49 +02:00
Bond-009
757f88b1a2 Merge pull request #10151 from jellyfin/faster-tm-qsv-windows 2023-08-27 10:17:47 +02:00
Nyanmisaka
fa732bf4a1 Fix performance loss of QSV HDR tone-mapping on Windows
jellyfin-ffmpeg 5.1.3-5, 6.0-6 or newer contains a more efficient fix than this one.
2023-08-26 03:06:16 +08:00
Cody Robibero
4f6edd9c3c Merge pull request #9952 from Bond-009/backportfix9503
fix for #9503
2023-07-02 18:18:21 -06:00
Bond_009
768497d0ff Backport fix for #9503 2023-07-03 00:07:04 +02:00
Bond-009
f1dc7d3a66 Merge pull request #9916 from Bond-009/playlist-backport 2023-06-27 16:22:14 +02:00
Bond-009
a732a28229 Merge pull request #9928 from nyanmisaka/disable-amd-global-header 2023-06-27 15:58:22 +02:00
nyanmisaka
17626b8e48 Disable global_header on AMD VA-API encoder
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-06-25 21:46:14 +08:00
Bond_009
98c6c34fbb Don't ignore parentId for playlists 2023-06-22 00:43:32 +02:00
Bond-009
bec8d7b3f5 Merge pull request #9723 from dmitrylyzo/fix-multiple-codec-checking 2023-06-13 14:54:57 +02:00
Bond-009
2acae258b8 Check for Imdb id for series (#9829) 2023-05-29 18:40:07 -06:00
Bond-009
643df48707 Don't crash when IPv6 subnet in LocalNetworkSubnets (backport) (#9832) 2023-05-29 18:38:59 -06:00
Bond-009
2b98ce052e Allow webp for local images (backport) (#9831) 2023-05-29 18:38:43 -06:00
Bond-009
702347df50 Don't add .spc audio files (backport) (#9830) 2023-05-29 18:38:29 -06:00
Dmitry Lyzo
9e5aa3e87e Fix multiple codec checking in CodecProfiles conditions
Partial revert 6d662b6587
2023-05-03 00:53:20 +03:00
Joshua M. Boniface
2cd29d1cfd Bump version to 10.8.10 2023-04-23 11:03:46 -04:00
Joshua M. Boniface
eba95cc7f0 Merge pull request #9671 from nyanmisaka/fix-canvas-dvbsub 2023-04-23 11:03:17 -04:00
Joshua M. Boniface
82ad2633fd Merge pull request from GHSA-9p5f-5x8v-x65m
Throw exception on path traversal in WriteDocumentAsync
2023-04-23 10:59:32 -04:00
nyanmisaka
d9f5619c9a Fix the canvas size for DVBSUB and DVDSUB subtitles
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-04-23 20:15:12 +08:00
Cody Robibero
d5a8419bc5 Merge pull request #9642 from nyanmisaka/tonemap-mode-selection
Fix the brightness of VPP tonemap and add the tonemap mode
2023-04-14 14:58:19 -06:00
nyanmisaka
c448a4f6a5 Fix the brightness of VPP tonemap and add the tonemap mode
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-04-14 19:34:47 +08:00
David Ullmer
faac37bcf9 Throw exception on path traversal in WriteDocumentAsync
This commit is not tested on a Windows machine. I however checked the
same behavior with UNIX paths and a client name resembling path traversal
path. With this change, an exception is thrown if the full path does not
start with the log directory path.
2023-04-12 16:57:45 +02:00
Bond-009
5921379a29 Merge pull request #9411 from nyanmisaka/next-fixes 2023-04-12 16:35:20 +02:00
Cody Robibero
79bb7560dc Merge pull request #9538 from TheTyrius/fix-nvenc-preset-order
Fix nvenc preset order
2023-03-25 09:04:08 -06:00
TheTyrius
bf37db7f42 Add self to CONTRIBUTORS.md as per dev docs 2023-03-25 15:39:00 +01:00
TheTyrius
0ad70bb699 Fix nvenc preset ordering 2023-03-25 15:28:01 +01:00
Bond-009
e6313d01eb Merge pull request #9409 from Shadowghost/output-bitrate-channels-release
Multiple HLS codec and bitrate fixes (10.8.z)
2023-03-19 15:30:53 +01:00
Shadowghost
876a6b9aec Add DCA and TrueHD to fMP4 audio codecs to support remuxing 2023-03-19 15:07:20 +01:00
Cody Robibero
e0344353cd Apply suggestions from code review
Co-authored-by: Bond-009 <bond.009@outlook.com>
2023-03-17 12:26:42 +01:00
nyanmisaka
9799136daf Add support for OPUS and fixes for FLAC case issue in HLS
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-03-17 12:26:40 +01:00
Shadowghost
3a5503be5f Fix condition in CanStreamCopyAudio 2023-03-17 12:16:50 +01:00
Shadowghost
2cc0869144 Prefer other codecs over DTS and TrueHD on transcode 2023-03-17 12:16:48 +01:00
Shadowghost
3d735e242a Use source audio bitrate if requested codec is lossless 2023-03-17 12:15:48 +01:00
Shadowghost
31712e5da9 Apply review suggestions 2023-03-17 12:01:34 +01:00
Shadowghost
060097703b Enforce HLS codec restrictions 2023-03-17 11:58:38 +01:00
Shadowghost
233e079e58 Add DTS and TrueHD bitrate limits, enforce bitrate limits if no bitrate is requested 2023-03-17 11:58:25 +01:00
Shadowghost
eafd785eb6 Fix encoder checks for DTS and TrueHD 2023-03-17 11:58:09 +01:00
Shadowghost
9908dad045 Take channels into account when calculating fallback audio bitrate 2023-03-17 11:57:51 +01:00
knackebrot
2b4bf81575 Calculate output bitrate from output channel count 2023-03-17 11:57:41 +01:00
Nyanmisaka
0c7ceb1545 Backport HWA permissions fix (#9006) to 10.8.z (#9433)
Co-authored-by: Shadowghost <Shadowghost@users.noreply.github.com>
fix (#9006) to 10.8.z
2023-03-14 16:29:50 -06:00
Nyanmisaka
173a963dbf Fix the bitrate scale factor for h264-to-hevc transcoding (#9485) 2023-03-14 16:06:10 -06:00
Bond-009
6821a2ab35 Merge pull request #9422 from nyanmisaka/fix-stream-map-filter-complex 2023-03-07 16:25:30 +01:00
Bond-009
efc79295de Merge pull request #9430 from nyanmisaka/livetv-hwdec 2023-03-07 16:25:11 +01:00
nyanmisaka
4d1a583297 Fix LiveTV hardware decoding
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-03-05 03:51:14 +08:00
nyanmisaka
c94a99fced Tiny optimizations for FFmpeg 6.0 2023-03-03 00:24:43 +08:00
nyanmisaka
5fdea32dca Add tests for FFmpeg version 5.1.2 and 6.0
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-03-02 21:21:44 +08:00
nyanmisaka
d1c668e230 Fix stream map when using filter_complex with unlabeled output
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-03-02 21:15:16 +08:00
nyanmisaka
6d662b6587 Fix codec checking in CodecProfiles conditions
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-02-28 03:49:19 +08:00
Bond-009
22a8283a9e Merge pull request #9391 from nyanmisaka/next-fixes 2023-02-25 16:44:47 +01:00
nyanmisaka
0d9d2e0690 Enable enhanced Nvdec by default for using DoVi tone-mapping
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-02-24 23:39:32 +08:00
nyanmisaka
edaba7dbe5 Fix H.264 baseline profile hwaccel
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-02-24 23:38:46 +08:00
Bond-009
e8b0ae07af Merge pull request #9351 from Shadowghost/condition-fix 2023-02-20 10:27:58 +01:00
Bond-009
c807712246 Merge pull request #9355 from nyanmisaka/va-vpp-pool-size 2023-02-20 10:27:47 +01:00
Shadowghost
9a14a624a8 Apply review suggestions 2023-02-19 15:11:15 +01:00
Shadowghost
037eeed746 Fix EqualsAny condition check for int and double 2023-02-19 14:59:30 +01:00
nyanmisaka
8ecb9558e2 Use CL_MAP_READ on OCL to reduce bandwidth overhead
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-02-19 21:26:15 +08:00
nyanmisaka
8d04c98e35 Increase pool size for VAAPI VPP
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-02-19 21:16:17 +08:00
Dmitry Lyzo
09f1c7f535 Escape the path to pass as a command line argument (#9178) 2023-01-28 07:42:58 -07:00
Joshua M. Boniface
0ac18a50f5 Bump version to 10.8.9 2023-01-22 14:09:40 -05:00
Joshua M. Boniface
4ebae248df Merge pull request #9130 from Shadowghost/livetv-security 2023-01-22 14:02:34 -05:00
Joshua M. Boniface
fbb9acf58b Merge pull request #9145 from nyanmisaka/fix-pgs-8602 2023-01-21 15:48:54 -05:00
nyanmisaka
87f081c8ac Fix PGS position issue in sw decoding #8602
Partially revert #7736

Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-01-21 22:34:46 +08:00
Shadowghost
44077b4f5c Apply review suggestions 2023-01-20 01:27:39 +01:00
Shadowghost
060a80bef7 Enforce download permission on download policy 2023-01-20 00:58:47 +01:00
Shadowghost
885a1b02c1 Secure endpoints in LiveTvController 2023-01-20 00:58:41 +01:00
Joshua M. Boniface
1dea309ae4 Merge pull request #9051 from dmitrylyzo/fix-transcode-reasons 2023-01-19 15:32:40 -05:00
Joshua M. Boniface
32227c76b7 Merge pull request #9112 from nyanmisaka/win-ffmpeg-link 2023-01-17 12:56:06 -05:00
Shadowghost
a7c43643a4 Fix Windows FFmpeg download link 2023-01-18 01:47:52 +08:00
Dmitry Lyzo
2a5efeb3bb Don't add additional entries if HEVC encoding is disabled (#9092) 2023-01-14 14:37:11 -07:00
Bond-009
464136cfc9 Merge pull request #9050 from nyanmisaka/update-wa-i915-hang 2023-01-10 20:54:47 +01:00
Bill Thornton
31673cc27d Disable splash screen image by default (#9060) 2023-01-10 09:15:21 -07:00
Dmitry Lyzo
6a909f956e cleanup: remove redundant condition 2023-01-09 23:21:57 +03:00
Dmitry Lyzo
20e9db8308 fix transcode reasons 2023-01-09 23:21:57 +03:00
nyanmisaka
f8b8fdace6 Update workaround for the i915 hang
The issue has been fixed in linux 6.2:
https://github.com/torvalds/linux/commit/3f882f2

And the fix was cherry picked into 6.0.18 and
6.1.4 (may be used by debian bookworm).

Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-01-10 03:41:20 +08:00
Cody Robibero
2a6e292153 Merge pull request #9049 from Shadowghost/codec-fix
Fixes https://github.com/jellyfin/jellyfin-androidtv/issues/2396
2023-01-09 12:18:09 -07:00
Shadowghost
c9f3d9bdde Add truehd to list of audio codecs which require strict -2 2023-01-09 20:07:17 +01:00
David Fairbrother
8d49e0099c Add DavidFair to contributors
Add myself to the list of contributors, as per the development
guidelines found on the JF website.
2023-01-09 19:50:46 +01:00
David Fairbrother
76e3da6a40 Add dts to list of audio codecs which require strict -2
Adds dts to the list of audio codecs where ffmpeg will throw asking us
to opt into experimental support. This is seen when the original content
is based on dts and we don't acopy using ffmpeg.
2023-01-09 19:50:37 +01:00
Bond-009
f0faddcc44 Backport 8726: Fix incorrect starting offset of buffer span in CheckTunerAvailability. (#9020)
Co-authored-by: Michael Powers <swedishborgie@gmail.com>
2023-01-07 11:30:34 -07:00
Bond-009
e6606d41ce Merge pull request #9009 from dmitrylyzo/fix-secondary-audio 2023-01-05 22:57:49 +01:00
Dmitry Lyzo
7c8aea7859 fix tests 2023-01-05 01:15:58 +03:00
Dmitry Lyzo
c4c5af40a1 fix secondary audio
Browsers (Chrome, Firefox) can only play the first track,
even if the second track is the default.

Ignore default flag when testing on secondary audio.

External audio tracks are not secondary.
2023-01-05 01:15:58 +03:00
Joshua M. Boniface
383d514353 Bump version to 10.8.8 2022-11-29 13:42:58 -05:00
Bond-009
6fc8237242 Merge pull request #8753 from thornbill/fix-items-access-backport 2022-11-22 21:52:53 +01:00
Bill Thornton
79d7a4d4df Remove unused using statement 2022-11-16 10:27:41 -05:00
Bill Thornton
e90031b4cc Use elevated access control for media folders endpoint 2022-11-15 16:52:49 -05:00
Bill Thornton
4f3d562d75 Fix media folders endpoint access control 2022-11-15 16:49:03 -05:00
Bill Thornton
6c8b40f413 Fix items endpoint not honoring library access control 2022-11-15 16:35:05 -05:00
Joshua M. Boniface
ec81dc9be2 Bump version to 10.8.7 2022-10-31 23:07:09 -04:00
Joshua M. Boniface
45f3fb1cfc Merge pull request #8662 from cvium/this_next_up_fix_wont_work_either 2022-10-31 17:05:38 -04:00
Cody Robibero
f83a24ec43 Merge pull request #8667 from daullmer/backport-omdb-fix 2022-10-31 14:33:33 -06:00
Joshua M. Boniface
84c03a2d93 Merge pull request #8649 from jellyfin/revert-8480-revert-data-streams 2022-10-31 12:55:56 -04:00
David Ullmer
bc8e249080 Enable OMDB plot for non-English languages as fallback 2022-10-31 13:10:00 +01:00
cvium
5ea9a74289 fix: use a combination of ParentIndexNumber and IndexNumber to determine next up episodes 2022-10-30 16:06:47 +01:00
Niels van Velzen
987d31ea16 Revert "Revert "Merge pull request #8298 from lomion0815/fix-data-stream"" 2022-10-29 11:38:14 +02:00
Joshua M. Boniface
f850779781 Bump version to 10.8.6 2022-10-28 22:41:11 -04:00
Claus Vium
3bdc2bff5f Merge pull request #8620 from nyanmisaka/fix-dg2-tonemap-tearing
Fix the DG2 HDR TM tearing issue on Windows
2022-10-25 12:43:47 +02:00
nyanmisaka
5c6a84549a Fix some encoding presets
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2022-10-25 16:26:55 +08:00
nyanmisaka
48da35f91f Fix the DG2 HDR TM tearing issue on Windows
Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2022-10-25 16:26:55 +08:00
Claus Vium
39b29eb9f1 Merge pull request #8608 from cvium/activitylog_datecreated_index
Add index for DateCreated on ActivityLogs
2022-10-24 18:46:54 +02:00
Anthony Lavado
a6740bf51e Merge pull request #8609 from anthonylavado/sd-image-fix 2022-10-23 13:37:29 -04:00
cvium
c7797d3ead fix build 2022-10-23 18:54:43 +02:00
Claus Vium
c86d5838be Merge pull request #8611 from nielsvanvelzen/transcodereasons 2022-10-22 14:17:47 +02:00
Niels van Velzen
43223b9036 Fix TranscodeReasons type in OpenAPI output 2022-10-22 13:59:24 +02:00
Anthony Lavado
c71385d2db Add token for program metadata download 2022-10-22 04:30:45 -04:00
Anthony Lavado
ad5becc524 Use token when retrieving images 2022-10-22 04:10:20 -04:00
cvium
7c75dcfb9c use filescoped namespace 2022-10-22 10:04:50 +02:00
cvium
7937e31a9b add index for DateCreated on ActivityLog 2022-10-22 10:02:52 +02:00
Claus Vium
1f1f26306b Merge pull request #8600 from Shadowghost/10.8-slow-load 2022-10-21 17:44:38 +02:00
Shadowghost
577399ca05 Prevent host lookup on GetSmartUrl for HTTP requests 2022-10-21 10:09:45 +02:00
Bond-009
e7ea7c0383 Merge pull request #8523 from Gylesie/tweak-lastplayeddate 2022-10-10 19:29:03 +02:00
Gylesie
9fe7751d05 Fallback only to the current time when marking item as watched 2022-10-09 11:28:49 +02:00
Claus Vium
bf129ab9b8 Merge pull request #8411 from Maxr1998/audio-stream-fix
Allow direct play even if no audio stream is available
2022-10-09 09:08:01 +02:00
Claus Vium
6d23de64c0 Merge pull request #8516 from cvium/kill_ffprobe_when_extraction_crashes 2022-10-09 08:27:34 +02:00
Claus Vium
e4f48bb486 Merge pull request #8517 from cvium/backport_8335 2022-10-09 08:27:22 +02:00
Andreas Egli
866b4460b1 change variable to camelCase 2022-10-08 19:00:36 +02:00
Andreas Egli
9db0b275ff allow additional flags after K_ for ffprobe keyframe extraction 2022-10-08 19:00:23 +02:00
Andreas Egli
14008fd7d0 add gentps flag to ffprobe for keyframe extraction 2022-10-08 19:00:02 +02:00
Andreas Egli
8532d88a71 add TryParse to FFProbe Keyframe extraction 2022-10-08 18:59:43 +02:00
cvium
737c739d33 fix: kill ffprobe if keyframe parsing fails 2022-10-08 18:56:07 +02:00
Claus Vium
679e83082f Merge pull request #8501 from cvium/fix_nextup 2022-10-07 07:50:04 +02:00
Claus Vium
d8e53f35a5 Merge pull request #8499 from cvium/add_basque_language 2022-10-07 07:49:17 +02:00
cvium
7a0e7b3cf8 add MinParentIndexNumber 2022-10-06 14:21:21 +02:00
cvium
373c63bcc7 fix: set MinIndexNumber for the next up query 2022-10-06 14:06:24 +02:00
cvium
be5d343efb chore: add Basque to the list of localization options 2022-10-06 09:44:11 +02:00
Cody Robibero
c9c91cc34e Merge pull request #8480 from thornbill/revert-data-streams 2022-10-01 08:52:39 -06:00
Bill Thornton
774b4a0d3f Revert "Merge pull request #8298 from lomion0815/fix-data-stream"
This reverts commit 848ea703bc, reversing
changes made to af87706379.
2022-10-01 00:56:46 -04:00
Joshua M. Boniface
a26cded0f5 Bump version to 10.8.5 2022-09-24 22:01:59 -04:00
Joshua M. Boniface
4ec82ec662 Merge pull request #8433 from jellyfin/dotnet-6.0.9 2022-09-20 09:45:01 -04:00
Cody Robibero
879787212e Update to dotnet 6.0.9 2022-09-19 08:19:32 -06:00
Maxr1998
23100c9b86 Use ICollection for candidateAudioStreams 2022-09-18 22:56:38 +02:00
Bond-009
e6124bc154 Merge pull request #8399 from cvium/respectTagRestrictions 2022-09-15 14:08:50 +02:00
Maxr1998
8753b7200f Allow direct play even if no audio stream is available 2022-09-15 02:04:12 +02:00
Bond-009
88d5230bab Merge pull request #8348 from jellyfin/revert-7985-revert-7961-next_up_idk 2022-09-14 01:54:45 +02:00
LogicalPhallacy
2920c52d61 Reorder and check for query user null to avoid null ref issues 2022-09-13 09:12:38 +02:00
LogicalPhallacy
de196a7687 Forces respecting IsVisible on people 2022-09-13 09:12:38 +02:00
Claus Vium
ba026716c1 Merge pull request #8213 from nyanmisaka/pause-cpu
Fix high single thread usage in throttler
2022-09-13 08:07:04 +02:00
Joshua M. Boniface
125ee88311 Merge pull request #8321 from strugee/fix-systemd-whitespace 2022-09-11 16:55:21 -04:00
nyanmisaka
c53f6a2890 Fix high single thread usage in throttler
this requires jellyfin-ffmpeg >= 5.0.1-8
2022-09-09 21:14:02 +08:00
Bond-009
649b4c49e0 Merge pull request #8327 from RealGreenDragon/subtitle-extraction-timeout-10.8.z 2022-09-09 12:51:25 +02:00
Cody Robibero
848ea703bc Merge pull request #8298 from lomion0815/fix-data-stream 2022-09-05 08:51:48 -06:00
AJ Jordan
0adadff3e7 Fix systemd not breaking whitespace in env vars
This is particularly important for JELLYFIN_ADDITIONAL_OPTS, where the
user is most likely to want to specify more than one word.
2022-08-27 16:20:06 -04:00
MagicGreenDragon
ffdc3a6734 Increased subtitle extraction timeout to 30 min 2022-08-27 18:10:42 +02:00
lomion0815
a51cd4f8db Improved error log as suggested by crobibero in the PR #8298 review 2022-08-24 21:01:32 +02:00
Bond-009
af87706379 Merge pull request #8214 from nielsvanvelzen/optional-userid 2022-08-22 18:25:20 +02:00
Niels van Velzen
8422ab687b Update Jellyfin.Api/Controllers/UniversalAudioController.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2022-08-22 17:47:59 +02:00
markus
64753cfc7f Streams with CodecType "data" (like "epg" streams in DVB recordings) get ignored. This results in wrong stream specifiers for all subsequential streams. This fix correctly handles "data" streams without any further processing. 2022-08-22 14:58:30 +02:00
Claus Vium
632fb05f46 Merge pull request #8280 from thornbill/fix-analyze-duration-priority
Fix ffmpeg analyze duration env var taking priority over media source
2022-08-18 08:08:58 +02:00
Claus Vium
527ed0607d Merge pull request #8189 from lukefor/getitems-outofrange
Fix GetItems IndexOutOfRangeException when IDs do not exist
2022-08-18 08:07:51 +02:00
Luke F
b59daab273 Tab -> space 2022-08-18 00:26:55 +01:00
Luke F
8f28d52929 Code review - simplify SortItemsByRequest to a single roughly-equivalent linq expression 2022-08-18 00:15:57 +01:00
Luke F
749b263c48 Merge remote-tracking branch 'upstream/release-10.8.z' into getitems-outofrange 2022-08-18 00:14:01 +01:00
Bill Thornton
80c68b8948 Fix ffmpeg analyze duration env var taking priority over media source 2022-08-17 15:04:51 -04:00
Claus Vium
a5687793c9 Revert "Revert "refactor: use season number and episode number for NextUp ordering instead of SortName"" 2022-08-15 14:10:42 +02:00
Joshua Boniface
b344771f8a Bump version to 10.8.4 2022-08-13 21:51:50 -04:00
Joshua Boniface
3ff78b687d Revert "Restore "Merge pull request #8087 from cvium/generic_subtitleparser""
This reverts commit 5bcab0f0f8.
2022-08-13 21:51:23 -04:00
Joshua M. Boniface
d260f30810 Merge pull request #8257 from joshuaboniface/fix-dotnetargs 2022-08-13 21:45:57 -04:00
Cody Robibero
7ffdde9a0b Merge pull request #8212 from SenorSmartyPants/Add384ResolutionText
Add resolution text for 384 sized video
2022-08-13 18:34:17 -07:00
Joshua M. Boniface
e14194bfe2 Fix remaining instances in root package configs 2022-08-13 21:23:01 -04:00
Joshua M. Boniface
3bf1a7e445 Use separate args for dotnet publish commands
Fixes #8245
2022-08-13 21:18:28 -04:00
Bond-009
1faee43b11 Merge pull request #8182 from Shadowghost/fix-sub-characterset 2022-08-12 19:47:42 +02:00
Joshua M. Boniface
31f9938e3a Merge pull request #8234 from crobibero/dotnet-6.0.8 2022-08-11 22:46:31 -04:00
Cody Robibero
ae9fd4ab35 update remaining dependencies 2022-08-09 17:57:51 -06:00
Cody Robibero
71ed7f7676 update to dotnet 6.0.8 2022-08-09 17:57:21 -06:00
SenorSmartyPants
3b6e003029 Add 404p Resolution Text 2022-08-09 12:09:29 -05:00
Claus Vium
9357d610b1 Merge pull request #8209 from Shadowghost/fix-playback
Fix series query including missing episodes when it should not
2022-08-08 22:57:56 +02:00
Joshua M. Boniface
1d4755894e Merge pull request #8219 from nyanmisaka/fedora-hardening
Move Fedora service hardening options to override config
2022-08-07 21:10:03 -04:00
nyanmisaka
2320f06666 Move Fedora service hardening options to override config 2022-08-07 18:01:26 +08:00
Niels van Velzen
8296f07a39 Make userId truly optional in UniversalAudioController 2022-08-06 14:17:04 +02:00
SenorSmartyPants
30f6263806 Add resolution text for 384 sized video 2022-08-05 23:32:22 -05:00
Shadowghost
a9249393e1 Fix series query including missing episodes when it should not 2022-08-05 19:38:33 +02:00
Shadowghost
f49a051a5f Respect timestamps when extracting subtitles 2022-08-02 13:21:10 +02:00
Joshua Boniface
5bcab0f0f8 Restore "Merge pull request #8087 from cvium/generic_subtitleparser"
After tagging v10.8.3, this can be restored to how it was and corrected
as required in a separate PR.

This reverts commit 494ed7e4d2.
2022-08-01 20:40:54 -04:00
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
Luke F
3bd2cc9860 Resolve a System.IndexOutOfRangeException when requesting IDs that do not exist via /Users/.../Items. Previously it was possible for the 'index' values in 'positions' to refer beyond 'size'.
[ERR] Error processing request. URL "GET" "/Users/.../Items".
System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at MediaBrowser.Controller.Entities.Folder.SortItemsByRequest(InternalItemsQuery query, IReadOnlyList`1 items)
   at MediaBrowser.Controller.Entities.Folder.GetItems(InternalItemsQuery query)
   at Jellyfin.Api.Controllers.ItemsController.GetItems
2022-07-29 20:43:38 +01:00
Shadowghost
feb035b9e0 Extract external subs from container before determining character set 2022-07-27 10:08:53 +02:00
137 changed files with 2177 additions and 757 deletions

View File

@@ -27,6 +27,7 @@
- [cvium](https://github.com/cvium) - [cvium](https://github.com/cvium)
- [dannymichel](https://github.com/dannymichel) - [dannymichel](https://github.com/dannymichel)
- [DaveChild](https://github.com/DaveChild) - [DaveChild](https://github.com/DaveChild)
- [DavidFair](https://github.com/DavidFair)
- [Delgan](https://github.com/Delgan) - [Delgan](https://github.com/Delgan)
- [dcrdev](https://github.com/dcrdev) - [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung) - [dhartung](https://github.com/dhartung)
@@ -36,6 +37,7 @@
- [dmitrylyzo](https://github.com/dmitrylyzo) - [dmitrylyzo](https://github.com/dmitrylyzo)
- [DMouse10462](https://github.com/DMouse10462) - [DMouse10462](https://github.com/DMouse10462)
- [DrPandemic](https://github.com/DrPandemic) - [DrPandemic](https://github.com/DrPandemic)
- [eglia](https://github.com/eglia)
- [EraYaN](https://github.com/EraYaN) - [EraYaN](https://github.com/EraYaN)
- [escabe](https://github.com/escabe) - [escabe](https://github.com/escabe)
- [excelite](https://github.com/excelite) - [excelite](https://github.com/excelite)
@@ -157,6 +159,9 @@
- [jonas-resch](https://github.com/jonas-resch) - [jonas-resch](https://github.com/jonas-resch)
- [vgambier](https://github.com/vgambier) - [vgambier](https://github.com/vgambier)
- [MinecraftPlaye](https://github.com/MinecraftPlaye) - [MinecraftPlaye](https://github.com/MinecraftPlaye)
- [RealGreenDragon](https://github.com/RealGreenDragon)
- [TheTyrius](https://github.com/TheTyrius)
- [Çağrı Sakaoğlu](https://github.com/ilovepilav)
# Emby Contributors # Emby Contributors
@@ -225,3 +230,6 @@
- [gnuyent](https://github.com/gnuyent) - [gnuyent](https://github.com/gnuyent)
- [Matthew Jones](https://github.com/matthew-jones-uk) - [Matthew Jones](https://github.com/matthew-jones-uk)
- [Jakob Kukla](https://github.com/jakobkukla) - [Jakob Kukla](https://github.com/jakobkukla)
- [Utku Özdemir](https://github.com/utkuozdemir)
- [JPUC1143](https://github.com/Jpuc1143/)
- [0x25CBFC4F](https://github.com/0x25CBFC4F)

View File

@@ -72,7 +72,7 @@ COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
FROM app FROM app

View File

@@ -64,7 +64,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists # Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r RUN find . -type d -name obj | xargs -r rm -r
# Build # Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
FROM app FROM app

View File

@@ -55,7 +55,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists # Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r RUN find . -type d -name obj | xargs -r rm -r
# Build # Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
FROM app FROM app

View File

@@ -250,7 +250,6 @@ namespace Emby.Naming.Common
".sfx", ".sfx",
".shn", ".shn",
".sid", ".sid",
".spc",
".stm", ".stm",
".strm", ".strm",
".ult", ".ult",

View File

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

View File

@@ -15,7 +15,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="TagLibSharp" Version="2.2.0" /> <PackageReference Include="TagLibSharp" Version="2.3.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -83,7 +83,6 @@ using MediaBrowser.Controller.SyncPlay;
using MediaBrowser.Controller.TV; using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
@@ -635,8 +634,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IAuthService, AuthService>(); serviceCollection.AddSingleton<IAuthService, AuthService>();
serviceCollection.AddSingleton<IQuickConnect, QuickConnectManager>(); serviceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
serviceCollection.AddSingleton<ISubtitleParser, SubtitleEditParser>(); serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
serviceCollection.AddSingleton<ISubtitleEncoder, SubtitleEncoder>();
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
@@ -1090,15 +1088,7 @@ namespace Emby.Server.Implementations
return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort); return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
} }
// Published server ends with a / return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
if (!string.IsNullOrEmpty(PublishedServerUrl))
{
// Published server ends with a '/', so we need to remove it.
return PublishedServerUrl.Trim('/');
}
string smart = NetManager.GetBindInterface(request, out var port);
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -3541,6 +3541,13 @@ namespace Emby.Server.Implementations.Data
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value); statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
} }
if (query.MinParentAndIndexNumber.HasValue)
{
whereClauses.Add("((ParentIndexNumber=@MinParentAndIndexNumberParent and IndexNumber>=@MinParentAndIndexNumberIndex) or ParentIndexNumber>@MinParentAndIndexNumberParent)");
statement?.TryBind("@MinParentAndIndexNumberParent", query.MinParentAndIndexNumber.Value.ParentIndexNumber);
statement?.TryBind("@MinParentAndIndexNumberIndex", query.MinParentAndIndexNumber.Value.IndexNumber);
}
if (query.MinDateCreated.HasValue) if (query.MinDateCreated.HasValue)
{ {
whereClauses.Add("DateCreated>=@MinDateCreated"); whereClauses.Add("DateCreated>=@MinDateCreated");

View File

@@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.People)) if (options.ContainsField(ItemFields.People))
{ {
AttachPeople(dto, item); AttachPeople(dto, item, user);
} }
if (options.ContainsField(ItemFields.PrimaryImageAspectRatio)) if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
@@ -503,7 +503,8 @@ namespace Emby.Server.Implementations.Dto
/// </summary> /// </summary>
/// <param name="dto">The dto.</param> /// <param name="dto">The dto.</param>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
private void AttachPeople(BaseItemDto dto, BaseItem item) /// <param name="user">The requesting user.</param>
private void AttachPeople(BaseItemDto dto, BaseItem item, User user = null)
{ {
// Ordering by person type to ensure actors and artists are at the front. // Ordering by person type to ensure actors and artists are at the front.
// This is taking advantage of the fact that they both begin with A // This is taking advantage of the fact that they both begin with A
@@ -560,6 +561,9 @@ namespace Emby.Server.Implementations.Dto
return null; return null;
} }
}).Where(i => i != null) }).Where(i => i != null)
.Where(i => user == null ?
true :
i.IsVisible(user))
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First()) .Select(x => x.First())
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);

View File

@@ -29,10 +29,10 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
<PackageReference Include="Mono.Nat" Version="3.0.3" /> <PackageReference Include="Mono.Nat" Version="3.0.3" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
<PackageReference Include="sharpcompress" Version="0.32.1" /> <PackageReference Include="sharpcompress" Version="0.32.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.3" /> <PackageReference Include="DotNet.Glob" Version="3.1.3" />
</ItemGroup> </ItemGroup>

View File

@@ -2766,7 +2766,8 @@ namespace Emby.Server.Implementations.Library
public List<Person> GetPeopleItems(InternalPeopleQuery query) public List<Person> GetPeopleItems(InternalPeopleQuery query)
{ {
return _itemRepository.GetPeopleNames(query).Select(i => return _itemRepository.GetPeopleNames(query)
.Select(i =>
{ {
try try
{ {
@@ -2777,7 +2778,12 @@ namespace Emby.Server.Implementations.Library
_logger.LogError(ex, "Error getting person"); _logger.LogError(ex, "Error getting person");
return null; return null;
} }
}).Where(i => i != null).ToList(); })
.Where(i => i != null)
.Where(i => query.User == null ?
true :
i.IsVisible(query.User))
.ToList();
} }
public List<string> GetPeopleNames(InternalPeopleQuery query) public List<string> GetPeopleNames(InternalPeopleQuery query)

View File

@@ -183,6 +183,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
private static void SetProviderIdFromPath(Series item, string path) private static void SetProviderIdFromPath(Series item, string path)
{ {
var justName = Path.GetFileName(path.AsSpan()); var justName = Path.GetFileName(path.AsSpan());
var imdbId = justName.GetAttributeValue("imdbid");
if (!string.IsNullOrEmpty(imdbId))
{
item.SetProviderId(MetadataProvider.Imdb, imdbId);
}
var tvdbId = justName.GetAttributeValue("tvdbid"); var tvdbId = justName.GetAttributeValue("tvdbid");
if (!string.IsNullOrEmpty(tvdbId)) if (!string.IsNullOrEmpty(tvdbId))

View File

@@ -165,12 +165,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
const double DesiredAspect = 2.0 / 3; const double DesiredAspect = 2.0 / 3;
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect) ?? programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect, token) ??
GetProgramImage(ApiUrl, allImages, DesiredAspect); GetProgramImage(ApiUrl, allImages, DesiredAspect, token);
const double WideAspect = 16.0 / 9; const double WideAspect = 16.0 / 9;
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect); programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect, token);
// Don't supply the same image twice // Don't supply the same image twice
if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal)) if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
@@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
programEntry.ThumbImage = null; programEntry.ThumbImage = null;
} }
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect); programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect, token);
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
@@ -399,7 +399,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return info; return info;
} }
private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect) private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect, string token)
{ {
var match = images var match = images
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i))) .OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
@@ -423,7 +423,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
} }
else else
{ {
return apiUrl + "/image/" + uri; return apiUrl + "/image/" + uri + "?token=" + token;
} }
} }
@@ -457,6 +457,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
IReadOnlyList<string> programIds, IReadOnlyList<string> programIds,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
if (programIds.Count == 0) if (programIds.Count == 0)
{ {
return Array.Empty<ShowImagesDto>(); return Array.Empty<ShowImagesDto>();
@@ -478,6 +480,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{ {
Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json) Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json)
}; };
message.Headers.TryAddWithoutValidation("token", token);
try try
{ {

View File

@@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
return VerifyReturnValueOfGetSet(buffer.AsSpan(receivedBytes), "none"); return VerifyReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), "none");
} }
finally finally
{ {

View File

@@ -386,6 +386,7 @@ namespace Emby.Server.Implementations.Localization
yield return new LocalizationOption("Español (Dominicana)", "es_DO"); yield return new LocalizationOption("Español (Dominicana)", "es_DO");
yield return new LocalizationOption("Español (México)", "es-MX"); yield return new LocalizationOption("Español (México)", "es-MX");
yield return new LocalizationOption("Eesti", "et"); yield return new LocalizationOption("Eesti", "et");
yield return new LocalizationOption("Basque", "eu");
yield return new LocalizationOption("فارسی", "fa"); yield return new LocalizationOption("فارسی", "fa");
yield return new LocalizationOption("Suomi", "fi"); yield return new LocalizationOption("Suomi", "fi");
yield return new LocalizationOption("Filipino", "fil"); yield return new LocalizationOption("Filipino", "fil");

View File

@@ -135,20 +135,15 @@ namespace Emby.Server.Implementations.TV
return GetResult(episodes, request); return GetResult(episodes, request);
} }
public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions) private IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions)
{ {
// Avoid implicitly captured closure var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false));
var currentUser = user;
var allNextUp = seriesKeys
.Select(i => GetNextUp(i, currentUser, dtoOptions, false));
if (request.EnableRewatching) if (request.EnableRewatching)
{ {
allNextUp = allNextUp.Concat( allNextUp = allNextUp.Concat(
seriesKeys.Select(i => GetNextUp(i, currentUser, dtoOptions, true)) seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, true)))
) .OrderByDescending(i => i.LastWatchedDate);
.OrderByDescending(i => i.Item1);
} }
// If viewing all next up for all series, remove first episodes // If viewing all next up for all series, remove first episodes
@@ -161,23 +156,18 @@ namespace Emby.Server.Implementations.TV
{ {
if (request.DisableFirstEpisode) if (request.DisableFirstEpisode)
{ {
return i.Item1 != DateTime.MinValue; return i.LastWatchedDate != DateTime.MinValue;
} }
if (alwaysEnableFirstEpisode || (i.Item1 != DateTime.MinValue && i.Item1.Date >= request.NextUpDateCutoff)) if (alwaysEnableFirstEpisode || (i.LastWatchedDate != DateTime.MinValue && i.LastWatchedDate.Date >= request.NextUpDateCutoff))
{ {
anyFound = true; anyFound = true;
return true; return true;
} }
if (!anyFound && i.Item1 == DateTime.MinValue) return !anyFound && i.LastWatchedDate == DateTime.MinValue;
{
return true;
}
return false;
}) })
.Select(i => i.Item2()) .Select(i => i.GetEpisodeFunction())
.Where(i => i != null); .Where(i => i != null);
} }
@@ -195,14 +185,13 @@ namespace Emby.Server.Implementations.TV
/// Gets the next up. /// Gets the next up.
/// </summary> /// </summary>
/// <returns>Task{Episode}.</returns> /// <returns>Task{Episode}.</returns>
private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching) private (DateTime LastWatchedDate, Func<Episode> GetEpisodeFunction) GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching)
{ {
var lastQuery = new InternalItemsQuery(user) var lastQuery = new InternalItemsQuery(user)
{ {
AncestorWithPresentationUniqueKey = null, AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey, SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode }, IncludeItemTypes = new[] { BaseItemKind.Episode },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Descending) },
IsPlayed = true, IsPlayed = true,
Limit = 1, Limit = 1,
ParentIndexNumberNotEquals = 0, ParentIndexNumberNotEquals = 0,
@@ -213,42 +202,38 @@ namespace Emby.Server.Implementations.TV
} }
}; };
if (rewatching) // If rewatching is enabled, sort first by date played and then by season and episode numbers
{ lastQuery.OrderBy = rewatching
// find last watched by date played, not by newest episode watched ? new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) }
lastQuery.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }; : new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) };
}
var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault(); var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault();
Func<Episode> getEpisode = () => Episode GetEpisode()
{ {
var nextQuery = new InternalItemsQuery(user) var nextQuery = new InternalItemsQuery(user)
{ {
AncestorWithPresentationUniqueKey = null, AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey, SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode }, IncludeItemTypes = new[] { BaseItemKind.Episode },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, OrderBy = new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.Ascending) },
Limit = 1, Limit = 1,
IsPlayed = rewatching, IsPlayed = rewatching,
IsVirtualItem = false, IsVirtualItem = false,
ParentIndexNumberNotEquals = 0, ParentIndexNumberNotEquals = 0,
MinSortName = lastWatchedEpisode?.SortName,
DtoOptions = dtoOptions DtoOptions = dtoOptions
}; };
Episode nextEpisode; // Locate the next up episode based on the last watched episode's season and episode number
if (rewatching) var lastWatchedParentIndexNumber = lastWatchedEpisode?.ParentIndexNumber;
var lastWatchedIndexNumber = lastWatchedEpisode?.IndexNumberEnd ?? lastWatchedEpisode?.IndexNumber;
if (lastWatchedParentIndexNumber.HasValue && lastWatchedIndexNumber.HasValue)
{ {
nextQuery.Limit = 2; nextQuery.MinParentAndIndexNumber = (lastWatchedParentIndexNumber.Value, lastWatchedIndexNumber.Value + 1);
// get watched episode after most recently watched
nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().ElementAtOrDefault(1);
}
else
{
nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault();
} }
var nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault();
if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons) if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
{ {
var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user) var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
@@ -297,7 +282,7 @@ namespace Emby.Server.Implementations.TV
} }
return nextEpisode; return nextEpisode;
}; }
if (lastWatchedEpisode != null) if (lastWatchedEpisode != null)
{ {
@@ -305,11 +290,11 @@ namespace Emby.Server.Implementations.TV
var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1); var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1);
return new Tuple<DateTime, Func<Episode>>(lastWatchedDate, getEpisode); return (lastWatchedDate, GetEpisode);
} }
// Return the first episode // Return the first episode
return new Tuple<DateTime, Func<Episode>>(DateTime.MinValue, getEpisode); return (DateTime.MinValue, GetEpisode);
} }
private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query) private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)

View File

@@ -324,9 +324,15 @@ namespace Emby.Server.Implementations.Updates
} }
_completedInstallationsInternal.Add(package); _completedInstallationsInternal.Add(package);
await _eventManager.PublishAsync(isUpdate
? (GenericEventArgs<InstallationInfo>)new PluginUpdatedEventArgs(package) if (isUpdate)
: new PluginInstalledEventArgs(package)).ConfigureAwait(false); {
await _eventManager.PublishAsync(new PluginUpdatedEventArgs(package)).ConfigureAwait(false);
}
else
{
await _eventManager.PublishAsync(new PluginInstalledEventArgs(package)).ConfigureAwait(false);
}
_applicationHost.NotifyPendingRestart(); _applicationHost.NotifyPendingRestart();
} }

View File

@@ -43,12 +43,16 @@ namespace Jellyfin.Api.Auth
/// <param name="ignoreSchedule">Whether to ignore parental control.</param> /// <param name="ignoreSchedule">Whether to ignore parental control.</param>
/// <param name="localAccessOnly">Whether access is to be allowed locally only.</param> /// <param name="localAccessOnly">Whether access is to be allowed locally only.</param>
/// <param name="requiredDownloadPermission">Whether validation requires download permission.</param> /// <param name="requiredDownloadPermission">Whether validation requires download permission.</param>
/// <param name="requireLiveTvManagementPermission">Whether validation requires LiveTV management permission.</param>
/// <param name="requireLiveTvAccessPermission">Whether validation requires LiveTV management permission.</param>
/// <returns>Validated claim status.</returns> /// <returns>Validated claim status.</returns>
protected bool ValidateClaims( protected bool ValidateClaims(
ClaimsPrincipal claimsPrincipal, ClaimsPrincipal claimsPrincipal,
bool ignoreSchedule = false, bool ignoreSchedule = false,
bool localAccessOnly = false, bool localAccessOnly = false,
bool requiredDownloadPermission = false) bool requiredDownloadPermission = false,
bool requireLiveTvManagementPermission = false,
bool requireLiveTvAccessPermission = false)
{ {
// ApiKey is currently global admin, always allow. // ApiKey is currently global admin, always allow.
var isApiKey = ClaimHelpers.GetIsApiKey(claimsPrincipal); var isApiKey = ClaimHelpers.GetIsApiKey(claimsPrincipal);
@@ -106,6 +110,20 @@ namespace Jellyfin.Api.Auth
return false; return false;
} }
// User attempting to access LiveTV without permission.
if (requireLiveTvAccessPermission
&& !user.HasPermission(PermissionKind.EnableLiveTvAccess))
{
return false;
}
// User attempting to manage LiveTV without permission.
if (requireLiveTvManagementPermission
&& !user.HasPermission(PermissionKind.EnableLiveTvManagement))
{
return false;
}
return true; return true;
} }
} }

View File

@@ -28,7 +28,7 @@ namespace Jellyfin.Api.Auth.DownloadPolicy
/// <inheritdoc /> /// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DownloadRequirement requirement) protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DownloadRequirement requirement)
{ {
var validated = ValidateClaims(context.User); var validated = ValidateClaims(context.User, requiredDownloadPermission: true);
if (validated) if (validated)
{ {
context.Succeed(requirement); context.Succeed(requirement);

View File

@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Jellyfin.Api.Auth.LiveTvAccessPolicy;
/// <summary>
/// Authorization handler for LiveTV access.
/// </summary>
public class LiveTvAccessHandler : BaseAuthorizationHandler<LiveTvAccessRequirement>
{
/// <summary>
/// Initializes a new instance of the <see cref="LiveTvAccessHandler"/> class.
/// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public LiveTvAccessHandler(
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
: base(userManager, networkManager, httpContextAccessor)
{
}
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LiveTvAccessRequirement requirement)
{
var validated = ValidateClaims(context.User, requireLiveTvAccessPermission: true);
if (validated)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.LiveTvAccessPolicy;
/// <summary>
/// The LiveTV access requirement.
/// </summary>
public class LiveTvAccessRequirement : IAuthorizationRequirement
{
}

View File

@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Jellyfin.Api.Auth.LiveTvManagementPolicy;
/// <summary>
/// Authorization handler for LiveTV management access.
/// </summary>
public class LiveTvManagementHandler : BaseAuthorizationHandler<LiveTvManagementRequirement>
{
/// <summary>
/// Initializes a new instance of the <see cref="LiveTvManagementHandler"/> class.
/// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public LiveTvManagementHandler(
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
: base(userManager, networkManager, httpContextAccessor)
{
}
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LiveTvManagementRequirement requirement)
{
var validated = ValidateClaims(context.User, requireLiveTvManagementPermission: true);
if (validated)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.LiveTvManagementPolicy;
/// <summary>
/// The LiveTV management requirement.
/// </summary>
public class LiveTvManagementRequirement : IAuthorizationRequirement
{
}

View File

@@ -74,5 +74,15 @@ namespace Jellyfin.Api.Constants
/// Policy name for accessing a SyncPlay group. /// Policy name for accessing a SyncPlay group.
/// </summary> /// </summary>
public const string SyncPlayIsInGroup = "SyncPlayIsInGroup"; public const string SyncPlayIsInGroup = "SyncPlayIsInGroup";
/// <summary>
/// Policy name for accessing LiveTV.
/// </summary>
public const string LiveTvAccess = "LiveTvAccess";
/// <summary>
/// Policy name for managing LiveTV.
/// </summary>
public const string LiveTvManagement = "LiveTvManagement";
} }
} }

View File

@@ -91,18 +91,18 @@ namespace Jellyfin.Api.Controllers
[ProducesAudioFile] [ProducesAudioFile]
public async Task<ActionResult> GetAudioStream( public async Task<ActionResult> GetAudioStream(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] string? container, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -132,8 +132,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -262,12 +262,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -297,8 +297,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,

View File

@@ -125,12 +125,14 @@ namespace Jellyfin.Api.Controllers
/// <param name="mediaEncoderPath">Media encoder path form body.</param> /// <param name="mediaEncoderPath">Media encoder path form body.</param>
/// <response code="204">Media encoder path updated.</response> /// <response code="204">Media encoder path updated.</response>
/// <returns>Status.</returns> /// <returns>Status.</returns>
[Obsolete("This endpoint is obsolete.")]
[HttpPost("MediaEncoder/Path")] [HttpPost("MediaEncoder/Path")]
[Authorize(Policy = Policies.FirstTimeSetupOrElevated)] [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult UpdateMediaEncoderPath([FromBody, Required] MediaEncoderPathDto mediaEncoderPath) public ActionResult UpdateMediaEncoderPath([FromBody, Required] MediaEncoderPathDto mediaEncoderPath)
{ {
_mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType); // API ENDPOINT DISABLED (NOOP) FOR SECURITY PURPOSES
//_mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType);
return NoContent(); return NoContent();
} }
} }

View File

@@ -13,6 +13,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Extensions;
using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.MediaEncoding.Hls.Playlist;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@@ -21,6 +22,7 @@ using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@@ -172,18 +174,18 @@ namespace Jellyfin.Api.Controllers
[ProducesPlaylistFile] [ProducesPlaylistFile]
public async Task<ActionResult> GetLiveHlsStream( public async Task<ActionResult> GetLiveHlsStream(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] string? container, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -213,8 +215,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -424,12 +426,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery, Required] string mediaSourceId, [FromQuery, Required] string mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -461,8 +463,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -594,12 +596,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery, Required] string mediaSourceId, [FromQuery, Required] string mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -630,8 +632,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -760,12 +762,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -797,8 +799,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -928,12 +930,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -964,8 +966,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -1105,12 +1107,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -1142,8 +1144,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -1286,12 +1288,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -1322,8 +1324,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -1662,8 +1664,8 @@ namespace Jellyfin.Api.Controllers
startNumber.ToString(CultureInfo.InvariantCulture), startNumber.ToString(CultureInfo.InvariantCulture),
baseUrlParam, baseUrlParam,
isEventPlaylist ? "event" : "vod", isEventPlaylist ? "event" : "vod",
outputTsArg, EncodingUtils.NormalizePath(outputTsArg),
outputPath).Trim(); EncodingUtils.NormalizePath(outputPath)).Trim();
} }
/// <summary> /// <summary>
@@ -1693,7 +1695,7 @@ namespace Jellyfin.Api.Controllers
audioTranscodeParams += "-acodec " + audioCodec; audioTranscodeParams += "-acodec " + audioCodec;
if (state.OutputAudioBitrate.HasValue) if (state.OutputAudioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
{ {
audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture); audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture);
} }
@@ -1712,11 +1714,13 @@ namespace Jellyfin.Api.Controllers
return audioTranscodeParams; return audioTranscodeParams;
} }
// flac and opus are experimental in mp4 muxer // dts, flac, opus and truehd are experimental in mp4 muxer
var strictArgs = string.Empty; var strictArgs = string.Empty;
var actualOutputAudioCodec = state.ActualOutputAudioCodec;
if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
|| string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase))
{ {
strictArgs = " -strict -2"; strictArgs = " -strict -2";
} }
@@ -1745,8 +1749,7 @@ namespace Jellyfin.Api.Controllers
} }
var bitrate = state.OutputAudioBitrate; var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
if (bitrate.HasValue)
{ {
args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
} }
@@ -1841,7 +1844,11 @@ namespace Jellyfin.Api.Controllers
// args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; // args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
// video processing filters. // video processing filters.
args += _encodingHelper.GetVideoProcessingFilterParam(state, _encodingOptions, codec); var videoProcessParam = _encodingHelper.GetVideoProcessingFilterParam(state, _encodingOptions, codec);
var negativeMapArgs = _encodingHelper.GetNegativeMapArgsByFilters(state, videoProcessParam);
args = negativeMapArgs + args + videoProcessParam;
// -start_at_zero is necessary to use with -ss when seeking, // -start_at_zero is necessary to use with -ss when seeking,
// otherwise the target position cannot be determined. // otherwise the target position cannot be determined.

View File

@@ -270,30 +270,13 @@ namespace Jellyfin.Api.Controllers
includeItemTypes = new[] { BaseItemKind.Playlist }; includeItemTypes = new[] { BaseItemKind.Playlist };
} }
var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
bool isInEnabledFolder = Array.IndexOf(user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
// Assume all folders inside an EnabledChannel are enabled
|| Array.IndexOf(enabledChannels, item.Id) != -1
// Assume all items inside an EnabledChannel are enabled
|| Array.IndexOf(enabledChannels, item.ChannelId) != -1;
var collectionFolders = _libraryManager.GetCollectionFolders(item);
foreach (var collectionFolder in collectionFolders)
{
if (user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
{
isInEnabledFolder = true;
}
}
if (item is not UserRootFolder if (item is not UserRootFolder
&& !isInEnabledFolder // api keys can always access all folders
&& !user.HasPermission(PermissionKind.EnableAllFolders) && !ClaimHelpers.GetIsApiKey(User)
&& !user.HasPermission(PermissionKind.EnableAllChannels) // check the item is visible for the user
&& !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase)) && !item.IsVisible(user))
{ {
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name); _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user!.Username, item.Name);
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}."); return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
} }

View File

@@ -492,7 +492,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Media folders returned.</response> /// <response code="200">Media folders returned.</response>
/// <returns>List of user media folders.</returns> /// <returns>List of user media folders.</returns>
[HttpGet("Library/MediaFolders")] [HttpGet("Library/MediaFolders")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden) public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden)
{ {

View File

@@ -93,7 +93,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Info")] [HttpGet("Info")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<LiveTvInfo> GetLiveTvInfo() public ActionResult<LiveTvInfo> GetLiveTvInfo()
{ {
return _liveTvManager.GetLiveTvInfo(CancellationToken.None); return _liveTvManager.GetLiveTvInfo(CancellationToken.None);
@@ -129,7 +129,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Channels")] [HttpGet("Channels")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels( public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels(
[FromQuery] ChannelType? type, [FromQuery] ChannelType? type,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@@ -208,7 +208,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
[HttpGet("Channels/{channelId}")] [HttpGet("Channels/{channelId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{ {
var user = userId is null || userId.Value.Equals(default) var user = userId is null || userId.Value.Equals(default)
@@ -249,7 +249,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
[HttpGet("Recordings")] [HttpGet("Recordings")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetRecordings( public ActionResult<QueryResult<BaseItemDto>> GetRecordings(
[FromQuery] string? channelId, [FromQuery] string? channelId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@@ -320,7 +320,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
[HttpGet("Recordings/Series")] [HttpGet("Recordings/Series")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")] [Obsolete("This endpoint is obsolete.")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
@@ -363,7 +363,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the recording groups.</returns> /// <returns>An <see cref="OkResult"/> containing the recording groups.</returns>
[HttpGet("Recordings/Groups")] [HttpGet("Recordings/Groups")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")] [Obsolete("This endpoint is obsolete.")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId) public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId)
@@ -379,7 +379,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the recording folders.</returns> /// <returns>An <see cref="OkResult"/> containing the recording folders.</returns>
[HttpGet("Recordings/Folders")] [HttpGet("Recordings/Folders")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid? userId) public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid? userId)
{ {
var user = userId is null || userId.Value.Equals(default) var user = userId is null || userId.Value.Equals(default)
@@ -401,7 +401,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
[HttpGet("Recordings/{recordingId}")] [HttpGet("Recordings/{recordingId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{ {
var user = userId is null || userId.Value.Equals(default) var user = userId is null || userId.Value.Equals(default)
@@ -423,10 +423,9 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Tuners/{tunerId}/Reset")] [HttpPost("Tuners/{tunerId}/Reset")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId) public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -441,7 +440,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Timers/{timerId}")] [HttpGet("Timers/{timerId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<TimerInfoDto>> GetTimer([FromRoute, Required] string timerId) public async Task<ActionResult<TimerInfoDto>> GetTimer([FromRoute, Required] string timerId)
{ {
return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false); return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false);
@@ -457,7 +456,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Timers/Defaults")] [HttpGet("Timers/Defaults")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId) public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId)
{ {
return string.IsNullOrEmpty(programId) return string.IsNullOrEmpty(programId)
@@ -477,7 +476,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Timers")] [HttpGet("Timers")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers( public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers(
[FromQuery] string? channelId, [FromQuery] string? channelId,
[FromQuery] string? seriesTimerId, [FromQuery] string? seriesTimerId,
@@ -531,7 +530,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Programs")] [HttpGet("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms( public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms(
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@@ -614,7 +613,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpPost("Programs")] [HttpPost("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body) public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
{ {
var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId); var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId);
@@ -680,7 +679,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Recommended epgs returned.</response> /// <response code="200">Recommended epgs returned.</response>
/// <returns>A <see cref="OkResult"/> containing the queryresult of recommended epgs.</returns> /// <returns>A <see cref="OkResult"/> containing the queryresult of recommended epgs.</returns>
[HttpGet("Programs/Recommended")] [HttpGet("Programs/Recommended")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms( public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms(
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@@ -732,7 +731,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Program returned.</response> /// <response code="200">Program returned.</response>
/// <returns>An <see cref="OkResult"/> containing the livetv program.</returns> /// <returns>An <see cref="OkResult"/> containing the livetv program.</returns>
[HttpGet("Programs/{programId}")] [HttpGet("Programs/{programId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetProgram( public async Task<ActionResult<BaseItemDto>> GetProgram(
[FromRoute, Required] string programId, [FromRoute, Required] string programId,
@@ -753,13 +752,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="404">Item not found.</response> /// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpDelete("Recordings/{recordingId}")] [HttpDelete("Recordings/{recordingId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteRecording([FromRoute, Required] Guid recordingId) public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
var item = _libraryManager.GetItemById(recordingId); var item = _libraryManager.GetItemById(recordingId);
if (item == null) if (item == null)
{ {
@@ -781,11 +778,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Timer deleted.</response> /// <response code="204">Timer deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Timers/{timerId}")] [HttpDelete("Timers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId) public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false); await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -798,12 +794,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Timer updated.</response> /// <response code="204">Timer updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Timers/{timerId}")] [HttpPost("Timers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo) public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -815,11 +810,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Timer created.</response> /// <response code="204">Timer created.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Timers")] [HttpPost("Timers")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo) public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -832,7 +826,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="404">Series timer not found.</response> /// <response code="404">Series timer not found.</response>
/// <returns>A <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if timer not found.</returns> /// <returns>A <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if timer not found.</returns>
[HttpGet("SeriesTimers/{timerId}")] [HttpGet("SeriesTimers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId) public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId)
@@ -854,7 +848,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Timers returned.</response> /// <response code="200">Timers returned.</response>
/// <returns>An <see cref="OkResult"/> of live tv series timers.</returns> /// <returns>An <see cref="OkResult"/> of live tv series timers.</returns>
[HttpGet("SeriesTimers")] [HttpGet("SeriesTimers")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder) public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder)
{ {
@@ -874,11 +868,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Timer cancelled.</response> /// <response code="204">Timer cancelled.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("SeriesTimers/{timerId}")] [HttpDelete("SeriesTimers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId) public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false); await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -891,12 +884,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Series timer updated.</response> /// <response code="204">Series timer updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("SeriesTimers/{timerId}")] [HttpPost("SeriesTimers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo) public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -908,11 +900,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Series timer info created.</response> /// <response code="204">Series timer info created.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("SeriesTimers")] [HttpPost("SeriesTimers")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo) public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -923,7 +914,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="groupId">Group id.</param> /// <param name="groupId">Group id.</param>
/// <returns>A <see cref="NotFoundResult"/>.</returns> /// <returns>A <see cref="NotFoundResult"/>.</returns>
[HttpGet("Recordings/Groups/{groupId}")] [HttpGet("Recordings/Groups/{groupId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("This endpoint is obsolete.")] [Obsolete("This endpoint is obsolete.")]
public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid groupId) public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid groupId)
@@ -937,7 +928,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Guid info returned.</response> /// <response code="200">Guid info returned.</response>
/// <returns>An <see cref="OkResult"/> containing the guide info.</returns> /// <returns>An <see cref="OkResult"/> containing the guide info.</returns>
[HttpGet("GuideInfo")] [HttpGet("GuideInfo")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<GuideInfo> GetGuideInfo() public ActionResult<GuideInfo> GetGuideInfo()
{ {
@@ -951,7 +942,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Created tuner host returned.</response> /// <response code="200">Created tuner host returned.</response>
/// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns> /// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns>
[HttpPost("TunerHosts")] [HttpPost("TunerHosts")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo) public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
{ {
@@ -965,7 +956,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Tuner host deleted.</response> /// <response code="204">Tuner host deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("TunerHosts")] [HttpDelete("TunerHosts")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteTunerHost([FromQuery] string? id) public ActionResult DeleteTunerHost([FromQuery] string? id)
{ {
@@ -981,7 +972,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Default listings provider info returned.</response> /// <response code="200">Default listings provider info returned.</response>
/// <returns>An <see cref="OkResult"/> containing the default listings provider info.</returns> /// <returns>An <see cref="OkResult"/> containing the default listings provider info.</returns>
[HttpGet("ListingProviders/Default")] [HttpGet("ListingProviders/Default")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<ListingsProviderInfo> GetDefaultListingProvider() public ActionResult<ListingsProviderInfo> GetDefaultListingProvider()
{ {
@@ -998,7 +989,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Created listings provider returned.</response> /// <response code="200">Created listings provider returned.</response>
/// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns> /// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns>
[HttpPost("ListingProviders")] [HttpPost("ListingProviders")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")]
public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider( public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider(
@@ -1025,7 +1016,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Listing provider deleted.</response> /// <response code="204">Listing provider deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("ListingProviders")] [HttpDelete("ListingProviders")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteListingProvider([FromQuery] string? id) public ActionResult DeleteListingProvider([FromQuery] string? id)
{ {
@@ -1043,7 +1034,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Available lineups returned.</response> /// <response code="200">Available lineups returned.</response>
/// <returns>A <see cref="OkResult"/> containing the available lineups.</returns> /// <returns>A <see cref="OkResult"/> containing the available lineups.</returns>
[HttpGet("ListingProviders/Lineups")] [HttpGet("ListingProviders/Lineups")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups( public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups(
[FromQuery] string? id, [FromQuery] string? id,
@@ -1060,7 +1051,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Available countries returned.</response> /// <response code="200">Available countries returned.</response>
/// <returns>A <see cref="FileResult"/> containing the available countries.</returns> /// <returns>A <see cref="FileResult"/> containing the available countries.</returns>
[HttpGet("ListingProviders/SchedulesDirect/Countries")] [HttpGet("ListingProviders/SchedulesDirect/Countries")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile(MediaTypeNames.Application.Json)] [ProducesFile(MediaTypeNames.Application.Json)]
public async Task<ActionResult> GetSchedulesDirectCountries() public async Task<ActionResult> GetSchedulesDirectCountries()
@@ -1081,7 +1072,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Channel mapping options returned.</response> /// <response code="200">Channel mapping options returned.</response>
/// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns> /// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns>
[HttpGet("ChannelMappingOptions")] [HttpGet("ChannelMappingOptions")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string? providerId) public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string? providerId)
{ {
@@ -1119,7 +1110,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Created channel mapping returned.</response> /// <response code="200">Created channel mapping returned.</response>
/// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns> /// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
[HttpPost("ChannelMappings")] [HttpPost("ChannelMappings")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto) public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto)
{ {
@@ -1132,7 +1123,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Tuner host types returned.</response> /// <response code="200">Tuner host types returned.</response>
/// <returns>An <see cref="OkResult"/> containing the tuner host types.</returns> /// <returns>An <see cref="OkResult"/> containing the tuner host types.</returns>
[HttpGet("TunerHosts/Types")] [HttpGet("TunerHosts/Types")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes() public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes()
{ {
@@ -1147,7 +1138,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the tuners.</returns> /// <returns>An <see cref="OkResult"/> containing the tuners.</returns>
[HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")] [HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
[HttpGet("Tuners/Discover")] [HttpGet("Tuners/Discover")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false) public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
{ {
@@ -1207,20 +1198,5 @@ namespace Jellyfin.Api.Controllers
var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream());
return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container)); return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container));
} }
private async Task AssertUserCanManageLiveTv()
{
var user = await _sessionContext.GetUser(Request).ConfigureAwait(false);
if (user == null)
{
throw new SecurityException("Anonymous live tv management is not allowed.");
}
if (!user.HasPermission(PermissionKind.EnableLiveTvManagement))
{
throw new SecurityException("The current user does not have permission to manage live tv.");
}
}
} }
} }

View File

@@ -98,7 +98,10 @@ namespace Jellyfin.Api.Controllers
Limit = limit ?? 0 Limit = limit ?? 0
}); });
return new QueryResult<BaseItemDto>(peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray()); return new QueryResult<BaseItemDto>(
peopleItems
.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user))
.ToArray());
} }
/// <summary> /// <summary>

View File

@@ -102,13 +102,13 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] int? maxAudioChannels, [FromQuery] int? maxAudioChannels,
[FromQuery] int? transcodingAudioChannels, [FromQuery] int? transcodingAudioChannels,
[FromQuery] int? maxStreamingBitrate, [FromQuery] int? maxStreamingBitrate,
[FromQuery] int? audioBitRate, [FromQuery] int? audioBitRate,
[FromQuery] long? startTimeTicks, [FromQuery] long? startTimeTicks,
[FromQuery] string? transcodingContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? transcodingContainer,
[FromQuery] string? transcodingProtocol, [FromQuery] string? transcodingProtocol,
[FromQuery] int? maxAudioSampleRate, [FromQuery] int? maxAudioSampleRate,
[FromQuery] int? maxAudioBitDepth, [FromQuery] int? maxAudioBitDepth,
@@ -117,7 +117,13 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableRedirection = true) [FromQuery] bool enableRedirection = true)
{ {
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
(await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId = deviceId; var authorizationInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
authorizationInfo.DeviceId = deviceId;
if (!userId.HasValue || userId.Value.Equals(Guid.Empty))
{
userId = authorizationInfo.UserId;
}
var authInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var authInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false);

View File

@@ -318,18 +318,18 @@ namespace Jellyfin.Api.Controllers
[ProducesVideoFile] [ProducesVideoFile]
public async Task<ActionResult> GetVideoStream( public async Task<ActionResult> GetVideoStream(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] string? container, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -361,8 +361,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,
@@ -578,12 +578,12 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? deviceProfileId, [FromQuery] string? deviceProfileId,
[FromQuery] string? playSessionId, [FromQuery] string? playSessionId,
[FromQuery] string? segmentContainer, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? segmentContainer,
[FromQuery] int? segmentLength, [FromQuery] int? segmentLength,
[FromQuery] int? minSegments, [FromQuery] int? minSegments,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] string? audioCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? audioCodec,
[FromQuery] bool? enableAutoStreamCopy, [FromQuery] bool? enableAutoStreamCopy,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery] bool? allowAudioStreamCopy,
@@ -615,8 +615,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? cpuCoreLimit, [FromQuery] int? cpuCoreLimit,
[FromQuery] string? liveStreamId, [FromQuery] string? liveStreamId,
[FromQuery] bool? enableMpegtsM2TsMode, [FromQuery] bool? enableMpegtsM2TsMode,
[FromQuery] string? videoCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? videoCodec,
[FromQuery] string? subtitleCodec, [FromQuery][RegularExpression(EncodingHelper.ValidationRegex)] string? subtitleCodec,
[FromQuery] string? transcodeReasons, [FromQuery] string? transcodeReasons,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? videoStreamIndex, [FromQuery] int? videoStreamIndex,

View File

@@ -8,6 +8,8 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@@ -202,8 +204,18 @@ namespace Jellyfin.Api.Helpers
if (state.VideoStream != null && state.VideoRequest != null) if (state.VideoStream != null && state.VideoRequest != null)
{ {
// Provide a workaround for the case issue between flac and fLaC.
var flacWaPlaylist = ApplyFlacCaseWorkaround(state, basicPlaylist.ToString());
if (!string.IsNullOrEmpty(flacWaPlaylist))
{
builder.Append(flacWaPlaylist);
}
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
// Provide SDR HEVC entrance for backward compatibility. // Provide SDR HEVC entrance for backward compatibility.
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) if (encodingOptions.AllowHevcEncoding
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange) && !string.IsNullOrEmpty(state.VideoStream.VideoRange)
&& string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) && string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
@@ -217,10 +229,25 @@ namespace Jellyfin.Api.Helpers
sdrVideoUrl += "&AllowVideoStreamCopy=false"; sdrVideoUrl += "&AllowVideoStreamCopy=false";
var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0; var sdrOutputAudioBitrate = 0;
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; if (EncodingHelper.LosslessAudioCodecs.Contains(state.VideoRequest.AudioCodec, StringComparison.OrdinalIgnoreCase))
{
sdrOutputAudioBitrate = state.AudioStream.BitRate ?? 0;
}
else
{
sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0;
}
AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);
// Provide a workaround for the case issue between flac and fLaC.
flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString());
if (!string.IsNullOrEmpty(flacWaPlaylist))
{
builder.Append(flacWaPlaylist);
}
// Restore the video codec // Restore the video codec
state.OutputVideoCodec = "copy"; state.OutputVideoCodec = "copy";
@@ -250,6 +277,13 @@ namespace Jellyfin.Api.Helpers
state.VideoStream.Level = originalLevel; state.VideoStream.Level = originalLevel;
var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField); var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField);
builder.Append(newPlaylist); builder.Append(newPlaylist);
// Provide a workaround for the case issue between flac and fLaC.
flacWaPlaylist = ApplyFlacCaseWorkaround(state, newPlaylist);
if (!string.IsNullOrEmpty(flacWaPlaylist))
{
builder.Append(flacWaPlaylist);
}
} }
} }
@@ -608,6 +642,11 @@ namespace Jellyfin.Api.Helpers
return HlsCodecStringHelpers.GetALACString(); return HlsCodecStringHelpers.GetALACString();
} }
if (string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
return HlsCodecStringHelpers.GetOPUSString();
}
return string.Empty; return string.Empty;
} }
@@ -706,7 +745,19 @@ namespace Jellyfin.Api.Helpers
return oldPlaylist.Replace( return oldPlaylist.Replace(
oldValue.ToString(), oldValue.ToString(),
newValue.ToString(), newValue.ToString(),
StringComparison.OrdinalIgnoreCase); StringComparison.Ordinal);
}
private string ApplyFlacCaseWorkaround(StreamState state, string srcPlaylist)
{
if (!string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase))
{
return string.Empty;
}
var newPlaylist = srcPlaylist.Replace(",flac\"", ",fLaC\"", StringComparison.Ordinal);
return newPlaylist.Contains(",fLaC\"", StringComparison.Ordinal) ? newPlaylist : string.Empty;
} }
} }
} }

View File

@@ -27,13 +27,18 @@ namespace Jellyfin.Api.Helpers
/// <summary> /// <summary>
/// Codec name for FLAC. /// Codec name for FLAC.
/// </summary> /// </summary>
public const string FLAC = "fLaC"; public const string FLAC = "flac";
/// <summary> /// <summary>
/// Codec name for ALAC. /// Codec name for ALAC.
/// </summary> /// </summary>
public const string ALAC = "alac"; public const string ALAC = "alac";
/// <summary>
/// Codec name for OPUS.
/// </summary>
public const string OPUS = "opus";
/// <summary> /// <summary>
/// Gets a MP3 codec string. /// Gets a MP3 codec string.
/// </summary> /// </summary>
@@ -101,6 +106,15 @@ namespace Jellyfin.Api.Helpers
return ALAC; return ALAC;
} }
/// <summary>
/// Gets an OPUS codec string.
/// </summary>
/// <returns>OPUS codec string.</returns>
public static string GetOPUSString()
{
return OPUS;
}
/// <summary> /// <summary>
/// Gets a H.264 codec string. /// Gets a H.264 codec string.
/// </summary> /// </summary>

View File

@@ -182,12 +182,18 @@ namespace Jellyfin.Api.Helpers
: GetOutputFileExtension(state, mediaSource); : GetOutputFileExtension(state, mediaSource);
} }
var outputAudioCodec = streamingRequest.AudioCodec;
if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec))
{
state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0;
}
else
{
state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels) ?? 0;
}
state.OutputAudioCodec = outputAudioCodec;
state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream);
state.OutputAudioCodec = streamingRequest.AudioCodec;
state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec);
if (state.VideoRequest != null) if (state.VideoRequest != null)

View File

@@ -654,7 +654,7 @@ namespace Jellyfin.Api.Helpers
{ {
if (EnableThrottling(state)) if (EnableThrottling(state))
{ {
transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem); transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem, _mediaEncoder);
transcodingJob.TranscodingThrottler.Start(); transcodingJob.TranscodingThrottler.Start();
} }
} }

View File

@@ -17,7 +17,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.9" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" />
@@ -26,6 +26,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" /> <ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj" />
<ProjectReference Include="..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" /> <ProjectReference Include="..\src\Jellyfin.MediaEncoding.Hls\Jellyfin.MediaEncoding.Hls.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -2,6 +2,7 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -17,6 +18,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
private readonly ILogger<TranscodingThrottler> _logger; private readonly ILogger<TranscodingThrottler> _logger;
private readonly IConfigurationManager _config; private readonly IConfigurationManager _config;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
private Timer? _timer; private Timer? _timer;
private bool _isPaused; private bool _isPaused;
@@ -27,12 +29,14 @@ namespace Jellyfin.Api.Models.PlaybackDtos
/// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param>
/// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param> /// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem) /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder)
{ {
_job = job; _job = job;
_logger = logger; _logger = logger;
_config = config; _config = config;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
} }
/// <summary> /// <summary>
@@ -55,7 +59,8 @@ namespace Jellyfin.Api.Models.PlaybackDtos
try try
{ {
await _job.Process!.StandardInput.WriteLineAsync().ConfigureAwait(false); var resumeKey = _mediaEncoder.IsPkeyPauseSupported ? "u" : Environment.NewLine;
await _job.Process!.StandardInput.WriteAsync(resumeKey).ConfigureAwait(false);
_isPaused = false; _isPaused = false;
} }
catch (Exception ex) catch (Exception ex)
@@ -125,11 +130,13 @@ namespace Jellyfin.Api.Models.PlaybackDtos
{ {
if (!_isPaused) if (!_isPaused)
{ {
_logger.LogDebug("Sending pause command to ffmpeg"); var pauseKey = _mediaEncoder.IsPkeyPauseSupported ? "p" : "c";
_logger.LogDebug("Sending pause command [{Key}] to ffmpeg", pauseKey);
try try
{ {
await _job.Process!.StandardInput.WriteAsync("c").ConfigureAwait(false); await _job.Process!.StandardInput.WriteAsync(pauseKey).ConfigureAwait(false);
_isPaused = true; _isPaused = true;
} }
catch (Exception ex) catch (Exception ex)

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,657 @@
#pragma warning disable CS1591
// <auto-generated />
using System;
using Jellyfin.Server.Implementations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations
{
[DbContext(typeof(JellyfinDb))]
[Migration("20221022080052_AddIndexActivityLogsDateCreated")]
partial class AddIndexActivityLogsDateCreated
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "6.0.9");
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("DayOfWeek")
.HasColumnType("INTEGER");
b.Property<double>("EndHour")
.HasColumnType("REAL");
b.Property<double>("StartHour")
.HasColumnType("REAL");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AccessSchedules", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<string>("ItemId")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<int>("LogSeverity")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("Overview")
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("ShortOverview")
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("DateCreated");
b.ToTable("ActivityLogs", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId", "ItemId", "Client", "Key")
.IsUnique();
b.ToTable("CustomItemDisplayPreferences", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ChromecastVersion")
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<string>("DashboardTheme")
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<bool>("EnableNextVideoInfoOverlay")
.HasColumnType("INTEGER");
b.Property<int?>("IndexBy")
.HasColumnType("INTEGER");
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<int>("ScrollDirection")
.HasColumnType("INTEGER");
b.Property<bool>("ShowBackdrop")
.HasColumnType("INTEGER");
b.Property<bool>("ShowSidebar")
.HasColumnType("INTEGER");
b.Property<int>("SkipBackwardLength")
.HasColumnType("INTEGER");
b.Property<int>("SkipForwardLength")
.HasColumnType("INTEGER");
b.Property<string>("TvHome")
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId", "ItemId", "Client")
.IsUnique();
b.ToTable("DisplayPreferences", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("DisplayPreferencesId")
.HasColumnType("INTEGER");
b.Property<int>("Order")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("DisplayPreferencesId");
b.ToTable("HomeSection", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Path")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("TEXT");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("ImageInfos", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Client")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<int?>("IndexBy")
.HasColumnType("INTEGER");
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<bool>("RememberIndexing")
.HasColumnType("INTEGER");
b.Property<bool>("RememberSorting")
.HasColumnType("INTEGER");
b.Property<string>("SortBy")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<int>("SortOrder")
.HasColumnType("INTEGER");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.Property<int>("ViewType")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("ItemDisplayPreferences", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Kind")
.HasColumnType("INTEGER");
b.Property<Guid?>("Permission_Permissions_Guid")
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
b.Property<bool>("Value")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId", "Kind")
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
b.ToTable("Permissions", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Kind")
.HasColumnType("INTEGER");
b.Property<Guid?>("Preference_Preferences_Guid")
.HasColumnType("TEXT");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId", "Kind")
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
b.ToTable("Preferences", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateLastActivity")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("AccessToken")
.IsUnique();
b.ToTable("ApiKeys", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("AppName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<string>("AppVersion")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateLastActivity")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<string>("DeviceId")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("DeviceName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("DeviceId");
b.HasIndex("AccessToken", "DateLastActivity");
b.HasIndex("DeviceId", "DateLastActivity");
b.HasIndex("UserId", "DeviceId");
b.ToTable("Devices", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CustomName")
.HasColumnType("TEXT");
b.Property<string>("DeviceId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("DeviceId")
.IsUnique();
b.ToTable("DeviceOptions", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AudioLanguagePreference")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("AuthenticationProviderId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("DisplayCollectionsView")
.HasColumnType("INTEGER");
b.Property<bool>("DisplayMissingEpisodes")
.HasColumnType("INTEGER");
b.Property<string>("EasyPassword")
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.Property<bool>("EnableAutoLogin")
.HasColumnType("INTEGER");
b.Property<bool>("EnableLocalPassword")
.HasColumnType("INTEGER");
b.Property<bool>("EnableNextEpisodeAutoPlay")
.HasColumnType("INTEGER");
b.Property<bool>("EnableUserPreferenceAccess")
.HasColumnType("INTEGER");
b.Property<bool>("HidePlayedInLatest")
.HasColumnType("INTEGER");
b.Property<long>("InternalId")
.HasColumnType("INTEGER");
b.Property<int>("InvalidLoginAttemptCount")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastActivityDate")
.HasColumnType("TEXT");
b.Property<DateTime?>("LastLoginDate")
.HasColumnType("TEXT");
b.Property<int?>("LoginAttemptsBeforeLockout")
.HasColumnType("INTEGER");
b.Property<int>("MaxActiveSessions")
.HasColumnType("INTEGER");
b.Property<int?>("MaxParentalAgeRating")
.HasColumnType("INTEGER");
b.Property<bool>("MustUpdatePassword")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.HasMaxLength(65535)
.HasColumnType("TEXT");
b.Property<string>("PasswordResetProviderId")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("PlayDefaultAudioTrack")
.HasColumnType("INTEGER");
b.Property<bool>("RememberAudioSelections")
.HasColumnType("INTEGER");
b.Property<bool>("RememberSubtitleSelections")
.HasColumnType("INTEGER");
b.Property<int?>("RemoteClientBitrateLimit")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("SubtitleLanguagePreference")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<int>("SubtitleMode")
.HasColumnType("INTEGER");
b.Property<int>("SyncPlayAccess")
.HasColumnType("INTEGER");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT")
.UseCollation("NOCASE");
b.HasKey("Id");
b.HasIndex("Username")
.IsUnique();
b.ToTable("Users", "jellyfin");
});
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("AccessSchedules")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("DisplayPreferences")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
{
b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
.WithMany("HomeSections")
.HasForeignKey("DisplayPreferencesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithOne("ProfileImage")
.HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("ItemDisplayPreferences")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Permissions")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", null)
.WithMany("Preferences")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
{
b.HasOne("Jellyfin.Data.Entities.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
{
b.Navigation("HomeSections");
});
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
{
b.Navigation("AccessSchedules");
b.Navigation("DisplayPreferences");
b.Navigation("ItemDisplayPreferences");
b.Navigation("Permissions");
b.Navigation("Preferences");
b.Navigation("ProfileImage");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,28 @@
#pragma warning disable CS1591, SA1601
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations
{
public partial class AddIndexActivityLogsDateCreated : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_ActivityLogs_DateCreated",
schema: "jellyfin",
table: "ActivityLogs",
column: "DateCreated");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_ActivityLogs_DateCreated",
schema: "jellyfin",
table: "ActivityLogs");
}
}
}

View File

@@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Jellyfin.Server.Implementations.Migrations namespace Jellyfin.Server.Implementations.Migrations
{ {
[DbContext(typeof(JellyfinDb))] [DbContext(typeof(JellyfinDb))]
@@ -15,7 +17,7 @@ namespace Jellyfin.Server.Implementations.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasDefaultSchema("jellyfin") .HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "5.0.7"); .HasAnnotation("ProductVersion", "6.0.9");
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{ {
@@ -39,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId"); b.HasIndex("UserId");
b.ToTable("AccessSchedules"); b.ToTable("AccessSchedules", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
@@ -85,7 +87,9 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("ActivityLogs"); b.HasIndex("DateCreated");
b.ToTable("ActivityLogs", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b => modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
@@ -117,7 +121,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId", "ItemId", "Client", "Key") b.HasIndex("UserId", "ItemId", "Client", "Key")
.IsUnique(); .IsUnique();
b.ToTable("CustomItemDisplayPreferences"); b.ToTable("CustomItemDisplayPreferences", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
@@ -174,7 +178,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId", "ItemId", "Client") b.HasIndex("UserId", "ItemId", "Client")
.IsUnique(); .IsUnique();
b.ToTable("DisplayPreferences"); b.ToTable("DisplayPreferences", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
@@ -196,7 +200,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("DisplayPreferencesId"); b.HasIndex("DisplayPreferencesId");
b.ToTable("HomeSection"); b.ToTable("HomeSection", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
@@ -221,7 +225,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId") b.HasIndex("UserId")
.IsUnique(); .IsUnique();
b.ToTable("ImageInfos"); b.ToTable("ImageInfos", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
@@ -265,7 +269,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId"); b.HasIndex("UserId");
b.ToTable("ItemDisplayPreferences"); b.ToTable("ItemDisplayPreferences", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
@@ -296,7 +300,7 @@ namespace Jellyfin.Server.Implementations.Migrations
.IsUnique() .IsUnique()
.HasFilter("[UserId] IS NOT NULL"); .HasFilter("[UserId] IS NOT NULL");
b.ToTable("Permissions"); b.ToTable("Permissions", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
@@ -329,7 +333,7 @@ namespace Jellyfin.Server.Implementations.Migrations
.IsUnique() .IsUnique()
.HasFilter("[UserId] IS NOT NULL"); .HasFilter("[UserId] IS NOT NULL");
b.ToTable("Preferences"); b.ToTable("Preferences", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b => modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
@@ -358,7 +362,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("AccessToken") b.HasIndex("AccessToken")
.IsUnique(); .IsUnique();
b.ToTable("ApiKeys"); b.ToTable("ApiKeys", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
@@ -416,7 +420,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("UserId", "DeviceId"); b.HasIndex("UserId", "DeviceId");
b.ToTable("Devices"); b.ToTable("Devices", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b => modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
@@ -437,7 +441,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("DeviceId") b.HasIndex("DeviceId")
.IsUnique(); .IsUnique();
b.ToTable("DeviceOptions"); b.ToTable("DeviceOptions", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.User", b => modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
@@ -550,7 +554,7 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasIndex("Username") b.HasIndex("Username")
.IsUnique(); .IsUnique();
b.ToTable("Users"); b.ToTable("Users", "jellyfin");
}); });
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>

View File

@@ -0,0 +1,17 @@
using Jellyfin.Data.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Jellyfin.Server.Implementations.ModelConfiguration;
/// <summary>
/// FluentAPI configuration for the ActivityLog entity.
/// </summary>
public class ActivityLogConfiguration : IEntityTypeConfiguration<ActivityLog>
{
/// <inheritdoc/>
public void Configure(EntityTypeBuilder<ActivityLog> builder)
{
builder.HasIndex(entity => entity.DateCreated);
}
}

View File

@@ -14,6 +14,8 @@ using Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy; using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
using Jellyfin.Api.Auth.IgnoreParentalControlPolicy; using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
using Jellyfin.Api.Auth.LiveTvAccessPolicy;
using Jellyfin.Api.Auth.LiveTvManagementPolicy;
using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy; using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy;
using Jellyfin.Api.Auth.LocalAccessPolicy; using Jellyfin.Api.Auth.LocalAccessPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy;
@@ -66,6 +68,8 @@ namespace Jellyfin.Server.Extensions
serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, LiveTvAccessHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, LiveTvManagementHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>();
return serviceCollection.AddAuthorizationCore(options => return serviceCollection.AddAuthorizationCore(options =>
{ {
@@ -167,6 +171,20 @@ namespace Jellyfin.Server.Extensions
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new AnonymousLanAccessRequirement()); policy.AddRequirements(new AnonymousLanAccessRequirement());
}); });
options.AddPolicy(
Policies.LiveTvAccess,
policy =>
{
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new LiveTvAccessRequirement());
});
options.AddPolicy(
Policies.LiveTvManagement,
policy =>
{
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new LiveTvManagementRequirement());
});
}); });
} }
@@ -434,11 +452,15 @@ namespace Jellyfin.Server.Extensions
options.MapType<TranscodeReason>(() => options.MapType<TranscodeReason>(() =>
new OpenApiSchema new OpenApiSchema
{ {
Type = "string", Type = "array",
Enum = Enum.GetNames<TranscodeReason>() Items = new OpenApiSchema
.Select(e => new OpenApiString(e)) {
.Cast<IOpenApiAny>() Reference = new OpenApiReference
.ToArray() {
Id = nameof(TranscodeReason),
Type = ReferenceType.Schema,
}
}
}); });
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it. // Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using Jellyfin.Server.Migrations; using Jellyfin.Server.Migrations;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
@@ -8,6 +9,7 @@ using MediaBrowser.Model.ApiClient;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using MediaBrowser.Model.SyncPlay; using MediaBrowser.Model.SyncPlay;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
@@ -56,6 +58,15 @@ namespace Jellyfin.Server.Filters
context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository); context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository);
} }
context.SchemaRepository.AddDefinition(nameof(TranscodeReason), new OpenApiSchema
{
Type = "string",
Enum = Enum.GetNames<TranscodeReason>()
.Select(e => new OpenApiString(e))
.Cast<IOpenApiAny>()
.ToArray()
});
} }
} }
} }

View File

@@ -37,8 +37,8 @@
<PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.7" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.9" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.7" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.9" />
<PackageReference Include="prometheus-net" Version="6.0.0" /> <PackageReference Include="prometheus-net" Version="6.0.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" /> <PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" /> <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />

View File

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

View File

@@ -170,6 +170,11 @@ namespace MediaBrowser.Common.Net
address = address.MapToIPv4(); address = address.MapToIPv4();
} }
if (address.AddressFamily != AddressFamily)
{
return false;
}
var (altAddress, altPrefix) = NetworkAddressOf(address, PrefixLength); var (altAddress, altPrefix) = NetworkAddressOf(address, PrefixLength);
return NetworkAddress.Address.Equals(altAddress) && NetworkAddress.PrefixLength >= altPrefix; return NetworkAddress.Address.Equals(altAddress) && NetworkAddress.PrefixLength >= altPrefix;
} }

View File

@@ -23,6 +23,11 @@ namespace MediaBrowser.Controller.ClientEvent
{ {
var fileName = $"upload_{clientName}_{clientVersion}_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}.log"; var fileName = $"upload_{clientName}_{clientVersion}_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}.log";
var logFilePath = Path.Combine(_applicationPaths.LogDirectoryPath, fileName); var logFilePath = Path.Combine(_applicationPaths.LogDirectoryPath, fileName);
if (!Path.GetFullPath(logFilePath).StartsWith(_applicationPaths.LogDirectoryPath, StringComparison.Ordinal))
{
throw new ArgumentException("Path resolved to filename not in log directory");
}
await using var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None); await using var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
await fileContents.CopyToAsync(fileStream).ConfigureAwait(false); await fileContents.CopyToAsync(fileStream).ConfigureAwait(false);
return fileName; return fileName;

View File

@@ -47,7 +47,7 @@ namespace MediaBrowser.Controller.Entities
/// The supported image extensions. /// The supported image extensions.
/// </summary> /// </summary>
public static readonly string[] SupportedImageExtensions public static readonly string[] SupportedImageExtensions
= new[] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" }; = new[] { ".png", ".jpg", ".jpeg", ".webp", ".tbn", ".gif" };
private static readonly List<string> _supportedExtensions = new List<string>(SupportedImageExtensions) private static readonly List<string> _supportedExtensions = new List<string>(SupportedImageExtensions)
{ {
@@ -1863,7 +1863,7 @@ namespace MediaBrowser.Controller.Entities
data.PlaybackPositionTicks = 0; data.PlaybackPositionTicks = 0;
} }
data.LastPlayedDate = datePlayed ?? data.LastPlayedDate ?? DateTime.UtcNow; data.LastPlayedDate = datePlayed ?? DateTime.UtcNow;
data.Played = true; data.Played = true;
UserDataManager.SaveUserData(user.Id, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None); UserDataManager.SaveUserData(user.Id, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None);

View File

@@ -892,29 +892,7 @@ namespace MediaBrowser.Controller.Entities
private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items) private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
{ {
var ids = query.ItemIds; return items.OrderBy(i => Array.IndexOf(query.ItemIds, i.Id)).ToArray();
int size = items.Count;
// ids can potentially contain non-unique guids, but query result cannot,
// so we include only first occurrence of each guid
var positions = new Dictionary<Guid, int>(size);
int index = 0;
for (int i = 0; i < ids.Length; i++)
{
if (positions.TryAdd(ids[i], index))
{
index++;
}
}
var newItems = new BaseItem[size];
for (int i = 0; i < size; i++)
{
var item = items[i];
newItems[positions[item.Id]] = item;
}
return newItems;
} }
public QueryResult<BaseItem> GetItems(InternalItemsQuery query) public QueryResult<BaseItem> GetItems(InternalItemsQuery query)

View File

@@ -205,6 +205,16 @@ namespace MediaBrowser.Controller.Entities
public int? MinIndexNumber { get; set; } public int? MinIndexNumber { get; set; }
/// <summary>
/// Gets or sets the minimum ParentIndexNumber and IndexNumber.
/// </summary>
/// <remarks>
/// It produces this where clause:
/// <para>(ParentIndexNumber = X and IndexNumber >= Y) or ParentIndexNumber > X.
/// </para>
/// </remarks>
public (int ParentIndexNumber, int IndexNumber)? MinParentAndIndexNumber { get; set; }
public int? AiredDuringSeason { get; set; } public int? AiredDuringSeason { get; set; }
public double? MinCriticRating { get; set; } public double? MinCriticRating { get; set; }

View File

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

View File

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

View File

@@ -25,6 +25,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
public class EncodingHelper public class EncodingHelper
{ {
/// <summary>
/// The codec validation regex.
/// </summary>
public const string ValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
private const string QsvAlias = "qs"; private const string QsvAlias = "qs";
private const string VaapiAlias = "va"; private const string VaapiAlias = "va";
private const string D3d11vaAlias = "dx11"; private const string D3d11vaAlias = "dx11";
@@ -35,7 +40,17 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder; private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config; private readonly IConfiguration _config;
private readonly Version _minKernelVersioni915Hang = new Version(5, 18);
private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled);
// i915 hang was fixed by linux 6.2 (3f882f2)
private readonly Version _minKerneli915Hang = new Version(5, 18);
private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0);
private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
private static readonly string[] _videoProfilesH264 = new[] private static readonly string[] _videoProfilesH264 = new[]
{ {
@@ -55,6 +70,16 @@ namespace MediaBrowser.Controller.MediaEncoding
"Main10" "Main10"
}; };
public static readonly string[] LosslessAudioCodecs = new string[]
{
"alac",
"ape",
"flac",
"mlp",
"truehd",
"wavpack"
};
public EncodingHelper( public EncodingHelper(
IApplicationPaths appPaths, IApplicationPaths appPaths,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
@@ -235,7 +260,10 @@ namespace MediaBrowser.Controller.MediaEncoding
return "libtheora"; return "libtheora";
} }
return codec.ToLowerInvariant(); if (_validationRegex.IsMatch(codec))
{
return codec.ToLowerInvariant();
}
} }
return "copy"; return "copy";
@@ -258,7 +286,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string GetInputFormat(string container) public static string GetInputFormat(string container)
{ {
if (string.IsNullOrEmpty(container)) if (string.IsNullOrEmpty(container) || !_validationRegex.IsMatch(container))
{ {
return null; return null;
} }
@@ -505,6 +533,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
var codec = state.OutputAudioCodec; var codec = state.OutputAudioCodec;
if (!_validationRegex.IsMatch(codec))
{
codec = "aac";
}
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)) if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{ {
// Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
@@ -541,6 +574,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return "flac"; return "flac";
} }
if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
{
return "dca";
}
return codec.ToLowerInvariant(); return codec.ToLowerInvariant();
} }
@@ -604,14 +642,20 @@ namespace MediaBrowser.Controller.MediaEncoding
private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string alias) private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string alias)
{ {
alias ??= VaapiAlias; alias ??= VaapiAlias;
renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
var options = string.IsNullOrEmpty(driver) // 'renderNodePath' has higher priority than 'kernelDriver'
? renderNodePath var driverOpts = string.IsNullOrEmpty(renderNodePath)
: ",driver=" + driver + (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver); ? (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver)
: renderNodePath;
// 'driver' behaves similarly to env LIBVA_DRIVER_NAME
driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
var options = string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts;
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
" -init_hw_device vaapi={0}:{1}", " -init_hw_device vaapi={0}{1}",
alias, alias,
options); options);
} }
@@ -643,9 +687,12 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetGraphicalSubCanvasSize(EncodingJobInfo state) public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
{ {
// DVBSUB and DVDSUB use the fixed canvas size 720x576
if (state.SubtitleStream != null if (state.SubtitleStream != null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& !state.SubtitleStream.IsTextSubtitleStream) && !state.SubtitleStream.IsTextSubtitleStream
&& !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(state.SubtitleStream.Codec, "DVDSUB", StringComparison.OrdinalIgnoreCase))
{ {
var inW = state.VideoStream?.Width; var inW = state.VideoStream?.Width;
var inH = state.VideoStream?.Height; var inH = state.VideoStream?.Height;
@@ -713,14 +760,14 @@ namespace MediaBrowser.Controller.MediaEncoding
if (_mediaEncoder.IsVaapiDeviceInteliHD) if (_mediaEncoder.IsVaapiDeviceInteliHD)
{ {
args.Append(GetVaapiDeviceArgs(null, "iHD", null, VaapiAlias)); args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, VaapiAlias));
} }
else if (_mediaEncoder.IsVaapiDeviceInteli965) else if (_mediaEncoder.IsVaapiDeviceInteli965)
{ {
// Only override i965 since it has lower priority than iHD in libva lookup. // Only override i965 since it has lower priority than iHD in libva lookup.
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965"); Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965"); Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
args.Append(GetVaapiDeviceArgs(null, "i965", null, VaapiAlias)); args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, VaapiAlias));
} }
else else
{ {
@@ -1152,16 +1199,15 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.SubtitleStream.IsExternal) if (state.SubtitleStream.IsExternal)
{ {
var subtitlePath = state.SubtitleStream.Path;
var charsetParam = string.Empty; var charsetParam = string.Empty;
if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
{ {
var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet( var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
subtitlePath, state.SubtitleStream,
state.SubtitleStream.Language, state.SubtitleStream.Language,
state.MediaSource.Protocol, state.MediaSource,
CancellationToken.None).GetAwaiter().GetResult(); CancellationToken.None).GetAwaiter().GetResult();
if (!string.IsNullOrEmpty(charenc)) if (!string.IsNullOrEmpty(charenc))
{ {
@@ -1173,7 +1219,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"subtitles=f='{0}'{1}{2}{3}{4}{5}", "subtitles=f='{0}'{1}{2}{3}{4}{5}",
_mediaEncoder.EscapeSubtitleFilterPath(subtitlePath), _mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
charsetParam, charsetParam,
alphaParam, alphaParam,
sub2videoParam, sub2videoParam,
@@ -1288,6 +1334,13 @@ namespace MediaBrowser.Controller.MediaEncoding
args += keyFrameArg + gopArg; args += keyFrameArg + gopArg;
} }
// global_header produced by AMD VA-API encoder causes non-playable fMP4 on iOS
if (codec.Contains("vaapi", StringComparison.OrdinalIgnoreCase)
&& _mediaEncoder.IsVaapiDeviceAmd)
{
args += " -flags:v -global_header";
}
return args; return args;
} }
@@ -1310,7 +1363,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping. // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
var intelLowPowerHwEncoding = false; var intelLowPowerHwEncoding = false;
// Workaround for linux 5.18+ i915 hang at cost of performance. // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
// https://github.com/intel/media-driver/issues/1456 // https://github.com/intel/media-driver/issues/1456
var enableWaFori915Hang = false; var enableWaFori915Hang = false;
@@ -1329,18 +1382,25 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{ {
if (OperatingSystem.IsLinux() && Environment.OSVersion.Version >= _minKernelVersioni915Hang) if (OperatingSystem.IsLinux())
{ {
var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty; var ver = Environment.OSVersion.Version;
var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase) var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
|| vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
&& IsVaapiSupported(state)
&& IsOpenclFullSupported()
&& !IsVaapiVppTonemapAvailable(state, encodingOptions)
&& IsHwTonemapAvailable(state, encodingOptions);
enableWaFori915Hang = isIntelDecoder && doOclTonemap; if (!(isUnaffectedKernel || isFixedKernel60))
{
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)) if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
@@ -1416,7 +1476,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -preset 7"; param += " -preset 7";
} }
param += " -look_ahead 0"; // Only h264_qsv has look_ahead option
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
param += " -look_ahead 0";
}
} }
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
@@ -1427,11 +1491,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -preset p7"; param += " -preset p7";
break; break;
case "slow": case "slower":
param += " -preset p6"; param += " -preset p6";
break; break;
case "slower": case "slow":
param += " -preset p5"; param += " -preset p5";
break; break;
@@ -1454,7 +1518,7 @@ namespace MediaBrowser.Controller.MediaEncoding
break; break;
default: default:
param += " -preset p4"; param += " -preset p1";
break; break;
} }
} }
@@ -1464,8 +1528,8 @@ namespace MediaBrowser.Controller.MediaEncoding
switch (encodingOptions.EncoderPreset) switch (encodingOptions.EncoderPreset)
{ {
case "veryslow": case "veryslow":
case "slow":
case "slower": case "slower":
case "slow":
param += " -quality quality"; param += " -quality quality";
break; break;
@@ -1938,9 +2002,9 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
} }
// Video bitrate must fall within requested value // Audio bitrate must fall within requested value
if (request.AudioBitRate.HasValue if (request.AudioBitRate.HasValue
&& audioStream.BitDepth.HasValue && audioStream.BitRate.HasValue
&& audioStream.BitRate.Value > request.AudioBitRate.Value) && audioStream.BitRate.Value > request.AudioBitRate.Value)
{ {
return false; return false;
@@ -2004,14 +2068,20 @@ namespace MediaBrowser.Controller.MediaEncoding
private static double GetVideoBitrateScaleFactor(string codec) private static double GetVideoBitrateScaleFactor(string codec)
{ {
// hevc & vp9 - 40% more efficient than h.264
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
|| string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
{ {
return .6; return .6;
} }
// av1 - 50% more efficient than h.264
if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
{
return .5;
}
return 1; return 1;
} }
@@ -2019,7 +2089,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec); var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec); var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
var scaleFactor = outputScaleFactor / inputScaleFactor;
// Don't scale the real bitrate lower than the requested bitrate
var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
if (bitrate <= 500000) if (bitrate <= 500000)
{ {
@@ -2041,56 +2113,55 @@ namespace MediaBrowser.Controller.MediaEncoding
return Convert.ToInt32(scaleFactor * bitrate); return Convert.ToInt32(scaleFactor * bitrate);
} }
public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChannels)
{ {
return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream); return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
} }
public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream) public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudioChannels)
{ {
if (audioStream == null) if (audioStream == null)
{ {
return null; return null;
} }
if (audioBitRate.HasValue && string.IsNullOrEmpty(audioCodec)) var inputChannels = audioStream.Channels ?? 0;
var outputChannels = outputAudioChannels ?? 0;
var bitrate = audioBitRate ?? int.MaxValue;
if (string.IsNullOrEmpty(audioCodec)
|| 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))
{ {
return Math.Min(384000, audioBitRate.Value); return (inputChannels, outputChannels) switch
{
(>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
(> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
(> 0, _) => Math.Min(inputChannels * 128000, bitrate),
(_, _) => Math.Min(384000, bitrate)
};
} }
if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec)) if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
{ {
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase) return (inputChannels, outputChannels) switch
|| 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))
{ {
if ((audioStream.Channels ?? 0) >= 6) (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
{ (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
return Math.Min(640000, audioBitRate.Value); (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
} (_, _) => Math.Min(672000, bitrate)
};
return Math.Min(384000, audioBitRate.Value);
}
if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
{
if ((audioStream.Channels ?? 0) >= 6)
{
return Math.Min(3584000, audioBitRate.Value);
}
return Math.Min(1536000, audioBitRate.Value);
}
} }
// Empty bitrate area is not allow on iOS // Empty bitrate area is not allow on iOS
// Default audio bitrate to 128K if it is not being requested // Default audio bitrate to 128K per channel if we don't have codec specific defaults
// https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
return 128000; return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
} }
public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -2383,6 +2454,30 @@ namespace MediaBrowser.Controller.MediaEncoding
return args; return args;
} }
/// <summary>
/// Gets the negative map args by filters.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="videoProcessFilters">The videoProcessFilters.</param>
/// <returns>System.String.</returns>
public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
{
string args = string.Empty;
// http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
if (state.VideoStream != null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordinal))
{
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
args += string.Format(
CultureInfo.InvariantCulture,
"-map -0:{0} ",
videoStreamIndex);
}
return args;
}
/// <summary> /// <summary>
/// Determines which stream will be used for playback. /// Determines which stream will be used for playback.
/// </summary> /// </summary>
@@ -2753,7 +2848,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty; return string.Empty;
} }
public static string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat) public string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
{ {
if (string.IsNullOrEmpty(hwTonemapSuffix)) if (string.IsNullOrEmpty(hwTonemapSuffix))
{ {
@@ -2764,7 +2859,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase)) if (hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
{ {
args += ",procamp_vaapi=b={2}:c={3}:extra_hw_frames=16"; args = "procamp_vaapi=b={2}:c={3}," + args + ":extra_hw_frames=32";
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
args, args,
@@ -2777,14 +2873,24 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
args += ":tonemap={2}:peak={3}:desat={4}"; args += ":tonemap={2}:peak={3}:desat={4}";
if (options.TonemappingParam != 0) if (string.Equals(options.TonemappingMode, "max", StringComparison.OrdinalIgnoreCase)
|| string.Equals(options.TonemappingMode, "rgb", StringComparison.OrdinalIgnoreCase))
{ {
args += ":param={5}"; if (_mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode)
{
args += ":tonemap_mode={5}";
}
} }
if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) if (options.TonemappingParam != 0)
{ {
args += ":range={6}"; args += ":param={6}";
}
if (string.Equals(options.TonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(options.TonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
{
args += ":range={7}";
} }
} }
@@ -2796,6 +2902,7 @@ namespace MediaBrowser.Controller.MediaEncoding
options.TonemappingAlgorithm, options.TonemappingAlgorithm,
options.TonemappingPeak, options.TonemappingPeak,
options.TonemappingDesat, options.TonemappingDesat,
options.TonemappingMode,
options.TonemappingParam, options.TonemappingParam,
options.TonemappingRange); options.TonemappingRange);
} }
@@ -2876,8 +2983,8 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else if (hasGraphicalSubs) else if (hasGraphicalSubs)
{ {
// [0:s]scale=expr // [0:s]scale=s=1280x720
var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3063,9 +3170,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3202,7 +3307,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory) // OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl. // prefer hwmap to hwdownload on opencl.
var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap"; var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
mainFilters.Add(hwTransferFilter); mainFilters.Add(hwTransferFilter);
mainFilters.Add("format=nv12"); mainFilters.Add("format=nv12");
} }
@@ -3265,9 +3370,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3441,7 +3544,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory) // OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl. // prefer hwmap to hwdownload on opencl.
// qsv hwmap is not fully implemented for the time being. // qsv hwmap is not fully implemented for the time being.
mainFilters.Add(isHwmapUsable ? "hwmap" : "hwdownload"); mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
mainFilters.Add("format=nv12"); mainFilters.Add("format=nv12");
} }
@@ -3513,9 +3616,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3601,6 +3702,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doTonemap ? string.Empty : "nv12"; var outFormat = doTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter(isVaapiDecoder ? "vaapi" : "qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); var hwScaleFilter = GetHwScaleFilter(isVaapiDecoder ? "vaapi" : "qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
// allocate extra pool sizes for vaapi vpp
if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
{
hwScaleFilter += ":extra_hw_frames=24";
}
// hw scale // hw scale
mainFilters.Add(hwScaleFilter); mainFilters.Add(hwScaleFilter);
} }
@@ -3647,7 +3755,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory) // OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl/vaapi. // prefer hwmap to hwdownload on opencl/vaapi.
// qsv hwmap is not fully implemented for the time being. // qsv hwmap is not fully implemented for the time being.
mainFilters.Add(isHwmapUsable ? "hwmap" : "hwdownload"); mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
mainFilters.Add("format=nv12"); mainFilters.Add("format=nv12");
} }
@@ -3724,9 +3832,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
} }
@@ -3866,6 +3972,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doTonemap ? string.Empty : "nv12"; var outFormat = doTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
// allocate extra pool sizes for vaapi vpp
if (!string.IsNullOrEmpty(hwScaleFilter))
{
hwScaleFilter += ":extra_hw_frames=24";
}
// hw scale // hw scale
mainFilters.Add(hwScaleFilter); mainFilters.Add(hwScaleFilter);
} }
@@ -3907,7 +4020,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory) // OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl/vaapi. // prefer hwmap to hwdownload on opencl/vaapi.
mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap"); mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
mainFilters.Add("format=nv12"); mainFilters.Add("format=nv12");
} }
@@ -3973,9 +4086,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4062,6 +4173,13 @@ namespace MediaBrowser.Controller.MediaEncoding
outFormat = doOclTonemap ? string.Empty : "nv12"; outFormat = doOclTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
// allocate extra pool sizes for vaapi vpp
if (!string.IsNullOrEmpty(hwScaleFilter))
{
hwScaleFilter += ":extra_hw_frames=24";
}
// hw scale // hw scale
mainFilters.Add(hwScaleFilter); mainFilters.Add(hwScaleFilter);
} }
@@ -4150,9 +4268,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4368,7 +4484,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
// HWA decoders can handle both video files and video folders. // HWA decoders can handle both video files and video folders.
var videoType = mediaSource.VideoType; var videoType = state.VideoType;
if (videoType != VideoType.VideoFile if (videoType != VideoType.VideoFile
&& videoType != VideoType.Iso && videoType != VideoType.Iso
&& videoType != VideoType.Dvd && videoType != VideoType.Dvd
@@ -4509,8 +4625,18 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox"); var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase); var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
var ffmpegVersion = _mediaEncoder.EncoderVersion;
// Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used. // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
var isAv1 = string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase); var isAv1 = ffmpegVersion < _minFFmpegImplictHwaccel
&& string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
// Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
// Disable the extra internal copy in nvdec. We already handle it in filter chain.
var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
if (bitDepth == 10 && isCodecAvailable) if (bitDepth == 10 && isCodecAvailable)
{ {
@@ -4536,14 +4662,14 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (isVaapiSupported && isCodecAvailable) if (isVaapiSupported && isCodecAvailable)
{ {
return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty)
+ (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
} }
if (isD3d11Supported && isCodecAvailable) if (isD3d11Supported && isCodecAvailable)
{ {
// set -threads 3 to intel d3d11va decoder explicitly. Lower threads may result in dead lock. return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty)
// on newer devices such as Xe, the larger the init_pool_size, the longer the initialization time for opencl to derive from d3d11. + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" + (isAv1 ? " -c:v av1" : string.Empty);
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
} }
} }
else else
@@ -4563,7 +4689,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (options.EnableEnhancedNvdecDecoder) if (options.EnableEnhancedNvdecDecoder)
{ {
// set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support. // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty); return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty)
+ (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty);
} }
else else
{ {
@@ -4578,7 +4705,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (isD3d11Supported && isCodecAvailable) if (isD3d11Supported && isCodecAvailable)
{ {
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty)
+ (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
} }
} }
@@ -4587,9 +4715,11 @@ namespace MediaBrowser.Controller.MediaEncoding
&& isVaapiSupported && isVaapiSupported
&& isCodecAvailable) && isCodecAvailable)
{ {
return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty)
+ (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
} }
// Apple videotoolbox
if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase) if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)
&& isVideotoolboxSupported && isVideotoolboxSupported
&& isCodecAvailable) && isCodecAvailable)
@@ -4977,14 +5107,14 @@ namespace MediaBrowser.Controller.MediaEncoding
// The default value of -probesize is more than enough, so leave it as is. // The default value of -probesize is more than enough, so leave it as is.
var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty; var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration)) if (state.MediaSource.AnalyzeDurationMs > 0)
{
analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
}
else if (state.MediaSource.AnalyzeDurationMs.HasValue)
{ {
analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToString(CultureInfo.InvariantCulture); analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToString(CultureInfo.InvariantCulture);
} }
else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
{
analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
}
if (!string.IsNullOrEmpty(analyzeDurationArgument)) if (!string.IsNullOrEmpty(analyzeDurationArgument))
{ {
@@ -5205,15 +5335,23 @@ namespace MediaBrowser.Controller.MediaEncoding
return; return;
} }
var inputChannels = audioStream == null ? 6 : audioStream.Channels ?? 6; var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
var shiftAudioCodecs = new List<string>();
if (inputChannels >= 6) if (inputChannels >= 6)
{ {
return; // DTS and TrueHD are not supported by HLS
// Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
shiftAudioCodecs.Add("dca");
shiftAudioCodecs.Add("truehd");
}
else
{
// Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
// Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
shiftAudioCodecs.Add("ac3");
shiftAudioCodecs.Add("eac3");
} }
// Transcoding to 2ch ac3 almost always causes a playback failure
// Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used
var shiftAudioCodecs = new[] { "ac3", "eac3" };
if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase))) if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
{ {
return; return;
@@ -5390,7 +5528,9 @@ namespace MediaBrowser.Controller.MediaEncoding
// video processing filters. // video processing filters.
var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec); var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
args += videoProcessParam; var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
args = negativeMapArgs + args + videoProcessParam;
hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase); hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
@@ -5455,7 +5595,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var bitrate = state.OutputAudioBitrate; var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue) if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
{ {
args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
} }
@@ -5475,8 +5615,10 @@ namespace MediaBrowser.Controller.MediaEncoding
var audioTranscodeParams = new List<string>(); var audioTranscodeParams = new List<string>();
var bitrate = state.OutputAudioBitrate; var bitrate = state.OutputAudioBitrate;
var channels = state.OutputAudioChannels;
var outputCodec = state.OutputAudioCodec;
if (bitrate.HasValue) if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
{ {
audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture)); audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
} }
@@ -5486,7 +5628,12 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
} }
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) if (!string.IsNullOrEmpty(outputCodec))
{
audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
}
if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
{ {
// opus only supports specific sampling rates // opus only supports specific sampling rates
var sampleRate = state.OutputAudioSampleRate; var sampleRate = state.OutputAudioSampleRate;
@@ -5536,7 +5683,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return index; return index;
} }
if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal)) if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
{ {
index++; index++;
} }

View File

@@ -37,6 +37,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>The version of encoder.</returns> /// <returns>The version of encoder.</returns>
Version EncoderVersion { get; } Version EncoderVersion { get; }
/// <summary>
/// Whether p key pausing is supported.
/// </summary>
/// <value><c>true</c> if p key pausing is supported, <c>false</c> otherwise.</value>
bool IsPkeyPauseSupported { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver). /// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver).
/// </summary> /// </summary>

View File

@@ -6,7 +6,8 @@ using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {
@@ -37,11 +38,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary> /// <summary>
/// Gets the subtitle language encoding parameter. /// Gets the subtitle language encoding parameter.
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="language">The language.</param> /// <param name="language">The language.</param>
/// <param name="protocol">The protocol.</param> /// <param name="mediaSource">The media source.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken); Task<string> GetSubtitleFileCharacterSet(MediaStream subtitleStream, string language, MediaSourceInfo mediaSource, CancellationToken cancellationToken);
} }
} }

View File

@@ -14,6 +14,7 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@@ -317,10 +318,10 @@ namespace MediaBrowser.MediaEncoding.Attachments
var processArgs = string.Format( var processArgs = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"-dump_attachment:{1} {2} -i {0} -t 0 -f null null", "-dump_attachment:{1} \"{2}\" -i {0} -t 0 -f null null",
inputPath, inputPath,
attachmentStreamIndex, attachmentStreamIndex,
outputPath); EncodingUtils.NormalizePath(outputPath));
int exitCode; int exitCode;

View File

@@ -25,11 +25,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
"mpeg2video", "mpeg2video",
"mpeg4", "mpeg4",
"msmpeg4", "msmpeg4",
"dts", "dca",
"ac3", "ac3",
"aac", "aac",
"mp3", "mp3",
"flac", "flac",
"truehd",
"h264_qsv", "h264_qsv",
"hevc_qsv", "hevc_qsv",
"mpeg2_qsv", "mpeg2_qsv",
@@ -58,10 +59,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
"aac", "aac",
"libfdk_aac", "libfdk_aac",
"ac3", "ac3",
"dca",
"libmp3lame", "libmp3lame",
"libopus", "libopus",
"libvorbis", "libvorbis",
"flac", "flac",
"truehd",
"srt", "srt",
"h264_amf", "h264_amf",
"hevc_amf", "hevc_amf",
@@ -153,7 +156,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output; string output;
try try
{ {
output = GetProcessOutput(_encoderPath, "-version", false); output = GetProcessOutput(_encoderPath, "-version", false, null);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -234,7 +237,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output; string output;
try try
{ {
output = GetProcessOutput(_encoderPath, "-version", false); output = GetProcessOutput(_encoderPath, "-version", false, null);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -341,7 +344,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try try
{ {
var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true); var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true, null);
return output.Contains(driverName, StringComparison.Ordinal); return output.Contains(driverName, StringComparison.Ordinal);
} }
catch (Exception ex) catch (Exception ex)
@@ -356,7 +359,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string? output = null; string? output = null;
try try
{ {
output = GetProcessOutput(_encoderPath, "-hwaccels", false); output = GetProcessOutput(_encoderPath, "-hwaccels", false, null);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -384,7 +387,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output; string output;
try try
{ {
output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false); output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false, null);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -402,13 +405,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false; return false;
} }
public bool CheckSupportedRuntimeKey(string keyDesc)
{
if (string.IsNullOrEmpty(keyDesc))
{
return false;
}
string output;
try
{
output = GetProcessOutput(_encoderPath, "-hide_banner -f lavfi -i nullsrc=s=1x1:d=500 -f null -", true, "?");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking supported runtime key");
return false;
}
return output.Contains(keyDesc, StringComparison.Ordinal);
}
private IEnumerable<string> GetCodecs(Codec codec) private IEnumerable<string> GetCodecs(Codec codec)
{ {
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
string output; string output;
try try
{ {
output = GetProcessOutput(_encoderPath, "-" + codecstr, false); output = GetProcessOutput(_encoderPath, "-" + codecstr, false, null);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -439,7 +463,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output; string output;
try try
{ {
output = GetProcessOutput(_encoderPath, "-filters", false); output = GetProcessOutput(_encoderPath, "-filters", false, null);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -477,7 +501,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return dict; return dict;
} }
private string GetProcessOutput(string path, string arguments, bool readStdErr) private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey)
{ {
using (var process = new Process() using (var process = new Process()
{ {
@@ -487,6 +511,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
UseShellExecute = false, UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden, WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false, ErrorDialog = false,
RedirectStandardInput = !string.IsNullOrEmpty(testKey),
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = true RedirectStandardError = true
} }
@@ -496,6 +521,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
process.Start(); process.Start();
if (!string.IsNullOrEmpty(testKey))
{
process.StandardInput.Write(testKey);
}
return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd(); return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd();
} }
} }

View File

@@ -56,7 +56,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
private static string NormalizePath(string path) public static string NormalizePath(string path)
{ {
// Quotes are valid path characters in linux and they need to be escaped here with a leading \ // Quotes are valid path characters in linux and they need to be escaped here with a leading \
return path.Replace("\"", "\\\"", StringComparison.Ordinal); return path.Replace("\"", "\\\"", StringComparison.Ordinal);

View File

@@ -67,6 +67,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
private List<string> _filters = new List<string>(); private List<string> _filters = new List<string>();
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>(); private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
private bool _isPkeyPauseSupported = false;
private bool _isVaapiDeviceAmd = false; private bool _isVaapiDeviceAmd = false;
private bool _isVaapiDeviceInteliHD = false; private bool _isVaapiDeviceInteliHD = false;
private bool _isVaapiDeviceInteli965 = false; private bool _isVaapiDeviceInteli965 = false;
@@ -100,6 +102,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
public Version EncoderVersion => _ffmpegVersion; public Version EncoderVersion => _ffmpegVersion;
public bool IsPkeyPauseSupported => _isPkeyPauseSupported;
public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd; public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD; public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
@@ -154,6 +158,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
_threads = EncodingHelper.GetNumberOfThreads(null, options, null); _threads = EncodingHelper.GetNumberOfThreads(null, options, null);
_isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding");
// Check the Vaapi device vendor // Check the Vaapi device vendor
if (OperatingSystem.IsLinux() if (OperatingSystem.IsLinux()
&& SupportsHwaccel("vaapi") && SupportsHwaccel("vaapi")
@@ -376,15 +382,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
string analyzeDuration = string.Empty; string analyzeDuration = string.Empty;
string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty; string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration)) if (request.MediaSource.AnalyzeDurationMs > 0)
{
analyzeDuration = "-analyzeduration " + ffmpegAnalyzeDuration;
}
else if (request.MediaSource.AnalyzeDurationMs > 0)
{ {
analyzeDuration = "-analyzeduration " + analyzeDuration = "-analyzeduration " +
(request.MediaSource.AnalyzeDurationMs * 1000).ToString(); (request.MediaSource.AnalyzeDurationMs * 1000).ToString();
} }
else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
{
analyzeDuration = "-analyzeduration " + ffmpegAnalyzeDuration;
}
var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File; var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;

View File

@@ -751,9 +751,11 @@ namespace MediaBrowser.MediaEncoding.Probing
} }
if (isAudio if (isAudio
|| string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase) && (string.Equals(stream.Codec, "bmp", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)) || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Codec, "webp", StringComparison.OrdinalIgnoreCase)))
{ {
stream.Type = MediaStreamType.EmbeddedImage; stream.Type = MediaStreamType.EmbeddedImage;
} }
@@ -863,8 +865,13 @@ namespace MediaBrowser.MediaEncoding.Probing
} }
} }
} }
else if (string.Equals(streamInfo.CodecType, "data", StringComparison.OrdinalIgnoreCase))
{
stream.Type = MediaStreamType.Data;
}
else else
{ {
_logger.LogError("Codec Type {CodecType} unknown. The stream (index: {Index}) will be ignored. Warning: Subsequential streams will have a wrong stream specifier!", streamInfo.CodecType, streamInfo.Index);
return null; return null;
} }

View File

@@ -0,0 +1,19 @@
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// Advanced SubStation Alpha subtitle parser.
/// </summary>
public class AssParser : SubtitleEditParser<AdvancedSubStationAlpha>
{
/// <summary>
/// Initializes a new instance of the <see cref="AssParser"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
public AssParser(ILogger logger) : base(logger)
{
}
}
}

View File

@@ -1,6 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.IO; using System.IO;
using System.Threading;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles namespace MediaBrowser.MediaEncoding.Subtitles
@@ -11,15 +12,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// Parses the specified stream. /// Parses the specified stream.
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="fileExtension">The file extension.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>SubtitleTrackInfo.</returns> /// <returns>SubtitleTrackInfo.</returns>
SubtitleTrackInfo Parse(Stream stream, string fileExtension); SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken);
/// <summary>
/// Determines whether the file extension is supported by the parser.
/// </summary>
/// <param name="fileExtension">The file extension.</param>
/// <returns>A value indicating whether the file extension is supported.</returns>
bool SupportsFileExtension(string fileExtension);
} }
} }

View File

@@ -0,0 +1,19 @@
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// SubRip subtitle parser.
/// </summary>
public class SrtParser : SubtitleEditParser<SubRip>
{
/// <summary>
/// Initializes a new instance of the <see cref="SrtParser"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
public SrtParser(ILogger logger) : base(logger)
{
}
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// SubStation Alpha subtitle parser.
/// </summary>
public class SsaParser : SubtitleEditParser<SubStationAlpha>
{
/// <summary>
/// Initializes a new instance of the <see cref="SsaParser"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
public SsaParser(ILogger logger) : base(logger)
{
}
}
}

View File

@@ -1,14 +1,12 @@
using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Threading;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.Common; using Nikse.SubtitleEdit.Core.Common;
using Nikse.SubtitleEdit.Core.SubtitleFormats; using ILogger = Microsoft.Extensions.Logging.ILogger;
using SubtitleFormat = Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat; using SubtitleFormat = Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat;
namespace MediaBrowser.MediaEncoding.Subtitles namespace MediaBrowser.MediaEncoding.Subtitles
@@ -16,57 +14,31 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary> /// <summary>
/// SubStation Alpha subtitle parser. /// SubStation Alpha subtitle parser.
/// </summary> /// </summary>
public class SubtitleEditParser : ISubtitleParser /// <typeparam name="T">The <see cref="SubtitleFormat" />.</typeparam>
public abstract class SubtitleEditParser<T> : ISubtitleParser
where T : SubtitleFormat, new()
{ {
private readonly ILogger<SubtitleEditParser> _logger; private readonly ILogger _logger;
private readonly Dictionary<string, SubtitleFormat[]> _subtitleFormats;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SubtitleEditParser"/> class. /// Initializes a new instance of the <see cref="SubtitleEditParser{T}"/> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
public SubtitleEditParser(ILogger<SubtitleEditParser> logger) protected SubtitleEditParser(ILogger logger)
{ {
_logger = logger; _logger = logger;
_subtitleFormats = GetSubtitleFormats()
.Where(subtitleFormat => !string.IsNullOrEmpty(subtitleFormat.Extension))
.GroupBy(subtitleFormat => subtitleFormat.Extension.TrimStart('.'), StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.ToArray(), StringComparer.OrdinalIgnoreCase);
} }
/// <inheritdoc /> /// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, string fileExtension) public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
{ {
var subtitle = new Subtitle(); var subtitle = new Subtitle();
var subRip = new T();
var lines = stream.ReadAllLines().ToList(); var lines = stream.ReadAllLines().ToList();
subRip.LoadSubtitle(subtitle, lines, "untitled");
if (!_subtitleFormats.TryGetValue(fileExtension, out var subtitleFormats)) if (subRip.ErrorCount > 0)
{ {
throw new ArgumentException($"Unsupported file extension: {fileExtension}", nameof(fileExtension)); _logger.LogError("{ErrorCount} errors encountered while parsing subtitle", subRip.ErrorCount);
}
foreach (var subtitleFormat in subtitleFormats)
{
_logger.LogDebug(
"Trying to parse '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser",
fileExtension,
subtitleFormat.Name);
subtitleFormat.LoadSubtitle(subtitle, lines, fileExtension);
if (subtitleFormat.ErrorCount == 0)
{
break;
}
_logger.LogError(
"{ErrorCount} errors encountered while parsing '{FileExtension}' subtitle using the {SubtitleFormatParser} format parser",
subtitleFormat.ErrorCount,
fileExtension,
subtitleFormat.Name);
}
if (subtitle.Paragraphs.Count == 0)
{
throw new ArgumentException("Unsupported format: " + fileExtension);
} }
var trackInfo = new SubtitleTrackInfo(); var trackInfo = new SubtitleTrackInfo();
@@ -85,36 +57,5 @@ namespace MediaBrowser.MediaEncoding.Subtitles
trackInfo.TrackEvents = trackEvents; trackInfo.TrackEvents = trackEvents;
return trackInfo; return trackInfo;
} }
/// <inheritdoc />
public bool SupportsFileExtension(string fileExtension)
=> _subtitleFormats.ContainsKey(fileExtension);
private IEnumerable<SubtitleFormat> GetSubtitleFormats()
{
var subtitleFormats = new List<SubtitleFormat>();
var assembly = typeof(SubtitleFormat).Assembly;
foreach (var type in assembly.GetTypes())
{
if (!type.IsSubclassOf(typeof(SubtitleFormat)) || type.IsAbstract)
{
continue;
}
try
{
// It shouldn't be null, but the exception is caught if it is
var subtitleFormat = (SubtitleFormat)Activator.CreateInstance(type, true)!;
subtitleFormats.Add(subtitleFormat);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to create instance of the subtitle format {SubtitleFormatType}", type.Name);
}
}
return subtitleFormats;
}
} }
} }

View File

@@ -35,7 +35,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly ISubtitleParser _subtitleParser;
/// <summary> /// <summary>
/// The _semaphoreLocks. /// The _semaphoreLocks.
@@ -49,8 +48,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IFileSystem fileSystem, IFileSystem fileSystem,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager)
ISubtitleParser subtitleParser)
{ {
_logger = logger; _logger = logger;
_appPaths = appPaths; _appPaths = appPaths;
@@ -58,7 +56,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_subtitleParser = subtitleParser;
} }
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles"); private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
@@ -76,7 +73,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try try
{ {
var trackInfo = _subtitleParser.Parse(stream, inputFormat); var reader = GetReader(inputFormat);
var trackInfo = reader.Parse(stream, cancellationToken);
FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps); FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps);
@@ -235,21 +233,54 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec) var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
.TrimStart('.'); .TrimStart('.');
// Fallback to ffmpeg conversion if (!TryGetReader(currentFormat, out _))
if (!_subtitleParser.SupportsFileExtension(currentFormat))
{ {
// Convert // Convert
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt"); var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt");
await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, mediaSource, outputPath, cancellationToken).ConfigureAwait(false); await ConvertTextSubtitleToSrt(subtitleStream, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true); return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true);
} }
// It's possible that the subtitleStream and mediaSource don't share the same protocol (e.g. .STRM file with local subs) // It's possbile that the subtitleStream and mediaSource don't share the same protocol (e.g. .STRM file with local subs)
return new SubtitleInfo(subtitleStream.Path, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), currentFormat, true); return new SubtitleInfo(subtitleStream.Path, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), currentFormat, true);
} }
private bool TryGetReader(string format, [NotNullWhen(true)] out ISubtitleParser? value)
{
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
{
value = new SrtParser(_logger);
return true;
}
if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{
value = new SsaParser(_logger);
return true;
}
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{
value = new AssParser(_logger);
return true;
}
value = null;
return false;
}
private ISubtitleParser GetReader(string format)
{
if (TryGetReader(format, out var reader))
{
return reader;
}
throw new ArgumentException("Unsupported format: " + format);
}
private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value) private bool TryGetWriter(string format, [NotNullWhen(true)] out ISubtitleWriter? value)
{ {
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
@@ -320,13 +351,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary> /// <summary>
/// Converts the text subtitle to SRT. /// Converts the text subtitle to SRT.
/// </summary> /// </summary>
/// <param name="inputPath">The input path.</param> /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="language">The language.</param>
/// <param name="mediaSource">The input mediaSource.</param> /// <param name="mediaSource">The input mediaSource.</param>
/// <param name="outputPath">The output path.</param> /// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
private async Task ConvertTextSubtitleToSrt(string inputPath, string language, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken) private async Task ConvertTextSubtitleToSrt(MediaStream subtitleStream, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
{ {
var semaphore = GetLock(outputPath); var semaphore = GetLock(outputPath);
@@ -336,7 +366,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{ {
if (!File.Exists(outputPath)) if (!File.Exists(outputPath))
{ {
await ConvertTextSubtitleToSrtInternal(inputPath, language, mediaSource, outputPath, cancellationToken).ConfigureAwait(false); await ConvertTextSubtitleToSrtInternal(subtitleStream, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
} }
} }
finally finally
@@ -348,8 +378,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <summary> /// <summary>
/// Converts the text subtitle to SRT internal. /// Converts the text subtitle to SRT internal.
/// </summary> /// </summary>
/// <param name="inputPath">The input path.</param> /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="language">The language.</param>
/// <param name="mediaSource">The input mediaSource.</param> /// <param name="mediaSource">The input mediaSource.</param>
/// <param name="outputPath">The output path.</param> /// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
@@ -357,8 +386,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// The <c>inputPath</c> or <c>outputPath</c> is <c>null</c>. /// The <c>inputPath</c> or <c>outputPath</c> is <c>null</c>.
/// </exception> /// </exception>
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken) private async Task ConvertTextSubtitleToSrtInternal(MediaStream subtitleStream, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
{ {
var inputPath = subtitleStream.Path;
if (string.IsNullOrEmpty(inputPath)) if (string.IsNullOrEmpty(inputPath))
{ {
throw new ArgumentNullException(nameof(inputPath)); throw new ArgumentNullException(nameof(inputPath));
@@ -371,7 +401,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath))); Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)));
var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, mediaSource.Protocol, cancellationToken).ConfigureAwait(false); var encodingParam = await GetSubtitleFileCharacterSet(subtitleStream, subtitleStream.Language, mediaSource, cancellationToken).ConfigureAwait(false);
// FFmpeg automatically convert character encoding when it is UTF-16 // FFmpeg automatically convert character encoding when it is UTF-16
// If we specify character encoding, it rejects with "do not specify a character encoding" and "Unable to recode subtitle event" // If we specify character encoding, it rejects with "do not specify a character encoding" and "Unable to recode subtitle event"
@@ -389,18 +419,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int exitCode; int exitCode;
using (var process = new Process using (var process = new Process
{
StartInfo = new ProcessStartInfo
{ {
StartInfo = new ProcessStartInfo CreateNoWindow = true,
{ UseShellExecute = false,
CreateNoWindow = true, FileName = _mediaEncoder.EncoderPath,
UseShellExecute = false, Arguments = string.Format(CultureInfo.InvariantCulture, "{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
FileName = _mediaEncoder.EncoderPath, WindowStyle = ProcessWindowStyle.Hidden,
Arguments = string.Format(CultureInfo.InvariantCulture, "{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath), ErrorDialog = false
WindowStyle = ProcessWindowStyle.Hidden, },
ErrorDialog = false EnableRaisingEvents = true
}, })
EnableRaisingEvents = true
})
{ {
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -540,7 +570,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var processArgs = string.Format( var processArgs = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", "-i {0} -copyts -map 0:{1} -an -vn -c:s {2} \"{3}\"",
inputPath, inputPath,
subtitleStreamIndex, subtitleStreamIndex,
outputCodec, outputCodec,
@@ -549,18 +579,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int exitCode; int exitCode;
using (var process = new Process using (var process = new Process
{
StartInfo = new ProcessStartInfo
{ {
StartInfo = new ProcessStartInfo CreateNoWindow = true,
{ UseShellExecute = false,
CreateNoWindow = true, FileName = _mediaEncoder.EncoderPath,
UseShellExecute = false, Arguments = processArgs,
FileName = _mediaEncoder.EncoderPath, WindowStyle = ProcessWindowStyle.Hidden,
Arguments = processArgs, ErrorDialog = false
WindowStyle = ProcessWindowStyle.Hidden, },
ErrorDialog = false EnableRaisingEvents = true
}, })
EnableRaisingEvents = true
})
{ {
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -575,7 +605,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw; throw;
} }
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false); var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
if (!ranToCompletion) if (!ranToCompletion)
{ {
@@ -698,9 +728,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken) public async Task<string> GetSubtitleFileCharacterSet(MediaStream subtitleStream, string language, MediaSourceInfo mediaSource, CancellationToken cancellationToken)
{ {
using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false)) var subtitleCodec = subtitleStream.Codec;
var path = subtitleStream.Path;
if (path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
{
path = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + subtitleCodec);
await ExtractTextSubtitle(mediaSource, subtitleStream, subtitleCodec, path, cancellationToken)
.ConfigureAwait(false);
}
using (var stream = await GetStream(path, mediaSource.Protocol, cancellationToken).ConfigureAwait(false))
{ {
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName ?? string.Empty; var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName ?? string.Empty;
@@ -723,12 +763,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
switch (protocol) switch (protocol)
{ {
case MediaProtocol.Http: case MediaProtocol.Http:
{ {
using var response = await _httpClientFactory.CreateClient(NamedClient.Default) using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.GetAsync(new Uri(path), cancellationToken) .GetAsync(new Uri(path), cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
} }
case MediaProtocol.File: case MediaProtocol.File:
return AsyncFile.OpenRead(path); return AsyncFile.OpenRead(path);

View File

@@ -23,7 +23,7 @@ public class BrandingOptions
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to enable the splashscreen. /// Gets or sets a value indicating whether to enable the splashscreen.
/// </summary> /// </summary>
public bool SplashscreenEnabled { get; set; } = true; public bool SplashscreenEnabled { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets the splashscreen location on disk. /// Gets or sets the splashscreen location on disk.

View File

@@ -21,20 +21,21 @@ namespace MediaBrowser.Model.Configuration
EnableTonemapping = false; EnableTonemapping = false;
EnableVppTonemapping = false; EnableVppTonemapping = false;
TonemappingAlgorithm = "bt2390"; TonemappingAlgorithm = "bt2390";
TonemappingMode = "auto";
TonemappingRange = "auto"; TonemappingRange = "auto";
TonemappingDesat = 0; TonemappingDesat = 0;
TonemappingThreshold = 0.8;
TonemappingPeak = 100; TonemappingPeak = 100;
TonemappingParam = 0; TonemappingParam = 0;
VppTonemappingBrightness = 0; VppTonemappingBrightness = 16;
VppTonemappingContrast = 1.2; VppTonemappingContrast = 1;
H264Crf = 23; H264Crf = 23;
H265Crf = 28; H265Crf = 28;
DeinterlaceDoubleRate = false; DeinterlaceDoubleRate = false;
DeinterlaceMethod = "yadif"; DeinterlaceMethod = "yadif";
EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true; EnableDecodingColorDepth10Vp9 = true;
EnableEnhancedNvdecDecoder = false; // Enhanced Nvdec or system native decoder is required for DoVi to SDR tone-mapping.
EnableEnhancedNvdecDecoder = true;
PreferSystemNativeHwDecoder = true; PreferSystemNativeHwDecoder = true;
EnableIntelLowPowerH264HwEncoder = false; EnableIntelLowPowerH264HwEncoder = false;
EnableIntelLowPowerHevcHwEncoder = false; EnableIntelLowPowerHevcHwEncoder = false;
@@ -81,12 +82,12 @@ namespace MediaBrowser.Model.Configuration
public string TonemappingAlgorithm { get; set; } public string TonemappingAlgorithm { get; set; }
public string TonemappingMode { get; set; }
public string TonemappingRange { get; set; } public string TonemappingRange { get; set; }
public double TonemappingDesat { get; set; } public double TonemappingDesat { get; set; }
public double TonemappingThreshold { get; set; }
public double TonemappingPeak { get; set; } public double TonemappingPeak { get; set; }
public double TonemappingParam { get; set; } public double TonemappingParam { get; set; }

View File

@@ -136,12 +136,26 @@ namespace MediaBrowser.Model.Dlna
return !condition.IsRequired; return !condition.IsRequired;
} }
if (int.TryParse(condition.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var expected)) var conditionType = condition.Condition;
if (condition.Condition == ProfileConditionType.EqualsAny)
{ {
switch (condition.Condition) foreach (var singleConditionString in condition.Value.AsSpan().Split('|'))
{
if (int.TryParse(singleConditionString, NumberStyles.Integer, CultureInfo.InvariantCulture, out int conditionValue)
&& conditionValue.Equals(currentValue))
{
return true;
}
}
return false;
}
if (int.TryParse(condition.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var expected))
{
switch (conditionType)
{ {
case ProfileConditionType.Equals: case ProfileConditionType.Equals:
case ProfileConditionType.EqualsAny:
return currentValue.Value.Equals(expected); return currentValue.Value.Equals(expected);
case ProfileConditionType.GreaterThanEqual: case ProfileConditionType.GreaterThanEqual:
return currentValue.Value >= expected; return currentValue.Value >= expected;
@@ -212,9 +226,24 @@ namespace MediaBrowser.Model.Dlna
return !condition.IsRequired; return !condition.IsRequired;
} }
if (double.TryParse(condition.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var expected)) var conditionType = condition.Condition;
if (condition.Condition == ProfileConditionType.EqualsAny)
{ {
switch (condition.Condition) foreach (var singleConditionString in condition.Value.AsSpan().Split('|'))
{
if (double.TryParse(singleConditionString, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double conditionValue)
&& conditionValue.Equals(currentValue))
{
return true;
}
}
return false;
}
if (double.TryParse(condition.Value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var expected))
{
switch (conditionType)
{ {
case ProfileConditionType.Equals: case ProfileConditionType.Equals:
return currentValue.Value.Equals(expected); return currentValue.Value.Equals(expected);

View File

@@ -23,6 +23,9 @@ namespace MediaBrowser.Model.Dlna
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport; private readonly ITranscoderSupport _transcoderSupport;
private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc" };
private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" };
private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" };
public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger) public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger)
{ {
@@ -436,9 +439,9 @@ namespace MediaBrowser.Model.Dlna
{ {
containerSupported = true; containerSupported = true;
videoSupported = videoStream != null && profile.SupportsVideoCodec(videoStream.Codec); videoSupported = videoStream == null || profile.SupportsVideoCodec(videoStream.Codec);
audioSupported = audioStream != null && profile.SupportsAudioCodec(audioStream.Codec); audioSupported = audioStream == null || profile.SupportsAudioCodec(audioStream.Codec);
if (videoSupported && audioSupported) if (videoSupported && audioSupported)
{ {
@@ -447,18 +450,17 @@ namespace MediaBrowser.Model.Dlna
} }
} }
var list = new List<TranscodeReason>();
if (!containerSupported) if (!containerSupported)
{ {
reasons |= TranscodeReason.ContainerNotSupported; reasons |= TranscodeReason.ContainerNotSupported;
} }
if (videoStream != null && !videoSupported) if (!videoSupported)
{ {
reasons |= TranscodeReason.VideoCodecNotSupported; reasons |= TranscodeReason.VideoCodecNotSupported;
} }
if (audioStream != null && !audioSupported) if (!audioSupported)
{ {
reasons |= TranscodeReason.AudioCodecNotSupported; reasons |= TranscodeReason.AudioCodecNotSupported;
} }
@@ -590,21 +592,19 @@ namespace MediaBrowser.Model.Dlna
} }
// Collect candidate audio streams // Collect candidate audio streams
IEnumerable<MediaStream> candidateAudioStreams = audioStream == null ? Array.Empty<MediaStream>() : new[] { audioStream }; ICollection<MediaStream> candidateAudioStreams = audioStream == null ? Array.Empty<MediaStream>() : new[] { audioStream };
if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0) if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0)
{ {
if (audioStream?.IsDefault == true) if (audioStream?.IsDefault == true)
{ {
candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault); candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault).ToArray();
} }
else else
{ {
candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.Language == audioStream?.Language); candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.Language == audioStream?.Language).ToArray();
} }
} }
candidateAudioStreams = candidateAudioStreams.ToArray();
var videoStream = item.VideoStream; var videoStream = item.VideoStream;
var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay); var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
@@ -735,7 +735,7 @@ namespace MediaBrowser.Model.Dlna
if (options.AllowVideoStreamCopy) if (options.AllowVideoStreamCopy)
{ {
// prefer direct copy profile // prefer direct copy profile
float videoFramerate = videoStream == null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; float videoFramerate = videoStream?.AverageFrameRate ?? videoStream?.RealFrameRate ?? 0;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp; TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio); int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video); int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
@@ -746,7 +746,7 @@ namespace MediaBrowser.Model.Dlna
if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec)) if (ContainerProfile.ContainsContainer(videoCodecs, item.VideoStream?.Codec))
{ {
var videoCodec = transcodingProfile.VideoCodec; var videoCodec = videoStream?.Codec;
var container = transcodingProfile.Container; var container = transcodingProfile.Container;
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.Video &&
@@ -773,6 +773,13 @@ namespace MediaBrowser.Model.Dlna
{ {
// Prefer matching video codecs // Prefer matching video codecs
var videoCodecs = ContainerProfile.SplitValue(videoCodec); var videoCodecs = ContainerProfile.SplitValue(videoCodec);
// Enforce HLS video codec restrictions
if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray();
}
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null; var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
if (directVideoCodec != null) if (directVideoCodec != null)
{ {
@@ -808,6 +815,20 @@ namespace MediaBrowser.Model.Dlna
// Prefer matching audio codecs, could do better here // Prefer matching audio codecs, could do better here
var audioCodecs = ContainerProfile.SplitValue(audioCodec); var audioCodecs = ContainerProfile.SplitValue(audioCodec);
// Enforce HLS audio codec restrictions
if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase))
{
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToArray();
}
else
{
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToArray();
}
}
var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)); var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
playlistItem.AudioCodecs = audioCodecs; playlistItem.AudioCodecs = audioCodecs;
if (directAudioStream != null) if (directAudioStream != null)
@@ -853,7 +874,7 @@ namespace MediaBrowser.Model.Dlna
var appliedVideoConditions = options.Profile.CodecProfiles var appliedVideoConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Video && .Where(i => i.Type == CodecType.Video &&
i.ContainsAnyCodec(videoCodec, container) && i.ContainsAnyCodec(videoStream?.Codec, container) &&
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))); i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)));
var isFirstAppliedCodecProfile = true; var isFirstAppliedCodecProfile = true;
foreach (var i in appliedVideoConditions) foreach (var i in appliedVideoConditions)
@@ -885,7 +906,7 @@ namespace MediaBrowser.Model.Dlna
var appliedAudioConditions = options.Profile.CodecProfiles var appliedAudioConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.VideoAudio && .Where(i => i.Type == CodecType.VideoAudio &&
i.ContainsAnyCodec(audioCodec, container) && i.ContainsAnyCodec(audioStream?.Codec, container) &&
i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio))); i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)));
isFirstAppliedCodecProfile = true; isFirstAppliedCodecProfile = true;
foreach (var i in appliedAudioConditions) foreach (var i in appliedAudioConditions)
@@ -1060,7 +1081,7 @@ namespace MediaBrowser.Model.Dlna
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
MediaStream videoStream, MediaStream videoStream,
MediaStream audioStream, MediaStream audioStream,
IEnumerable<MediaStream> candidateAudioStreams, ICollection<MediaStream> candidateAudioStreams,
MediaStream subtitleStream, MediaStream subtitleStream,
bool isEligibleForDirectPlay, bool isEligibleForDirectPlay,
bool isEligibleForDirectStream) bool isEligibleForDirectStream)
@@ -1091,9 +1112,6 @@ namespace MediaBrowser.Model.Dlna
bool? isInterlaced = videoStream?.IsInterlaced; bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag; string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = videoStream?.IsAVC; bool? isAvc = videoStream?.IsAVC;
// Audio
var defaultLanguage = audioStream?.Language ?? string.Empty;
var defaultMarked = audioStream?.IsDefault ?? false;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp; TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
int? packetLength = videoStream?.PacketLength; int? packetLength = videoStream?.PacketLength;
@@ -1120,12 +1138,13 @@ namespace MediaBrowser.Model.Dlna
profile, profile,
"VideoCodecProfile", "VideoCodecProfile",
profile.CodecProfiles profile.CodecProfiles
.Where(codecProfile => codecProfile.Type == CodecType.Video && codecProfile.ContainsAnyCodec(videoStream?.Codec, container) && .Where(codecProfile => codecProfile.Type == CodecType.Video &&
codecProfile.ContainsAnyCodec(videoStream?.Codec, container) &&
!checkVideoConditions(codecProfile.ApplyConditions).Any()) !checkVideoConditions(codecProfile.ApplyConditions).Any())
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions))); .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
// Check audiocandidates profile conditions // Check audiocandidates profile conditions
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked)); var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream));
TranscodeReason subtitleProfileReasons = 0; TranscodeReason subtitleProfileReasons = 0;
if (subtitleStream != null) if (subtitleStream != null)
@@ -1150,7 +1169,6 @@ namespace MediaBrowser.Model.Dlna
var reason = a & flag; var reason = a & flag;
if (reason != 0) if (reason != 0)
{ {
a = reason;
return index; return index;
} }
@@ -1160,6 +1178,8 @@ namespace MediaBrowser.Model.Dlna
return index; return index;
}; };
var containerSupported = false;
// Check DirectPlay profiles to see if it can be direct played // Check DirectPlay profiles to see if it can be direct played
var analyzedProfiles = profile.DirectPlayProfiles var analyzedProfiles = profile.DirectPlayProfiles
.Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video) .Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video)
@@ -1173,6 +1193,10 @@ namespace MediaBrowser.Model.Dlna
{ {
directPlayProfileReasons |= TranscodeReason.ContainerNotSupported; directPlayProfileReasons |= TranscodeReason.ContainerNotSupported;
} }
else
{
containerSupported = true;
}
// Check video codec // Check video codec
string videoCodec = videoStream?.Codec; string videoCodec = videoStream?.Codec;
@@ -1182,14 +1206,18 @@ namespace MediaBrowser.Model.Dlna
} }
// Check audio codec // Check audio codec
var selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.SupportsAudioCodec(audioStream.Codec)); MediaStream selectedAudioStream = null;
if (selectedAudioStream == null) if (candidateAudioStreams.Any())
{ {
directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported; selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.SupportsAudioCodec(audioStream.Codec));
} if (selectedAudioStream == null)
else {
{ directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported;
audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream); }
else
{
audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
}
} }
var failureReasons = directPlayProfileReasons | containerProfileReasons | subtitleProfileReasons; var failureReasons = directPlayProfileReasons | containerProfileReasons | subtitleProfileReasons;
@@ -1211,7 +1239,7 @@ namespace MediaBrowser.Model.Dlna
{ {
playMethod = PlayMethod.DirectPlay; playMethod = PlayMethod.DirectPlay;
} }
else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream && directPlayProfile != null) else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream)
{ {
playMethod = PlayMethod.DirectStream; playMethod = PlayMethod.DirectStream;
} }
@@ -1233,7 +1261,10 @@ namespace MediaBrowser.Model.Dlna
return profileMatch; return profileMatch;
} }
var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason; var failureReasons = analyzedProfiles[false]
.Select(analysis => analysis.Result)
.Where(result => !containerSupported || (result.TranscodeReason & TranscodeReason.ContainerNotSupported) == 0)
.FirstOrDefault().TranscodeReason;
if (failureReasons == 0) if (failureReasons == 0)
{ {
failureReasons = TranscodeReason.DirectPlayError; failureReasons = TranscodeReason.DirectPlayError;
@@ -1242,10 +1273,10 @@ namespace MediaBrowser.Model.Dlna
return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons); return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons);
} }
private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, string language, bool isDefault) private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream)
{ {
var profile = options.Profile; var profile = options.Profile;
var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, !audioStream.IsDefault); var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream));
var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions); var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions);
if (audioStream?.IsExternal == true) if (audioStream?.IsExternal == true)
@@ -1529,7 +1560,8 @@ namespace MediaBrowser.Model.Dlna
bool? isSecondaryAudio) bool? isSecondaryAudio)
{ {
return codecProfiles return codecProfiles
.Where(profile => profile.Type == CodecType.VideoAudio && profile.ContainsAnyCodec(codec, container) && .Where(profile => profile.Type == CodecType.VideoAudio &&
profile.ContainsAnyCodec(codec, container) &&
profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio))) profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)))
.SelectMany(profile => profile.Conditions) .SelectMany(profile => profile.Conditions)
.Where(condition => !ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)); .Where(condition => !ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio));
@@ -1546,7 +1578,8 @@ namespace MediaBrowser.Model.Dlna
bool checkConditions) bool checkConditions)
{ {
var conditions = codecProfiles var conditions = codecProfiles
.Where(profile => profile.Type == CodecType.Audio && profile.ContainsAnyCodec(codec, container) && .Where(profile => profile.Type == CodecType.Audio &&
profile.ContainsAnyCodec(codec, container) &&
profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth))) profile.ApplyConditions.All(applyCondition => ConditionProcessor.IsAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth)))
.SelectMany(profile => profile.Conditions); .SelectMany(profile => profile.Conditions);

View File

@@ -230,19 +230,15 @@ namespace MediaBrowser.Model.Dto
public bool? IsSecondaryAudio(MediaStream stream) public bool? IsSecondaryAudio(MediaStream stream)
{ {
// Look for the first audio track marked as default if (stream.IsExternal)
foreach (var currentStream in MediaStreams)
{ {
if (currentStream.Type == MediaStreamType.Audio && currentStream.IsDefault) return false;
{
return currentStream.Index != stream.Index;
}
} }
// Look for the first audio track // Look for the first audio track
foreach (var currentStream in MediaStreams) foreach (var currentStream in MediaStreams)
{ {
if (currentStream.Type == MediaStreamType.Audio) if (currentStream.Type == MediaStreamType.Audio && !currentStream.IsExternal)
{ {
return currentStream.Index != stream.Index; return currentStream.Index != stream.Index;
} }

View File

@@ -594,6 +594,10 @@ namespace MediaBrowser.Model.Entities
<= 426 when Height <= 240 => IsInterlaced ? "240i" : "240p", <= 426 when Height <= 240 => IsInterlaced ? "240i" : "240p",
// 640x360 (16:9 square pixel format) // 640x360 (16:9 square pixel format)
<= 640 when Height <= 360 => IsInterlaced ? "360i" : "360p", <= 640 when Height <= 360 => IsInterlaced ? "360i" : "360p",
// 682x384 (16:9 square pixel format)
<= 682 when Height <= 384 => IsInterlaced ? "384i" : "384p",
// 720x404 (16:9 square pixel format)
<= 720 when Height <= 404 => IsInterlaced ? "404i" : "404p",
// 854x480 (16:9 square pixel format) // 854x480 (16:9 square pixel format)
<= 854 when Height <= 480 => IsInterlaced ? "480i" : "480p", <= 854 when Height <= 480 => IsInterlaced ? "480i" : "480p",
// 960x544 (16:9 square pixel format) // 960x544 (16:9 square pixel format)

View File

@@ -23,6 +23,11 @@ namespace MediaBrowser.Model.Entities
/// <summary> /// <summary>
/// The embedded image. /// The embedded image.
/// </summary> /// </summary>
EmbeddedImage EmbeddedImage,
/// <summary>
/// The data.
/// </summary>
Data
} }
} }

View File

@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId> <PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.8.2</VersionPrefix> <VersionPrefix>10.8.13</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -34,13 +34,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
<PackageReference Include="MimeTypes" Version="2.4.0"> <PackageReference Include="MimeTypes" Version="2.4.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="System.Globalization" Version="4.3.0" /> <PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="6.0.5" /> <PackageReference Include="System.Text.Json" Version="6.0.6" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -181,6 +181,10 @@ namespace MediaBrowser.Providers.Manager
{ {
contentType = "image/png"; contentType = "image/png";
} }
else
{
throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode);
}
} }
// thetvdb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons... // thetvdb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons...

View File

@@ -176,9 +176,11 @@ namespace MediaBrowser.Providers.MediaInfo
var format = imageStream.Codec switch var format = imageStream.Codec switch
{ {
"bmp" => ImageFormat.Bmp,
"gif" => ImageFormat.Gif,
"mjpeg" => ImageFormat.Jpg, "mjpeg" => ImageFormat.Jpg,
"png" => ImageFormat.Png, "png" => ImageFormat.Png,
"gif" => ImageFormat.Gif, "webp" => ImageFormat.Webp,
_ => ImageFormat.Jpg _ => ImageFormat.Jpg
}; };

View File

@@ -408,10 +408,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
} }
} }
if (isEnglishRequested) item.Overview = result.Plot;
{
item.Overview = result.Plot;
}
if (!Plugin.Instance.Configuration.CastAndCrew) if (!Plugin.Instance.Configuration.CastAndCrew)
{ {

View File

@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("10.8.2")] [assembly: AssemblyVersion("10.8.13")]
[assembly: AssemblyFileVersion("10.8.2")] [assembly: AssemblyFileVersion("10.8.13")]

View File

@@ -1,7 +1,7 @@
--- ---
# We just wrap `build` so this is really it # We just wrap `build` so this is really it
name: "jellyfin" name: "jellyfin"
version: "10.8.2" version: "10.8.13"
packages: packages:
- debian.amd64 - debian.amd64
- debian.arm64 - debian.arm64

66
debian/changelog vendored
View File

@@ -1,3 +1,69 @@
jellyfin-server (10.8.13-1) unstable; urgency=medium
* New upstream version 10.8.13; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.13
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 28 Nov 2023 22:21:55 -0500
jellyfin-server (10.8.12-1) unstable; urgency=medium
* New upstream version 10.8.12; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.12
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 04 Nov 2023 14:42:37 -0400
jellyfin-server (10.8.11-1) unstable; urgency=medium
* New upstream version 10.8.11; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.11
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 23 Sep 2023 21:40:37 -0400
jellyfin-server (10.8.10-1) unstable; urgency=medium
* New upstream version 10.8.10; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.10
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 23 Apr 2023 11:02:05 -0400
jellyfin-server (10.8.9-1) unstable; urgency=medium
* New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.9
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 22 Jan 2023 14:09:37 -0500
jellyfin-server (10.8.8-1) unstable; urgency=medium
* New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.8
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 29 Nov 2022 13:42:47 -0500
jellyfin-server (10.8.7-1) unstable; urgency=medium
* New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.7
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 31 Oct 2022 23:07:06 -0400
jellyfin-server (10.8.6-1) unstable; urgency=medium
* New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.6
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 28 Oct 2022 22:41:01 -0400
jellyfin-server (10.8.5-1) unstable; urgency=medium
* New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.5
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 24 Sep 2022 22:01:56 -0400
jellyfin-server (10.8.4-1) unstable; urgency=medium
* New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.4
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 13 Aug 2022 21:51:45 -0400
jellyfin-server (10.8.3-1) unstable; urgency=medium
* New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 20:19:48 -0400
jellyfin-server (10.8.2-1) unstable; urgency=medium jellyfin-server (10.8.2-1) unstable; urgency=medium
* New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.2 * New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.2

View File

@@ -27,6 +27,9 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh"
# ffmpeg binary paths, overriding the system values # ffmpeg binary paths, overriding the system values
JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg" JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
# Disable glibc dynamic heap adjustment
MALLOC_TRIM_THRESHOLD_=131072
# [OPTIONAL] run Jellyfin as a headless service # [OPTIONAL] run Jellyfin as a headless service
#JELLYFIN_SERVICE_OPT="--service" #JELLYFIN_SERVICE_OPT="--service"

View File

@@ -8,7 +8,7 @@ EnvironmentFile = /etc/default/jellyfin
User = jellyfin User = jellyfin
Group = jellyfin Group = jellyfin
WorkingDirectory = /var/lib/jellyfin WorkingDirectory = /var/lib/jellyfin
ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} ${JELLYFIN_ADDITIONAL_OPTS} ExecStart = /usr/bin/jellyfin $JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT $JELLYFIN_ADDITIONAL_OPTS
Restart = on-failure Restart = on-failure
TimeoutSec = 15 TimeoutSec = 15
SuccessExitStatus=0 143 SuccessExitStatus=0 143

View File

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

10
debian/postinst vendored
View File

@@ -10,6 +10,8 @@ if [[ -f $DEFAULT_FILE ]]; then
fi fi
JELLYFIN_USER=${JELLYFIN_USER:-jellyfin} JELLYFIN_USER=${JELLYFIN_USER:-jellyfin}
RENDER_GROUP=${RENDER_GROUP:-render}
VIDEO_GROUP=${VIDEO_GROUP:-video}
# Data directories for program data (cache, db), configs, and logs # Data directories for program data (cache, db), configs, and logs
PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME}
@@ -28,6 +30,14 @@ case "$1" in
adduser --system --ingroup ${JELLYFIN_USER} --shell /bin/false ${JELLYFIN_USER} --no-create-home --home ${PROGRAMDATA} \ adduser --system --ingroup ${JELLYFIN_USER} --shell /bin/false ${JELLYFIN_USER} --no-create-home --home ${PROGRAMDATA} \
--gecos "Jellyfin default user" > /dev/null 2>&1 --gecos "Jellyfin default user" > /dev/null 2>&1
fi fi
# Add jellyfin to the render group for hwa
if [[ ! -z "$(getent group ${RENDER_GROUP})" ]]; then
usermod -aG ${RENDER_GROUP} ${JELLYFIN_USER} > /dev/null 2>&1
fi
# Add jellyfin to the video group for hwa
if [[ ! -z "$(getent group ${VIDEO_GROUP})" ]]; then
usermod -aG ${VIDEO_GROUP} ${JELLYFIN_USER} > /dev/null 2>&1
fi
# ensure $PROGRAMDATA exists # ensure $PROGRAMDATA exists
if [[ ! -d $PROGRAMDATA ]]; then if [[ ! -d $PROGRAMDATA ]]; then
mkdir $PROGRAMDATA mkdir $PROGRAMDATA

2
debian/rules vendored
View File

@@ -40,7 +40,7 @@ override_dh_clistrip:
override_dh_auto_build: override_dh_auto_build:
dotnet publish -maxcpucount:1 --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \ dotnet publish -maxcpucount:1 --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
"-p:DebugSymbols=false;DebugType=none" Jellyfin.Server -p:DebugSymbols=false -p:DebugType=none Jellyfin.Server
override_dh_auto_clean: override_dh_auto_clean:
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true

View File

@@ -13,7 +13,7 @@ RUN yum update -yq \
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
# Install DotNET SDK # Install DotNET SDK
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/0e83f50a-0619-45e6-8f16-dc4f41d1bb16/e0de908b2f070ef9e7e3b6ddea9d268c/dotnet-sdk-6.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -10,4 +10,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none

View File

@@ -10,4 +10,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none

View File

@@ -10,4 +10,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# because of changes in docker and systemd we need to not build in parallel at the moment # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none

View File

@@ -12,7 +12,7 @@ RUN dnf update -yq \
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
# Install DotNET SDK # Install DotNET SDK
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/0e83f50a-0619-45e6-8f16-dc4f41d1bb16/e0de908b2f070ef9e7e3b6ddea9d268c/dotnet-sdk-6.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -17,7 +17,7 @@ RUN apt-get update -yqq \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
# Install dotnet repository # Install dotnet repository
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/0e83f50a-0619-45e6-8f16-dc4f41d1bb16/e0de908b2f070ef9e7e3b6ddea9d268c/dotnet-sdk-6.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release mmv build-essential lsb-release
# Install dotnet repository # Install dotnet repository
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/0e83f50a-0619-45e6-8f16-dc4f41d1bb16/e0de908b2f070ef9e7e3b6ddea9d268c/dotnet-sdk-6.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release mmv build-essential lsb-release
# Install dotnet repository # Install dotnet repository
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/0e83f50a-0619-45e6-8f16-dc4f41d1bb16/e0de908b2f070ef9e7e3b6ddea9d268c/dotnet-sdk-6.0.302-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

View File

@@ -16,7 +16,7 @@ else
fi fi
# Build archives # Build archives
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true" dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version} tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
rm -rf dist/jellyfin-server_${version} rm -rf dist/jellyfin-server_${version}

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