Compare commits

..

458 Commits

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

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

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

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

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

Regex used:
```
await using \(.+\)
\W+await using
```
2022-06-04 14:34:59 +02:00
Cody Robibero
3721b5e985 Backport all dependency updates 2022-06-03 18:53:05 -06:00
Luke Brown
3e8fe1ce11 change placeholder text in LogDebug message 2022-05-31 21:49:01 -05:00
Cody Robibero
77c73e241f Merge pull request #7781 from crobibero/live-tv-infinite 2022-05-29 08:49:50 -06:00
Cody Robibero
9954cbd550 Merge pull request #7802 from jellyfin/external 2022-05-29 08:49:36 -06:00
Cody Robibero
d8f1a87c85 Update Emby.Server.Implementations/Session/SessionManager.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-05-27 16:25:31 -06:00
Cody Robibero
bf0a7c374c Close live stream on session end 2022-05-27 15:58:31 -06:00
Cody Robibero
8a6b26cd42 initial patch 2022-05-27 15:57:51 -06:00
Joe Rogers
b369194710 Fix tests 2022-05-27 06:12:16 +08:00
Nyanmisaka
293bcfb342 Exclude streams with mismatched types in external files 2022-05-27 06:12:10 +08:00
Cody Robibero
1c5571b24e Remove conditional skia inclusion (#7799) 2022-05-26 16:32:37 +02:00
Cody Robibero
b507d1a780 Merge pull request #7792 from crobibero/skia-native
Conditionally include platform specific Skia assets
2022-05-24 19:40:05 -06:00
Cody Robibero
d471be8d92 Merge pull request #7784 from crobibero/support-transcoding 2022-05-24 14:01:40 -06:00
Cody Robibero
3c5b4b9a27 Conditionally include platform specific Skia assets 2022-05-24 11:19:17 -06:00
Cody Robibero
c9491cf317 Merge pull request #7785 from dmitrylyzo/clear-transcodinginfo
Clear TranscodingInfo if play method changed
2022-05-24 07:59:13 -06:00
Dmitry Lyzo
c5dae18034 Apply suggestions from review 2022-05-23 07:49:54 -06:00
Dmitry Lyzo
ff4f624850 Clear TranscodingInfo if play method changed 2022-05-22 22:17:03 +03:00
Cody Robibero
d29a423475 Enable SupportsTranscoding if device has transcoding profiles 2022-05-22 11:06:38 -06:00
Cody Robibero
4c0510ee6d Merge pull request #7775 from crobibero/openapi-version 2022-05-21 08:59:24 -06:00
Cody Robibero
492c6bbd7e Merge pull request #7780 from 1337joe/fix-tv-guide-search-2 2022-05-21 08:59:13 -06:00
Joe Rogers
84878f537c Support searching with tv program filters 2022-05-21 13:11:26 +02:00
Cody Robibero
825e6460c9 Merge pull request #7774 from crobibero/api-authinfo 2022-05-20 20:49:25 -06:00
Cody Robibero
760b021032 Manually describe Version for openapi 2022-05-20 16:29:43 -06:00
Cody Robibero
a532a866e3 Populate authentication info with server details if using API key 2022-05-19 17:50:29 -06:00
Joshua M. Boniface
71bf567045 Merge pull request #7766 from crobibero/dotnet-6.0.5 2022-05-19 19:06:40 -04:00
Luke Brown
044ff0542b adding interface changes 2022-05-19 16:40:48 -05:00
Luke Brown
abfbd04782 Moved IAsyncDisposable and IDisposable from WebSocketConnection to it's interface. Added private variable and guard statement to avoid disposing of WebSocketConnection unnecessarily. 2022-05-18 21:04:22 -05:00
Luke Brown
8d0024ec49 change logging back to debug 2022-05-16 22:35:45 -05:00
Luke Brown
8f761a64f5 revert changes to SessionManager 2022-05-16 22:33:04 -05:00
Luke Brown
a64eebe79f changes to use dispose 2022-05-16 22:09:41 -05:00
Cody Robibero
a82e378da9 Update to dotnet 6.0.5 2022-05-16 14:14:26 -06:00
Joshua M. Boniface
884a59da07 Merge pull request #7724 from jtcasper/perms 2022-05-15 20:26:00 -04:00
Joshua M. Boniface
5a9e5e0d5d Merge pull request #7712 from jellyfin/fix-hevc-disable-option 2022-05-15 20:25:01 -04:00
Joshua M. Boniface
85cfea4c50 Merge branch 'release-10.8.z' into fix-hevc-disable-option 2022-05-15 20:24:52 -04:00
Joshua M. Boniface
3ea67374ae Merge pull request #7741 from LewkyB/fix-improperly-labeled-four-digit-episode-numbering
Fix to allow for episode numbering over 999 in certain scenarios
2022-05-15 20:23:59 -04:00
Joshua M. Boniface
5da4bcc782 Merge pull request #7736 from jellyfin/fix-swscale-pgs 2022-05-15 20:22:24 -04:00
Joshua M. Boniface
b46d61dfdf Merge pull request #7699 from Shadowghost/streambuilder-fix 2022-05-15 20:22:13 -04:00
Joshua M. Boniface
8119e4a573 Merge pull request #7749 from cvium/disable_auto_add_collection 2022-05-15 20:21:40 -04:00
Joshua M. Boniface
de3c68d474 Bump version to 10.8.0-beta3 2022-05-15 20:16:25 -04:00
cvium
7aa0db24d8 fix: disable "Automatically add to collection" by default 2022-05-15 14:53:40 +02:00
Luke Brown
be832e82cc change capture groups to take up to 4 digits and adding inline data to test 2022-05-11 22:22:52 -05:00
nyanmisaka
368d10d042 Fix the mismatched resolution in sw PGS burn-in 2022-05-11 19:18:31 +08:00
Nyanmisaka
9523a1682b Apply suggestions from code review
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2022-05-11 16:38:30 +08:00
Luke Brown
8bb4cd017c applied reviewer suggestions: removing unnecessary async and adding necessary async 2022-05-10 08:28:01 -05:00
Luke Brown
c5f09ab650 Fix broken CI by adding await 2022-05-09 23:59:31 -05:00
luke92brown
99df9c8fcd Replace redundant xml comments with inheritdoc. Make async changes to CloseSocket. 2022-05-09 23:44:37 -05:00
luke brown
4b563f4d7e Fix web sockets closing ungracefully 2022-05-09 23:44:24 -05:00
Jacob Casper
e67d8ce077 Don't let permission denied kill library scans
I have a single `./Movies` directory that contains symlinks to many
other directories, some of which are in shared paths. Other daemons
sometimes overwrite permissions on these paths and prevent Jellyfin from
being able to scan. Because this exception is unhandled within a LINQ
statement it can randomly kill the metadata refresh for my entire Movies
library.

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

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

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

View File

@@ -0,0 +1,93 @@
parameters:
- name: Packages
type: object
default: {}
- name: LinuxImage
type: string
default: "ubuntu-latest"
- name: DotNetSdkVersion
type: string
default: 6.0.x
jobs:
- job: CompatibilityCheck
displayName: Compatibility Check
dependsOn: Build
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
pool:
vmImage: "${{ parameters.LinuxImage }}"
strategy:
matrix:
${{ each Package in parameters.Packages }}:
${{ Package.key }}:
NugetPackageName: ${{ Package.value.NugetPackageName }}
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
maxParallel: 2
steps:
- checkout: none
- task: UseDotNet@2
displayName: "Update DotNet"
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2
displayName: 'Install ABI CompatibilityChecker Tool'
inputs:
command: custom
custom: tool
arguments: 'update compatibilitychecker -g'
- task: DownloadPipelineArtifact@2
displayName: 'Download New Assembly Build Artifact'
inputs:
source: 'current'
artifact: "$(NugetPackageName)"
path: "$(System.ArtifactsDirectory)/new-artifacts"
runVersion: "latest"
- task: CopyFiles@2
displayName: 'Copy New Assembly Build Artifact'
inputs:
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/new-release
cleanTargetFolder: true
overWrite: true
flattenFolders: true
- task: DownloadPipelineArtifact@2
displayName: 'Download Reference Assembly Build Artifact'
enabled: false
inputs:
source: "specific"
artifact: "$(NugetPackageName)"
path: "$(System.ArtifactsDirectory)/current-artifacts"
project: "$(System.TeamProjectId)"
pipeline: "$(System.DefinitionId)"
runVersion: "latestFromBranch"
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
- task: CopyFiles@2
displayName: 'Copy Reference Assembly Build Artifact'
enabled: false
inputs:
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/current-release
cleanTargetFolder: true
overWrite: true
flattenFolders: true
- task: DotNetCoreCLI@2
displayName: 'Execute ABI Compatibility Check Tool'
enabled: false
inputs:
command: custom
custom: compat
arguments: 'current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
workingDirectory: $(System.ArtifactsDirectory)

View File

@@ -0,0 +1,100 @@
parameters:
LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
DotNetSdkVersion: 6.0.x
jobs:
- job: Build
displayName: Build
strategy:
matrix:
Release:
BuildConfiguration: Release
Debug:
BuildConfiguration: Debug
pool:
vmImage: '${{ parameters.LinuxImage }}'
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: true
- task: DownloadPipelineArtifact@2
displayName: 'Download Web Branch'
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
inputs:
path: '$(Agent.TempDirectory)'
artifact: 'jellyfin-web-production'
source: 'specific'
project: 'jellyfin'
pipeline: 'Jellyfin Web'
runBranch: variables['Build.SourceBranch']
- task: DownloadPipelineArtifact@2
displayName: 'Download Web Target'
condition: eq(variables['Build.Reason'], 'PullRequest')
inputs:
path: '$(Agent.TempDirectory)'
artifact: 'jellyfin-web-production'
source: 'specific'
project: 'jellyfin'
pipeline: 'Jellyfin Web'
runBranch: variables['System.PullRequest.TargetBranch']
- task: ExtractFiles@1
displayName: 'Extract Web Client'
inputs:
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
cleanDestinationFolder: false
- task: UseDotNet@2
displayName: 'Update DotNet'
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2
displayName: 'Publish Server'
inputs:
command: publish
publishWebProjects: false
projects: '${{ parameters.RestoreBuildProjects }}'
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: false
- task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Naming'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
artifactName: 'Jellyfin.Naming'
- task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Controller'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
artifactName: 'Jellyfin.Controller'
- task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Model'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
artifactName: 'Jellyfin.Model'
- task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Common'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
artifactName: 'Jellyfin.Common'
- task: PublishPipelineArtifact@1
displayName: 'Publish Artifact Extensions'
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Jellyfin.Extensions.dll'
artifactName: 'Jellyfin.Extensions'

View File

@@ -0,0 +1,269 @@
jobs:
- job: BuildPackage
displayName: 'Build Packages'
strategy:
matrix:
CentOS.amd64:
BuildConfiguration: centos.amd64
Fedora.amd64:
BuildConfiguration: fedora.amd64
Debian.amd64:
BuildConfiguration: debian.amd64
Debian.arm64:
BuildConfiguration: debian.arm64
Debian.armhf:
BuildConfiguration: debian.armhf
Ubuntu.amd64:
BuildConfiguration: ubuntu.amd64
Ubuntu.arm64:
BuildConfiguration: ubuntu.arm64
Ubuntu.armhf:
BuildConfiguration: ubuntu.armhf
Linux.amd64:
BuildConfiguration: linux.amd64
Linux.amd64-musl:
BuildConfiguration: linux.amd64-musl
Linux.arm64:
BuildConfiguration: linux.arm64
Linux.armhf:
BuildConfiguration: linux.armhf
Windows.amd64:
BuildConfiguration: windows.amd64
MacOS:
BuildConfiguration: macos
Portable:
BuildConfiguration: portable
pool:
vmImage: 'ubuntu-latest'
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
displayName: 'Run Dockerfile (unstable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
displayName: 'Run Dockerfile (stable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
inputs:
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-server-$(BuildConfiguration)'
- task: SSH@0
displayName: 'Create target directory on repository server'
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
inputs:
sshEndpoint: repository
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
contents: '**'
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- job: OpenAPISpec
dependsOn: Test
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/master'),startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
displayName: 'Push OpenAPI Spec to repository'
pool:
vmImage: 'ubuntu-latest'
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- task: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec'
inputs:
source: 'current'
artifact: "OpenAPI Spec"
path: "$(System.ArtifactsDirectory)/openapispec"
runVersion: "latest"
- task: SSH@0
displayName: 'Create target directory on repository server'
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)'
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
inputs:
sshEndpoint: repository
sourceFolder: '$(System.ArtifactsDirectory)/openapispec'
contents: 'openapi.json'
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)'
- job: BuildDocker
displayName: 'Build Docker'
strategy:
matrix:
amd64:
BuildConfiguration: amd64
arm64:
BuildConfiguration: arm64
armhf:
BuildConfiguration: armhf
pool:
vmImage: 'ubuntu-latest'
variables:
- name: JellyfinVersion
value: 0.0.0
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- task: Docker@2
displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
repository: 'jellyfin/jellyfin-server'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
containerRegistry: Docker Hub
tags: |
unstable-$(Build.BuildNumber)-$(BuildConfiguration)
unstable-$(BuildConfiguration)
- task: Docker@2
displayName: 'Push Stable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs:
repository: 'jellyfin/jellyfin-server'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
containerRegistry: Docker Hub
tags: |
stable-$(Build.BuildNumber)-$(BuildConfiguration)
$(JellyfinVersion)-$(BuildConfiguration)
- job: CollectArtifacts
timeoutInMinutes: 20
displayName: 'Collect Artifacts'
continueOnError: true
dependsOn:
- BuildPackage
- BuildDocker
pool:
vmImage: 'ubuntu-latest'
steps:
- task: SSH@0
displayName: 'Update Unstable Repository'
continueOnError: true
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
sshEndpoint: repository
runOptions: 'commands'
commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable &
- task: SSH@0
displayName: 'Update Stable Repository'
continueOnError: true
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs:
sshEndpoint: repository
runOptions: 'commands'
commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) $(Build.SourceBranch) &
- job: PublishNuget
displayName: 'Publish NuGet packages'
pool:
vmImage: 'ubuntu-latest'
variables:
- name: JellyfinVersion
value: $[replace(variables['Build.SourceBranch'],'refs/tags/v','')]
steps:
- task: UseDotNet@2
displayName: 'Use .NET 6.0 sdk'
inputs:
packageType: 'sdk'
version: '6.0.x'
- task: DotNetCoreCLI@2
displayName: 'Build Stable Nuget packages'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs:
command: 'custom'
projects: |
Jellyfin.Data/Jellyfin.Data.csproj
MediaBrowser.Common/MediaBrowser.Common.csproj
MediaBrowser.Controller/MediaBrowser.Controller.csproj
MediaBrowser.Model/MediaBrowser.Model.csproj
Emby.Naming/Emby.Naming.csproj
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
custom: 'pack'
arguments: -o $(Build.ArtifactStagingDirectory) -p:Version=$(JellyfinVersion)
- task: DotNetCoreCLI@2
displayName: 'Build Unstable Nuget packages'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
command: 'custom'
projects: |
Jellyfin.Data/Jellyfin.Data.csproj
MediaBrowser.Common/MediaBrowser.Common.csproj
MediaBrowser.Controller/MediaBrowser.Controller.csproj
MediaBrowser.Model/MediaBrowser.Model.csproj
Emby.Naming/Emby.Naming.csproj
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
custom: 'pack'
arguments: '--version-suffix $(Build.BuildNumber) -o $(Build.ArtifactStagingDirectory) -p:Stability=Unstable'
- task: PublishBuildArtifacts@1
displayName: 'Publish Nuget packages'
inputs:
pathToPublish: $(Build.ArtifactStagingDirectory)
artifactName: Jellyfin Nuget Packages
- task: NuGetCommand@2
displayName: 'Push Nuget packages to stable feed'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
nuGetFeedType: 'external'
publishFeedCredentials: 'NugetOrg'
allowPackageConflicts: true # This ignores an error if the version already exists
- task: NuGetAuthenticate@0
displayName: 'Authenticate to unstable Nuget feed'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- task: NuGetCommand@2
displayName: 'Push Nuget packages to unstable feed'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg' # No symbols since Azure Artifact does not support it
nuGetFeedType: 'internal'
publishVstsFeed: '7cce6c46-d610-45e3-9fb7-65a6bfd1b671/a5746b79-f369-42db-93ff-59cd066f9327'
allowPackageConflicts: true # This ignores an error if the version already exists

View File

@@ -0,0 +1,98 @@
parameters:
- name: ImageNames
type: object
default:
Linux: "ubuntu-latest"
Windows: "windows-latest"
macOS: "macos-latest"
- name: TestProjects
type: string
default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion
type: string
default: 6.0.x
jobs:
- job: Test
displayName: Test
strategy:
matrix:
${{ each imageName in parameters.ImageNames }}:
${{ imageName.key }}:
ImageName: ${{ imageName.value }}
pool:
vmImage: "$(ImageName)"
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: false
# This is required for the SonarCloud analyzer
- task: UseDotNet@2
displayName: "Install .NET SDK 5.x"
condition: eq(variables['ImageName'], 'ubuntu-latest')
inputs:
packageType: sdk
version: '5.x'
- task: UseDotNet@2
displayName: "Update DotNet"
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: SonarCloudPrepare@1
displayName: 'Prepare analysis on SonarCloud'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
inputs:
SonarCloud: 'Sonarcloud for Jellyfin'
organization: 'jellyfin'
projectKey: 'jellyfin_jellyfin'
- task: DotNetCoreCLI@2
displayName: 'Run CLI Tests'
inputs:
command: "test"
projects: ${{ parameters.TestProjects }}
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal'
publishTestResults: true
testRunTitle: $(Agent.JobName)
workingDirectory: "$(Build.SourcesDirectory)"
- task: SonarCloudAnalyze@1
displayName: 'Run Code Analysis'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
- task: SonarCloudPublish@1
displayName: 'Publish Quality Gate Result'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Run ReportGenerator'
inputs:
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
targetdir: "$(Agent.TempDirectory)/merged/"
reporttypes: "Cobertura"
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
- task: PublishCodeCoverageResults@1
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Publish Code Coverage'
inputs:
codeCoverageTool: "cobertura"
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
pathToSources: $(Build.SourcesDirectory)
failIfCoverageEmpty: true
- task: PublishPipelineArtifact@1
displayName: 'Publish OpenAPI Artifact'
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
inputs:
targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json"
artifactName: 'OpenAPI Spec'

64
.ci/azure-pipelines.yml Normal file
View File

@@ -0,0 +1,64 @@
name: $(Date:yyyyMMdd)$(Rev:.r)
variables:
- name: TestProjects
value: 'tests/**/*Tests.csproj'
- name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
pr:
autoCancel: true
trigger:
batch: true
branches:
include:
- '*'
tags:
include:
- 'v*'
jobs:
- ${{ if not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) }}:
- template: azure-pipelines-main.yml
parameters:
LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: $(RestoreBuildProjects)
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-test.yml
parameters:
ImageNames:
Linux: 'ubuntu-latest'
Windows: 'windows-latest'
macOS: 'macos-latest'
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-test.yml
parameters:
ImageNames:
Linux: 'ubuntu-latest'
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-abi.yml
parameters:
Packages:
Naming:
NugetPackageName: Jellyfin.Naming
AssemblyFileName: Emby.Naming.dll
Controller:
NugetPackageName: Jellyfin.Controller
AssemblyFileName: MediaBrowser.Controller.dll
Model:
NugetPackageName: Jellyfin.Model
AssemblyFileName: MediaBrowser.Model.dll
Common:
NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll
Extensions:
NugetPackageName: Jellyfin.Extensions
AssemblyFileName: Jellyfin.Extensions.dll
LinuxImage: 'ubuntu-latest'
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-package.yml

View File

@@ -1,12 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.6",
"commands": [
"dotnet-ef"
]
}
}
}

1
.copr Symbolic link
View File

@@ -0,0 +1 @@
fedora

View File

@@ -1,28 +0,0 @@
{
"name": "Development Jellyfin Server - FFmpeg",
"image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy",
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
"postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust; sudo bash \"./.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh\"",
// reads the extensions list and installs them
"postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "none",
"dotnetRuntimeVersions": "8.0",
"aspNetCoreRuntimeVersions": "8.0"
},
"ghcr.io/devcontainers-contrib/features/apt-packages:1": {
"preserve_apt_list": false,
"packages": ["libfontconfig1"]
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"dockerDashComposeVersion": "v2"
},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
},
"hostRequirements": {
"memory": "8gb",
"cpus": 4
}
}

View File

@@ -1,32 +0,0 @@
#!/bin/bash
## configure the following for a manuall install of a specific version from the repo
# wget https://repo.jellyfin.org/releases/server/ubuntu/versions/jellyfin-ffmpeg/6.0.1-1/jellyfin-ffmpeg6_6.0.1-1-jammy_amd64.deb -O ffmpeg.deb
# sudo apt update
# sudo apt install -f ./ffmpeg.deb -y
# rm ffmpeg.deb
## Add the jellyfin repo
sudo apt install curl gnupg -y
sudo apt-get install software-properties-common -y
sudo add-apt-repository universe -y
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg
export VERSION_OS="$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release )"
export VERSION_CODENAME="$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )"
export DPKG_ARCHITECTURE="$( dpkg --print-architecture )"
cat <<EOF | sudo tee /etc/apt/sources.list.d/jellyfin.sources
Types: deb
URIs: https://repo.jellyfin.org/${VERSION_OS}
Suites: ${VERSION_CODENAME}
Components: main
Architectures: ${DPKG_ARCHITECTURE}
Signed-By: /etc/apt/keyrings/jellyfin.gpg
EOF
sudo apt update -y
sudo apt install jellyfin-ffmpeg6 -y

View File

@@ -1,28 +0,0 @@
{
"name": "Development Jellyfin Server",
"image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy",
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
"postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust",
// reads the extensions list and installs them
"postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "none",
"dotnetRuntimeVersions": "8.0",
"aspNetCoreRuntimeVersions": "8.0"
},
"ghcr.io/devcontainers-contrib/features/apt-packages:1": {
"preserve_apt_list": false,
"packages": ["libfontconfig1"]
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"dockerDashComposeVersion": "v2"
},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
},
"hostRequirements": {
"memory": "8gb",
"cpus": 4
}
}

View File

@@ -6,11 +6,7 @@ body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report an issue. Before submitting a report, please do the following:
1. Please head to our forum or chat rooms and troubleshoot with volunteers if you haven't already. Links can be found here: https://jellyfin.org/contact/
2. Please search the bug tracker for similar issues. If you do find one, please comment there instead of opening a new bug report.
3. If you decide to open a new report, please provide as much detail as possible.
4. Please **ONLY** report **ONE** issue per report. If you are experiencing multiple issues, please open multiple reports.
Thanks for taking the time to fill out this bug report! Please provide as much detail as necessary, most questions may not be applicable to you. If you need real-time help, join us on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [Discord](https://discord.gg/zHBxVSXdBV).
- type: textarea
id: what-happened
attributes:
@@ -18,18 +14,14 @@ body:
description: Also tell us, what did you expect to happen?
placeholder: |
The more information that you are able to provide, the better. Did you do anything before this happened? Did you upgrade or change anything? Any screenshots or logs you can provide will be helpful.
If you are using an old release of Jellyfin, please also explain why.
validations:
required: true
- type: textarea
id: repro-steps
attributes:
label: Reproduction Steps
placeholder: |
1. In this environment...
2. With this config...
3. Run '...'
4. See error...
This is my issue.
Steps to Reproduce
1. In this environment...
2. With this config...
3. Run '...'
4. See error...
validations:
required: true
- type: dropdown
@@ -38,11 +30,10 @@ body:
label: Jellyfin Version
description: What version of Jellyfin are you running?
options:
- 10.9.0
- 10.8.13
- 10.8.12 or older (please specify)
- Weekly unstable (please specify)
- Master branch
- 10.7.7
- 10.7.z
- 10.6.4
- Other
validations:
required: true
- type: input
@@ -55,15 +46,13 @@ body:
label: Environment
description: |
Examples:
- **OS**: [e.g. Debian 11, Windows 10]
- **Linux Kernel**: [e.g. none, 5.15, 6.1, etc.]
- **OS**: [e.g. Debian, Windows]
- **Virtualization**: [e.g. Docker, KVM, LXC]
- **Clients**: [Browser, Android, Fire Stick, etc.]
- **Browser**: [e.g. Firefox 91, Chrome 93, Safari 13]
- **FFmpeg Version**: [e.g. 5.1.2-Jellyfin]
- **FFmpeg Version**: [e.g. 4.3.2-Jellyfin]
- **Playback**: [Direct Play, Remux, Direct Stream, Transcode]
- **Hardware Acceleration**: [e.g. none, VAAPI, NVENC, etc.]
- **GPU Model**: [e.g. none, UHD630, GTX1050, etc.]
- **Installed Plugins**: [e.g. none, Fanart, Anime, etc.]
- **Reverse Proxy**: [e.g. none, nginx, apache, etc.]
- **Base URL**: [e.g. none, yes: /example]
@@ -71,32 +60,18 @@ body:
- **Storage**: [e.g. local, NFS, cloud]
value: |
- OS:
- Linux Kernel:
- Virtualization:
- Clients:
- Browser:
- FFmpeg Version:
- Playback Method:
- Hardware Acceleration:
- GPU Model:
- Plugins:
- Reverse Proxy:
- Base URL:
- Networking:
- Storage:
render: markdown
validations:
required: true
- type: markdown
attributes:
value: |
When providing logs, please keep the following things in mind.
1. **DO NOT** use external paste services.
2. Please provide complete logs.
- For server logs, include everything you think is important plus *10 lines before and after*
- For ffmpeg logs, please provide the entire file unmodified.
3. Please do not run logs through any translation program. Especially beware if your browser translates pages by default.
4. Please do not include logs as screenshots, with the only exception being client logs in browsers.
- type: textarea
id: logs
attributes:
@@ -104,14 +79,12 @@ body:
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
placeholder: For playback issues, browser/client and FFmpeg logs may be more useful.
render: shell
validations:
required: true
- type: textarea
id: ffmpeg-logs
attributes:
label: FFmpeg logs
description: Please copy and paste recent FFmpeg log output. This can be found in Dashboard > Logs > FFmpeg*.log.
placeholder: This field is mandatory for debugging hardware transcoding issues. It's important to include the specific codec details. If no FFmpeg logs appear, the file was Direct Played and did not use FFmpeg.
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
placeholder: It's important to include the specific codec details. If no FFmpeg logs appear, the file was Direct Played and did not use FFmpeg.
render: shell
- type: textarea
id: browserlogs

View File

@@ -0,0 +1,34 @@
---
name: Media playback issue
about: Create a media playback issue report
title: ''
labels: mediaplayback
assignees: ''
---
**Media Info of the file**
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
**Logs**
<!-- Please paste any log messages from during the playback issue. -->
**FFmpeg Logs**
<!-- Please paste any FFmpeg logs if remuxing or transcoding appears to be part of the issue. -->
**Stats for Nerds Screenshots**
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
**Server System (please complete the following information):**
- OS: [e.g. Docker on Linux, Docker on Windows, Debian, Windows]
- Jellyfin Version: [e.g. 10.0.1]
- Hardware settings & device: [e.g. NVENC on GTX1060, VAAPI on Intel i7 8700K]
- Reverse proxy: [e.g. no, nginx, apache, etc.]
- Other hardware notes: [e.g. Media mounted in CIFS/SMB share, Media mounted from Google Drive]
**Client System (please complete the following information):**
- Device: [e.g. Apple iPhone XS, Xbox One S, LG OLED55C8, Samsung Galaxy Note9, Custom HTPC]
- OS: [e.g. iOS, Android, Windows, macOS]
- Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
- Browser (if Web client): [e.g. Firefox, Chrome, Safari]
- Client and Browser Version: [e.g. 10.3.4 and 68.0]

15
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: weekly
time: '12:00'
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: weekly
time: '12:00'
open-pull-requests-limit: 10

View File

@@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>jellyfin/.github//renovate-presets/dotnet"
]
}

View File

@@ -1,4 +1,4 @@
name: Project Automation
name: Automation
on:
push:
@@ -7,15 +7,26 @@ on:
pull_request_target:
issue_comment:
permissions: {}
jobs:
label:
name: Labeling
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Apply label
uses: eps1lon/actions-label-merge-conflict@v2.0.1
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
with:
dirtyLabel: 'merge conflict'
repoToken: ${{ secrets.JF_BOT_TOKEN }}
project:
name: Project board
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Remove from 'Current Release' project
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
uses: alex-page/github-project-automation-plus@v0.8.1
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
@@ -24,7 +35,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
uses: alex-page/github-project-automation-plus@v0.8.1
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
continue-on-error: true
with:
@@ -33,7 +44,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Current Release' project
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
uses: alex-page/github-project-automation-plus@v0.8.1
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
@@ -47,7 +58,7 @@ jobs:
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
- name: Move issue to needs triage
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
uses: alex-page/github-project-automation-plus@v0.8.1
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
continue-on-error: true
with:
@@ -56,7 +67,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
uses: alex-page/github-project-automation-plus@v0.8.1
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
continue-on-error: true
with:

View File

@@ -1,271 +0,0 @@
name: OpenAPI
on:
push:
branches:
- master
tags:
- 'v*'
pull_request_target:
permissions: {}
jobs:
openapi-head:
name: OpenAPI - HEAD
runs-on: ubuntu-latest
permissions: read-all
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: '8.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: openapi-head
retention-days: 14
if-no-files-found: error
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json
openapi-base:
name: OpenAPI - BASE
if: ${{ github.base_ref != '' }}
runs-on: ubuntu-latest
permissions: read-all
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
- name: Checkout common ancestor
env:
HEAD_REF: ${{ github.head_ref }}
run: |
git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
git -c protocol.version=2 fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
git checkout --progress --force $ANCESTOR_REF
- name: Setup .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: '8.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: openapi-base
retention-days: 14
if-no-files-found: error
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json
openapi-diff:
permissions:
pull-requests: write # to create or update comment (peter-evans/create-or-update-comment)
name: OpenAPI - Difference
if: ${{ github.event_name == 'pull_request_target' }}
runs-on: ubuntu-latest
needs:
- openapi-head
- openapi-base
steps:
- name: Download openapi-head
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: openapi-head
path: openapi-head
- name: Download openapi-base
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: openapi-base
path: openapi-base
- name: Workaround openapi-diff issue
run: |
sed -i 's/"allOf"/"oneOf"/g' openapi-head/openapi.json
sed -i 's/"allOf"/"oneOf"/g' openapi-base/openapi.json
- name: Calculate OpenAPI difference
uses: docker://openapitools/openapi-diff
continue-on-error: true
with:
args: --fail-on-changed --markdown openapi-changes.md openapi-base/openapi.json openapi-head/openapi.json
- id: read-diff
name: Read openapi-diff output
run: |
# Read and fix markdown
body=$(cat openapi-changes.md)
# Write to workflow summary
echo "$body" >> $GITHUB_STEP_SUMMARY
# Set ApiChanged var
if [ "$body" != '' ]; then
echo "ApiChanged=1" >> "$GITHUB_OUTPUT"
else
echo "ApiChanged=0" >> "$GITHUB_OUTPUT"
fi
# Add header/footer for diff comment
echo '<!--openapi-diff-workflow-comment-->' > openapi-changes-reply.md
echo "<details>" >> openapi-changes-reply.md
echo "<summary>Changes in OpenAPI specification found. Expand to see details.</summary>" >> openapi-changes-reply.md
echo "" >> openapi-changes-reply.md
echo "$body" >> openapi-changes-reply.md
echo "" >> openapi-changes-reply.md
echo "</details>" >> openapi-changes-reply.md
- name: Find difference comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
direction: last
body-includes: openapi-diff-workflow-comment
- name: Reply or edit difference comment (changed)
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
if: ${{ steps.read-diff.outputs.ApiChanged == '1' }}
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
body-path: openapi-changes-reply.md
- name: Edit difference comment (unchanged)
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
if: ${{ steps.read-diff.outputs.ApiChanged == '0' && steps.find-comment.outputs.comment-id != '' }}
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
body: |
<!--openapi-diff-workflow-comment-->
No changes to OpenAPI specification found. See history of this comment for previous changes.
publish-unstable:
name: OpenAPI - Publish Unstable Spec
if: ${{ github.event_name != 'pull_request_target' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
runs-on: ubuntu-latest
needs:
- openapi-head
steps:
- name: Set unstable dated version
id: version
run: |-
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
- name: Download openapi-head
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: openapi-head
path: openapi-head
- name: Upload openapi.json (unstable) to repository server
uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # v0.1.7
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
source: openapi-head/openapi.json
strip_components: 1
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (unstable) into place
uses: appleboy/ssh-action@029f5b4aeeeb58fdfe1410a5d17f967dacf36262 # v1.0.3
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
debug: false
script_stop: false
script: |
if ! test -d /run/workflows; then
sudo mkdir -p /run/workflows
sudo chown ${{ secrets.REPO_USER }} /run/workflows
fi
(
flock -x -w 300 200 || exit 1
TGT_DIR="/srv/repository/main/openapi"
LAST_SPEC="$( ls -lt ${TGT_DIR}/unstable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
# If new and previous spec don't differ (diff retcode 0), remove incoming and finish
if diff /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/unstable/${LAST_SPEC} &>/dev/null; then
rm -r /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
exit 0
fi
# Move new spec into place
sudo mv /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
# Delete previous jellyfin-openapi-unstable_previous.json
sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
# Move current jellyfin-openapi-unstable.json symlink to jellyfin-openapi-unstable_previous.json
sudo mv ${TGT_DIR}/jellyfin-openapi-unstable.json ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
# Create new jellyfin-openapi-unstable.json symlink
sudo ln -s unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-unstable.json
# Check that the previous openapi unstable spec link is correct
if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-unstable_previous.json )" != "unstable/${LAST_SPEC}" ]]; then
sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
fi
) 200>/run/workflows/openapi-unstable.lock
publish-stable:
name: OpenAPI - Publish Stable Spec
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
runs-on: ubuntu-latest
needs:
- openapi-head
steps:
- name: Set version number
id: version
run: |-
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Download openapi-head
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: openapi-head
path: openapi-head
- name: Upload openapi.json (stable) to repository server
uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # v0.1.7
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
source: openapi-head/openapi.json
strip_components: 1
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (stable) into place
uses: appleboy/ssh-action@029f5b4aeeeb58fdfe1410a5d17f967dacf36262 # v1.0.3
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
debug: false
script_stop: false
script: |
if ! test -d /run/workflows; then
sudo mkdir -p /run/workflows
sudo chown ${{ secrets.REPO_USER }} /run/workflows
fi
(
flock -x -w 300 200 || exit 1
TGT_DIR="/srv/repository/main/openapi"
LAST_SPEC="$( ls -lt ${TGT_DIR}/stable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
# If new and previous spec don't differ (diff retcode 0), remove incoming and finish
if diff /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/${LAST_SPEC} &>/dev/null; then
rm -r /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
exit 0
fi
# Move new spec into place
sudo mv /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
# Delete previous jellyfin-openapi-stable_previous.json
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
# Move current jellyfin-openapi-stable.json symlink to jellyfin-openapi-stable_previous.json
sudo mv ${TGT_DIR}/jellyfin-openapi-stable.json ${TGT_DIR}/jellyfin-openapi-stable_previous.json
# Create new jellyfin-openapi-stable.json symlink
sudo ln -s stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-stable.json
# Check that the previous openapi stable spec link is correct
if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-stable_previous.json )" != "stable/${LAST_SPEC}" ]]; then
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
sudo ln -s stable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-stable_previous.json
fi
) 200>/run/workflows/openapi-stable.lock

View File

@@ -1,44 +0,0 @@
name: Tests
on:
push:
branches:
- master
# Run tests against the forked branch, but
# do not allow access to secrets
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflows-in-forked-repositories
pull_request:
env:
SDK_VERSION: "8.0.x"
jobs:
run-tests:
strategy:
matrix:
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
runs-on: "${{ matrix.os }}"
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: ${{ env.SDK_VERSION }}
- name: Run DotNet CLI Tests
run: >
dotnet test Jellyfin.sln
--configuration Release
--collect:"XPlat Code Coverage"
--settings tests/coverletArgs.runsettings
--verbosity minimal
- name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@fa728091745cdd279fddda1e0e80fb29265d0977 # 5.3.5
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"
reporttypes: "Cobertura"
# TODO - which action / tool to use to publish code coverage results?
# - name: Publish code coverage results

View File

@@ -20,18 +20,18 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Setup .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
uses: actions/checkout@v3
- name: Setup .NET Core
uses: actions/setup-dotnet@v2
with:
dotnet-version: '8.0.x'
dotnet-version: '6.0.x'
- name: Initialize CodeQL
uses: github/codeql-action/init@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7
uses: github/codeql-action/analyze@v1

View File

@@ -9,7 +9,6 @@ on:
- labeled
- synchronize
permissions: {}
jobs:
rebase:
name: Rebase
@@ -17,33 +16,30 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Notify as seen
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
uses: peter-evans/create-or-update-comment@v1.4.5
with:
token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }}
reactions: '+1'
- name: Checkout the latest code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@v3
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
- name: Automatic Rebase
uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8
uses: cirrus-actions/rebase@1.5
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
check-backport:
permissions:
contents: read
name: Check Backport
if: ${{ ( github.event.issue.pull_request && contains(github.event.comment.body, '@jellyfin-bot check backport') ) || github.event.label.name == 'stable backport' || contains(github.event.pull_request.labels.*.name, 'stable backport' ) }}
runs-on: ubuntu-latest
steps:
- name: Notify as seen
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
uses: peter-evans/create-or-update-comment@v1.4.5
if: ${{ github.event.comment != null }}
with:
token: ${{ secrets.JF_BOT_TOKEN }}
@@ -51,14 +47,14 @@ jobs:
reactions: eyes
- name: Checkout the latest code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@v3
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
- name: Notify as running
id: comment_running
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
uses: peter-evans/create-or-update-comment@v1.4.5
if: ${{ github.event.comment != null }}
with:
token: ${{ secrets.JF_BOT_TOKEN }}
@@ -93,7 +89,7 @@ jobs:
exit ${retcode}
- name: Notify with result success
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
uses: peter-evans/create-or-update-comment@v1.4.5
if: ${{ github.event.comment != null && success() }}
with:
token: ${{ secrets.JF_BOT_TOKEN }}
@@ -108,7 +104,7 @@ jobs:
reactions: hooray
- name: Notify with result failure
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
uses: peter-evans/create-or-update-comment@v1.4.5
if: ${{ github.event.comment != null && failure() }}
with:
token: ${{ secrets.JF_BOT_TOKEN }}
@@ -121,28 +117,3 @@ jobs:
${{ steps.run_tests.outputs.output }}
reactions: confused
rename:
name: Rename
if: contains(github.event.comment.body, '@jellyfin-bot rename') && github.event.comment.author_association == 'MEMBER'
runs-on: ubuntu-latest
steps:
- name: pull in script
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
repository: jellyfin/jellyfin-triage-script
- name: install python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:
python-version: '3.12'
cache: 'pip'
- name: install python packages
run: pip install -r rename/requirements.txt
- name: run rename script
run: python3 rename.py
working-directory: ./rename
env:
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
GH_REPO: ${{ github.repository }}
ISSUE: ${{ github.event.issue.number }}
COMMENT_ID: ${{ github.event.comment.id }}

View File

@@ -1,35 +0,0 @@
name: Stale Issue Labeler
on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
actions: write
jobs:
issues:
name: Check for stale issues
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
ascending: true
days-before-stale: 120
days-before-pr-stale: -1
days-before-close: 21
days-before-pr-close: -1
operations-per-run: 500
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
stale-issue-label: stale
stale-issue-message: |-
This issue has gone 120 days without an update and will be closed within 21 days if there is no new activity. To prevent this issue from being closed, please confirm the issue has not already been fixed by providing updated examples or logs.
If you have any questions you can use one of several ways to [contact us](https://jellyfin.org/contact).
close-issue-message: |-
This issue was closed due to inactivity.

View File

@@ -1,29 +0,0 @@
name: Check Issue Template
on:
issues:
types:
- opened
jobs:
check_issue:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: pull in script
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
repository: jellyfin/jellyfin-triage-script
- name: install python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
with:
python-version: '3.12'
cache: 'pip'
- name: install python packages
run: pip install -r main-repo-triage/requirements.txt
- name: check and comment issue
working-directory: ./main-repo-triage
run: python3 single_issue_gha.py
env:
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
GH_REPO: ${{ github.repository }}
ISSUE: ${{ github.event.issue.number }}

124
.github/workflows/openapi.yml vendored Normal file
View File

@@ -0,0 +1,124 @@
name: OpenAPI
on:
push:
branches:
- master
pull_request_target:
jobs:
openapi-head:
name: OpenAPI - HEAD
runs-on: ubuntu-latest
permissions: read-all
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup .NET Core
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@v2
with:
name: openapi-head
retention-days: 14
if-no-files-found: error
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json
openapi-base:
name: OpenAPI - BASE
if: ${{ github.base_ref != '' }}
runs-on: ubuntu-latest
permissions: read-all
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.base_ref }}
- name: Setup .NET Core
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@v2
with:
name: openapi-base
retention-days: 14
if-no-files-found: error
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json
openapi-diff:
name: OpenAPI - Difference
if: ${{ github.event_name == 'pull_request_target' }}
runs-on: ubuntu-latest
needs:
- openapi-head
- openapi-base
steps:
- name: Download openapi-head
uses: actions/download-artifact@v2
with:
name: openapi-head
path: openapi-head
- name: Download openapi-base
uses: actions/download-artifact@v2
with:
name: openapi-base
path: openapi-base
- name: Workaround openapi-diff issue
run: |
sed -i 's/"allOf"/"oneOf"/g' openapi-head/openapi.json
sed -i 's/"allOf"/"oneOf"/g' openapi-base/openapi.json
- name: Calculate OpenAPI difference
uses: docker://openapitools/openapi-diff
continue-on-error: true
with:
args: --fail-on-changed --markdown openapi-changes.md openapi-base/openapi.json openapi-head/openapi.json
- id: read-diff
name: Read openapi-diff output
run: |
body=$(cat openapi-changes.md)
body="${body//'%'/'%25'}"
body="${body//$'\n'/'%0A'}"
body="${body//$'\r'/'%0D'}"
echo ::set-output name=body::$body
- name: Find difference comment
uses: peter-evans/find-comment@v1
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
direction: last
body-includes: openapi-diff-workflow-comment
- name: Reply or edit difference comment (changed)
uses: peter-evans/create-or-update-comment@v1.4.5
if: ${{ steps.read-diff.outputs.body != '' }}
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
body: |
<!--openapi-diff-workflow-comment-->
<details>
<summary>Changes in OpenAPI specification found. Expand to see details.</summary>
${{ steps.read-diff.outputs.body }}
</details>
- name: Edit difference comment (unchanged)
uses: peter-evans/create-or-update-comment@v1.4.5
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace
body: |
<!--openapi-diff-workflow-comment-->
No changes to OpenAPI specification found. See history of this comment for previous changes.

View File

@@ -1,23 +0,0 @@
name: Merge Conflict Labeler
on:
push:
branches:
- master
pull_request_target:
issue_comment:
permissions: {}
jobs:
label:
name: Labeling
runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Apply label
uses: eps1lon/actions-label-merge-conflict@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
with:
dirtyLabel: 'merge conflict'
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
repoToken: ${{ secrets.JF_BOT_TOKEN }}

View File

@@ -1,30 +0,0 @@
name: Stale PR Check
on:
schedule:
- cron: '30 */12 * * *'
workflow_dispatch:
permissions:
pull-requests: write
actions: write
jobs:
prs-stale-conflicts:
name: Check PRs with merge conflicts
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
ascending: true
operations-per-run: 150
# The merge conflict action will remove the label when updated
remove-stale-when-updated: false
days-before-stale: -1
days-before-close: 90
days-before-issue-close: -1
stale-pr-label: merge conflict
close-pr-message: |-
This PR has been closed due to having unresolved merge conflicts.

View File

@@ -1,82 +0,0 @@
name: '🆙 Auto bump_version'
on:
release:
types:
- published
workflow_dispatch:
inputs:
TAG_BRANCH:
required: true
description: release-x.y.z
NEXT_VERSION:
required: true
description: x.y.z
jobs:
auto_bump_version:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'release' && !contains(github.event.release.tag_name, 'rc') }}
env:
TAG_BRANCH: ${{ github.event.release.target_commitish }}
steps:
- name: Wait for deploy checks to finish
uses: jitterbit/await-check-suites@292a541bb7618078395b2ce711a0d89cfb8a568a # v1
with:
ref: ${{ env.TAG_BRANCH }}
intervalSeconds: 60
timeoutSeconds: 3600
- name: Setup YQ
uses: chrisdickinson/setup-yq@latest
with:
yq-version: v4.9.8
- name: Checkout Repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
ref: ${{ env.TAG_BRANCH }}
- name: Setup EnvVars
run: |-
CURRENT_VERSION=$(yq e '.version' build.yaml)
CURRENT_MAJOR_MINOR=${CURRENT_VERSION%.*}
CURRENT_PATCH=${CURRENT_VERSION##*.}
echo "CURRENT_VERSION=${CURRENT_VERSION}" >> $GITHUB_ENV
echo "CURRENT_MAJOR_MINOR=${CURRENT_MAJOR_MINOR}" >> $GITHUB_ENV
echo "CURRENT_PATCH=${CURRENT_PATCH}" >> $GITHUB_ENV
echo "NEXT_VERSION=${CURRENT_MAJOR_MINOR}.$(($CURRENT_PATCH + 1))" >> $GITHUB_ENV
- name: Run bump_version
run: ./bump_version ${{ env.NEXT_VERSION }}
- name: Commit Changes
run: |-
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout ${{ env.TAG_BRANCH }}
git commit -am "Bump version to ${{ env.NEXT_VERSION }}"
git push origin ${{ env.TAG_BRANCH }}
manual_bump_version:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
env:
TAG_BRANCH: ${{ github.event.inputs.TAG_BRANCH }}
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
steps:
- name: Checkout Repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
ref: ${{ env.TAG_BRANCH }}
- name: Run bump_version
run: ./bump_version ${{ env.NEXT_VERSION }}
- name: Commit Changes
run: |-
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout ${{ env.TAG_BRANCH }}
git commit -am "Bump version to ${{ env.NEXT_VERSION }}"
git push origin ${{ env.TAG_BRANCH }}

27
.github/workflows/repo-stale.yaml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Issue Stale Check
on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch:
jobs:
stale:
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- uses: actions/stale@v5
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
days-before-stale: 120
days-before-pr-stale: -1
days-before-close: 21
days-before-pr-close: -1
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
stale-issue-label: stale
stale-issue-message: |-
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).

4
.gitignore vendored
View File

@@ -150,6 +150,8 @@ publish/
*.pubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
# packages/
dlls/
dllssigned/
@@ -164,6 +166,7 @@ AppPackages/
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
@@ -273,6 +276,7 @@ BenchmarkDotNet.Artifacts
# Ignore web artifacts from native builds
web/
web-src.*
MediaBrowser.WebDashboard/jellyfin-web
apiclient/generated
# Omnisharp crash logs

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
registry=https://registry.npmjs.org/
@jellyfin:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/
always-auth=true

View File

@@ -1,11 +1,13 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"ms-dotnettools.csharp",
"editorconfig.editorconfig",
"github.vscode-github-actions",
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csdevkit"
"editorconfig.editorconfig"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]

22
.vscode/launch.json vendored
View File

@@ -2,11 +2,11 @@
"version": "0.2.0",
"configurations": [
{
"name": ".NET Launch (console)",
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net6.0/jellyfin.dll",
"args": [],
"cwd": "${workspaceFolder}/Jellyfin.Server",
"console": "internalConsole",
@@ -18,11 +18,11 @@
}
},
{
"name": ".NET Launch (nowebclient)",
"name": ".NET Core Launch (nowebclient)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net6.0/jellyfin.dll",
"args": ["--nowebclient"],
"cwd": "${workspaceFolder}/Jellyfin.Server",
"console": "internalConsole",
@@ -30,19 +30,7 @@
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "ghcs .NET Launch (nowebclient, ffmpeg)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
"args": ["--nowebclient", "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"],
"cwd": "${workspaceFolder}/Jellyfin.Server",
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": ".NET Attach",
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"

View File

@@ -4,7 +4,6 @@
- [97carmine](https://github.com/97carmine)
- [Abbe98](https://github.com/Abbe98)
- [agrenott](https://github.com/agrenott)
- [alltilla](https://github.com/alltilla)
- [AndreCarvalho](https://github.com/AndreCarvalho)
- [anthonylavado](https://github.com/anthonylavado)
- [Artiume](https://github.com/Artiume)
@@ -39,7 +38,6 @@
- [DMouse10462](https://github.com/DMouse10462)
- [DrPandemic](https://github.com/DrPandemic)
- [eglia](https://github.com/eglia)
- [EgorBakanov](https://github.com/EgorBakanov)
- [EraYaN](https://github.com/EraYaN)
- [escabe](https://github.com/escabe)
- [excelite](https://github.com/excelite)
@@ -58,9 +56,7 @@
- [hawken93](https://github.com/hawken93)
- [HelloWorld017](https://github.com/HelloWorld017)
- [ikomhoog](https://github.com/ikomhoog)
- [iwalton3](https://github.com/iwalton3)
- [jftuga](https://github.com/jftuga)
- [jmshrv](https://github.com/jmshrv)
- [joern-h](https://github.com/joern-h)
- [joshuaboniface](https://github.com/joshuaboniface)
- [JustAMan](https://github.com/JustAMan)
@@ -78,12 +74,10 @@
- [Marenz](https://github.com/Marenz)
- [marius-luca-87](https://github.com/marius-luca-87)
- [mark-monteiro](https://github.com/mark-monteiro)
- [MarkCiliaVincenti](https://github.com/MarkCiliaVincenti)
- [Matt07211](https://github.com/Matt07211)
- [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00)
- [mitchfizz05](https://github.com/mitchfizz05)
- [mohd-akram](https://github.com/mohd-akram)
- [MrTimscampi](https://github.com/MrTimscampi)
- [n8225](https://github.com/n8225)
- [Nalsai](https://github.com/Nalsai)
@@ -92,7 +86,6 @@
- [neilsb](https://github.com/neilsb)
- [nevado](https://github.com/nevado)
- [Nickbert7](https://github.com/Nickbert7)
- [nicknsy](https://github.com/nicknsy)
- [nvllsvm](https://github.com/nvllsvm)
- [nyanmisaka](https://github.com/nyanmisaka)
- [OancaAndrei](https://github.com/OancaAndrei)
@@ -131,7 +124,6 @@
- [SuperSandro2000](https://github.com/SuperSandro2000)
- [tbraeutigam](https://github.com/tbraeutigam)
- [teacupx](https://github.com/teacupx)
- [TelepathicWalrus](https://github.com/TelepathicWalrus)
- [Terror-Gene](https://github.com/Terror-Gene)
- [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
- [ThibaultNocchi](https://github.com/ThibaultNocchi)
@@ -157,7 +149,6 @@
- [xosdy](https://github.com/xosdy)
- [XVicarious](https://github.com/XVicarious)
- [YouKnowBlom](https://github.com/YouKnowBlom)
- [ZachPhelan](https://github.com/ZachPhelan)
- [KristupasSavickas](https://github.com/KristupasSavickas)
- [Pusta](https://github.com/pusta)
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
@@ -169,21 +160,7 @@
- [vgambier](https://github.com/vgambier)
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
- [RealGreenDragon](https://github.com/RealGreenDragon)
- [ipitio](https://github.com/ipitio)
- [TheTyrius](https://github.com/TheTyrius)
- [tallbl0nde](https://github.com/tallbl0nde)
- [sleepycatcoding](https://github.com/sleepycatcoding)
- [scampower3](https://github.com/scampower3)
- [Chris-Codes-It](https://github.com/Chris-Codes-It)
- [Pithaya](https://github.com/Pithaya)
- [Çağrı Sakaoğlu](https://github.com/ilovepilav)
- [Barasingha](https://github.com/MaVdbussche)
- [Gauvino](https://github.com/Gauvino)
- [felix920506](https://github.com/felix920506)
- [btopherjohnson](https://github.com/btopherjohnson)
- [GeorgeH005](https://github.com/GeorgeH005)
- [Vedant](https://github.com/viktory36/)
- [NotSaifA](https://github.com/NotSaifA)
# Emby Contributors
@@ -252,8 +229,3 @@
- [gnuyent](https://github.com/gnuyent)
- [Matthew Jones](https://github.com/matthew-jones-uk)
- [Jakob Kukla](https://github.com/jakobkukla)
- [Utku Özdemir](https://github.com/utkuozdemir)
- [JPUC1143](https://github.com/Jpuc1143/)
- [0x25CBFC4F](https://github.com/0x25CBFC4F)
- [Robert Lützner](https://github.com/rluetzner)
- [Nathan McCrina](https://github.com/nfmccrina)

View File

@@ -15,8 +15,7 @@
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)/BannedSymbols.txt" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)/stylecop.json" />
<AdditionalFiles Include="$(SolutionDir)/BannedSymbols.txt" />
</ItemGroup>
</Project>

View File

@@ -1,91 +0,0 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
<ItemGroup Label="Package Dependencies">
<PackageVersion Include="AsyncKeyedLock" Version="6.4.2" />
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="AutoFixture" Version="4.18.1" />
<PackageVersion Include="BDInfo" Version="0.8.0" />
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.3.2" />
<PackageVersion Include="BlurHashSharp" Version="1.3.2" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Diacritics" Version="3.3.29" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.5.0" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.2" />
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.7" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="4.0.5" />
<PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.6" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="NEbml" Version="0.11.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="PlaylistsNET" Version="1.4.1" />
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
<PackageVersion Include="SharpFuzz" Version="2.1.1" />
<PackageVersion Include="SkiaSharp" Version="2.88.8" />
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.8" />
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Svg.Skia" Version="1.0.0.18" />
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.3" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="TMDbLib" Version="2.2.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.8" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageVersion Include="xunit" Version="2.7.1" />
</ItemGroup>
</Project>

92
Dockerfile Normal file
View File

@@ -0,0 +1,92 @@
# DESIGNED FOR BUILDING ON AMD64 ONLY
#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=6.0
FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
FROM debian:stable-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
# https://github.com/intel/compute-runtime/releases
ARG GMMLIB_VERSION=22.0.2
ARG IGC_VERSION=1.0.10395
ARG NEO_VERSION=22.08.22549
ARG LEVEL_ZERO_VERSION=1.3.22549
# Install dependencies:
# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
# curl: healthcheck
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https curl \
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
mesa-va-drivers \
jellyfin-ffmpeg \
openssl \
locales \
# Intel VAAPI Tone mapping dependencies:
# Prefer NEO to Beignet since the latter one doesn't support Comet Lake or newer for now.
# Do not use the intel-opencl-icd package from repo since they will not build with RELEASE_WITH_REGKEYS enabled.
&& mkdir intel-compute-runtime \
&& cd intel-compute-runtime \
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
&& dpkg -i *.deb \
&& cd .. \
&& rm -rf intel-compute-runtime \
&& apt-get remove gnupg wget apt-transport-https -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# 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
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
FROM app
ENV HEALTHCHECK_URL=http://localhost:8096/health
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1

84
Dockerfile.arm Normal file
View File

@@ -0,0 +1,84 @@
# DESIGNED FOR BUILDING ON ARM ONLY
#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=6.0
FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM arm32v7/debian:stable-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
# curl: setup & healthcheck
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
apt-get update && \
apt-get install --no-install-recommends --no-install-suggests -y \
jellyfin-ffmpeg \
libssl-dev \
libfontconfig1 \
libfreetype6 \
libomxil-bellagio0 \
libomxil-bellagio-bin \
libraspberrypi0 \
vainfo \
libva2 \
locales \
&& apt-get remove gnupg -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
FROM app
ENV HEALTHCHECK_URL=http://localhost:8096/health
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1

75
Dockerfile.arm64 Normal file
View File

@@ -0,0 +1,75 @@
# DESIGNED FOR BUILDING ON ARM64 ONLY
#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=6.0
FROM node:lts-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \
&& mv dist /dist
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM arm64v8/debian:stable-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
# curl: healcheck
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
ffmpeg \
libssl-dev \
ca-certificates \
libfontconfig1 \
libfreetype6 \
libomxil-bellagio0 \
libomxil-bellagio-bin \
locales \
curl \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
FROM app
ENV HEALTHCHECK_URL=http://localhost:8096/health
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
VOLUME /cache /config
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/bin/ffmpeg"]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1

View File

@@ -0,0 +1,25 @@
#pragma warning disable CS1591
using System.Buffers.Binary;
using System.IO;
namespace DvdLib
{
public class BigEndianBinaryReader : BinaryReader
{
public BigEndianBinaryReader(Stream input)
: base(input)
{
}
public override ushort ReadUInt16()
{
return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
}
public override uint ReadUInt32()
{
return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
}
}
}

20
DvdLib/DvdLib.csproj Normal file
View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AnalysisMode>AllDisabledByDefault</AnalysisMode>
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>

23
DvdLib/Ifo/Cell.cs Normal file
View File

@@ -0,0 +1,23 @@
#pragma warning disable CS1591
using System.IO;
namespace DvdLib.Ifo
{
public class Cell
{
public CellPlaybackInfo PlaybackInfo { get; private set; }
public CellPositionInfo PositionInfo { get; private set; }
internal void ParsePlayback(BinaryReader br)
{
PlaybackInfo = new CellPlaybackInfo(br);
}
internal void ParsePosition(BinaryReader br)
{
PositionInfo = new CellPositionInfo(br);
}
}
}

View File

@@ -0,0 +1,52 @@
#pragma warning disable CS1591
using System.IO;
namespace DvdLib.Ifo
{
public enum BlockMode
{
NotInBlock = 0,
FirstCell = 1,
InBlock = 2,
LastCell = 3,
}
public enum BlockType
{
Normal = 0,
Angle = 1,
}
public enum PlaybackMode
{
Normal = 0,
StillAfterEachVOBU = 1,
}
public class CellPlaybackInfo
{
public readonly BlockMode Mode;
public readonly BlockType Type;
public readonly bool SeamlessPlay;
public readonly bool Interleaved;
public readonly bool STCDiscontinuity;
public readonly bool SeamlessAngle;
public readonly PlaybackMode PlaybackMode;
public readonly bool Restricted;
public readonly byte StillTime;
public readonly byte CommandNumber;
public readonly DvdTime PlaybackTime;
public readonly uint FirstSector;
public readonly uint FirstILVUEndSector;
public readonly uint LastVOBUStartSector;
public readonly uint LastSector;
internal CellPlaybackInfo(BinaryReader br)
{
br.BaseStream.Seek(0x4, SeekOrigin.Current);
PlaybackTime = new DvdTime(br.ReadBytes(4));
br.BaseStream.Seek(0x10, SeekOrigin.Current);
}
}
}

View File

@@ -0,0 +1,19 @@
#pragma warning disable CS1591
using System.IO;
namespace DvdLib.Ifo
{
public class CellPositionInfo
{
public readonly ushort VOBId;
public readonly byte CellId;
internal CellPositionInfo(BinaryReader br)
{
VOBId = br.ReadUInt16();
br.ReadByte();
CellId = br.ReadByte();
}
}
}

20
DvdLib/Ifo/Chapter.cs Normal file
View File

@@ -0,0 +1,20 @@
#pragma warning disable CS1591
namespace DvdLib.Ifo
{
public class Chapter
{
public ushort ProgramChainNumber { get; private set; }
public ushort ProgramNumber { get; private set; }
public uint ChapterNumber { get; private set; }
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
{
ProgramChainNumber = pgcNum;
ProgramNumber = programNum;
ChapterNumber = chapterNum;
}
}
}

167
DvdLib/Ifo/Dvd.cs Normal file
View File

@@ -0,0 +1,167 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
namespace DvdLib.Ifo
{
public class Dvd
{
private readonly ushort _titleSetCount;
public readonly List<Title> Titles;
private ushort _titleCount;
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
public Dvd(string path)
{
Titles = new List<Title>();
var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
if (vmgPath == null)
{
foreach (var ifo in allFiles)
{
if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var nums = ifo.Name.Split('_', StringSplitOptions.RemoveEmptyEntries);
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
{
ReadVTS(ifoNumber, ifo.FullName);
}
}
}
else
{
using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
{
vmgFs.Seek(0x3E, SeekOrigin.Begin);
_titleSetCount = vmgRead.ReadUInt16();
// read address of TT_SRPT
vmgFs.Seek(0xC4, SeekOrigin.Begin);
uint ttSectorPtr = vmgRead.ReadUInt32();
vmgFs.Seek(ttSectorPtr * 2048, SeekOrigin.Begin);
ReadTT_SRPT(vmgRead);
}
}
for (ushort titleSetNum = 1; titleSetNum <= _titleSetCount; titleSetNum++)
{
ReadVTS(titleSetNum, allFiles);
}
}
}
private void ReadTT_SRPT(BinaryReader read)
{
_titleCount = read.ReadUInt16();
read.BaseStream.Seek(6, SeekOrigin.Current);
for (uint titleNum = 1; titleNum <= _titleCount; titleNum++)
{
var t = new Title(titleNum);
t.ParseTT_SRPT(read);
Titles.Add(t);
}
}
private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
{
var filename = string.Format(CultureInfo.InvariantCulture, "VTS_{0:00}_0.IFO", vtsNum);
var vtsPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, Path.ChangeExtension(filename, ".bup"), StringComparison.OrdinalIgnoreCase));
if (vtsPath == null)
{
throw new FileNotFoundException("Unable to find VTS IFO file");
}
ReadVTS(vtsNum, vtsPath.FullName);
}
private void ReadVTS(ushort vtsNum, string vtsPath)
{
VTSPaths[vtsNum] = vtsPath;
using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
{
// Read VTS_PTT_SRPT
vtsFs.Seek(0xC8, SeekOrigin.Begin);
uint vtsPttSrptSecPtr = vtsRead.ReadUInt32();
uint baseAddr = (vtsPttSrptSecPtr * 2048);
vtsFs.Seek(baseAddr, SeekOrigin.Begin);
ushort numTitles = vtsRead.ReadUInt16();
vtsRead.ReadUInt16();
uint endaddr = vtsRead.ReadUInt32();
uint[] offsets = new uint[numTitles];
for (ushort titleNum = 0; titleNum < numTitles; titleNum++)
{
offsets[titleNum] = vtsRead.ReadUInt32();
}
for (uint titleNum = 0; titleNum < numTitles; titleNum++)
{
uint chapNum = 1;
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
if (t == null)
{
continue;
}
do
{
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1]))
{
break;
}
chapNum++;
}
while (vtsFs.Position < (baseAddr + endaddr));
}
// Read VTS_PGCI
vtsFs.Seek(0xCC, SeekOrigin.Begin);
uint vtsPgciSecPtr = vtsRead.ReadUInt32();
vtsFs.Seek(vtsPgciSecPtr * 2048, SeekOrigin.Begin);
long startByte = vtsFs.Position;
ushort numPgcs = vtsRead.ReadUInt16();
vtsFs.Seek(6, SeekOrigin.Current);
for (ushort pgcNum = 1; pgcNum <= numPgcs; pgcNum++)
{
byte pgcCat = vtsRead.ReadByte();
bool entryPgc = (pgcCat & 0x80) != 0;
uint titleNum = (uint)(pgcCat & 0x7F);
vtsFs.Seek(3, SeekOrigin.Current);
uint vtsPgcOffset = vtsRead.ReadUInt32();
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
if (t != null)
{
t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
}
}
}
}
}
}
}

39
DvdLib/Ifo/DvdTime.cs Normal file
View File

@@ -0,0 +1,39 @@
#pragma warning disable CS1591
using System;
namespace DvdLib.Ifo
{
public class DvdTime
{
public readonly byte Hour, Minute, Second, Frames, FrameRate;
public DvdTime(byte[] data)
{
Hour = GetBCDValue(data[0]);
Minute = GetBCDValue(data[1]);
Second = GetBCDValue(data[2]);
Frames = GetBCDValue((byte)(data[3] & 0x3F));
if ((data[3] & 0x80) != 0)
{
FrameRate = 30;
}
else if ((data[3] & 0x40) != 0)
{
FrameRate = 25;
}
}
private static byte GetBCDValue(byte data)
{
return (byte)((((data & 0xF0) >> 4) * 10) + (data & 0x0F));
}
public static explicit operator TimeSpan(DvdTime time)
{
int ms = (int)(((1.0 / (double)time.FrameRate) * time.Frames) * 1000.0);
return new TimeSpan(0, time.Hour, time.Minute, time.Second, ms);
}
}
}

16
DvdLib/Ifo/Program.cs Normal file
View File

@@ -0,0 +1,16 @@
#pragma warning disable CS1591
using System.Collections.Generic;
namespace DvdLib.Ifo
{
public class Program
{
public IReadOnlyList<Cell> Cells { get; }
public Program(List<Cell> cells)
{
Cells = cells;
}
}
}

121
DvdLib/Ifo/ProgramChain.cs Normal file
View File

@@ -0,0 +1,121 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace DvdLib.Ifo
{
public enum ProgramPlaybackMode
{
Sequential,
Random,
Shuffle
}
public class ProgramChain
{
private byte _programCount;
public readonly List<Program> Programs;
private byte _cellCount;
public readonly List<Cell> Cells;
public DvdTime PlaybackTime { get; private set; }
public UserOperation ProhibitedUserOperations { get; private set; }
public byte[] AudioStreamControl { get; private set; } // 8*2 entries
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
private ushort _nextProgramNumber;
private ushort _prevProgramNumber;
private ushort _goupProgramNumber;
public ProgramPlaybackMode PlaybackMode { get; private set; }
public uint ProgramCount { get; private set; }
public byte StillTime { get; private set; }
public byte[] Palette { get; private set; } // 16*4 entries
private ushort _commandTableOffset;
private ushort _programMapOffset;
private ushort _cellPlaybackOffset;
private ushort _cellPositionOffset;
public readonly uint VideoTitleSetIndex;
internal ProgramChain(uint vtsPgcNum)
{
VideoTitleSetIndex = vtsPgcNum;
Cells = new List<Cell>();
Programs = new List<Program>();
}
internal void ParseHeader(BinaryReader br)
{
long startPos = br.BaseStream.Position;
br.ReadUInt16();
_programCount = br.ReadByte();
_cellCount = br.ReadByte();
PlaybackTime = new DvdTime(br.ReadBytes(4));
ProhibitedUserOperations = (UserOperation)br.ReadUInt32();
AudioStreamControl = br.ReadBytes(16);
SubpictureStreamControl = br.ReadBytes(128);
_nextProgramNumber = br.ReadUInt16();
_prevProgramNumber = br.ReadUInt16();
_goupProgramNumber = br.ReadUInt16();
StillTime = br.ReadByte();
byte pbMode = br.ReadByte();
if (pbMode == 0)
{
PlaybackMode = ProgramPlaybackMode.Sequential;
}
else
{
PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
}
ProgramCount = (uint)(pbMode & 0x7F);
Palette = br.ReadBytes(64);
_commandTableOffset = br.ReadUInt16();
_programMapOffset = br.ReadUInt16();
_cellPlaybackOffset = br.ReadUInt16();
_cellPositionOffset = br.ReadUInt16();
// read position info
br.BaseStream.Seek(startPos + _cellPositionOffset, SeekOrigin.Begin);
for (int cellNum = 0; cellNum < _cellCount; cellNum++)
{
var c = new Cell();
c.ParsePosition(br);
Cells.Add(c);
}
br.BaseStream.Seek(startPos + _cellPlaybackOffset, SeekOrigin.Begin);
for (int cellNum = 0; cellNum < _cellCount; cellNum++)
{
Cells[cellNum].ParsePlayback(br);
}
br.BaseStream.Seek(startPos + _programMapOffset, SeekOrigin.Begin);
var cellNumbers = new List<int>();
for (int progNum = 0; progNum < _programCount; progNum++) cellNumbers.Add(br.ReadByte() - 1);
for (int i = 0; i < cellNumbers.Count; i++)
{
int max = (i + 1 == cellNumbers.Count) ? _cellCount : cellNumbers[i + 1];
Programs.Add(new Program(Cells.Where((c, idx) => idx >= cellNumbers[i] && idx < max).ToList()));
}
}
}
}

70
DvdLib/Ifo/Title.cs Normal file
View File

@@ -0,0 +1,70 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.IO;
namespace DvdLib.Ifo
{
public class Title
{
public uint TitleNumber { get; private set; }
public uint AngleCount { get; private set; }
public ushort ChapterCount { get; private set; }
public byte VideoTitleSetNumber { get; private set; }
private ushort _parentalManagementMask;
private byte _titleNumberInVTS;
private uint _vtsStartSector; // relative to start of entire disk
public ProgramChain EntryProgramChain { get; private set; }
public readonly List<ProgramChain> ProgramChains;
public readonly List<Chapter> Chapters;
public Title(uint titleNum)
{
ProgramChains = new List<ProgramChain>();
Chapters = new List<Chapter>();
Chapters = new List<Chapter>();
TitleNumber = titleNum;
}
public bool IsVTSTitle(uint vtsNum, uint vtsTitleNum)
{
return (vtsNum == VideoTitleSetNumber && vtsTitleNum == _titleNumberInVTS);
}
internal void ParseTT_SRPT(BinaryReader br)
{
byte titleType = br.ReadByte();
// TODO parse Title Type
AngleCount = br.ReadByte();
ChapterCount = br.ReadUInt16();
_parentalManagementMask = br.ReadUInt16();
VideoTitleSetNumber = br.ReadByte();
_titleNumberInVTS = br.ReadByte();
_vtsStartSector = br.ReadUInt32();
}
internal void AddPgc(BinaryReader br, long startByte, bool entryPgc, uint pgcNum)
{
long curPos = br.BaseStream.Position;
br.BaseStream.Seek(startByte, SeekOrigin.Begin);
var pgc = new ProgramChain(pgcNum);
pgc.ParseHeader(br);
ProgramChains.Add(pgc);
if (entryPgc)
{
EntryProgramChain = pgc;
}
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
}
}
}

View File

@@ -0,0 +1,37 @@
#pragma warning disable CS1591
using System;
namespace DvdLib.Ifo
{
[Flags]
public enum UserOperation
{
None = 0,
TitleOrTimePlay = 1,
ChapterSearchOrPlay = 2,
TitlePlay = 4,
Stop = 8,
GoUp = 16,
TimeOrChapterSearch = 32,
PrevOrTopProgramSearch = 64,
NextProgramSearch = 128,
ForwardScan = 256,
BackwardScan = 512,
TitleMenuCall = 1024,
RootMenuCall = 2048,
SubpictureMenuCall = 4096,
AudioMenuCall = 8192,
AngleMenuCall = 16384,
ChapterMenuCall = 32768,
Resume = 65536,
ButtonSelectOrActive = 131072,
StillOff = 262144,
PauseOn = 524288,
AudioStreamChange = 1048576,
SubpictureStreamChange = 2097152,
AngleChange = 4194304,
KaraokeAudioPresentationModeChange = 8388608,
VideoPresentationModeChange = 16777216,
}
}

View File

@@ -0,0 +1,21 @@
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("DvdLib")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

View File

@@ -0,0 +1,23 @@
namespace Emby.Dlna.Common
{
/// <summary>
/// DLNA Query parameter type, used when querying DLNA devices via SOAP.
/// </summary>
public class Argument
{
/// <summary>
/// Gets or sets name of the DLNA argument.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the direction of the parameter.
/// </summary>
public string Direction { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the related DLNA state variable for this argument.
/// </summary>
public string RelatedStateVariable { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,41 @@
using System.Globalization;
namespace Emby.Dlna.Common
{
/// <summary>
/// Defines the <see cref="DeviceIcon" />.
/// </summary>
public class DeviceIcon
{
/// <summary>
/// Gets or sets the Url.
/// </summary>
public string Url { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the MimeType.
/// </summary>
public string MimeType { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the Width.
/// </summary>
public int Width { get; set; }
/// <summary>
/// Gets or sets the Height.
/// </summary>
public int Height { get; set; }
/// <summary>
/// Gets or sets the Depth.
/// </summary>
public string Depth { get; set; } = string.Empty;
/// <inheritdoc />
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}x{1}", Height, Width);
}
}
}

View File

@@ -0,0 +1,36 @@
namespace Emby.Dlna.Common
{
/// <summary>
/// Defines the <see cref="DeviceService" />.
/// </summary>
public class DeviceService
{
/// <summary>
/// Gets or sets the Service Type.
/// </summary>
public string ServiceType { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the Service Id.
/// </summary>
public string ServiceId { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the Scpd Url.
/// </summary>
public string ScpdUrl { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the Control Url.
/// </summary>
public string ControlUrl { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the EventSubUrl.
/// </summary>
public string EventSubUrl { get; set; } = string.Empty;
/// <inheritdoc />
public override string ToString() => ServiceId;
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
namespace Emby.Dlna.Common
{
/// <summary>
/// Defines the <see cref="ServiceAction" />.
/// </summary>
public class ServiceAction
{
/// <summary>
/// Initializes a new instance of the <see cref="ServiceAction"/> class.
/// </summary>
public ServiceAction()
{
ArgumentList = new List<Argument>();
}
/// <summary>
/// Gets or sets the name of the action.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets the ArgumentList.
/// </summary>
public List<Argument> ArgumentList { get; }
/// <inheritdoc />
public override string ToString() => Name;
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
namespace Emby.Dlna.Common
{
/// <summary>
/// Defines the <see cref="StateVariable" />.
/// </summary>
public class StateVariable
{
/// <summary>
/// Gets or sets the name of the state variable.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the data type of the state variable.
/// </summary>
public string DataType { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether it sends events.
/// </summary>
public bool SendsEvents { get; set; }
/// <summary>
/// Gets or sets the allowed values range.
/// </summary>
public IReadOnlyList<string> AllowedValues { get; set; } = Array.Empty<string>();
/// <inheritdoc />
public override string ToString() => Name;
}
}

View File

@@ -0,0 +1,92 @@
#pragma warning disable CS1591
namespace Emby.Dlna.Configuration
{
/// <summary>
/// The DlnaOptions class contains the user definable parameters for the dlna subsystems.
/// </summary>
public class DlnaOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="DlnaOptions"/> class.
/// </summary>
public DlnaOptions()
{
EnablePlayTo = true;
EnableServer = false;
BlastAliveMessages = true;
SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
AliveMessageIntervalSeconds = 1800;
}
/// <summary>
/// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna playTo subsystem.
/// </summary>
public bool EnablePlayTo { get; set; }
/// <summary>
/// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna server subsystem.
/// </summary>
public bool EnableServer { get; set; }
/// <summary>
/// Gets or sets a value indicating whether detailed dlna server logs are sent to the console/log.
/// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work.
/// </summary>
public bool EnableDebugLog { get; set; }
/// <summary>
/// Gets or sets a value indicating whether whether detailed playTo debug logs are sent to the console/log.
/// If the setting "Emby.Dlna.PlayTo": "Debug" msut be set in logging.default.json for this property to work.
/// </summary>
public bool EnablePlayToTracing { get; set; }
/// <summary>
/// Gets or sets the ssdp client discovery interval time (in seconds).
/// This is the time after which the server will send a ssdp search request.
/// </summary>
public int ClientDiscoveryIntervalSeconds { get; set; }
/// <summary>
/// Gets or sets the frequency at which ssdp alive notifications are transmitted.
/// </summary>
public int AliveMessageIntervalSeconds { get; set; }
/// <summary>
/// Gets or sets the frequency at which ssdp alive notifications are transmitted. MIGRATING - TO BE REMOVED ONCE WEB HAS BEEN ALTERED.
/// </summary>
public int BlastAliveMessageIntervalSeconds
{
get
{
return AliveMessageIntervalSeconds;
}
set
{
AliveMessageIntervalSeconds = value;
}
}
/// <summary>
/// Gets or sets the default user account that the dlna server uses.
/// </summary>
public string? DefaultUserId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether playTo device profiles should be created.
/// </summary>
public bool AutoCreatePlayToProfiles { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to blast alive messages.
/// </summary>
public bool BlastAliveMessages { get; set; } = true;
/// <summary>
/// gets or sets a value indicating whether to send only matched host.
/// </summary>
public bool SendOnlyMatchedHost { get; set; } = true;
}
}

View File

@@ -0,0 +1,15 @@
#pragma warning disable CS1591
using Emby.Dlna.Configuration;
using MediaBrowser.Common.Configuration;
namespace Emby.Dlna
{
public static class ConfigurationExtension
{
public static DlnaOptions GetDlnaConfiguration(this IConfigurationManager manager)
{
return manager.GetConfiguration<DlnaOptions>("dlna");
}
}
}

View File

@@ -0,0 +1,53 @@
#pragma warning disable CS1591
using System.Net.Http;
using System.Threading.Tasks;
using Emby.Dlna.Service;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager
{
/// <summary>
/// Defines the <see cref="ConnectionManagerService" />.
/// </summary>
public class ConnectionManagerService : BaseService, IConnectionManager
{
private readonly IDlnaManager _dlna;
private readonly IServerConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionManagerService"/> class.
/// </summary>
/// <param name="dlna">The <see cref="IDlnaManager"/> for use with the <see cref="ConnectionManagerService"/> instance.</param>
/// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="ConnectionManagerService"/> instance.</param>
/// <param name="logger">The <see cref="ILogger{ConnectionManagerService}"/> for use with the <see cref="ConnectionManagerService"/> instance..</param>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for use with the <see cref="ConnectionManagerService"/> instance..</param>
public ConnectionManagerService(
IDlnaManager dlna,
IServerConfigurationManager config,
ILogger<ConnectionManagerService> logger,
IHttpClientFactory httpClientFactory)
: base(logger, httpClientFactory)
{
_dlna = dlna;
_config = config;
}
/// <inheritdoc />
public string GetServiceXml()
{
return ConnectionManagerXmlBuilder.GetXml();
}
/// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
return new ControlHandler(_config, Logger, profile).ProcessControlRequestAsync(request);
}
}
}

View File

@@ -0,0 +1,121 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
namespace Emby.Dlna.ConnectionManager
{
/// <summary>
/// Defines the <see cref="ConnectionManagerXmlBuilder" />.
/// </summary>
public static class ConnectionManagerXmlBuilder
{
/// <summary>
/// Gets the ConnectionManager:1 service template.
/// See http://upnp.org/specs/av/UPnP-av-ConnectionManager-v1-Service.pdf.
/// </summary>
/// <returns>An XML description of this service.</returns>
public static string GetXml()
{
return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
}
/// <summary>
/// Get the list of state variables for this invocation.
/// </summary>
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
private static IEnumerable<StateVariable> GetStateVariables()
{
var list = new List<StateVariable>
{
new StateVariable
{
Name = "SourceProtocolInfo",
DataType = "string",
SendsEvents = true
},
new StateVariable
{
Name = "SinkProtocolInfo",
DataType = "string",
SendsEvents = true
},
new StateVariable
{
Name = "CurrentConnectionIDs",
DataType = "string",
SendsEvents = true
},
new StateVariable
{
Name = "A_ARG_TYPE_ConnectionStatus",
DataType = "string",
SendsEvents = false,
AllowedValues = new[]
{
"OK",
"ContentFormatMismatch",
"InsufficientBandwidth",
"UnreliableChannel",
"Unknown"
}
},
new StateVariable
{
Name = "A_ARG_TYPE_ConnectionManager",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_Direction",
DataType = "string",
SendsEvents = false,
AllowedValues = new[]
{
"Output",
"Input"
}
},
new StateVariable
{
Name = "A_ARG_TYPE_ProtocolInfo",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_ConnectionID",
DataType = "ui4",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_AVTransportID",
DataType = "ui4",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_RcsID",
DataType = "ui4",
SendsEvents = false
}
};
return list;
}
}
}

View File

@@ -0,0 +1,55 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Xml;
using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Dlna;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager
{
/// <summary>
/// Defines the <see cref="ControlHandler" />.
/// </summary>
public class ControlHandler : BaseControlHandler
{
private readonly DeviceProfile _profile;
/// <summary>
/// Initializes a new instance of the <see cref="ControlHandler"/> class.
/// </summary>
/// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
/// <param name="logger">The <see cref="ILogger"/> for use with the <see cref="ControlHandler"/> instance.</param>
/// <param name="profile">The <see cref="DeviceProfile"/> for use with the <see cref="ControlHandler"/> instance.</param>
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
: base(config, logger)
{
_profile = profile;
}
/// <inheritdoc />
protected override void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter)
{
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
{
HandleGetProtocolInfo(xmlWriter);
return;
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
/// <summary>
/// Builds the response to the GetProtocolInfo request.
/// </summary>
/// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
private void HandleGetProtocolInfo(XmlWriter xmlWriter)
{
xmlWriter.WriteElementString("Source", _profile.ProtocolInfo);
xmlWriter.WriteElementString("Sink", string.Empty);
}
}
}

View File

@@ -0,0 +1,234 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Common;
namespace Emby.Dlna.ConnectionManager
{
/// <summary>
/// Defines the <see cref="ServiceActionListBuilder" />.
/// </summary>
public static class ServiceActionListBuilder
{
/// <summary>
/// Returns an enumerable of the ConnectionManagar:1 DLNA actions.
/// </summary>
/// <returns>An <see cref="IEnumerable{ServiceAction}"/>.</returns>
public static IEnumerable<ServiceAction> GetActions()
{
var list = new List<ServiceAction>
{
GetCurrentConnectionInfo(),
GetProtocolInfo(),
GetCurrentConnectionIDs(),
ConnectionComplete(),
PrepareForConnection()
};
return list;
}
/// <summary>
/// Returns the action details for "PrepareForConnection".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction PrepareForConnection()
{
var action = new ServiceAction
{
Name = "PrepareForConnection"
};
action.ArgumentList.Add(new Argument
{
Name = "RemoteProtocolInfo",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo"
});
action.ArgumentList.Add(new Argument
{
Name = "PeerConnectionManager",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ConnectionManager"
});
action.ArgumentList.Add(new Argument
{
Name = "PeerConnectionID",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
});
action.ArgumentList.Add(new Argument
{
Name = "Direction",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Direction"
});
action.ArgumentList.Add(new Argument
{
Name = "ConnectionID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
});
action.ArgumentList.Add(new Argument
{
Name = "AVTransportID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_AVTransportID"
});
action.ArgumentList.Add(new Argument
{
Name = "RcsID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_RcsID"
});
return action;
}
/// <summary>
/// Returns the action details for "GetCurrentConnectionInfo".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetCurrentConnectionInfo()
{
var action = new ServiceAction
{
Name = "GetCurrentConnectionInfo"
};
action.ArgumentList.Add(new Argument
{
Name = "ConnectionID",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
});
action.ArgumentList.Add(new Argument
{
Name = "RcsID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_RcsID"
});
action.ArgumentList.Add(new Argument
{
Name = "AVTransportID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_AVTransportID"
});
action.ArgumentList.Add(new Argument
{
Name = "ProtocolInfo",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo"
});
action.ArgumentList.Add(new Argument
{
Name = "PeerConnectionManager",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_ConnectionManager"
});
action.ArgumentList.Add(new Argument
{
Name = "PeerConnectionID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
});
action.ArgumentList.Add(new Argument
{
Name = "Direction",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Direction"
});
action.ArgumentList.Add(new Argument
{
Name = "Status",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_ConnectionStatus"
});
return action;
}
/// <summary>
/// Returns the action details for "GetProtocolInfo".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetProtocolInfo()
{
var action = new ServiceAction
{
Name = "GetProtocolInfo"
};
action.ArgumentList.Add(new Argument
{
Name = "Source",
Direction = "out",
RelatedStateVariable = "SourceProtocolInfo"
});
action.ArgumentList.Add(new Argument
{
Name = "Sink",
Direction = "out",
RelatedStateVariable = "SinkProtocolInfo"
});
return action;
}
/// <summary>
/// Returns the action details for "GetCurrentConnectionIDs".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetCurrentConnectionIDs()
{
var action = new ServiceAction
{
Name = "GetCurrentConnectionIDs"
};
action.ArgumentList.Add(new Argument
{
Name = "ConnectionIDs",
Direction = "out",
RelatedStateVariable = "CurrentConnectionIDs"
});
return action;
}
/// <summary>
/// Returns the action details for "ConnectionComplete".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction ConnectionComplete()
{
var action = new ServiceAction
{
Name = "ConnectionComplete"
};
action.ArgumentList.Add(new Argument
{
Name = "ConnectionID",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
});
return action;
}
}
}

View File

@@ -0,0 +1,176 @@
#pragma warning disable CS1591
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Emby.Dlna.Service;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the <see cref="ContentDirectoryService" />.
/// </summary>
public class ContentDirectoryService : BaseService, IContentDirectory
{
private readonly ILibraryManager _libraryManager;
private readonly IImageProcessor _imageProcessor;
private readonly IUserDataManager _userDataManager;
private readonly IDlnaManager _dlna;
private readonly IServerConfigurationManager _config;
private readonly IUserManager _userManager;
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IUserViewManager _userViewManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly ITVSeriesManager _tvSeriesManager;
/// <summary>
/// Initializes a new instance of the <see cref="ContentDirectoryService"/> class.
/// </summary>
/// <param name="dlna">The <see cref="IDlnaManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="userDataManager">The <see cref="IUserDataManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="imageProcessor">The <see cref="IImageProcessor"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="libraryManager">The <see cref="ILibraryManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="config">The <see cref="IServerConfigurationManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="userManager">The <see cref="IUserManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="logger">The <see cref="ILogger{ContentDirectoryService}"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="httpClient">The <see cref="IHttpClientFactory"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="localization">The <see cref="ILocalizationManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="mediaSourceManager">The <see cref="IMediaSourceManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="userViewManager">The <see cref="IUserViewManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="mediaEncoder">The <see cref="IMediaEncoder"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="tvSeriesManager">The <see cref="ITVSeriesManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
public ContentDirectoryService(
IDlnaManager dlna,
IUserDataManager userDataManager,
IImageProcessor imageProcessor,
ILibraryManager libraryManager,
IServerConfigurationManager config,
IUserManager userManager,
ILogger<ContentDirectoryService> logger,
IHttpClientFactory httpClient,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
IUserViewManager userViewManager,
IMediaEncoder mediaEncoder,
ITVSeriesManager tvSeriesManager)
: base(logger, httpClient)
{
_dlna = dlna;
_userDataManager = userDataManager;
_imageProcessor = imageProcessor;
_libraryManager = libraryManager;
_config = config;
_userManager = userManager;
_localization = localization;
_mediaSourceManager = mediaSourceManager;
_userViewManager = userViewManager;
_mediaEncoder = mediaEncoder;
_tvSeriesManager = tvSeriesManager;
}
/// <summary>
/// Gets the system id. (A unique id which changes on when our definition changes.)
/// </summary>
private static int SystemUpdateId
{
get
{
var now = DateTime.UtcNow;
return now.Year + now.DayOfYear + now.Hour;
}
}
/// <inheritdoc />
public string GetServiceXml()
{
return ContentDirectoryXmlBuilder.GetXml();
}
/// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile();
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
var user = GetUser(profile);
return new ControlHandler(
Logger,
_libraryManager,
profile,
serverAddress,
null,
_imageProcessor,
_userDataManager,
user,
SystemUpdateId,
_config,
_localization,
_mediaSourceManager,
_userViewManager,
_mediaEncoder,
_tvSeriesManager)
.ProcessControlRequestAsync(request);
}
/// <summary>
/// Get the user stored in the device profile.
/// </summary>
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
/// <returns>The <see cref="User"/>.</returns>
private User? GetUser(DeviceProfile profile)
{
if (!string.IsNullOrEmpty(profile.UserId))
{
var user = _userManager.GetUserById(Guid.Parse(profile.UserId));
if (user != null)
{
return user;
}
}
var userId = _config.GetDlnaConfiguration().DefaultUserId;
if (!string.IsNullOrEmpty(userId))
{
var user = _userManager.GetUserById(Guid.Parse(userId));
if (user != null)
{
return user;
}
}
foreach (var user in _userManager.Users)
{
if (user.HasPermission(PermissionKind.IsAdministrator))
{
return user;
}
}
return _userManager.Users.FirstOrDefault();
}
}
}

View File

@@ -0,0 +1,161 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the <see cref="ContentDirectoryXmlBuilder" />.
/// </summary>
public static class ContentDirectoryXmlBuilder
{
/// <summary>
/// Gets the ContentDirectory:1 service template.
/// See http://upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf.
/// </summary>
/// <returns>An XML description of this service.</returns>
public static string GetXml()
{
return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
}
/// <summary>
/// Get the list of state variables for this invocation.
/// </summary>
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
private static IEnumerable<StateVariable> GetStateVariables()
{
var list = new List<StateVariable>
{
new StateVariable
{
Name = "A_ARG_TYPE_Filter",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_SortCriteria",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_Index",
DataType = "ui4",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_Count",
DataType = "ui4",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_UpdateID",
DataType = "ui4",
SendsEvents = false
},
new StateVariable
{
Name = "SearchCapabilities",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "SortCapabilities",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "SystemUpdateID",
DataType = "ui4",
SendsEvents = true
},
new StateVariable
{
Name = "A_ARG_TYPE_SearchCriteria",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_Result",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_ObjectID",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_BrowseFlag",
DataType = "string",
SendsEvents = false,
AllowedValues = new[]
{
"BrowseMetadata",
"BrowseDirectChildren"
}
},
new StateVariable
{
Name = "A_ARG_TYPE_BrowseLetter",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_CategoryType",
DataType = "ui4",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_RID",
DataType = "ui4",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_PosSec",
DataType = "ui4",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_Featurelist",
DataType = "string",
SendsEvents = false
}
};
return list;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
using MediaBrowser.Controller.Entities;
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the <see cref="ServerItem" />.
/// </summary>
internal class ServerItem
{
/// <summary>
/// Initializes a new instance of the <see cref="ServerItem"/> class.
/// </summary>
/// <param name="item">The <see cref="BaseItem"/>.</param>
/// <param name="stubType">The stub type.</param>
public ServerItem(BaseItem item, StubType? stubType)
{
Item = item;
if (stubType.HasValue)
{
StubType = stubType;
}
else if (item is IItemByName and not Folder)
{
StubType = Dlna.ContentDirectory.StubType.Folder;
}
}
/// <summary>
/// Gets the underlying base item.
/// </summary>
public BaseItem Item { get; }
/// <summary>
/// Gets the DLNA item type.
/// </summary>
public StubType? StubType { get; }
}
}

View File

@@ -0,0 +1,415 @@
using System.Collections.Generic;
using Emby.Dlna.Common;
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the <see cref="ServiceActionListBuilder" />.
/// </summary>
public static class ServiceActionListBuilder
{
/// <summary>
/// Returns a list of services that this instance provides.
/// </summary>
/// <returns>An <see cref="IEnumerable{ServiceAction}"/>.</returns>
public static IEnumerable<ServiceAction> GetActions()
{
return new[]
{
GetSearchCapabilitiesAction(),
GetSortCapabilitiesAction(),
GetGetSystemUpdateIDAction(),
GetBrowseAction(),
GetSearchAction(),
GetX_GetFeatureListAction(),
GetXSetBookmarkAction(),
GetBrowseByLetterAction()
};
}
/// <summary>
/// Returns the action details for "GetSystemUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetSystemUpdateIDAction()
{
var action = new ServiceAction
{
Name = "GetSystemUpdateID"
};
action.ArgumentList.Add(new Argument
{
Name = "Id",
Direction = "out",
RelatedStateVariable = "SystemUpdateID"
});
return action;
}
/// <summary>
/// Returns the action details for "GetSearchCapabilities".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetSearchCapabilitiesAction()
{
var action = new ServiceAction
{
Name = "GetSearchCapabilities"
};
action.ArgumentList.Add(new Argument
{
Name = "SearchCaps",
Direction = "out",
RelatedStateVariable = "SearchCapabilities"
});
return action;
}
/// <summary>
/// Returns the action details for "GetSortCapabilities".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetSortCapabilitiesAction()
{
var action = new ServiceAction
{
Name = "GetSortCapabilities"
};
action.ArgumentList.Add(new Argument
{
Name = "SortCaps",
Direction = "out",
RelatedStateVariable = "SortCapabilities"
});
return action;
}
/// <summary>
/// Returns the action details for "X_GetFeatureList".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetX_GetFeatureListAction()
{
var action = new ServiceAction
{
Name = "X_GetFeatureList"
};
action.ArgumentList.Add(new Argument
{
Name = "FeatureList",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Featurelist"
});
return action;
}
/// <summary>
/// Returns the action details for "Search".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetSearchAction()
{
var action = new ServiceAction
{
Name = "Search"
};
action.ArgumentList.Add(new Argument
{
Name = "ContainerID",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
});
action.ArgumentList.Add(new Argument
{
Name = "SearchCriteria",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_SearchCriteria"
});
action.ArgumentList.Add(new Argument
{
Name = "Filter",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Filter"
});
action.ArgumentList.Add(new Argument
{
Name = "StartingIndex",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Index"
});
action.ArgumentList.Add(new Argument
{
Name = "RequestedCount",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "SortCriteria",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_SortCriteria"
});
action.ArgumentList.Add(new Argument
{
Name = "Result",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Result"
});
action.ArgumentList.Add(new Argument
{
Name = "NumberReturned",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "TotalMatches",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "UpdateID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_UpdateID"
});
return action;
}
/// <summary>
/// Returns the action details for "Browse".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetBrowseAction()
{
var action = new ServiceAction
{
Name = "Browse"
};
action.ArgumentList.Add(new Argument
{
Name = "ObjectID",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
});
action.ArgumentList.Add(new Argument
{
Name = "BrowseFlag",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_BrowseFlag"
});
action.ArgumentList.Add(new Argument
{
Name = "Filter",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Filter"
});
action.ArgumentList.Add(new Argument
{
Name = "StartingIndex",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Index"
});
action.ArgumentList.Add(new Argument
{
Name = "RequestedCount",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "SortCriteria",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_SortCriteria"
});
action.ArgumentList.Add(new Argument
{
Name = "Result",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Result"
});
action.ArgumentList.Add(new Argument
{
Name = "NumberReturned",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "TotalMatches",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "UpdateID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_UpdateID"
});
return action;
}
/// <summary>
/// Returns the action details for "X_BrowseByLetter".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetBrowseByLetterAction()
{
var action = new ServiceAction
{
Name = "X_BrowseByLetter"
};
action.ArgumentList.Add(new Argument
{
Name = "ObjectID",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
});
action.ArgumentList.Add(new Argument
{
Name = "BrowseFlag",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_BrowseFlag"
});
action.ArgumentList.Add(new Argument
{
Name = "Filter",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Filter"
});
action.ArgumentList.Add(new Argument
{
Name = "StartingLetter",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_BrowseLetter"
});
action.ArgumentList.Add(new Argument
{
Name = "RequestedCount",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "SortCriteria",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_SortCriteria"
});
action.ArgumentList.Add(new Argument
{
Name = "Result",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Result"
});
action.ArgumentList.Add(new Argument
{
Name = "NumberReturned",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "TotalMatches",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Count"
});
action.ArgumentList.Add(new Argument
{
Name = "UpdateID",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_UpdateID"
});
action.ArgumentList.Add(new Argument
{
Name = "StartingIndex",
Direction = "out",
RelatedStateVariable = "A_ARG_TYPE_Index"
});
return action;
}
/// <summary>
/// Returns the action details for "X_SetBookmark".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetXSetBookmarkAction()
{
var action = new ServiceAction
{
Name = "X_SetBookmark"
};
action.ArgumentList.Add(new Argument
{
Name = "CategoryType",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_CategoryType"
});
action.ArgumentList.Add(new Argument
{
Name = "RID",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_RID"
});
action.ArgumentList.Add(new Argument
{
Name = "ObjectID",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
});
action.ArgumentList.Add(new Argument
{
Name = "PosSecond",
Direction = "in",
RelatedStateVariable = "A_ARG_TYPE_PosSec"
});
return action;
}
}
}

View File

@@ -0,0 +1,30 @@
#pragma warning disable CS1591
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the DLNA item types.
/// </summary>
public enum StubType
{
Folder = 0,
Latest = 2,
Playlists = 3,
Albums = 4,
AlbumArtists = 5,
Artists = 6,
Songs = 7,
Genres = 8,
FavoriteSongs = 9,
FavoriteArtists = 10,
FavoriteAlbums = 11,
ContinueWatching = 12,
Movies = 13,
Collections = 14,
Favorites = 15,
NextUp = 16,
Series = 17,
FavoriteSeries = 18,
FavoriteEpisodes = 19
}
}

View File

@@ -0,0 +1,25 @@
#nullable disable
#pragma warning disable CS1591
using System.IO;
using Microsoft.AspNetCore.Http;
namespace Emby.Dlna
{
public class ControlRequest
{
public ControlRequest(IHeaderDictionary headers)
{
Headers = headers;
}
public IHeaderDictionary Headers { get; }
public Stream InputXml { get; set; }
public string TargetServerUuId { get; set; }
public string RequestedUrl { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
#pragma warning disable CS1591
using System.Collections.Generic;
namespace Emby.Dlna
{
public class ControlResponse
{
public ControlResponse(string xml, bool isSuccessful)
{
Headers = new Dictionary<string, string>();
Xml = xml;
IsSuccessful = isSuccessful;
}
public IDictionary<string, string> Headers { get; }
public string Xml { get; set; }
public bool IsSuccessful { get; set; }
/// <inheritdoc />
public override string ToString()
{
return Xml;
}
}
}

File diff suppressed because it is too large Load Diff

28
Emby.Dlna/Didl/Filter.cs Normal file
View File

@@ -0,0 +1,28 @@
#pragma warning disable CS1591
using System;
namespace Emby.Dlna.Didl
{
public class Filter
{
private readonly string[] _fields;
private readonly bool _all;
public Filter()
: this("*")
{
}
public Filter(string filter)
{
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
_fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries);
}
public bool Contains(string field)
{
return _all || Array.Exists(_fields, x => x.Equals(field, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@@ -0,0 +1,58 @@
#pragma warning disable CS1591
#pragma warning disable CA1305
using System;
using System.IO;
using System.Text;
namespace Emby.Dlna.Didl
{
public class StringWriterWithEncoding : StringWriter
{
private readonly Encoding? _encoding;
public StringWriterWithEncoding()
{
}
public StringWriterWithEncoding(IFormatProvider formatProvider)
: base(formatProvider)
{
}
public StringWriterWithEncoding(StringBuilder sb)
: base(sb)
{
}
public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider)
: base(sb, formatProvider)
{
}
public StringWriterWithEncoding(Encoding encoding)
{
_encoding = encoding;
}
public StringWriterWithEncoding(IFormatProvider formatProvider, Encoding encoding)
: base(formatProvider)
{
_encoding = encoding;
}
public StringWriterWithEncoding(StringBuilder sb, Encoding encoding)
: base(sb)
{
_encoding = encoding;
}
public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider, Encoding encoding)
: base(sb, formatProvider)
{
_encoding = encoding;
}
public override Encoding Encoding => _encoding ?? base.Encoding;
}
}

View File

@@ -0,0 +1,23 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Configuration;
using MediaBrowser.Common.Configuration;
namespace Emby.Dlna
{
public class DlnaConfigurationFactory : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new[]
{
new ConfigurationStore
{
Key = "dlna",
ConfigurationType = typeof(DlnaOptions)
}
};
}
}
}

567
Emby.Dlna/DlnaManager.cs Normal file
View File

@@ -0,0 +1,567 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Emby.Dlna.Profiles;
using Emby.Dlna.Server;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Emby.Dlna
{
public class DlnaManager : IDlnaManager
{
private readonly IApplicationPaths _appPaths;
private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILogger<DlnaManager> _logger;
private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
public DlnaManager(
IXmlSerializer xmlSerializer,
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILoggerFactory loggerFactory,
IServerApplicationHost appHost)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
_appPaths = appPaths;
_logger = loggerFactory.CreateLogger<DlnaManager>();
_appHost = appHost;
}
private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
public async Task InitProfilesAsync()
{
try
{
await ExtractSystemProfilesAsync().ConfigureAwait(false);
LoadProfiles();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error extracting DLNA profiles.");
}
}
private void LoadProfiles()
{
var list = GetProfiles(UserProfilesPath, DeviceProfileType.User)
.OrderBy(i => i.Name)
.ToList();
list.AddRange(GetProfiles(SystemProfilesPath, DeviceProfileType.System)
.OrderBy(i => i.Name));
}
public IEnumerable<DeviceProfile> GetProfiles()
{
lock (_profiles)
{
return _profiles.Values
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Item1.Info.Name)
.Select(i => i.Item2)
.ToList();
}
}
/// <inheritdoc />
public DeviceProfile GetDefaultProfile()
{
return new DefaultProfile();
}
/// <inheritdoc />
public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
{
if (deviceInfo == null)
{
throw new ArgumentNullException(nameof(deviceInfo));
}
var profile = GetProfiles()
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
if (profile == null)
{
_logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo);
}
else
{
_logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
}
return profile;
}
/// <summary>
/// Attempts to match a device with a profile.
/// Rules:
/// - If the profile field has no value, the field matches irregardless of its contents.
/// - the profile field can be an exact match, or a reg exp.
/// </summary>
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
/// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
/// <returns><b>True</b> if they match.</returns>
public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{
return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
&& IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
&& IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
&& IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
&& IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
&& IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
&& IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
&& IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
}
private bool IsRegexOrSubstringMatch(string input, string pattern)
{
if (string.IsNullOrEmpty(pattern))
{
// In profile identification: An empty pattern matches anything.
return true;
}
if (string.IsNullOrEmpty(input))
{
// The profile contains a value, and the device doesn't.
return false;
}
try
{
return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
|| Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
catch (ArgumentException ex)
{
_logger.LogError(ex, "Error evaluating regex pattern {Pattern}", pattern);
return false;
}
}
/// <inheritdoc />
public DeviceProfile? GetProfile(IHeaderDictionary headers)
{
if (headers == null)
{
throw new ArgumentNullException(nameof(headers));
}
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
if (profile == null)
{
_logger.LogDebug("No matching device profile found. {@Headers}", headers);
}
else
{
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
}
return profile;
}
private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
{
return profileInfo.Headers.Any(i => IsMatch(headers, i));
}
private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header)
{
// Handle invalid user setup
if (string.IsNullOrEmpty(header.Name))
{
return false;
}
if (headers.TryGetValue(header.Name, out StringValues value))
{
switch (header.Match)
{
case HeaderMatchType.Equals:
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring:
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
// _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
return isMatch;
case HeaderMatchType.Regex:
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
default:
throw new ArgumentException("Unrecognized HeaderMatchType");
}
}
return false;
}
private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
{
try
{
return _fileSystem.GetFilePaths(path)
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
.Select(i => ParseProfileFile(i, type))
.Where(i => i != null)
.ToList()!; // We just filtered out all the nulls
}
catch (IOException)
{
return Array.Empty<DeviceProfile>();
}
}
private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
{
lock (_profiles)
{
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile>? profileTuple))
{
return profileTuple.Item2;
}
try
{
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
var profile = ReserializeProfile(tempProfile);
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
return profile;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error parsing profile file: {Path}", path);
return null;
}
}
}
/// <inheritdoc />
public DeviceProfile? GetProfile(string id)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
if (info == null)
{
return null;
}
return ParseProfileFile(info.Path, info.Info.Type);
}
private IEnumerable<InternalProfileInfo> GetProfileInfosInternal()
{
lock (_profiles)
{
return _profiles.Values
.Select(i => i.Item1)
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Info.Name);
}
}
/// <inheritdoc />
public IEnumerable<DeviceProfileInfo> GetProfileInfos()
{
return GetProfileInfosInternal().Select(i => i.Info);
}
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
{
return new InternalProfileInfo(
new DeviceProfileInfo
{
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type
},
file.FullName);
}
private async Task ExtractSystemProfilesAsync()
{
var namespaceName = GetType().Namespace + ".Profiles.Xml.";
var systemProfilesPath = SystemProfilesPath;
foreach (var name in _assembly.GetManifestResourceNames())
{
if (!name.StartsWith(namespaceName, StringComparison.Ordinal))
{
continue;
}
var path = Path.Join(
systemProfilesPath,
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
// The stream should exist as we just got its name from GetManifestResourceNames
using (var stream = _assembly.GetManifestResourceStream(name)!)
{
var length = stream.Length;
var fileInfo = _fileSystem.GetFileInfo(path);
if (!fileInfo.Exists || fileInfo.Length != length)
{
Directory.CreateDirectory(systemProfilesPath);
var fileOptions = AsyncFile.WriteOptions;
fileOptions.Mode = FileMode.Create;
fileOptions.PreallocationSize = length;
var fileStream = new FileStream(path, fileOptions);
await using (fileStream.ConfigureAwait(false))
{
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
}
}
}
// Not necessary, but just to make it easy to find
Directory.CreateDirectory(UserProfilesPath);
}
/// <inheritdoc />
public void DeleteProfile(string id)
{
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
if (info.Info.Type == DeviceProfileType.System)
{
throw new ArgumentException("System profiles cannot be deleted.");
}
_fileSystem.DeleteFile(info.Path);
lock (_profiles)
{
_profiles.Remove(info.Path);
}
}
/// <inheritdoc />
public void CreateProfile(DeviceProfile profile)
{
profile = ReserializeProfile(profile);
if (string.IsNullOrEmpty(profile.Name))
{
throw new ArgumentException("Profile is missing Name");
}
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
var path = Path.Combine(UserProfilesPath, newFilename);
SaveProfile(profile, path, DeviceProfileType.User);
}
/// <inheritdoc />
public void UpdateProfile(string profileId, DeviceProfile profile)
{
profile = ReserializeProfile(profile);
if (string.IsNullOrEmpty(profile.Id))
{
throw new ArgumentException("Profile is missing Id");
}
if (string.IsNullOrEmpty(profile.Name))
{
throw new ArgumentException("Profile is missing Name");
}
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profileId, StringComparison.OrdinalIgnoreCase));
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
var path = Path.Combine(UserProfilesPath, newFilename);
if (!string.Equals(path, current.Path, StringComparison.Ordinal) &&
current.Info.Type != DeviceProfileType.System)
{
_fileSystem.DeleteFile(current.Path);
}
SaveProfile(profile, path, DeviceProfileType.User);
}
private void SaveProfile(DeviceProfile profile, string path, DeviceProfileType type)
{
lock (_profiles)
{
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
}
SerializeToXml(profile, path);
}
internal void SerializeToXml(DeviceProfile profile, string path)
{
_xmlSerializer.SerializeToFile(profile, path);
}
/// <summary>
/// Recreates the object using serialization, to ensure it's not a subclass.
/// If it's a subclass it may not serialize properly to xml (different root element tag name).
/// </summary>
/// <param name="profile">The device profile.</param>
/// <returns>The re-serialized device profile.</returns>
private DeviceProfile ReserializeProfile(DeviceProfile profile)
{
if (profile.GetType() == typeof(DeviceProfile))
{
return profile;
}
var json = JsonSerializer.Serialize(profile, _jsonOptions);
// Output can't be null if the input isn't null
return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions)!;
}
/// <inheritdoc />
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{
var profile = GetProfile(headers) ?? GetDefaultProfile();
var serverId = _appHost.SystemId;
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
}
/// <inheritdoc />
public ImageStream? GetIcon(string filename)
{
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
? ImageFormat.Png
: ImageFormat.Jpg;
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
var stream = _assembly.GetManifestResourceStream(resource);
if (stream == null)
{
return null;
}
return new ImageStream(stream)
{
Format = format
};
}
private class InternalProfileInfo
{
internal InternalProfileInfo(DeviceProfileInfo info, string path)
{
Info = info;
Path = path;
}
internal DeviceProfileInfo Info { get; }
internal string Path { get; }
}
}
/*
class DlnaProfileEntryPoint : IServerEntryPoint
{
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly IXmlSerializer _xmlSerializer;
public DlnaProfileEntryPoint(IApplicationPaths appPaths, IFileSystem fileSystem, IXmlSerializer xmlSerializer)
{
_appPaths = appPaths;
_fileSystem = fileSystem;
_xmlSerializer = xmlSerializer;
}
public void Run()
{
DumpProfiles();
}
private void DumpProfiles()
{
DeviceProfile[] list = new[]
{
new SamsungSmartTvProfile(),
new XboxOneProfile(),
new SonyPs3Profile(),
new SonyPs4Profile(),
new SonyBravia2010Profile(),
new SonyBravia2011Profile(),
new SonyBravia2012Profile(),
new SonyBravia2013Profile(),
new SonyBravia2014Profile(),
new SonyBlurayPlayer2013(),
new SonyBlurayPlayer2014(),
new SonyBlurayPlayer2015(),
new SonyBlurayPlayer2016(),
new SonyBlurayPlayerProfile(),
new PanasonicVieraProfile(),
new WdtvLiveProfile(),
new DenonAvrProfile(),
new LinksysDMA2100Profile(),
new LgTvProfile(),
new Foobar2000Profile(),
new SharpSmartTvProfile(),
new MediaMonkeyProfile(),
// new Windows81Profile(),
// new WindowsMediaCenterProfile(),
// new WindowsPhoneProfile(),
new DirectTvProfile(),
new DishHopperJoeyProfile(),
new DefaultProfile(),
new PopcornHourProfile(),
new MarantzProfile()
};
foreach (var item in list)
{
var path = Path.Combine(_appPaths.ProgramDataPath, _fileSystem.GetValidFilename(item.Name) + ".xml");
_xmlSerializer.SerializeToFile(item, path);
}
}
public void Dispose()
{
}
}*/
}

View File

@@ -0,0 +1,86 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\RSSDP\RSSDP.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Images\logo120.jpg" />
<EmbeddedResource Include="Images\logo120.png" />
<EmbeddedResource Include="Images\logo240.jpg" />
<EmbeddedResource Include="Images\logo240.png" />
<EmbeddedResource Include="Images\logo48.jpg" />
<EmbeddedResource Include="Images\logo48.png" />
<EmbeddedResource Include="Images\people48.jpg" />
<EmbeddedResource Include="Images\people48.png" />
<EmbeddedResource Include="Images\people480.jpg" />
<EmbeddedResource Include="Images\people480.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Profiles\Xml\Default.xml" />
<EmbeddedResource Include="Profiles\Xml\Denon AVR.xml" />
<EmbeddedResource Include="Profiles\Xml\DirecTV HD-DVR.xml" />
<EmbeddedResource Include="Profiles\Xml\Dish Hopper-Joey.xml" />
<EmbeddedResource Include="Profiles\Xml\foobar2000.xml" />
<EmbeddedResource Include="Profiles\Xml\LG Smart TV.xml" />
<EmbeddedResource Include="Profiles\Xml\Linksys DMA2100.xml" />
<EmbeddedResource Include="Profiles\Xml\Marantz.xml" />
<EmbeddedResource Include="Profiles\Xml\MediaMonkey.xml" />
<EmbeddedResource Include="Profiles\Xml\Panasonic Viera.xml" />
<EmbeddedResource Include="Profiles\Xml\Popcorn Hour.xml" />
<EmbeddedResource Include="Profiles\Xml\Samsung Smart TV.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player 2013.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player 2014.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player 2015.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player 2016.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282010%29.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282011%29.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282012%29.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282013%29.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282014%29.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony PlayStation 3.xml" />
<EmbeddedResource Include="Profiles\Xml\Sony PlayStation 4.xml" />
<EmbeddedResource Include="Profiles\Xml\WDTV Live.xml" />
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,22 @@
#pragma warning disable CS1591
using System.Collections.Generic;
namespace Emby.Dlna
{
public class EventSubscriptionResponse
{
public EventSubscriptionResponse(string content, string contentType)
{
Content = content;
ContentType = contentType;
Headers = new Dictionary<string, string>();
}
public string Content { get; set; }
public string ContentType { get; set; }
public Dictionary<string, string> Headers { get; }
}
}

View File

@@ -0,0 +1,184 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.Eventing
{
public class DlnaEventManager : IDlnaEventManager
{
private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
}
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
{
var subscription = GetSubscription(subscriptionId, false);
if (subscription != null)
{
subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
int timeoutSeconds = subscription.TimeoutSeconds;
subscription.SubscriptionTime = DateTime.UtcNow;
_logger.LogDebug(
"Renewing event subscription for {0} with timeout of {1} to {2}",
subscription.NotificationType,
timeoutSeconds,
subscription.CallbackUrl);
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
}
return new EventSubscriptionResponse(string.Empty, "text/plain");
}
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
{
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
_logger.LogDebug(
"Creating event subscription for {0} with timeout of {1} to {2}",
notificationType,
timeout,
callbackUrl);
_subscriptions.TryAdd(id, new EventSubscription
{
Id = id,
CallbackUrl = callbackUrl,
SubscriptionTime = DateTime.UtcNow,
TimeoutSeconds = timeout,
NotificationType = notificationType
});
return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout);
}
private int? ParseTimeout(string header)
{
if (!string.IsNullOrEmpty(header))
{
// Starts with SECOND-
if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
{
return val;
}
}
return null;
}
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
{
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
_subscriptions.TryRemove(subscriptionId, out _);
return new EventSubscriptionResponse(string.Empty, "text/plain");
}
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
{
var response = new EventSubscriptionResponse(string.Empty, "text/plain");
response.Headers["SID"] = subscriptionId;
response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(CultureInfo.InvariantCulture)) : requestedTimeoutString;
return response;
}
public EventSubscription GetSubscription(string id)
{
return GetSubscription(id, false);
}
private EventSubscription GetSubscription(string id, bool throwOnMissing)
{
if (!_subscriptions.TryGetValue(id, out EventSubscription e) && throwOnMissing)
{
throw new ResourceNotFoundException("Event with Id " + id + " not found.");
}
return e;
}
public Task TriggerEvent(string notificationType, IDictionary<string, string> stateVariables)
{
var subs = _subscriptions.Values
.Where(i => !i.IsExpired && string.Equals(notificationType, i.NotificationType, StringComparison.OrdinalIgnoreCase))
.ToList();
var tasks = subs.Select(i => TriggerEvent(i, stateVariables));
return Task.WhenAll(tasks);
}
private async Task TriggerEvent(EventSubscription subscription, IDictionary<string, string> stateVariables)
{
var builder = new StringBuilder();
builder.Append("<?xml version=\"1.0\"?>");
builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
foreach (var key in stateVariables.Keys)
{
builder.Append("<e:property>")
.Append('<')
.Append(key)
.Append('>')
.Append(stateVariables[key])
.Append("</")
.Append(key)
.Append('>')
.Append("</e:property>");
}
builder.Append("</e:propertyset>");
using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml);
options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType);
options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange");
options.Headers.TryAddWithoutValidation("SID", subscription.Id);
options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(CultureInfo.InvariantCulture));
try
{
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
catch
{
// Already logged at lower levels
}
finally
{
subscription.IncrementTriggerCount();
}
}
}
}

View File

@@ -0,0 +1,35 @@
#nullable disable
#pragma warning disable CS1591
using System;
namespace Emby.Dlna.Eventing
{
public class EventSubscription
{
public string Id { get; set; }
public string CallbackUrl { get; set; }
public string NotificationType { get; set; }
public DateTime SubscriptionTime { get; set; }
public int TimeoutSeconds { get; set; }
public long TriggerCount { get; set; }
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
public void IncrementTriggerCount()
{
if (TriggerCount == long.MaxValue)
{
TriggerCount = 0;
}
TriggerCount++;
}
}
}

View File

@@ -0,0 +1,8 @@
#pragma warning disable CS1591
namespace Emby.Dlna
{
public interface IConnectionManager : IDlnaEventManager, IUpnpService
{
}
}

View File

@@ -0,0 +1,8 @@
#pragma warning disable CS1591
namespace Emby.Dlna
{
public interface IContentDirectory : IDlnaEventManager, IUpnpService
{
}
}

View File

@@ -0,0 +1,33 @@
#pragma warning disable CS1591
namespace Emby.Dlna
{
public interface IDlnaEventManager
{
/// <summary>
/// Cancels the event subscription.
/// </summary>
/// <param name="subscriptionId">The subscription identifier.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse CancelEventSubscription(string subscriptionId);
/// <summary>
/// Renews the event subscription.
/// </summary>
/// <param name="subscriptionId">The subscription identifier.</param>
/// <param name="notificationType">The notification type.</param>
/// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
/// <param name="callbackUrl">The callback url.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl);
/// <summary>
/// Creates the event subscription.
/// </summary>
/// <param name="notificationType">The notification type.</param>
/// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
/// <param name="callbackUrl">The callback url.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
}
}

View File

@@ -0,0 +1,8 @@
#pragma warning disable CS1591
namespace Emby.Dlna
{
public interface IMediaReceiverRegistrar : IDlnaEventManager, IUpnpService
{
}
}

22
Emby.Dlna/IUpnpService.cs Normal file
View File

@@ -0,0 +1,22 @@
#pragma warning disable CS1591
using System.Threading.Tasks;
namespace Emby.Dlna
{
public interface IUpnpService
{
/// <summary>
/// Gets the content directory XML.
/// </summary>
/// <returns>System.String.</returns>
string GetServiceXml();
/// <summary>
/// Processes the control request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>ControlResponse.</returns>
Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
Emby.Dlna/Images/logo48.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
Emby.Dlna/Images/logo48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,474 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading.Tasks;
using Emby.Dlna.PlayTo;
using Emby.Dlna.Ssdp;
using Jellyfin.Networking.Configuration;
using Jellyfin.Networking.Manager;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
using Rssdp;
using Rssdp.Infrastructure;
namespace Emby.Dlna.Main
{
public sealed class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
{
private readonly IServerConfigurationManager _config;
private readonly ILogger<DlnaEntryPoint> _logger;
private readonly IServerApplicationHost _appHost;
private readonly ISessionManager _sessionManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IDlnaManager _dlnaManager;
private readonly IImageProcessor _imageProcessor;
private readonly IUserDataManager _userDataManager;
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
private readonly object _syncLock = new object();
private readonly bool _disabled;
private PlayToManager _manager;
private SsdpDevicePublisher _publisher;
private ISsdpCommunicationsServer _communicationsServer;
private bool _disposed;
public DlnaEntryPoint(
IServerConfigurationManager config,
ILoggerFactory loggerFactory,
IServerApplicationHost appHost,
ISessionManager sessionManager,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IUserManager userManager,
IDlnaManager dlnaManager,
IImageProcessor imageProcessor,
IUserDataManager userDataManager,
ILocalizationManager localizationManager,
IMediaSourceManager mediaSourceManager,
IDeviceDiscovery deviceDiscovery,
IMediaEncoder mediaEncoder,
ISocketFactory socketFactory,
INetworkManager networkManager,
IUserViewManager userViewManager,
ITVSeriesManager tvSeriesManager)
{
_config = config;
_appHost = appHost;
_sessionManager = sessionManager;
_httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
_userManager = userManager;
_dlnaManager = dlnaManager;
_imageProcessor = imageProcessor;
_userDataManager = userDataManager;
_localization = localizationManager;
_mediaSourceManager = mediaSourceManager;
_deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder;
_socketFactory = socketFactory;
_networkManager = networkManager;
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
ContentDirectory = new ContentDirectory.ContentDirectoryService(
dlnaManager,
userDataManager,
imageProcessor,
libraryManager,
config,
userManager,
loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
httpClientFactory,
localizationManager,
mediaSourceManager,
userViewManager,
mediaEncoder,
tvSeriesManager);
ConnectionManager = new ConnectionManager.ConnectionManagerService(
dlnaManager,
config,
loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
httpClientFactory);
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
httpClientFactory,
config);
Current = this;
var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
{
_logger.LogError("The DLNA specification does not support HTTPS.");
}
}
public static DlnaEntryPoint Current { get; private set; }
/// <summary>
/// Gets a value indicating whether the dlna server is enabled.
/// </summary>
public static bool Enabled { get; private set; }
public IContentDirectory ContentDirectory { get; private set; }
public IConnectionManager ConnectionManager { get; private set; }
public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
public async Task RunAsync()
{
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
if (_disabled)
{
// No use starting as dlna won't work, as we're running purely on HTTPS.
return;
}
ReloadComponents();
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
}
private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
{
ReloadComponents();
}
}
private void ReloadComponents()
{
var options = _config.GetDlnaConfiguration();
Enabled = options.EnableServer;
StartSsdpHandler();
if (options.EnableServer)
{
StartDevicePublisher(options);
}
else
{
DisposeDevicePublisher();
}
if (options.EnablePlayTo)
{
StartPlayToManager();
}
else
{
DisposePlayToManager();
}
}
private void StartSsdpHandler()
{
try
{
if (_communicationsServer == null)
{
var enableMultiSocketBinding = OperatingSystem.IsWindows() ||
OperatingSystem.IsLinux();
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{
IsShared = true
};
StartDeviceDiscovery(_communicationsServer);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting ssdp handlers");
}
}
private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer)
{
try
{
if (communicationsServer != null)
{
((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting device discovery");
}
}
private void DisposeDeviceDiscovery()
{
try
{
_logger.LogInformation("Disposing DeviceDiscovery");
((DeviceDiscovery)_deviceDiscovery).Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error stopping device discovery");
}
}
public void StartDevicePublisher(Configuration.DlnaOptions options)
{
if (!options.BlastAliveMessages)
{
return;
}
if (_publisher != null)
{
return;
}
try
{
_publisher = new SsdpDevicePublisher(
_communicationsServer,
MediaBrowser.Common.System.OperatingSystem.Name,
Environment.OSVersion.VersionString,
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
{
LogFunction = (msg) => _logger.LogDebug("{Msg}", msg),
SupportPnpRootDevice = false
};
RegisterServerEndpoints();
_publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
}
catch (Exception ex)
{
_logger.LogError(ex, "Error registering endpoint");
}
}
private void RegisterServerEndpoints()
{
var udn = CreateUuid(_appHost.SystemId);
var descriptorUri = "/dlna/" + udn + "/description.xml";
var bindAddresses = NetworkManager.CreateCollection(
_networkManager.GetInternalBindAddresses()
.Where(i => i.AddressFamily == AddressFamily.InterNetwork || (i.AddressFamily == AddressFamily.InterNetworkV6 && i.Address.ScopeId != 0)));
if (bindAddresses.Count == 0)
{
// No interfaces returned, so use loopback.
bindAddresses = _networkManager.GetLoopbacks();
}
foreach (IPNetAddress address in bindAddresses)
{
if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
// Not supporting IPv6 right now
continue;
}
// Limit to LAN addresses only
if (!_networkManager.IsInLocalNetwork(address))
{
continue;
}
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, address);
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(address, false) + descriptorUri);
var device = new SsdpRootDevice
{
CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document.
Address = address.Address,
PrefixLength = address.PrefixLength,
FriendlyName = "Jellyfin",
Manufacturer = "Jellyfin",
ModelName = "Jellyfin Server",
Uuid = udn
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
};
SetProperies(device, fullService);
_publisher.AddDevice(device);
var embeddedDevices = new[]
{
"urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:schemas-upnp-org:service:ConnectionManager:1",
// "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
};
foreach (var subDevice in embeddedDevices)
{
var embeddedDevice = new SsdpEmbeddedDevice
{
FriendlyName = device.FriendlyName,
Manufacturer = device.Manufacturer,
ModelName = device.ModelName,
Uuid = udn
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
};
SetProperies(embeddedDevice, subDevice);
device.AddDevice(embeddedDevice);
}
}
}
private string CreateUuid(string text)
{
if (!Guid.TryParse(text, out var guid))
{
guid = text.GetMD5();
}
return guid.ToString("D", CultureInfo.InvariantCulture);
}
private void SetProperies(SsdpDevice device, string fullDeviceType)
{
var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase);
var serviceParts = service.Split(':');
var deviceTypeNamespace = serviceParts[0].Replace('.', '-');
device.DeviceTypeNamespace = deviceTypeNamespace;
device.DeviceClass = serviceParts[1];
device.DeviceType = serviceParts[2];
}
private void StartPlayToManager()
{
lock (_syncLock)
{
if (_manager != null)
{
return;
}
try
{
_manager = new PlayToManager(
_logger,
_sessionManager,
_libraryManager,
_userManager,
_dlnaManager,
_appHost,
_imageProcessor,
_deviceDiscovery,
_httpClientFactory,
_userDataManager,
_localization,
_mediaSourceManager,
_mediaEncoder);
_manager.Start();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting PlayTo manager");
}
}
}
private void DisposePlayToManager()
{
lock (_syncLock)
{
if (_manager != null)
{
try
{
_logger.LogInformation("Disposing PlayToManager");
_manager.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing PlayTo manager");
}
_manager = null;
}
}
}
public void DisposeDevicePublisher()
{
if (_publisher != null)
{
_logger.LogInformation("Disposing SsdpDevicePublisher");
_publisher.Dispose();
_publisher = null;
}
}
/// <inheritdoc />
public void Dispose()
{
if (_disposed)
{
return;
}
DisposeDevicePublisher();
DisposePlayToManager();
DisposeDeviceDiscovery();
if (_communicationsServer != null)
{
_logger.LogInformation("Disposing SsdpCommunicationsServer");
_communicationsServer.Dispose();
_communicationsServer = null;
}
ContentDirectory = null;
ConnectionManager = null;
MediaReceiverRegistrar = null;
Current = null;
_disposed = true;
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Xml;
using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.MediaReceiverRegistrar
{
/// <summary>
/// Defines the <see cref="ControlHandler" />.
/// </summary>
public class ControlHandler : BaseControlHandler
{
/// <summary>
/// Initializes a new instance of the <see cref="ControlHandler"/> class.
/// </summary>
/// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
/// <param name="logger">The <see cref="ILogger"/> for use with the <see cref="ControlHandler"/> instance.</param>
public ControlHandler(IServerConfigurationManager config, ILogger logger)
: base(config, logger)
{
}
/// <inheritdoc />
protected override void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter)
{
if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
{
HandleIsAuthorized(xmlWriter);
return;
}
if (string.Equals(methodName, "IsValidated", StringComparison.OrdinalIgnoreCase))
{
HandleIsValidated(xmlWriter);
return;
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
/// <summary>
/// Records that the handle is authorized in the xml stream.
/// </summary>
/// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
private static void HandleIsAuthorized(XmlWriter xmlWriter)
=> xmlWriter.WriteElementString("Result", "1");
/// <summary>
/// Records that the handle is validated in the xml stream.
/// </summary>
/// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
private static void HandleIsValidated(XmlWriter xmlWriter)
=> xmlWriter.WriteElementString("Result", "1");
}
}

View File

@@ -0,0 +1,46 @@
using System.Net.Http;
using System.Threading.Tasks;
using Emby.Dlna.Service;
using MediaBrowser.Controller.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.MediaReceiverRegistrar
{
/// <summary>
/// Defines the <see cref="MediaReceiverRegistrarService" />.
/// </summary>
public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar
{
private readonly IServerConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="MediaReceiverRegistrarService"/> class.
/// </summary>
/// <param name="logger">The <see cref="ILogger{MediaReceiverRegistrarService}"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param>
/// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param>
public MediaReceiverRegistrarService(
ILogger<MediaReceiverRegistrarService> logger,
IHttpClientFactory httpClientFactory,
IServerConfigurationManager config)
: base(logger, httpClientFactory)
{
_config = config;
}
/// <inheritdoc />
public string GetServiceXml()
{
return MediaReceiverRegistrarXmlBuilder.GetXml();
}
/// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
return new ControlHandler(
_config,
Logger)
.ProcessControlRequestAsync(request);
}
}
}

View File

@@ -0,0 +1,90 @@
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
namespace Emby.Dlna.MediaReceiverRegistrar
{
/// <summary>
/// Defines the <see cref="MediaReceiverRegistrarXmlBuilder" />.
/// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drmnd/5d37515e-7a63-4709-8258-8fd4e0ed4482.
/// </summary>
public static class MediaReceiverRegistrarXmlBuilder
{
/// <summary>
/// Retrieves an XML description of the X_MS_MediaReceiverRegistrar.
/// </summary>
/// <returns>An XML representation of this service.</returns>
public static string GetXml()
{
return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
}
/// <summary>
/// The a list of all the state variables for this invocation.
/// </summary>
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
private static IEnumerable<StateVariable> GetStateVariables()
{
var list = new List<StateVariable>
{
new StateVariable
{
Name = "AuthorizationGrantedUpdateID",
DataType = "ui4",
SendsEvents = true
},
new StateVariable
{
Name = "A_ARG_TYPE_DeviceID",
DataType = "string",
SendsEvents = false
},
new StateVariable
{
Name = "AuthorizationDeniedUpdateID",
DataType = "ui4",
SendsEvents = true
},
new StateVariable
{
Name = "ValidationSucceededUpdateID",
DataType = "ui4",
SendsEvents = true
},
new StateVariable
{
Name = "A_ARG_TYPE_RegistrationRespMsg",
DataType = "bin.base64",
SendsEvents = false
},
new StateVariable
{
Name = "A_ARG_TYPE_RegistrationReqMsg",
DataType = "bin.base64",
SendsEvents = false
},
new StateVariable
{
Name = "ValidationRevokedUpdateID",
DataType = "ui4",
SendsEvents = true
},
new StateVariable
{
Name = "A_ARG_TYPE_Result",
DataType = "int",
SendsEvents = false
}
};
return list;
}
}
}

View File

@@ -0,0 +1,187 @@
using System.Collections.Generic;
using Emby.Dlna.Common;
namespace Emby.Dlna.MediaReceiverRegistrar
{
/// <summary>
/// Defines the <see cref="ServiceActionListBuilder" />.
/// </summary>
public static class ServiceActionListBuilder
{
/// <summary>
/// Returns a list of services that this instance provides.
/// </summary>
/// <returns>An <see cref="IEnumerable{ServiceAction}"/>.</returns>
public static IEnumerable<ServiceAction> GetActions()
{
return new[]
{
GetIsValidated(),
GetIsAuthorized(),
GetRegisterDevice(),
GetGetAuthorizationDeniedUpdateID(),
GetGetAuthorizationGrantedUpdateID(),
GetGetValidationRevokedUpdateID(),
GetGetValidationSucceededUpdateID()
};
}
/// <summary>
/// Returns the action details for "IsValidated".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetIsValidated()
{
var action = new ServiceAction
{
Name = "IsValidated"
};
action.ArgumentList.Add(new Argument
{
Name = "DeviceID",
Direction = "in"
});
action.ArgumentList.Add(new Argument
{
Name = "Result",
Direction = "out"
});
return action;
}
/// <summary>
/// Returns the action details for "IsAuthorized".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetIsAuthorized()
{
var action = new ServiceAction
{
Name = "IsAuthorized"
};
action.ArgumentList.Add(new Argument
{
Name = "DeviceID",
Direction = "in"
});
action.ArgumentList.Add(new Argument
{
Name = "Result",
Direction = "out"
});
return action;
}
/// <summary>
/// Returns the action details for "RegisterDevice".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetRegisterDevice()
{
var action = new ServiceAction
{
Name = "RegisterDevice"
};
action.ArgumentList.Add(new Argument
{
Name = "RegistrationReqMsg",
Direction = "in"
});
action.ArgumentList.Add(new Argument
{
Name = "RegistrationRespMsg",
Direction = "out"
});
return action;
}
/// <summary>
/// Returns the action details for "GetValidationSucceededUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetValidationSucceededUpdateID()
{
var action = new ServiceAction
{
Name = "GetValidationSucceededUpdateID"
};
action.ArgumentList.Add(new Argument
{
Name = "ValidationSucceededUpdateID",
Direction = "out"
});
return action;
}
/// <summary>
/// Returns the action details for "GetGetAuthorizationDeniedUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetAuthorizationDeniedUpdateID()
{
var action = new ServiceAction
{
Name = "GetAuthorizationDeniedUpdateID"
};
action.ArgumentList.Add(new Argument
{
Name = "AuthorizationDeniedUpdateID",
Direction = "out"
});
return action;
}
/// <summary>
/// Returns the action details for "GetValidationRevokedUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetValidationRevokedUpdateID()
{
var action = new ServiceAction
{
Name = "GetValidationRevokedUpdateID"
};
action.ArgumentList.Add(new Argument
{
Name = "ValidationRevokedUpdateID",
Direction = "out"
});
return action;
}
/// <summary>
/// Returns the action details for "GetAuthorizationGrantedUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetAuthorizationGrantedUpdateID()
{
var action = new ServiceAction
{
Name = "GetAuthorizationGrantedUpdateID"
};
action.ArgumentList.Add(new Argument
{
Name = "AuthorizationGrantedUpdateID",
Direction = "out"
});
return action;
}
}
}

1312
Emby.Dlna/PlayTo/Device.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Common;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.PlayTo
{
public class DeviceInfo
{
private readonly List<DeviceService> _services = new List<DeviceService>();
private string _baseUrl = string.Empty;
public DeviceInfo()
{
Name = "Generic Device";
}
public string UUID { get; set; }
public string Name { get; set; }
public string ModelName { get; set; }
public string ModelNumber { get; set; }
public string ModelDescription { get; set; }
public string ModelUrl { get; set; }
public string Manufacturer { get; set; }
public string SerialNumber { get; set; }
public string ManufacturerUrl { get; set; }
public string PresentationUrl { get; set; }
public string BaseUrl
{
get => _baseUrl;
set => _baseUrl = value;
}
public DeviceIcon Icon { get; set; }
public List<DeviceService> Services => _services;
public DeviceIdentification ToDeviceIdentification()
{
return new DeviceIdentification
{
Manufacturer = Manufacturer,
ModelName = ModelName,
ModelNumber = ModelNumber,
FriendlyName = Name,
ManufacturerUrl = ManufacturerUrl,
ModelUrl = ModelUrl,
ModelDescription = ModelDescription,
SerialNumber = SerialNumber
};
}
}
}

View File

@@ -0,0 +1,19 @@
#pragma warning disable CS1591
using System;
namespace Emby.Dlna.PlayTo
{
public class MediaChangedEventArgs : EventArgs
{
public MediaChangedEventArgs(UBaseObject oldMediaInfo, UBaseObject newMediaInfo)
{
OldMediaInfo = oldMediaInfo;
NewMediaInfo = newMediaInfo;
}
public UBaseObject OldMediaInfo { get; set; }
public UBaseObject NewMediaInfo { get; set; }
}
}

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