Compare commits

...

213 Commits

Author SHA1 Message Date
Cody Robibero
8b4a36d6f7 Merge pull request #6898 from jonas-resch/support-external-audio-files
Add support for external audio files
2021-12-11 12:05:34 -07:00
Cody Robibero
4d1b824651 Merge pull request #6902 from cvium/migrate_networkconfig
Migrate network configuration safely
2021-12-11 12:04:58 -07:00
Cody Robibero
fbc79cb4ce Merge pull request #6965 from bendardenne/patch-1
Add artist to '/' split whitelist
2021-12-11 12:04:39 -07:00
WWWesten
4d446eb614 Translated using Weblate (Esperanto)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/
2021-12-10 02:05:47 -05:00
Benoît Dardenne
de2d292197 Added artist to '/' split whitelist 2021-12-09 16:07:57 +01:00
WWWesten
13ac3e3665 Translated using Weblate (Macedonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/
2021-12-08 14:05:53 -05:00
WWWesten
5c407dbab6 Translated using Weblate (Romanian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ro/
2021-12-08 14:05:53 -05:00
Weevild
f50b2f7959 Translated using Weblate (Swedish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/
2021-12-08 14:05:53 -05:00
archon eleven
1acb9e1d45 Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-12-08 14:05:53 -05:00
WWWesten
6640f3f782 Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-12-08 14:05:53 -05:00
WWWesten
8aef2cb282 Translated using Weblate (Lithuanian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/
2021-12-08 14:05:53 -05:00
WWWesten
8bd331f5fa Translated using Weblate (Croatian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hr/
2021-12-08 14:05:53 -05:00
Muhammed Aljailane
ee80602a0d Translated using Weblate (Arabic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
2021-12-08 14:05:53 -05:00
Jonas Resch
03b3f08354 Format code in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-08 18:55:28 +01:00
Jonas Resch
65833076db Add "Async" suffix to AddExternalAudio method
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-08 16:49:27 +01:00
Jonas Resch
e18d966874 Add "Async" suffix to AddExternalAudio method
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-08 16:49:20 +01:00
Jonas Resch
4cdb590291 Exclude .strm files when searching for external audio files 2021-12-08 10:18:09 +01:00
Jonas Resch
d47811bdaf Fix wrong ffmpeg map argument due to wrong calculation 2021-12-08 10:17:25 +01:00
Jonas Resch
01a0a4a87c Add audioResolver argument to FFProbeVideoInfo initialization 2021-12-08 10:16:48 +01:00
Jonas Resch
87a6fdf847 Merge branch 'support-external-audio-files' of github.com:jonas-resch/jellyfin into support-external-audio-files 2021-12-08 09:55:16 +01:00
Claus Vium
a327b43ab7 Update MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs 2021-12-07 20:28:51 +01:00
Claus Vium
3f69eeab27 Merge branch 'master' into support-external-audio-files 2021-12-07 20:16:32 +01:00
Claus Vium
dd8b9e9d23 Merge pull request #6953 from matthiasdv/mdv/harden-systemd-service
Add more hardening to systemd service
2021-12-07 19:46:45 +01:00
matthiasdv
3176a4ddd9 add more hardening to systemd service 2021-12-06 22:40:00 +01:00
Bond-009
9cafa2cab4 Merge pull request #6946 from jellyfin/dependabot/nuget/libse-3.6.4 2021-12-06 14:46:10 +01:00
dependabot[bot]
c4c7d7431f Bump libse from 3.6.2 to 3.6.4
Bumps [libse](https://github.com/SubtitleEdit/subtitleedit) from 3.6.2 to 3.6.4.
- [Release notes](https://github.com/SubtitleEdit/subtitleedit/releases)
- [Changelog](https://github.com/SubtitleEdit/subtitleedit/blob/master/Changelog.txt)
- [Commits](https://github.com/SubtitleEdit/subtitleedit/compare/3.6.2...3.6.4)

---
updated-dependencies:
- dependency-name: libse
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 13:29:10 +00:00
Bond-009
f2648f3eee Merge pull request #6947 from jellyfin/dependabot/nuget/BDInfo-0.7.6.2 2021-12-06 14:28:17 +01:00
dependabot[bot]
29095ab390 Bump BDInfo from 0.7.6.1 to 0.7.6.2
Bumps [BDInfo](https://github.com/jellyfin/BDInfo) from 0.7.6.1 to 0.7.6.2.
- [Release notes](https://github.com/jellyfin/BDInfo/releases)
- [Commits](https://github.com/jellyfin/BDInfo/commits)

---
updated-dependencies:
- dependency-name: BDInfo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 12:01:19 +00:00
WWWesten
b9b3959223 Translated using Weblate (Punjabi)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pa/
2021-12-06 03:45:54 -05:00
WWWesten
9b71bf8cfe Translated using Weblate (Nepali)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ne/
2021-12-06 03:45:54 -05:00
WWWesten
5eff80fb8c Translated using Weblate (Macedonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/
2021-12-06 03:45:54 -05:00
mikixd586
2d77c729f2 Translated using Weblate (Serbian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/
2021-12-06 03:45:54 -05:00
mio2
9d387c542a Translated using Weblate (Japanese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ja/
2021-12-06 03:45:54 -05:00
Bas Goos
60f4d70cc5 Translated using Weblate (Dutch)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nl/
2021-12-06 03:45:54 -05:00
oxixes
bd48b74d5b Translated using Weblate (Spanish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es/
2021-12-06 03:45:54 -05:00
Mehyar
838adaea48 Translated using Weblate (Arabic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
2021-12-06 03:45:54 -05:00
archon eleven
46543ead27 Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-12-06 03:45:54 -05:00
Cody Robibero
707e5bab97 Merge pull request #6941 from holahmeds/mime-type
Use MimeTypes package to determine MIME type
2021-12-04 17:55:24 -07:00
Ahmed Rafiq
cf75f99f0e Update unit test name
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-04 21:14:52 +06:00
Ahmed Rafiq
fa6f6515a6 Update unit test name
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-12-04 21:14:16 +06:00
Ahmed Rafiq
0e491025ae Refactor MimeTypes.ToExtension()
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-12-04 21:13:18 +06:00
Ahmed Rafiq
40e05a7993 Update documentation 2021-12-04 21:06:51 +06:00
Ahmed Rafiq
6193fdea69 Use MimeTypes package to determine MIME type
This simplifies the code since we don't have to keep large mappings of extensions and MIME types.

We still keep the ability to override the mappings for:
- filling in entries not present in the package, for e.g. ".azw3"
- picking preferred extensions, for e.g. MimeTypes provides ".conf" as a possible extionsion for "text/plain", and while that is correct, ".txt" would be preferrable
- compatibility reasons
2021-12-04 20:08:16 +06:00
Jonas Resch
ca2d94ee97 Merge branch 'support-external-audio-files' of github.com:jonas-resch/jellyfin into support-external-audio-files 2021-12-03 19:19:53 +01:00
Jonas Resch
99a48554a6 Optimize calculation of external audio stream index in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-12-03 19:19:22 +01:00
Jonas Resch
120828d8d0 Replace escaped quote string with quote character in MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-12-03 19:18:43 +01:00
Cody Robibero
9cea773d29 Merge pull request #6874 from 1337joe/tmdb-image-size-options 2021-12-03 07:03:02 -07:00
Cody Robibero
a474e4cf3b Merge pull request #6914 from marius-luca-87/dlna 2021-12-02 06:56:07 -07:00
Cody Robibero
b1025121b8 Merge pull request #6929 from Bond-009/jsoncontent 2021-12-02 06:55:28 -07:00
Bond-009
2029076ce8 Merge pull request #6936 from cvium/ffprobeprovider_tweak 2021-12-02 13:42:45 +01:00
cvium
5535b9c01f Reduce allocations 2021-12-02 11:21:59 +01:00
Jonas Resch
180e2dc329 Prevent crashes in specific scenarios 2021-12-01 21:05:43 +01:00
Bond_009
40be86eec0 Use PostAsJsonAsync where possible 2021-12-01 15:58:23 +01:00
Bond_009
beafd6eaab Use JsonContent where possible
Should reduce the # of allocated bytes
2021-12-01 15:40:06 +01:00
hoanghuy309
f6d8c19a7a Translated using Weblate (Vietnamese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/
2021-11-30 17:48:37 -05:00
snieguzary
2da7777e6d Translated using Weblate (Polish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pl/
2021-11-30 17:48:37 -05:00
WWWesten
a0df79d8a5 Translated using Weblate (Persian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fa/
2021-11-30 17:48:37 -05:00
Bond-009
01b95cf8e6 Merge pull request #6927 from 1337joe/use-ssl-for-tmdb-images 2021-11-30 23:43:26 +01:00
Jonas Resch
6bbfcf1906 Add documentation to AudioResolver class 2021-11-30 21:05:43 +01:00
Jonas Resch
7b50048020 Add ConfigureAwait true in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 20:45:47 +01:00
Jonas Resch
a9a53dc657 Add ConfigureAwait true in MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 20:45:21 +01:00
Jonas Resch
0d8170cedb Move variable in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 20:44:57 +01:00
Jonas Resch
1a35690834 Don't disable warnings in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 20:44:16 +01:00
Jonas Resch
c61b9ef05a Fix warning due to new line after opening bracket 2021-11-30 19:52:44 +01:00
Jonas Resch
b5b994b22f Fix compiler warning due to missing EnumeratorCancellation attribute 2021-11-30 19:31:46 +01:00
Jonas Resch
0894a6193f Implement coding standards from 2nd code feedback 2021-11-30 19:31:46 +01:00
Jonas Resch
9d34d6339a Change return type from List<string> to IEnumerable<string> in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
bbf1399826 Check language for null or empty instead of only null in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
d016d483ae Change return type from Task<List<MediaStream>> to Task<IAsyncEnumerable<MediaStream>> in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
61b191d345 Fix indentation in MediaBrowser.Providers/MediaInfo/AudioResolver.cs
If statement which checks if filename of audio and video file match or if audio file starts with video filename

Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
9433072f90 Only search in video folder for external audio files
Don't search in video metadata folder since audio files won't be stored there

Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
a3c5afa443 Add ConfigureAwait false MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-11-30 19:31:46 +01:00
Jonas Resch
f1862f9b1a Add ConfigureAwait false to MediaBrowser.Providers/MediaInfo/AudioResolver.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-11-30 19:31:46 +01:00
Jonas Resch
a68e58556c Implement code feedback
- Rewrite AudioResolver
- Use async & await instead of .Result
- Add support for audio containers with multiple audio streams (e.g.
  mka)
- Fix bug when using external subtitle and external audio streams at the
  same time
2021-11-30 19:31:46 +01:00
Jonas Resch
5e91f50c43 Update CONTRIBUTORS.md 2021-11-30 19:31:46 +01:00
Jonas Resch
c1a8385c9c Shorten calculation of audio startIndex in MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-30 19:31:46 +01:00
Jonas Resch
9978164438 Add support for external audio files 2021-11-30 19:31:46 +01:00
Marius Luca
eaa003775f - take into account the streams dlnaheaders query parameter set by the DidlBuilder NormalizeDlnaMediaUrl function 2021-11-30 16:09:02 +02:00
Joe Rogers
e778462955 Use SSL for tmdb images 2021-11-30 14:31:32 +01:00
WWWesten
c677b4f6b7 Translated using Weblate (Mongolian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mn/
2021-11-29 07:05:47 -05:00
WWWesten
20b0499b00 Translated using Weblate (Telugu)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/te/
2021-11-29 07:05:47 -05:00
rimasx
09f2456919 Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-29 07:05:47 -05:00
WWWesten
bcd84dedda Translated using Weblate (Pirate)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pr/
2021-11-29 07:05:47 -05:00
WWWesten
2496c5d589 Translated using Weblate (Vietnamese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/
2021-11-29 07:05:47 -05:00
WWWesten
19c9319d2c Translated using Weblate (Albanian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sq/
2021-11-29 07:05:47 -05:00
WWWesten
6ef9ac6e53 Translated using Weblate (Tamil)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ta/
2021-11-29 07:05:47 -05:00
WontTell
502a5fc932 Translated using Weblate (Spanish (Latin America))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/
2021-11-29 07:05:47 -05:00
WWWesten
02ce95edaf Translated using Weblate (Serbian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sr/
2021-11-29 07:05:47 -05:00
WWWesten
5addb24e9b Translated using Weblate (Portuguese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/
2021-11-29 07:05:47 -05:00
wolong gl
dc27e7618f Translated using Weblate (Chinese (Simplified))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/
2021-11-29 07:05:47 -05:00
Weevild
3e1bbdd3d4 Translated using Weblate (Swedish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sv/
2021-11-29 07:05:47 -05:00
WWWesten
2ff786fd3d Translated using Weblate (Malay)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ms/
2021-11-29 07:05:47 -05:00
Bond-009
2a09d4244c Merge pull request #6923 from cvium/query_a_bit_less 2021-11-29 13:00:38 +01:00
cvium
beef6f0855 Don't query series twice 2021-11-28 19:56:31 +01:00
Cody Robibero
80c7119537 Merge pull request #6915 from 1337joe/subtitle-parsing-fix 2021-11-27 16:55:51 -07:00
Joe Rogers
1df5b5034b Address suppressed warnings 2021-11-27 20:35:18 +01:00
Joe Rogers
4a20ae6cb4 Allow default/forced tag without setting language 2021-11-27 20:13:21 +01:00
Claus Vium
a87b87825d Update Jellyfin.Server/Migrations/MigrationRunner.cs
Co-authored-by: Cody Robibero <cody@robibe.ro>
2021-11-27 16:01:27 +01:00
Cody Robibero
976e3160b8 Merge pull request #6907 from marius-luca-87/dlna 2021-11-27 07:47:54 -07:00
Claus Vium
ca887518dd Use the Memory overload for ReadAsync (#6865) 2021-11-27 03:23:46 +01:00
Marius Luca
5b5ae1ef52 - enable seek function when direct streaming over DLNA 2021-11-26 18:09:34 +02:00
WWWesten
3eec137100 Translated using Weblate (Spanish (Latin America))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/
2021-11-26 04:09:26 -05:00
WontTell
402a3797a4 Translated using Weblate (Spanish (Latin America))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/
2021-11-26 04:09:26 -05:00
cvium
0b871505a6 Remove stray datamember 2021-11-24 14:36:01 +01:00
cvium
0485ff1899 Create a store key constant for network 2021-11-24 13:42:14 +01:00
cvium
69df004b9f Migrate network configuration safely 2021-11-24 13:00:12 +01:00
N4v41
8b9ff893b3 Translated using Weblate (Portuguese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/
2021-11-24 06:28:00 -05:00
Pedro Almeida
b3d5383073 Translated using Weblate (Portuguese (Portugal))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_PT/
2021-11-24 06:28:00 -05:00
Csaba
9d5f328a09 Translated using Weblate (Hungarian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/
2021-11-24 06:27:59 -05:00
WWWesten
f469ee3010 Translated using Weblate (English)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en/
2021-11-24 06:27:59 -05:00
Bond-009
e30ac91f16 Merge pull request #6900 from ianjazz246/local-time-test 2021-11-24 09:14:22 +01:00
ianjazz246
95183c365a Use local time for RecordingHelperTests 2021-11-23 12:57:05 -08:00
Joe Rogers
0af5e60094 Address review comments
Store null instead of calculating scaled image sizes.
Add endpoint to provide TMDb image size options.
2021-11-22 21:08:07 +01:00
Cody Robibero
3c030740ea Merge pull request #6895 from jellyfin/dependabot/nuget/prometheus-net.AspNetCore-5.0.2
Bump prometheus-net.AspNetCore from 5.0.1 to 5.0.2
2021-11-22 06:50:16 -07:00
dependabot[bot]
bb9ce98379 Bump prometheus-net.AspNetCore from 5.0.1 to 5.0.2
Bumps [prometheus-net.AspNetCore](https://github.com/prometheus-net/prometheus-net) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/prometheus-net/prometheus-net/releases)
- [Changelog](https://github.com/prometheus-net/prometheus-net/blob/master/History)
- [Commits](https://github.com/prometheus-net/prometheus-net/compare/v5.0.1...v5.0.2)

---
updated-dependencies:
- dependency-name: prometheus-net.AspNetCore
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 13:33:18 +00:00
Cody Robibero
02fda265b8 Merge pull request #6894 from jellyfin/dependabot/nuget/prometheus-net-5.0.2
Bump prometheus-net from 5.0.1 to 5.0.2
2021-11-22 06:32:48 -07:00
Cody Robibero
501919df35 Merge pull request #6893 from jellyfin/dependabot/nuget/sharpcompress-0.30.1
Bump sharpcompress from 0.30.0 to 0.30.1
2021-11-22 06:32:35 -07:00
Cody Robibero
107909de30 Merge pull request #6896 from jellyfin/dependabot/nuget/Serilog.Sinks.Console-4.0.1
Bump Serilog.Sinks.Console from 4.0.0 to 4.0.1
2021-11-22 06:32:19 -07:00
dependabot[bot]
052b667d6a Bump Serilog.Sinks.Console from 4.0.0 to 4.0.1
Bumps [Serilog.Sinks.Console](https://github.com/serilog/serilog-sinks-console) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/serilog/serilog-sinks-console/releases)
- [Commits](https://github.com/serilog/serilog-sinks-console/compare/v4.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: Serilog.Sinks.Console
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 12:01:30 +00:00
dependabot[bot]
e5ea607853 Bump prometheus-net from 5.0.1 to 5.0.2
Bumps [prometheus-net](https://github.com/prometheus-net/prometheus-net) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/prometheus-net/prometheus-net/releases)
- [Changelog](https://github.com/prometheus-net/prometheus-net/blob/master/History)
- [Commits](https://github.com/prometheus-net/prometheus-net/compare/v5.0.1...v5.0.2)

---
updated-dependencies:
- dependency-name: prometheus-net
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 12:01:20 +00:00
dependabot[bot]
223cc8314a Bump sharpcompress from 0.30.0 to 0.30.1
Bumps [sharpcompress](https://github.com/adamhathcock/sharpcompress) from 0.30.0 to 0.30.1.
- [Release notes](https://github.com/adamhathcock/sharpcompress/releases)
- [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.30...0.30.1)

---
updated-dependencies:
- dependency-name: sharpcompress
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 12:01:01 +00:00
Claus Vium
39e6658d01 Merge pull request #6879 from crobibero/client-log-cleanup
Remove ClientLog endpoints
2021-11-21 23:57:13 +01:00
Claus Vium
8f051c86f7 Merge pull request #6873 from crobibero/resume-ignore-active
Add ability to exclude active sessions from resumable items
2021-11-21 23:54:17 +01:00
Claus Vium
11419cbbfd Merge pull request #6890 from crobibero/dlna-create
Fix creating DLNA profiles
2021-11-21 20:46:41 +01:00
Cody Robibero
5b7e8a27fb Fix creating DLNA profiles 2021-11-21 11:48:30 -07:00
WWWesten
718e4b8665 Translated using Weblate (Mongolian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mn/
2021-11-20 14:05:43 -05:00
hoanghuy309
d387d4df92 Translated using Weblate (Vietnamese)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/vi/
2021-11-20 14:05:43 -05:00
WWWesten
1fa845de6e Translated using Weblate (Ukrainian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/uk/
2021-11-20 14:05:43 -05:00
WWWesten
7126f9dffc Translated using Weblate (English (United Kingdom))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en_GB/
2021-11-20 14:05:43 -05:00
Cody Robibero
b2b4bd82d7 Merge pull request #6862 from 1337joe/query-instead-of-filtering 2021-11-20 08:50:39 -07:00
Cody Robibero
03c7bcf9c6 Merge pull request #6870 from cvium/fix_omdb_image_provider 2021-11-20 08:50:22 -07:00
Cody Robibero
ea355b4262 Remove ClientLog endpoints 2021-11-20 08:47:05 -07:00
cvium
71a0abe211 Remove unnecessary N/A checks (converter changes them to null) 2021-11-20 08:32:07 +01:00
cvium
d8c3b8e7f8 Don't use AppendFormat 2021-11-20 08:24:52 +01:00
Joe Rogers
88baff5693 Apply suggestions from code review
Co-authored-by: Claus Vium <cvium@users.noreply.github.com>
2021-11-19 22:40:42 +01:00
Joe Rogers
6d3b129666 Add image scaling options for tmdb 2021-11-19 22:21:21 +01:00
Cody Robibero
8295a3be0d Add ability to exclude active sessions from resumable items 2021-11-19 14:07:50 -07:00
WWWesten
17a273d237 Update LocalizationManager.cs (#6839) 2021-11-19 18:00:20 +01:00
Cody Robibero
752bb88445 Merge pull request #6871 from cvium/fix_localapiurl 2021-11-19 09:18:25 -07:00
cvium
a263f3978b Only force scheme and port when HTTPS is disallowed 2021-11-19 16:57:48 +01:00
Cody Robibero
0009e5cc27 Merge pull request #6850 from PetrusZ/collection-validator 2021-11-19 07:54:41 -07:00
cvium
96ea865681 Refactor omdb providers 2021-11-19 15:32:07 +01:00
Cody Robibero
4a7498a0be Merge pull request #6869 from cvium/optimize_childcount
Small optimization to child count field
2021-11-19 05:07:54 -07:00
cvium
e3f0a53f59 Small optimization to child count field 2021-11-19 09:15:06 +01:00
Joe Rogers
9ba7bf96ef Query MediaSourceManager directly in image providers
Add doc comments/minor tweaks to AudioImageProvider
2021-11-18 15:11:50 +01:00
Joe Rogers
bff5ff0cb8 Merge similar tests with Theories 2021-11-18 14:29:22 +01:00
Petrus.Z
acb86066ff Replace library option to AutomaticallyAddToCollection
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 16:00:14 +08:00
Petrus.Z
263bbf897a Reduce one query
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 15:34:35 +08:00
Joe Rogers
97124f5fce Add missed override 2021-11-18 07:42:04 +01:00
Petrus.Z
6565b0cfbe Fix style issues
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 13:33:44 +08:00
Petrus.Z
b635b5a7e3 Paginate movies query
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 13:08:56 +08:00
Petrus.Z
6d74c83ddb Fix issues mentioned in review, except for option name
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-18 12:07:27 +08:00
Joe Rogers
7cf5767949 Query media streams by type instead of filtering 2021-11-17 22:34:04 +01:00
Cody Robibero
fa366f0099 Merge pull request #6860 from cvium/fix_xml_endless_loops 2021-11-17 08:39:49 -07:00
Petrus.Z
989013c974 Add new line at end of file
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 22:22:52 +08:00
Petrus.Z
c09e999916 Use List<Gid> instead of List<Movie>
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 22:18:59 +08:00
cvium
61b75c82ce Read past empty elements 2021-11-17 14:49:30 +01:00
rimasx
773a4ae1f3 Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-17 08:48:37 -05:00
WWWesten
38ac84ad71 Translated using Weblate (Esperanto)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/
2021-11-17 08:48:37 -05:00
WWWesten
054ca6a8fb Translated using Weblate (Kazakh)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/
2021-11-17 08:48:37 -05:00
millallo
3d9f865247 Translated using Weblate (Italian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/
2021-11-17 08:48:37 -05:00
Csaba
5cd6fba11a Translated using Weblate (Hungarian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/
2021-11-17 08:48:37 -05:00
blob03
f7b18d97a7 Translated using Weblate (French)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/
2021-11-17 08:48:37 -05:00
Lukáš Kucharczyk
45e6e2adee Translated using Weblate (Czech)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/
2021-11-17 08:48:37 -05:00
blob03
704c1539a2 Translated using Weblate (English)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en/
2021-11-17 08:48:37 -05:00
WWWesten
00b293d2e9 Added translation using Weblate (Mongolian) 2021-11-17 08:48:37 -05:00
Petrus.Z
52e9dc66f5 Remove extra blank line
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 21:07:02 +08:00
Petrus.Z
3c8f7d380f Improve performance
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 20:56:14 +08:00
Petrus.Z
0a0ddb0eaf Add AutoCollection option
it can determine whether auto create/add movies to collection

Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-17 14:02:07 +08:00
Cody Robibero
84b8c9c2bc Merge pull request #6857 from Bond-009/warn56 2021-11-16 12:25:54 -07:00
Bond_009
257e1be95f Fix some warnings 2021-11-16 16:31:57 +01:00
rimasx
6003289717 Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-16 09:50:35 -05:00
WWWesten
19d285149b Translated using Weblate (Macedonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/mk/
2021-11-16 09:50:35 -05:00
blob03
33a2e1bd50 Translated using Weblate (French)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/
2021-11-16 09:50:35 -05:00
emmanuel billeaud
b8507371d5 Translated using Weblate (French)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/
2021-11-16 09:50:35 -05:00
Bond-009
bddded96ff Merge pull request #6856 from cvium/unused_deps 2021-11-16 15:42:32 +01:00
cvium
24024706bf Fix release build 2021-11-16 12:55:35 +01:00
cvium
93fd1c7075 Fix 2021-11-16 12:27:27 +01:00
cvium
5b1b2621ab Fix build 2021-11-16 12:25:46 +01:00
cvium
b50c3852ef Remove unused dependencies 2021-11-16 12:24:17 +01:00
Petrus.Z
1924d0740d Fix style and performance issues mentioned in review
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-16 19:03:18 +08:00
Petrus.Z
74459ec403 Fix issues mentioned in review
Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-16 11:50:04 +08:00
Cody Robibero
c32a421ea7 Merge pull request #6851 from cvium/remove_references_to_ilibrarymanager 2021-11-15 15:45:49 -07:00
Cody Robibero
4cfe8fe588 Merge pull request #6831 from 1337joe/image-provider-cleanup 2021-11-15 15:45:33 -07:00
Cody Robibero
06c82973c6 Merge pull request #6854 from 1337joe/add-customprefs-set 2021-11-15 15:45:21 -07:00
Claus Vium
f638cd08ea Merge pull request #6852 from Bond-009/warn54
Fix some warnings
2021-11-15 23:35:53 +01:00
Claus Vium
31b87130a2 Merge pull request #6848 from marius-luca-87/dlna
Ensure the proper StartTimeTicks variable is forwarded to the AddDlnaHeaders function
2021-11-15 23:34:34 +01:00
Joe Rogers
ca99a27611 Add setter for CustomPrefs 2021-11-15 22:03:19 +01:00
Petrus.Z
5eb1fde88c Add Collection Validator, create collection based on nfo
Based on nfo's set element, automatically add movie to collection.

Signed-off-by: Petrus.Z <silencly07@gmail.com>
2021-11-16 00:17:14 +08:00
cvium
24679af2e8 Fix comment 2021-11-15 16:01:34 +01:00
Bond_009
474b035d99 Fix some warnings 2021-11-15 15:57:07 +01:00
cvium
4f45c52674 Remove ILibraryManager as a dependency in resolvers etc. 2021-11-15 15:56:02 +01:00
Joe Rogers
f059be8e4d Add logging and fast return 2021-11-15 15:30:43 +01:00
Cody Robibero
03435641c8 Merge pull request #6849 from 1337joe/server-disabled-metadata 2021-11-15 07:25:56 -07:00
Joe Rogers
58be1d7759 Actually check server disabled metadata providers 2021-11-15 14:47:06 +01:00
Marius Luca
4e0edaf544 - ensure the proper StartTimeTicks variable is forwarded to the AddDlnaHeaders function 2021-11-15 15:34:02 +02:00
rimasx
9c74103fbe Translated using Weblate (Estonian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/et/
2021-11-15 07:48:24 -05:00
WWWesten
551c6f02a2 Added translation using Weblate (Assamese) 2021-11-15 07:48:24 -05:00
Bond-009
358cf48506 Merge pull request #6847 from jellyfin/dependabot/nuget/Microsoft.SourceLink.GitHub-1.1.1 2021-11-15 13:31:56 +01:00
Bond-009
331d4ad849 Merge pull request #6846 from jellyfin/dependabot/nuget/Diacritics-3.3.10 2021-11-15 13:31:05 +01:00
dependabot[bot]
bd32cecf7a Bump Microsoft.SourceLink.GitHub from 1.1.0 to 1.1.1
Bumps [Microsoft.SourceLink.GitHub](https://github.com/dotnet/sourcelink) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/dotnet/sourcelink/releases)
- [Commits](https://github.com/dotnet/sourcelink/compare/1.1.0...1.1.1)

---
updated-dependencies:
- dependency-name: Microsoft.SourceLink.GitHub
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 12:01:08 +00:00
dependabot[bot]
c84f2e48b0 Bump Diacritics from 3.3.4 to 3.3.10
Bumps [Diacritics](https://github.com/thomasgalliker/Diacritics.NET) from 3.3.4 to 3.3.10.
- [Release notes](https://github.com/thomasgalliker/Diacritics.NET/releases)
- [Commits](https://github.com/thomasgalliker/Diacritics.NET/commits)

---
updated-dependencies:
- dependency-name: Diacritics
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 12:00:50 +00:00
Joe Rogers
370b7f8e12 Handle unexpected case more gracefully 2021-11-14 23:13:45 +01:00
Cody Robibero
4cb649853d Merge pull request #6845 from marius-luca-87/dlna
Flush the XmlWriter before calling the StringBuilder ToString() method
2021-11-14 13:02:46 -07:00
Marius Luca
a774d1fa10 - flush the XmlWriter before calling the StringBuilder ToString() method 2021-11-14 20:46:17 +02:00
Haadiy Rozzaq
5254e74719 Translated using Weblate (Indonesian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/id/
2021-11-14 01:36:37 -05:00
WWWesten
14b5e85461 Translated using Weblate (Belarusian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/be/
2021-11-14 01:36:37 -05:00
WWWesten
25f1cdbcb5 Translated using Weblate (Catalan)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ca/
2021-11-14 01:36:37 -05:00
Cody Robibero
34df1a030b Merge pull request #6818 from Bond-009/password 2021-11-13 16:56:25 -07:00
Joe Rogers
1d729b2b0f Use codec to determine image format 2021-11-12 16:30:30 +01:00
Joe Rogers
f73a7a6ed8 Use ImageFormat instead of string for extension 2021-11-12 16:22:11 +01:00
Joe Rogers
de9bf327c6 Merge similar tests with Theories 2021-11-12 13:44:48 +01:00
Bond_009
5265b3eee7 Replace PBKDF2-SHA1 with PBKDF2-SHA512
This also migrates already created passwords on login

Source for the number of iterations:
https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
2021-11-10 22:34:54 +01:00
179 changed files with 2473 additions and 2015 deletions

View File

@@ -150,6 +150,7 @@
- [ianjazz246](https://github.com/ianjazz246) - [ianjazz246](https://github.com/ianjazz246)
- [peterspenler](https://github.com/peterspenler) - [peterspenler](https://github.com/peterspenler)
- [MBR-0001](https://github.com/MBR-0001) - [MBR-0001](https://github.com/MBR-0001)
- [jonas-resch](https://github.com/jonas-resch)
# Emby Contributors # Emby Contributors

View File

@@ -395,6 +395,7 @@ namespace Emby.Dlna.ContentDirectory
} }
writer.WriteFullEndElement(); writer.WriteFullEndElement();
writer.Flush();
xmlWriter.WriteElementString("Result", builder.ToString()); xmlWriter.WriteElementString("Result", builder.ToString());
} }
@@ -484,6 +485,7 @@ namespace Emby.Dlna.ContentDirectory
} }
writer.WriteFullEndElement(); writer.WriteFullEndElement();
writer.Flush();
xmlWriter.WriteElementString("Result", builder.ToString()); xmlWriter.WriteElementString("Result", builder.ToString());
} }

View File

@@ -350,7 +350,7 @@ namespace Emby.Dlna
Directory.CreateDirectory(systemProfilesPath); Directory.CreateDirectory(systemProfilesPath);
var fileOptions = AsyncFile.WriteOptions; var fileOptions = AsyncFile.WriteOptions;
fileOptions.Mode = FileMode.CreateNew; fileOptions.Mode = FileMode.Create;
fileOptions.PreallocationSize = length; fileOptions.PreallocationSize = length;
using (var fileStream = new FileStream(path, fileOptions)) using (var fileStream = new FileStream(path, fileOptions))
{ {

View File

@@ -124,7 +124,7 @@ namespace Emby.Dlna.Main
config); config);
Current = this; Current = this;
var netConfig = config.GetConfiguration<NetworkConfiguration>("network"); var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps; _disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
if (_disabled && _config.GetDlnaConfiguration().EnableServer) if (_disabled && _config.GetDlnaConfiguration().EnableServer)
@@ -262,7 +262,6 @@ namespace Emby.Dlna.Main
{ {
_publisher = new SsdpDevicePublisher( _publisher = new SsdpDevicePublisher(
_communicationsServer, _communicationsServer,
_networkManager,
MediaBrowser.Common.System.OperatingSystem.Name, MediaBrowser.Common.System.OperatingSystem.Name,
Environment.OSVersion.VersionString, Environment.OSVersion.VersionString,
_config.GetDlnaConfiguration().SendOnlyMatchedHost) _config.GetDlnaConfiguration().SendOnlyMatchedHost)
@@ -400,7 +399,6 @@ namespace Emby.Dlna.Main
_imageProcessor, _imageProcessor,
_deviceDiscovery, _deviceDiscovery,
_httpClientFactory, _httpClientFactory,
_config,
_userDataManager, _userDataManager,
_localization, _localization,
_mediaSourceManager, _mediaSourceManager,

View File

@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -35,7 +34,6 @@ namespace Emby.Dlna.PlayTo
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config;
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
@@ -47,7 +45,7 @@ namespace Emby.Dlna.PlayTo
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
{ {
_logger = logger; _logger = logger;
_sessionManager = sessionManager; _sessionManager = sessionManager;
@@ -58,7 +56,6 @@ namespace Emby.Dlna.PlayTo
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_deviceDiscovery = deviceDiscovery; _deviceDiscovery = deviceDiscovery;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_config = config;
_userDataManager = userDataManager; _userDataManager = userDataManager;
_localization = localization; _localization = localization;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;

View File

@@ -38,7 +38,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<!-- Code Analyzers--> <!-- Code Analyzers-->

View File

@@ -19,6 +19,7 @@ using Emby.Dlna;
using Emby.Dlna.Main; using Emby.Dlna.Main;
using Emby.Dlna.Ssdp; using Emby.Dlna.Ssdp;
using Emby.Drawing; using Emby.Drawing;
using Emby.Naming.Common;
using Emby.Notifications; using Emby.Notifications;
using Emby.Photos; using Emby.Photos;
using Emby.Server.Implementations.Archiving; using Emby.Server.Implementations.Archiving;
@@ -312,22 +313,6 @@ namespace Emby.Server.Implementations
? Environment.MachineName ? Environment.MachineName
: ConfigurationManager.Configuration.ServerName; : ConfigurationManager.Configuration.ServerName;
/// <summary>
/// Temporary function to migration network settings out of system.xml and into network.xml.
/// TODO: remove at the point when a fixed migration path has been decided upon.
/// </summary>
private void MigrateNetworkConfiguration()
{
string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml");
if (!File.Exists(path))
{
var networkSettings = new NetworkConfiguration();
ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings);
_xmlSerializer.SerializeToFile(networkSettings, path);
Logger.LogDebug("Successfully migrated network settings.");
}
}
public string ExpandVirtualPath(string path) public string ExpandVirtualPath(string path)
{ {
var appPaths = ApplicationPaths; var appPaths = ApplicationPaths;
@@ -512,8 +497,6 @@ namespace Emby.Server.Implementations
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>()); ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
// Have to migrate settings here as migration subsystem not yet initialised.
MigrateNetworkConfiguration();
NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>()); NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
// Initialize runtime stat collection // Initialize runtime stat collection
@@ -596,6 +579,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>)); serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>)); serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>(); serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
serviceCollection.AddSingleton<NamingOptions>();
serviceCollection.AddSingleton<IMusicManager, MusicManager>(); serviceCollection.AddSingleton<IMusicManager, MusicManager>();
@@ -1126,12 +1110,12 @@ namespace Emby.Server.Implementations
} }
/// <inheritdoc/> /// <inheritdoc/>
public string GetApiUrlForLocalAccess(bool allowHttps) public string GetApiUrlForLocalAccess(bool allowHttps = true)
{ {
// With an empty source, the port will be null // With an empty source, the port will be null
string smart = NetManager.GetBindInterface(string.Empty, out _); string smart = NetManager.GetBindInterface(string.Empty, out _);
var scheme = allowHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp; var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
var port = allowHttps ? HttpsPort : HttpPort; int? port = !allowHttps ? HttpPort : null;
return GetLocalApiUrl(smart.Trim('/'), scheme, port); return GetLocalApiUrl(smart.Trim('/'), scheme, port);
} }

View File

@@ -1075,14 +1075,6 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true; forceUpdate = true;
} }
// was used for status
// if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal))
// {
// item.ExternalEtag = info.Etag;
// forceUpdate = true;
// _logger.LogDebug("Forcing update due to ExternalEtag {0}", item.Name);
// }
if (!internalChannelId.Equals(item.ChannelId)) if (!internalChannelId.Equals(item.ChannelId))
{ {
forceUpdate = true; forceUpdate = true;

View File

@@ -1,9 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using static MediaBrowser.Common.Cryptography.Constants; using static MediaBrowser.Model.Cryptography.Constants;
namespace Emby.Server.Implementations.Cryptography namespace Emby.Server.Implementations.Cryptography
{ {
@@ -12,10 +14,7 @@ namespace Emby.Server.Implementations.Cryptography
/// </summary> /// </summary>
public class CryptographyProvider : ICryptoProvider public class CryptographyProvider : ICryptoProvider
{ {
// FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto // TODO: remove when not needed for backwards compat
// Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
// there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
// Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>() private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
{ {
"MD5", "MD5",
@@ -35,60 +34,81 @@ namespace Emby.Server.Implementations.Cryptography
}; };
/// <inheritdoc /> /// <inheritdoc />
public string DefaultHashMethod => "PBKDF2"; public string DefaultHashMethod => "PBKDF2-SHA512";
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<string> GetSupportedHashMethods() public PasswordHash CreatePasswordHash(ReadOnlySpan<char> password)
=> _supportedHashMethods;
private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
{ {
// downgrading for now as we need this library to be dotnetstandard compliant byte[] salt = GenerateSalt();
// with this downgrade we'll add a check to make sure we're on the downgrade method at the moment return new PasswordHash(
if (method != DefaultHashMethod) DefaultHashMethod,
{ Rfc2898DeriveBytes.Pbkdf2(
throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); password,
} salt,
DefaultIterations,
using var r = new Rfc2898DeriveBytes(bytes, salt, iterations); HashAlgorithmName.SHA512,
return r.GetBytes(32); DefaultOutputLength),
salt,
new Dictionary<string, string>
{
{ "iterations", DefaultIterations.ToString(CultureInfo.InvariantCulture) }
});
} }
/// <inheritdoc /> /// <inheritdoc />
public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) public bool Verify(PasswordHash hash, ReadOnlySpan<char> password)
{ {
if (hashMethod == DefaultHashMethod) if (string.Equals(hash.Id, "PBKDF2", StringComparison.Ordinal))
{ {
return PBKDF2(hashMethod, bytes, salt, DefaultIterations); return hash.Hash.SequenceEqual(
Rfc2898DeriveBytes.Pbkdf2(
password,
hash.Salt,
int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
HashAlgorithmName.SHA1,
32));
} }
if (!_supportedHashMethods.Contains(hashMethod)) if (string.Equals(hash.Id, "PBKDF2-SHA512", StringComparison.Ordinal))
{ {
throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); return hash.Hash.SequenceEqual(
Rfc2898DeriveBytes.Pbkdf2(
password,
hash.Salt,
int.Parse(hash.Parameters["iterations"], CultureInfo.InvariantCulture),
HashAlgorithmName.SHA512,
DefaultOutputLength));
} }
using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException($"Unknown hash method: {hashMethod}."); if (!_supportedHashMethods.Contains(hash.Id))
if (salt.Length == 0)
{ {
return h.ComputeHash(bytes); throw new CryptographicException($"Requested hash method is not supported: {hash.Id}");
} }
byte[] salted = new byte[bytes.Length + salt.Length]; using var h = HashAlgorithm.Create(hash.Id) ?? throw new ResourceNotFoundException($"Unknown hash method: {hash.Id}.");
var bytes = Encoding.UTF8.GetBytes(password.ToArray());
if (hash.Salt.Length == 0)
{
return hash.Hash.SequenceEqual(h.ComputeHash(bytes));
}
byte[] salted = new byte[bytes.Length + hash.Salt.Length];
Array.Copy(bytes, salted, bytes.Length); Array.Copy(bytes, salted, bytes.Length);
Array.Copy(salt, 0, salted, bytes.Length, salt.Length); hash.Salt.CopyTo(salted.AsSpan(bytes.Length));
return h.ComputeHash(salted); return hash.Hash.SequenceEqual(h.ComputeHash(salted));
} }
/// <inheritdoc />
public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
=> PBKDF2(DefaultHashMethod, bytes, salt, DefaultIterations);
/// <inheritdoc /> /// <inheritdoc />
public byte[] GenerateSalt() public byte[] GenerateSalt()
=> GenerateSalt(DefaultSaltLength); => GenerateSalt(DefaultSaltLength);
/// <inheritdoc /> /// <inheritdoc />
public byte[] GenerateSalt(int length) public byte[] GenerateSalt(int length)
=> RandomNumberGenerator.GetBytes(length); {
var salt = new byte[length];
using var rng = RandomNumberGenerator.Create();
rng.GetNonZeroBytes(salt);
return salt;
}
} }
} }

View File

@@ -7,7 +7,7 @@ using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data namespace Emby.Server.Implementations.Data
{ {
public class ManagedConnection : IDisposable public sealed class ManagedConnection : IDisposable
{ {
private readonly SemaphoreSlim _writeLock; private readonly SemaphoreSlim _writeLock;

View File

@@ -370,6 +370,12 @@ namespace Emby.Server.Implementations.Dto
if (item is MusicAlbum || item is Season || item is Playlist) if (item is MusicAlbum || item is Season || item is Playlist)
{ {
dto.ChildCount = dto.RecursiveItemCount; dto.ChildCount = dto.RecursiveItemCount;
var folderChildCount = folder.LinkedChildren.Length;
// The default is an empty array, so we can't reliably use the count when it's empty
if (folderChildCount > 0)
{
dto.ChildCount ??= folderChildCount;
}
} }
if (options.ContainsField(ItemFields.ChildCount)) if (options.ContainsField(ItemFields.ChildCount))

View File

@@ -32,7 +32,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0" />
<PackageReference Include="Mono.Nat" Version="3.0.2" /> <PackageReference Include="Mono.Nat" Version="3.0.2" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
<PackageReference Include="sharpcompress" Version="0.30.0" /> <PackageReference Include="sharpcompress" Version="0.30.1" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.3" /> <PackageReference Include="DotNet.Glob" Version="3.1.3" />
</ItemGroup> </ItemGroup>

View File

@@ -27,7 +27,6 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ILogger<ExternalPortForwarding> _logger; private readonly ILogger<ExternalPortForwarding> _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>(); private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
@@ -42,17 +41,14 @@ namespace Emby.Server.Implementations.EntryPoints
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="appHost">The application host.</param> /// <param name="appHost">The application host.</param>
/// <param name="config">The configuration manager.</param> /// <param name="config">The configuration manager.</param>
/// <param name="deviceDiscovery">The device discovery.</param>
public ExternalPortForwarding( public ExternalPortForwarding(
ILogger<ExternalPortForwarding> logger, ILogger<ExternalPortForwarding> logger,
IServerApplicationHost appHost, IServerApplicationHost appHost,
IServerConfigurationManager config, IServerConfigurationManager config)
IDeviceDiscovery deviceDiscovery)
{ {
_logger = logger; _logger = logger;
_appHost = appHost; _appHost = appHost;
_config = config; _config = config;
_deviceDiscovery = deviceDiscovery;
} }
private string GetConfigIdentifier() private string GetConfigIdentifier()

View File

@@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@@ -14,7 +12,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.IO namespace Emby.Server.Implementations.IO
{ {
public class FileRefresher : IDisposable public sealed class FileRefresher : IDisposable
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@@ -22,7 +20,7 @@ namespace Emby.Server.Implementations.IO
private readonly List<string> _affectedPaths = new List<string>(); private readonly List<string> _affectedPaths = new List<string>();
private readonly object _timerLock = new object(); private readonly object _timerLock = new object();
private Timer _timer; private Timer? _timer;
private bool _disposed; private bool _disposed;
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger) public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
@@ -36,7 +34,7 @@ namespace Emby.Server.Implementations.IO
AddPath(path); AddPath(path);
} }
public event EventHandler<EventArgs> Completed; public event EventHandler<EventArgs>? Completed;
public string Path { get; private set; } public string Path { get; private set; }
@@ -111,7 +109,7 @@ namespace Emby.Server.Implementations.IO
RestartTimer(); RestartTimer();
} }
private void OnTimerCallback(object state) private void OnTimerCallback(object? state)
{ {
List<string> paths; List<string> paths;
@@ -127,7 +125,7 @@ namespace Emby.Server.Implementations.IO
try try
{ {
ProcessPathChanges(paths.ToList()); ProcessPathChanges(paths);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -137,12 +135,12 @@ namespace Emby.Server.Implementations.IO
private void ProcessPathChanges(List<string> paths) private void ProcessPathChanges(List<string> paths)
{ {
var itemsToRefresh = paths IEnumerable<BaseItem> itemsToRefresh = paths
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.Select(GetAffectedBaseItem) .Select(GetAffectedBaseItem)
.Where(item => item != null) .Where(item => item != null)
.GroupBy(x => x.Id) .GroupBy(x => x!.Id) // Removed null values in the previous .Where()
.Select(x => x.First()); .Select(x => x.First())!;
foreach (var item in itemsToRefresh) foreach (var item in itemsToRefresh)
{ {
@@ -176,15 +174,15 @@ namespace Emby.Server.Implementations.IO
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="path">The path.</param>
/// <returns>BaseItem.</returns> /// <returns>BaseItem.</returns>
private BaseItem GetAffectedBaseItem(string path) private BaseItem? GetAffectedBaseItem(string path)
{ {
BaseItem item = null; BaseItem? item = null;
while (item == null && !string.IsNullOrEmpty(path)) while (item == null && !string.IsNullOrEmpty(path))
{ {
item = _libraryManager.FindByPath(path, null); item = _libraryManager.FindByPath(path, null);
path = System.IO.Path.GetDirectoryName(path); path = System.IO.Path.GetDirectoryName(path) ?? string.Empty;
} }
if (item != null) if (item != null)

View File

@@ -449,12 +449,12 @@ namespace Emby.Server.Implementations.IO
} }
var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger); var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger);
newRefresher.Completed += NewRefresher_Completed; newRefresher.Completed += OnNewRefresherCompleted;
_activeRefreshers.Add(newRefresher); _activeRefreshers.Add(newRefresher);
} }
} }
private void NewRefresher_Completed(object sender, EventArgs e) private void OnNewRefresherCompleted(object sender, EventArgs e)
{ {
var refresher = (FileRefresher)sender; var refresher = (FileRefresher)sender;
DisposeRefresher(refresher); DisposeRefresher(refresher);
@@ -481,6 +481,7 @@ namespace Emby.Server.Implementations.IO
{ {
lock (_activeRefreshers) lock (_activeRefreshers)
{ {
refresher.Completed -= OnNewRefresherCompleted;
refresher.Dispose(); refresher.Dispose();
_activeRefreshers.Remove(refresher); _activeRefreshers.Remove(refresher);
} }
@@ -492,6 +493,7 @@ namespace Emby.Server.Implementations.IO
{ {
foreach (var refresher in _activeRefreshers.ToList()) foreach (var refresher in _activeRefreshers.ToList())
{ {
refresher.Completed -= OnNewRefresherCompleted;
refresher.Dispose(); refresher.Dispose();
} }

View File

@@ -1,8 +1,9 @@
using System; using System;
using System.IO; using System.IO;
using Emby.Naming.Audio;
using Emby.Naming.Common;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@@ -13,17 +14,17 @@ namespace Emby.Server.Implementations.Library
/// </summary> /// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{ {
private readonly ILibraryManager _libraryManager; private readonly NamingOptions _namingOptions;
private readonly IServerApplicationPaths _serverApplicationPaths; private readonly IServerApplicationPaths _serverApplicationPaths;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class. /// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
/// </summary> /// </summary>
/// <param name="libraryManager">The library manager.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="serverApplicationPaths">The server application paths.</param> /// <param name="serverApplicationPaths">The server application paths.</param>
public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths) public CoreResolutionIgnoreRule(NamingOptions namingOptions, IServerApplicationPaths serverApplicationPaths)
{ {
_libraryManager = libraryManager; _namingOptions = namingOptions;
_serverApplicationPaths = serverApplicationPaths; _serverApplicationPaths = serverApplicationPaths;
} }
@@ -78,7 +79,7 @@ namespace Emby.Server.Implementations.Library
{ {
// Don't resolve these into audio files // Don't resolve these into audio files
if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal) if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFileName, StringComparison.Ordinal)
&& _libraryManager.IsAudioFile(filename)) && AudioFileParser.IsAudioFile(filename, _namingOptions))
{ {
return true; return true;
} }

View File

@@ -79,6 +79,7 @@ namespace Emby.Server.Implementations.Library
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository; private readonly IItemRepository _itemRepository;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly NamingOptions _namingOptions;
/// <summary> /// <summary>
/// The _root folder sync lock. /// The _root folder sync lock.
@@ -88,9 +89,6 @@ namespace Emby.Server.Implementations.Library
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
private NamingOptions _namingOptions;
private string[] _videoFileExtensions;
/// <summary> /// <summary>
/// The _root folder. /// The _root folder.
/// </summary> /// </summary>
@@ -116,6 +114,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="itemRepository">The item repository.</param> /// <param name="itemRepository">The item repository.</param>
/// <param name="imageProcessor">The image processor.</param> /// <param name="imageProcessor">The image processor.</param>
/// <param name="memoryCache">The memory cache.</param> /// <param name="memoryCache">The memory cache.</param>
/// <param name="namingOptions">The naming options.</param>
public LibraryManager( public LibraryManager(
IServerApplicationHost appHost, IServerApplicationHost appHost,
ILogger<LibraryManager> logger, ILogger<LibraryManager> logger,
@@ -130,7 +129,8 @@ namespace Emby.Server.Implementations.Library
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IItemRepository itemRepository, IItemRepository itemRepository,
IImageProcessor imageProcessor, IImageProcessor imageProcessor,
IMemoryCache memoryCache) IMemoryCache memoryCache,
NamingOptions namingOptions)
{ {
_appHost = appHost; _appHost = appHost;
_logger = logger; _logger = logger;
@@ -146,6 +146,7 @@ namespace Emby.Server.Implementations.Library
_itemRepository = itemRepository; _itemRepository = itemRepository;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_memoryCache = memoryCache; _memoryCache = memoryCache;
_namingOptions = namingOptions;
_configurationManager.ConfigurationUpdated += ConfigurationUpdated; _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -2500,16 +2501,6 @@ namespace Emby.Server.Implementations.Library
return RootFolder; return RootFolder;
} }
/// <inheritdoc />
public bool IsVideoFile(string path)
{
return VideoResolver.IsVideoFile(path, GetNamingOptions());
}
/// <inheritdoc />
public bool IsAudioFile(string path)
=> AudioFileParser.IsAudioFile(path, GetNamingOptions());
/// <inheritdoc /> /// <inheritdoc />
public int? GetSeasonNumberFromPath(string path) public int? GetSeasonNumberFromPath(string path)
=> SeasonPathParser.Parse(path, true, true).SeasonNumber; => SeasonPathParser.Parse(path, true, true).SeasonNumber;
@@ -2525,7 +2516,7 @@ namespace Emby.Server.Implementations.Library
isAbsoluteNaming = null; isAbsoluteNaming = null;
} }
var resolver = new EpisodeResolver(GetNamingOptions()); var resolver = new EpisodeResolver(_namingOptions);
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
@@ -2682,21 +2673,9 @@ namespace Emby.Server.Implementations.Library
return changed; return changed;
} }
/// <inheritdoc />
public NamingOptions GetNamingOptions()
{
if (_namingOptions == null)
{
_namingOptions = new NamingOptions();
_videoFileExtensions = _namingOptions.VideoFileExtensions;
}
return _namingOptions;
}
public ItemLookupInfo ParseName(string name) public ItemLookupInfo ParseName(string name)
{ {
var namingOptions = GetNamingOptions(); var namingOptions = _namingOptions;
var result = VideoResolver.CleanDateTime(name, namingOptions); var result = VideoResolver.CleanDateTime(name, namingOptions);
return new ItemLookupInfo return new ItemLookupInfo
@@ -2708,11 +2687,11 @@ namespace Emby.Server.Implementations.Library
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{ {
var namingOptions = GetNamingOptions(); var namingOptions = _namingOptions;
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory) var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)) .Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false))
.ToList(); .ToList();
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions); var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
@@ -2726,7 +2705,7 @@ namespace Emby.Server.Implementations.Library
var resolvers = new IItemResolver[] var resolvers = new IItemResolver[]
{ {
new GenericVideoResolver<Trailer>(this) new GenericVideoResolver<Trailer>(_namingOptions)
}; };
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers) return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
@@ -2752,11 +2731,11 @@ namespace Emby.Server.Implementations.Library
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{ {
var namingOptions = GetNamingOptions(); var namingOptions = _namingOptions;
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory) var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty)) .Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false)) .SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false))
.ToList(); .ToList();
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions); var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
@@ -2840,7 +2819,7 @@ namespace Emby.Server.Implementations.Library
private void SetExtraTypeFromFilename(Video item) private void SetExtraTypeFromFilename(Video item)
{ {
var resolver = new ExtraResolver(GetNamingOptions()); var resolver = new ExtraResolver(_namingOptions);
var result = resolver.GetExtraInfo(item.Path); var result = resolver.GetExtraInfo(item.Path);

View File

@@ -6,7 +6,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Emby.Naming.Audio;
using Emby.Naming.AudioBook; using Emby.Naming.AudioBook;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@@ -21,11 +24,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// </summary> /// </summary>
public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
{ {
private readonly ILibraryManager _libraryManager; private readonly NamingOptions _namingOptions;
public AudioResolver(ILibraryManager libraryManager) public AudioResolver(NamingOptions namingOptions)
{ {
_libraryManager = libraryManager; _namingOptions = namingOptions;
} }
/// <summary> /// <summary>
@@ -40,7 +43,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
string collectionType, string collectionType,
IDirectoryService directoryService) IDirectoryService directoryService)
{ {
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService); var result = ResolveMultipleInternal(parent, files, collectionType);
if (result != null) if (result != null)
{ {
@@ -56,12 +59,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
private MultiItemResolverResult ResolveMultipleInternal( private MultiItemResolverResult ResolveMultipleInternal(
Folder parent, Folder parent,
List<FileSystemMetadata> files, List<FileSystemMetadata> files,
string collectionType, string collectionType)
IDirectoryService directoryService)
{ {
if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase)) if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
{ {
return ResolveMultipleAudio<AudioBook>(parent, files, directoryService, false, collectionType, true); return ResolveMultipleAudio(parent, files, true);
} }
return null; return null;
@@ -87,14 +89,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return null; return null;
} }
var files = args.FileSystemChildren return FindAudioBook(args, false);
.Where(i => !_libraryManager.IgnoreFile(i, args.Parent))
.ToList();
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
} }
if (_libraryManager.IsAudioFile(args.Path)) if (AudioFileParser.IsAudioFile(args.Path, _namingOptions))
{ {
var extension = Path.GetExtension(args.Path); var extension = Path.GetExtension(args.Path);
@@ -107,7 +105,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMixedCollectionType = string.IsNullOrEmpty(collectionType); var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
// For conflicting extensions, give priority to videos // For conflicting extensions, give priority to videos
if (isMixedCollectionType && _libraryManager.IsVideoFile(args.Path)) if (isMixedCollectionType && VideoResolver.IsVideoFile(args.Path, _namingOptions))
{ {
return null; return null;
} }
@@ -141,29 +139,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return null; return null;
} }
private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName) private AudioBook FindAudioBook(ItemResolveArgs args, bool parseName)
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
{ {
// TODO: Allow GetMultiDiscMovie in here // TODO: Allow GetMultiDiscMovie in here
const bool supportsMultiVersion = false; var result = ResolveMultipleAudio(args.Parent, args.GetActualFileSystemChildren(), parseName);
var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ?? if (result == null || result.Items.Count != 1 || result.Items[0] is not AudioBook item)
new MultiItemResolverResult();
if (result.Items.Count == 1)
{ {
// If we were supporting this we'd be checking filesFromOtherItems return null;
var item = (T)result.Items[0];
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath);
return item;
} }
return null; // If we were supporting this we'd be checking filesFromOtherItems
item.IsInMixedFolder = false;
item.Name = Path.GetFileName(item.ContainingFolderPath);
return item;
} }
private MultiItemResolverResult ResolveMultipleAudio<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName) private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName)
where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
{ {
var files = new List<FileSystemMetadata>(); var files = new List<FileSystemMetadata>();
var items = new List<BaseItem>(); var items = new List<BaseItem>();
@@ -176,15 +168,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{ {
leftOver.Add(child); leftOver.Add(child);
} }
else if (!IsIgnored(child.Name)) else
{ {
files.Add(child); files.Add(child);
} }
} }
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var resolver = new AudioBookListResolver(_namingOptions);
var resolver = new AudioBookListResolver(namingOptions);
var resolverResult = resolver.Resolve(files).ToList(); var resolverResult = resolver.Resolve(files).ToList();
var result = new MultiItemResolverResult var result = new MultiItemResolverResult
@@ -210,7 +200,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var firstMedia = resolvedItem.Files[0]; var firstMedia = resolvedItem.Files[0];
var libraryItem = new T var libraryItem = new AudioBook
{ {
Path = firstMedia.Path, Path = firstMedia.Path,
IsInMixedFolder = isInMixedFolder, IsInMixedFolder = isInMixedFolder,
@@ -230,12 +220,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return result; return result;
} }
private bool ContainsFile(List<AudioBookInfo> result, FileSystemMetadata file) private static bool ContainsFile(IEnumerable<AudioBookInfo> result, FileSystemMetadata file)
{ {
return result.Any(i => ContainsFile(i, file)); return result.Any(i => ContainsFile(i, file));
} }
private bool ContainsFile(AudioBookInfo result, FileSystemMetadata file) private static bool ContainsFile(AudioBookInfo result, FileSystemMetadata file)
{ {
return result.Files.Any(i => ContainsFile(i, file)) || return result.Files.Any(i => ContainsFile(i, file)) ||
result.AlternateVersions.Any(i => ContainsFile(i, file)) || result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
@@ -246,10 +236,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{ {
return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase); return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
} }
private static bool IsIgnored(string filename)
{
return false;
}
} }
} }

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Naming.Audio; using Emby.Naming.Audio;
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@@ -22,20 +23,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
public class MusicAlbumResolver : ItemResolver<MusicAlbum> public class MusicAlbumResolver : ItemResolver<MusicAlbum>
{ {
private readonly ILogger<MusicAlbumResolver> _logger; private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem; private readonly NamingOptions _namingOptions;
private readonly ILibraryManager _libraryManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class. /// Initializes a new instance of the <see cref="MusicAlbumResolver"/> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="fileSystem">The file system.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="libraryManager">The library manager.</param> public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, NamingOptions namingOptions)
public MusicAlbumResolver(ILogger<MusicAlbumResolver> logger, IFileSystem fileSystem, ILibraryManager libraryManager)
{ {
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _namingOptions = namingOptions;
_libraryManager = libraryManager;
} }
/// <summary> /// <summary>
@@ -87,7 +85,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns>
public bool IsMusicAlbum(string path, IDirectoryService directoryService) public bool IsMusicAlbum(string path, IDirectoryService directoryService)
{ {
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager); return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService);
} }
/// <summary> /// <summary>
@@ -101,7 +99,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
if (args.IsDirectory) if (args.IsDirectory)
{ {
// if (args.Parent is MusicArtist) return true; // saves us from testing children twice // if (args.Parent is MusicArtist) return true; // saves us from testing children twice
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager)) if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService))
{ {
return true; return true;
} }
@@ -116,13 +114,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
private bool ContainsMusic( private bool ContainsMusic(
IEnumerable<FileSystemMetadata> list, IEnumerable<FileSystemMetadata> list,
bool allowSubfolders, bool allowSubfolders,
IDirectoryService directoryService, IDirectoryService directoryService)
ILogger<MusicAlbumResolver> logger,
IFileSystem fileSystem,
ILibraryManager libraryManager)
{ {
// check for audio files before digging down into directories // check for audio files before digging down into directories
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && libraryManager.IsAudioFile(fileSystemInfo.FullName)); var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && AudioFileParser.IsAudioFile(fileSystemInfo.FullName, _namingOptions));
if (foundAudioFile) if (foundAudioFile)
{ {
// at least one audio file exists // at least one audio file exists
@@ -137,21 +132,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var discSubfolderCount = 0; var discSubfolderCount = 0;
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var parser = new AlbumParser(_namingOptions);
var parser = new AlbumParser(namingOptions);
var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory); var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory);
var result = Parallel.ForEach(directories, (fileSystemInfo, state) => var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
{ {
var path = fileSystemInfo.FullName; var path = fileSystemInfo.FullName;
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService);
if (hasMusic) if (hasMusic)
{ {
if (parser.IsMultiPart(path)) if (parser.IsMultiPart(path))
{ {
logger.LogDebug("Found multi-disc folder: {Path}", path); _logger.LogDebug("Found multi-disc folder: {Path}", path);
Interlocked.Increment(ref discSubfolderCount); Interlocked.Increment(ref discSubfolderCount);
} }
else else

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Naming.Common;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -19,27 +20,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
public class MusicArtistResolver : ItemResolver<MusicArtist> public class MusicArtistResolver : ItemResolver<MusicArtist>
{ {
private readonly ILogger<MusicAlbumResolver> _logger; private readonly ILogger<MusicAlbumResolver> _logger;
private readonly IFileSystem _fileSystem; private NamingOptions _namingOptions;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MusicArtistResolver"/> class. /// Initializes a new instance of the <see cref="MusicArtistResolver"/> class.
/// </summary> /// </summary>
/// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param> /// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param>
/// <param name="fileSystem">The file system.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="config">The configuration manager.</param>
public MusicArtistResolver( public MusicArtistResolver(
ILogger<MusicAlbumResolver> logger, ILogger<MusicAlbumResolver> logger,
IFileSystem fileSystem, NamingOptions namingOptions)
ILibraryManager libraryManager,
IServerConfigurationManager config)
{ {
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _namingOptions = namingOptions;
_libraryManager = libraryManager;
_config = config;
} }
/// <summary> /// <summary>
@@ -89,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var directoryService = args.DirectoryService; var directoryService = args.DirectoryService;
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); var albumResolver = new MusicAlbumResolver(_logger, _namingOptions);
// If we contain an album assume we are an artist folder // If we contain an album assume we are an artist folder
var directories = args.FileSystemChildren.Where(i => i.IsDirectory); var directories = args.FileSystemChildren.Where(i => i.IsDirectory);

View File

@@ -6,6 +6,7 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using DiscUtils.Udf; using DiscUtils.Udf;
using Emby.Naming.Common;
using Emby.Naming.Video; using Emby.Naming.Video;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -21,12 +22,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T> public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T>
where T : Video, new() where T : Video, new()
{ {
protected BaseVideoResolver(ILibraryManager libraryManager) protected BaseVideoResolver(NamingOptions namingOptions)
{ {
LibraryManager = libraryManager; NamingOptions = namingOptions;
} }
protected ILibraryManager LibraryManager { get; } protected NamingOptions NamingOptions { get; }
/// <summary> /// <summary>
/// Resolves the specified args. /// Resolves the specified args.
@@ -48,7 +49,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new() where TVideoType : Video, new()
{ {
var namingOptions = LibraryManager.GetNamingOptions(); var namingOptions = NamingOptions;
// If the path is a file check for a matching extensions // If the path is a file check for a matching extensions
if (args.IsDirectory) if (args.IsDirectory)
@@ -138,7 +139,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null; return null;
} }
if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub) if (VideoResolver.IsVideoFile(args.Path, NamingOptions) || videoInfo.IsStub)
{ {
var path = args.Path; var path = args.Path;
@@ -267,7 +268,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void Set3DFormat(Video video) protected void Set3DFormat(Video video)
{ {
var result = Format3DParser.Parse(video.Path, LibraryManager.GetNamingOptions()); var result = Format3DParser.Parse(video.Path, NamingOptions);
Set3DFormat(video, result.Is3D, result.Format3D); Set3DFormat(video, result.Is3D, result.Format3D);
} }

View File

@@ -2,16 +2,16 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.Library.Resolvers namespace Emby.Server.Implementations.Library.Resolvers
{ {
public class GenericVideoResolver<T> : BaseVideoResolver<T> public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new() where T : Video, new()
{ {
public GenericVideoResolver(ILibraryManager libraryManager) public GenericVideoResolver(NamingOptions namingOptions)
: base(libraryManager) : base(namingOptions)
{ {
} }
} }

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Emby.Naming.Common;
using Emby.Naming.Video; using Emby.Naming.Video;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@@ -25,6 +26,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly StackResolver _stackResolver;
private string[] _validCollectionTypes = new[] private string[] _validCollectionTypes = new[]
{ {
@@ -38,12 +40,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MovieResolver"/> class. /// Initializes a new instance of the <see cref="MovieResolver"/> class.
/// </summary> /// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="imageProcessor">The image processor.</param> /// <param name="imageProcessor">The image processor.</param>
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor) /// <param name="namingOptions">The naming options.</param>
: base(libraryManager) public MovieResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
: base(namingOptions)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_stackResolver = new StackResolver(NamingOptions);
} }
/// <summary> /// <summary>
@@ -89,9 +92,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
var files = args.FileSystemChildren var files = args.GetActualFileSystemChildren().ToList();
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{ {
@@ -258,9 +259,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
} }
} }
var namingOptions = LibraryManager.GetNamingOptions(); var resolverResult = VideoListResolver.Resolve(files, NamingOptions, suppportMultiEditions).ToList();
var resolverResult = VideoListResolver.Resolve(files, namingOptions, suppportMultiEditions).ToList();
var result = new MultiItemResolverResult var result = new MultiItemResolverResult
{ {
@@ -438,7 +437,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (result.Items.Count == 1) if (result.Items.Count == 1)
{ {
var videoPath = result.Items[0].Path; var videoPath = result.Items[0].Path;
var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, videoPath, i.Name)); var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name));
if (!hasPhotos) if (!hasPhotos)
{ {
@@ -511,9 +510,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions(); var result = _stackResolver.ResolveDirectories(folderPaths).ToList();
var result = new StackResolver(namingOptions).ResolveDirectories(folderPaths).ToList();
if (result.Count != 1) if (result.Count != 1)
{ {

View File

@@ -1,6 +1,7 @@
#nullable disable #nullable disable
using System; using System;
using Emby.Naming.Common;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -15,17 +16,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
public class PhotoAlbumResolver : GenericFolderResolver<PhotoAlbum> public class PhotoAlbumResolver : GenericFolderResolver<PhotoAlbum>
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager; private readonly NamingOptions _namingOptions;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class. /// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class.
/// </summary> /// </summary>
/// <param name="imageProcessor">The image processor.</param> /// <param name="imageProcessor">The image processor.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="namingOptions">The naming options.</param>
public PhotoAlbumResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager) public PhotoAlbumResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_libraryManager = libraryManager; _namingOptions = namingOptions;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -73,7 +74,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
foreach (var siblingFile in files) foreach (var siblingFile in files)
{ {
if (PhotoResolver.IsOwnedByMedia(_libraryManager, siblingFile.FullName, filename)) if (PhotoResolver.IsOwnedByMedia(_namingOptions, siblingFile.FullName, filename))
{ {
ownedByMedia = true; ownedByMedia = true;
break; break;

View File

@@ -6,6 +6,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -16,7 +18,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
public class PhotoResolver : ItemResolver<Photo> public class PhotoResolver : ItemResolver<Photo>
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager; private readonly NamingOptions _namingOptions;
private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> _ignoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{ {
"folder", "folder",
@@ -30,10 +33,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
"default" "default"
}; };
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_libraryManager = libraryManager; _namingOptions = namingOptions;
} }
/// <summary> /// <summary>
@@ -60,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
foreach (var file in files) foreach (var file in files)
{ {
if (IsOwnedByMedia(_libraryManager, file.FullName, filename)) if (IsOwnedByMedia(_namingOptions, file.FullName, filename))
{ {
return null; return null;
} }
@@ -77,17 +81,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null; return null;
} }
internal static bool IsOwnedByMedia(ILibraryManager libraryManager, string file, string imageFilename) internal static bool IsOwnedByMedia(NamingOptions namingOptions, string file, string imageFilename)
{ {
if (libraryManager.IsVideoFile(file)) return VideoResolver.IsVideoFile(file, namingOptions) && IsOwnedByResolvedMedia(file, imageFilename);
{
return IsOwnedByResolvedMedia(libraryManager, file, imageFilename);
}
return false;
} }
internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, string file, string imageFilename) internal static bool IsOwnedByResolvedMedia(string file, string imageFilename)
=> imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase); => imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
internal static bool IsImageFile(string path, IImageProcessor imageProcessor) internal static bool IsImageFile(string path, IImageProcessor imageProcessor)

View File

@@ -2,6 +2,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -17,9 +18,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EpisodeResolver"/> class. /// Initializes a new instance of the <see cref="EpisodeResolver"/> class.
/// </summary> /// </summary>
/// <param name="libraryManager">The library manager.</param> /// <param name="namingOptions">The naming options.</param>
public EpisodeResolver(ILibraryManager libraryManager) public EpisodeResolver(NamingOptions namingOptions)
: base(libraryManager) : base(namingOptions)
{ {
} }

View File

@@ -1,6 +1,7 @@
#nullable disable #nullable disable
using System.Globalization; using System.Globalization;
using Emby.Naming.Common;
using Emby.Naming.TV; using Emby.Naming.TV;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -14,22 +15,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// </summary> /// </summary>
public class SeasonResolver : GenericFolderResolver<Season> public class SeasonResolver : GenericFolderResolver<Season>
{ {
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly ILogger<SeasonResolver> _logger; private readonly ILogger<SeasonResolver> _logger;
private readonly NamingOptions _namingOptions;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SeasonResolver"/> class. /// Initializes a new instance of the <see cref="SeasonResolver"/> class.
/// </summary> /// </summary>
/// <param name="libraryManager">The library manager.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="localization">The localization.</param> /// <param name="localization">The localization.</param>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
public SeasonResolver( public SeasonResolver(
ILibraryManager libraryManager, NamingOptions namingOptions,
ILocalizationManager localization, ILocalizationManager localization,
ILogger<SeasonResolver> logger) ILogger<SeasonResolver> logger)
{ {
_libraryManager = libraryManager; _namingOptions = namingOptions;
_localization = localization; _localization = localization;
_logger = logger; _logger = logger;
} }
@@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{ {
if (args.Parent is Series series && args.IsDirectory) if (args.Parent is Series series && args.IsDirectory)
{ {
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var namingOptions = _namingOptions;
var path = args.Path; var path = args.Path;
@@ -65,18 +66,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var episodeInfo = resolver.Resolve(testPath, true); var episodeInfo = resolver.Resolve(testPath, true);
if (episodeInfo != null) if (episodeInfo?.EpisodeNumber != null && episodeInfo.SeasonNumber.HasValue)
{ {
if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue) _logger.LogDebug(
{ "Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
_logger.LogDebug( path,
"Found folder underneath series with episode number: {0}. Season {1}. Episode {2}", episodeInfo.SeasonNumber.Value,
path, episodeInfo.EpisodeNumber.Value);
episodeInfo.SeasonNumber.Value,
episodeInfo.EpisodeNumber.Value);
return null; return null;
}
} }
} }

View File

@@ -5,7 +5,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common;
using Emby.Naming.TV; using Emby.Naming.TV;
using Emby.Naming.Video;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
@@ -21,17 +26,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
public class SeriesResolver : GenericFolderResolver<Series> public class SeriesResolver : GenericFolderResolver<Series>
{ {
private readonly ILogger<SeriesResolver> _logger; private readonly ILogger<SeriesResolver> _logger;
private readonly ILibraryManager _libraryManager; private readonly NamingOptions _namingOptions;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SeriesResolver"/> class. /// Initializes a new instance of the <see cref="SeriesResolver"/> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="namingOptions">The naming options.</param>
public SeriesResolver(ILogger<SeriesResolver> logger, ILibraryManager libraryManager) public SeriesResolver(ILogger<SeriesResolver> logger, NamingOptions namingOptions)
{ {
_logger = logger; _logger = logger;
_libraryManager = libraryManager; _namingOptions = namingOptions;
} }
/// <summary> /// <summary>
@@ -54,12 +59,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null; return null;
} }
var seriesInfo = Naming.TV.SeriesResolver.Resolve(_libraryManager.GetNamingOptions(), args.Path); var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path);
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{ {
var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path); // TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType
var configuredContentType = args.GetConfiguredContentType();
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{ {
return new Series return new Series
@@ -91,7 +97,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null; return null;
} }
if (IsSeriesFolder(args.Path, args.FileSystemChildren, _logger, _libraryManager, false)) if (IsSeriesFolder(args.Path, args.FileSystemChildren, false))
{ {
return new Series return new Series
{ {
@@ -105,11 +111,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null; return null;
} }
public static bool IsSeriesFolder( private bool IsSeriesFolder(
string path, string path,
IEnumerable<FileSystemMetadata> fileSystemChildren, IEnumerable<FileSystemMetadata> fileSystemChildren,
ILogger<SeriesResolver> logger,
ILibraryManager libraryManager,
bool isTvContentType) bool isTvContentType)
{ {
foreach (var child in fileSystemChildren) foreach (var child in fileSystemChildren)
@@ -118,21 +122,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{ {
if (IsSeasonFolder(child.FullName, isTvContentType)) if (IsSeasonFolder(child.FullName, isTvContentType))
{ {
logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName); _logger.LogDebug("{Path} is a series because of season folder {Dir}.", path, child.FullName);
return true; return true;
} }
} }
else else
{ {
string fullName = child.FullName; string fullName = child.FullName;
if (libraryManager.IsVideoFile(fullName)) if (VideoResolver.IsVideoFile(path, _namingOptions))
{ {
if (isTvContentType) if (isTvContentType)
{ {
return true; return true;
} }
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions(); var namingOptions = _namingOptions;
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions); var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions);
@@ -145,7 +149,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
} }
} }
logger.LogDebug("{Path} is not a series folder.", path); _logger.LogDebug("{Path} is not a series folder.", path);
return false; return false;
} }

View File

@@ -0,0 +1,156 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
using Jellyfin.Data.Enums;
using Microsoft.Extensions.Logging;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Validators
{
/// <summary>
/// Class CollectionPostScanTask.
/// </summary>
public class CollectionPostScanTask : ILibraryPostScanTask
{
private readonly ILibraryManager _libraryManager;
private readonly ICollectionManager _collectionManager;
private readonly ILogger<CollectionPostScanTask> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="CollectionPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="collectionManager">The collection manager.</param>
/// <param name="logger">The logger.</param>
public CollectionPostScanTask(
ILibraryManager libraryManager,
ICollectionManager collectionManager,
ILogger<CollectionPostScanTask> logger)
{
_libraryManager = libraryManager;
_collectionManager = collectionManager;
_logger = logger;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var collectionNameMoviesMap = new Dictionary<string, HashSet<Guid>>();
foreach (var library in _libraryManager.RootFolder.Children)
{
if (!_libraryManager.GetLibraryOptions(library).AutomaticallyAddToCollection)
{
continue;
}
var startIndex = 0;
var pagesize = 1000;
while (true)
{
var movies = _libraryManager.GetItemList(new InternalItemsQuery
{
MediaTypes = new string[] { MediaType.Video },
IncludeItemTypes = new[] { nameof(Movie) },
IsVirtualItem = false,
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Parent = library,
StartIndex = startIndex,
Limit = pagesize,
Recursive = true
});
foreach (var m in movies)
{
if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName))
{
if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList))
{
movieList.Add(movie.Id);
}
else
{
collectionNameMoviesMap[movie.CollectionName] = new HashSet<Guid> { movie.Id };
}
}
}
if (movies.Count < pagesize)
{
break;
}
startIndex += pagesize;
}
}
var numComplete = 0;
var count = collectionNameMoviesMap.Count;
if (count == 0)
{
progress.Report(100);
return;
}
var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(BoxSet) },
CollapseBoxSetItems = false,
Recursive = true
});
foreach (var (collectionName, movieIds) in collectionNameMoviesMap)
{
try
{
var boxSet = boxSets.FirstOrDefault(b => b?.Name == collectionName) as BoxSet;
if (boxSet == null)
{
// won't automatically create collection if only one movie in it
if (movieIds.Count >= 2)
{
boxSet = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions
{
Name = collectionName,
IsLocked = true
});
await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
}
}
else
{
await _collectionManager.AddToCollectionAsync(boxSet.Id, movieIds);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error refreshing {CollectionName} with {@MovieIds}", collectionName, movieIds);
}
}
progress.Report(100);
}
}
}

View File

@@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"-i \"{0}\" {2} -map_metadata -1 -threads {6} {3}{4}{5} -y \"{1}\"", "-i \"{0}\" {2} -map_metadata -1 -threads {6} {3}{4}{5} -y \"{1}\"",
inputTempFile, inputTempFile,
targetFile.Replace("\"", "\\\""), // Escape quotes in filename targetFile.Replace("\"", "\\\"", StringComparison.Ordinal), // Escape quotes in filename
videoArgs, videoArgs,
GetAudioArgs(mediaSource), GetAudioArgs(mediaSource),
subtitleArgs, subtitleArgs,

View File

@@ -9,8 +9,10 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Mime; using System.Net.Mime;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
@@ -20,7 +22,6 @@ using Jellyfin.Extensions;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
@@ -35,7 +36,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ILogger<SchedulesDirect> _logger; private readonly ILogger<SchedulesDirect> _logger;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
@@ -43,12 +43,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public SchedulesDirect( public SchedulesDirect(
ILogger<SchedulesDirect> logger, ILogger<SchedulesDirect> logger,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory)
ICryptoProvider cryptoProvider)
{ {
_logger = logger; _logger = logger;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_cryptoProvider = cryptoProvider;
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -104,11 +102,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
} }
}; };
var requestString = JsonSerializer.Serialize(requestList, _jsonOptions); _logger.LogDebug("Request string for schedules is: {@RequestString}", requestList);
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules"); using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules");
options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json); options.Content = JsonContent.Create(requestList, options: _jsonOptions);
options.Headers.TryAddWithoutValidation("token", token); options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false); using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
@@ -124,8 +121,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
programRequestOptions.Headers.TryAddWithoutValidation("token", token); programRequestOptions.Headers.TryAddWithoutValidation("token", token);
var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct(); var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
programRequestOptions.Content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(programIds, _jsonOptions)); programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions);
programRequestOptions.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false); using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
@@ -169,12 +165,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
const double DesiredAspect = 2.0 / 3; const double DesiredAspect = 2.0 / 3;
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, true, DesiredAspect) ?? programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect) ??
GetProgramImage(ApiUrl, allImages, true, DesiredAspect); GetProgramImage(ApiUrl, allImages, DesiredAspect);
const double WideAspect = 16.0 / 9; const double WideAspect = 16.0 / 9;
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, true, WideAspect); programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect);
// Don't supply the same image twice // Don't supply the same image twice
if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal)) if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
@@ -182,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
programEntry.ThumbImage = null; programEntry.ThumbImage = null;
} }
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect); programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect);
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
@@ -403,7 +399,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return info; return info;
} }
private string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, bool returnDefaultImage, double desiredAspect) private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect)
{ {
var match = images var match = images
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i))) .OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
@@ -648,7 +644,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token"); using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
var hashedPasswordBytes = _cryptoProvider.ComputeHash("SHA1", Encoding.ASCII.GetBytes(password), Array.Empty<byte>()); var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password));
// TODO: remove ToLower when Convert.ToHexString supports lowercase // TODO: remove ToLower when Convert.ToHexString supports lowercase
// Schedules Direct requires the hex to be lowercase // Schedules Direct requires the hex to be lowercase
string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant(); string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant();

View File

@@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken) public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken)
{ {
using var client = new TcpClient(); using var client = new TcpClient();
client.Connect(remoteIp, HdHomeRunPort); await client.ConnectAsync(remoteIp, HdHomeRunPort).ConfigureAwait(false);
using var stream = client.GetStream(); using var stream = client.GetStream();
return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false); return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false);

View File

@@ -283,7 +283,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
// #EXTINF:0,84.0 - VOX Schweiz // #EXTINF:0,84.0 - VOX Schweiz
if (!string.IsNullOrWhiteSpace(nameInExtInf)) if (!string.IsNullOrWhiteSpace(nameInExtInf))
{ {
var numberIndex = nameInExtInf.IndexOf(' '); var numberIndex = nameInExtInf.IndexOf(' ', StringComparison.Ordinal);
if (numberIndex > 0) if (numberIndex > 0)
{ {
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' }); var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });

View File

@@ -11,11 +11,11 @@
"Collections": "التجميعات", "Collections": "التجميعات",
"DeviceOfflineWithName": "قُطِع الاتصال ب{0}", "DeviceOfflineWithName": "قُطِع الاتصال ب{0}",
"DeviceOnlineWithName": "{0} متصل", "DeviceOnlineWithName": "{0} متصل",
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}", "FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فشلت من {0}",
"Favorites": "مفضلات", "Favorites": "مفضلات",
"Folders": "المجلدات", "Folders": "المجلدات",
"Genres": "التضنيفات", "Genres": "التضنيفات",
"HeaderAlbumArtists": "ألبوم الفنان", "HeaderAlbumArtists": "فناني الألبوم",
"HeaderContinueWatching": "استمر بالمشاهدة", "HeaderContinueWatching": "استمر بالمشاهدة",
"HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون", "HeaderFavoriteArtists": "الفنانون المفضلون",

View File

@@ -0,0 +1 @@
{}

View File

@@ -1 +1,4 @@
{} {
"Sync": "Сінхранізацыя",
"Playlists": "Плэйліст"
}

View File

@@ -25,7 +25,7 @@
"HeaderLiveTV": "TV en Directe", "HeaderLiveTV": "TV en Directe",
"HeaderNextUp": "A continuació", "HeaderNextUp": "A continuació",
"HeaderRecordingGroups": "Grups d'Enregistrament", "HeaderRecordingGroups": "Grups d'Enregistrament",
"HomeVideos": "Vídeos domèstics", "HomeVideos": "Vídeos Domèstics",
"Inherit": "Hereta", "Inherit": "Hereta",
"ItemAddedWithName": "{0} ha estat afegit a la biblioteca", "ItemAddedWithName": "{0} ha estat afegit a la biblioteca",
"ItemRemovedWithName": "{0} ha estat eliminat de la biblioteca", "ItemRemovedWithName": "{0} ha estat eliminat de la biblioteca",
@@ -39,7 +39,7 @@
"MixedContent": "Contingut barrejat", "MixedContent": "Contingut barrejat",
"Movies": "Pel·lícules", "Movies": "Pel·lícules",
"Music": "Música", "Music": "Música",
"MusicVideos": "Vídeos musicals", "MusicVideos": "Vídeos Musicals",
"NameInstallFailed": "Instalació de {0} fallida", "NameInstallFailed": "Instalació de {0} fallida",
"NameSeasonNumber": "Temporada {0}", "NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada Desconeguda", "NameSeasonUnknown": "Temporada Desconeguda",

View File

@@ -15,7 +15,7 @@
"Favorites": "Oblíbené", "Favorites": "Oblíbené",
"Folders": "Složky", "Folders": "Složky",
"Genres": "Žánry", "Genres": "Žánry",
"HeaderAlbumArtists": "Album umělce", "HeaderAlbumArtists": "Umělci alba",
"HeaderContinueWatching": "Pokračovat ve sledování", "HeaderContinueWatching": "Pokračovat ve sledování",
"HeaderFavoriteAlbums": "Oblíbená alba", "HeaderFavoriteAlbums": "Oblíbená alba",
"HeaderFavoriteArtists": "Oblíbení interpreti", "HeaderFavoriteArtists": "Oblíbení interpreti",

View File

@@ -15,7 +15,7 @@
"Favorites": "Favourites", "Favorites": "Favourites",
"Folders": "Folders", "Folders": "Folders",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Artist's Album", "HeaderAlbumArtists": "Album artists",
"HeaderContinueWatching": "Continue Watching", "HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favourite Albums", "HeaderFavoriteAlbums": "Favourite Albums",
"HeaderFavoriteArtists": "Favourite Artists", "HeaderFavoriteArtists": "Favourite Artists",

View File

@@ -12,12 +12,12 @@
"Default": "Default", "Default": "Default",
"DeviceOfflineWithName": "{0} has disconnected", "DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected", "DeviceOnlineWithName": "{0} is connected",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "FailedLoginAttemptWithUserName": "Failed login try from {0}",
"Favorites": "Favorites", "Favorites": "Favorites",
"Folders": "Folders", "Folders": "Folders",
"Forced": "Forced", "Forced": "Forced",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Artist's Album", "HeaderAlbumArtists": "Album artists",
"HeaderContinueWatching": "Continue Watching", "HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favorite Albums", "HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteArtists": "Favorite Artists", "HeaderFavoriteArtists": "Favorite Artists",

View File

@@ -11,7 +11,7 @@
"ItemAddedWithName": "{0} aldonis al la plurmediteko", "ItemAddedWithName": "{0} aldonis al la plurmediteko",
"HeaderLiveTV": "TV-etero", "HeaderLiveTV": "TV-etero",
"HeaderContinueWatching": "Daŭrigi Spektadon", "HeaderContinueWatching": "Daŭrigi Spektadon",
"HeaderAlbumArtists": "Albumo de artisto", "HeaderAlbumArtists": "Artistoj de albumo",
"Folders": "Dosierujoj", "Folders": "Dosierujoj",
"DeviceOnlineWithName": "{0} estas konektita", "DeviceOnlineWithName": "{0} estas konektita",
"Default": "Defaŭlte", "Default": "Defaŭlte",
@@ -104,7 +104,7 @@
"TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.", "TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
"TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn", "TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn",
"TaskCleanTranscode": "Malplenigi Transkodadan Katalogon", "TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
"TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn", "TaskRefreshChapterImages": "Eltiri Ĉapitrajn Bildojn",
"TaskCleanCache": "Malplenigi Staplan Katalogon", "TaskCleanCache": "Malplenigi Staplan Katalogon",
"TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon", "TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon",
"PluginUpdatedWithName": "{0} estis ĝisdatigita", "PluginUpdatedWithName": "{0} estis ĝisdatigita",

View File

@@ -15,8 +15,8 @@
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artista del álbum", "HeaderAlbumArtists": "Artistas del álbum",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos",

View File

@@ -15,7 +15,7 @@
"HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteShows": "Programas favoritos",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Continuar viendo",
"HeaderAlbumArtists": "Artistas del álbum", "HeaderAlbumArtists": "Artistas de álbum",
"Genres": "Géneros", "Genres": "Géneros",
"Folders": "Carpetas", "Folders": "Carpetas",
"Favorites": "Favoritos", "Favorites": "Favoritos",
@@ -29,7 +29,7 @@
"TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
"TaskRefreshChannels": "Actualizar canales", "TaskRefreshChannels": "Actualizar canales",
"TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
"TaskCleanTranscode": "Limpiar directorio de transcodificado", "TaskCleanTranscode": "Limpiar el directorio de transcodificaciones",
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
"TaskUpdatePlugins": "Actualizar complementos", "TaskUpdatePlugins": "Actualizar complementos",
"TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.", "TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.",
@@ -105,7 +105,7 @@
"Inherit": "Heredar", "Inherit": "Heredar",
"HomeVideos": "Videos caseros", "HomeVideos": "Videos caseros",
"HeaderRecordingGroups": "Grupos de grabación", "HeaderRecordingGroups": "Grupos de grabación",
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}",
"DeviceOnlineWithName": "{0} está conectado", "DeviceOnlineWithName": "{0} está conectado",
"DeviceOfflineWithName": "{0} se ha desconectado", "DeviceOfflineWithName": "{0} se ha desconectado",
"ChapterNameValue": "Capítulo {0}", "ChapterNameValue": "Capítulo {0}",
@@ -114,10 +114,10 @@
"Application": "Aplicación", "Application": "Aplicación",
"AppDeviceValues": "App: {0}, Dispositivo: {1}", "AppDeviceValues": "App: {0}, Dispositivo: {1}",
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.", "TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
"TaskCleanActivityLog": "Limpiar Registro de Actividades", "TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir", "Undefined": "Sin definir",
"Forced": "Forzado", "Forced": "Forzado",
"Default": "Por Defecto", "Default": "Por defecto",
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y restaura el espacio libre. Ejecutar esta tarea después de actualizar las librerías o realizar otros cambios que impliquen modificar las bases de datos puede mejorar la performance.", "TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
"TaskOptimizeDatabase": "Optimización de base de datos" "TaskOptimizeDatabase": "Optimizar base de datos"
} }

View File

@@ -32,9 +32,9 @@
"ValueSpecialEpisodeName": "Eriepisood - {0}", "ValueSpecialEpisodeName": "Eriepisood - {0}",
"ValueHasBeenAddedToLibrary": "{0} lisati meediakogusse", "ValueHasBeenAddedToLibrary": "{0} lisati meediakogusse",
"UserStartedPlayingItemWithValues": "{0} taasesitab {1} serveris {2}", "UserStartedPlayingItemWithValues": "{0} taasesitab {1} serveris {2}",
"UserPasswordChangedWithName": "Kasutaja {0} parooli on muudetud", "UserPasswordChangedWithName": "Kasutaja {0} parool muudeti",
"UserLockedOutWithName": "Kasutaja {0} on lukustatud", "UserLockedOutWithName": "Kasutaja {0} lukustati",
"UserDeletedWithName": "Kasutaja {0} on kustutatud", "UserDeletedWithName": "Kasutaja {0} kustutati",
"UserCreatedWithName": "Kasutaja {0} on loodud", "UserCreatedWithName": "Kasutaja {0} on loodud",
"ScheduledTaskStartedWithName": "{0} käivitati", "ScheduledTaskStartedWithName": "{0} käivitati",
"ProviderValue": "Allikas: {0}", "ProviderValue": "Allikas: {0}",
@@ -54,9 +54,9 @@
"Plugin": "Plugin", "Plugin": "Plugin",
"Playlists": "Pleilistid", "Playlists": "Pleilistid",
"Photos": "Fotod", "Photos": "Fotod",
"NotificationOptionVideoPlaybackStopped": "Video taasesitus on peatatud", "NotificationOptionVideoPlaybackStopped": "Video taasesitus lõppes",
"NotificationOptionVideoPlayback": "Video taasesitus algas", "NotificationOptionVideoPlayback": "Video taasesitus algas",
"NotificationOptionUserLockedOut": "Kasutaja on lukustatud", "NotificationOptionUserLockedOut": "Kasutaja lukustati",
"NotificationOptionTaskFailed": "Ajastatud ülesanne nurjus", "NotificationOptionTaskFailed": "Ajastatud ülesanne nurjus",
"NotificationOptionServerRestartRequired": "Vajalik on serveri taaskäivitamine", "NotificationOptionServerRestartRequired": "Vajalik on serveri taaskäivitamine",
"NotificationOptionPluginUpdateInstalled": "Paigaldati plugina uuendus", "NotificationOptionPluginUpdateInstalled": "Paigaldati plugina uuendus",
@@ -66,7 +66,7 @@
"NotificationOptionNewLibraryContent": "Lisati uut sisu", "NotificationOptionNewLibraryContent": "Lisati uut sisu",
"NotificationOptionInstallationFailed": "Paigaldamine nurjus", "NotificationOptionInstallationFailed": "Paigaldamine nurjus",
"NotificationOptionCameraImageUploaded": "Kaamera pilt on üles laaditud", "NotificationOptionCameraImageUploaded": "Kaamera pilt on üles laaditud",
"NotificationOptionAudioPlaybackStopped": "Heli taasesitus peatati", "NotificationOptionAudioPlaybackStopped": "Heli taasesitus lõppes",
"NotificationOptionAudioPlayback": "Heli taasesitus algas", "NotificationOptionAudioPlayback": "Heli taasesitus algas",
"NotificationOptionApplicationUpdateInstalled": "Rakenduse uuendus paigaldati", "NotificationOptionApplicationUpdateInstalled": "Rakenduse uuendus paigaldati",
"NotificationOptionApplicationUpdateAvailable": "Rakenduse uuendus on saadaval", "NotificationOptionApplicationUpdateAvailable": "Rakenduse uuendus on saadaval",
@@ -97,12 +97,12 @@
"HeaderFavoriteArtists": "Lemmikesitajad", "HeaderFavoriteArtists": "Lemmikesitajad",
"HeaderFavoriteAlbums": "Lemmikalbumid", "HeaderFavoriteAlbums": "Lemmikalbumid",
"HeaderContinueWatching": "Jätka vaatamist", "HeaderContinueWatching": "Jätka vaatamist",
"HeaderAlbumArtists": "Albumi esitaja", "HeaderAlbumArtists": "Albumi esitajad",
"Genres": "Žanrid", "Genres": "Žanrid",
"Forced": "Sunnitud", "Forced": "Sunnitud",
"Folders": "Kaustad", "Folders": "Kaustad",
"Favorites": "Lemmikud", "Favorites": "Lemmikud",
"FailedLoginAttemptWithUserName": "Ebaõnnestunud sisselogimiskatse kasutajalt {0}", "FailedLoginAttemptWithUserName": "{0} - sisselogimine nurjus",
"DeviceOnlineWithName": "{0} on ühendatud", "DeviceOnlineWithName": "{0} on ühendatud",
"DeviceOfflineWithName": "{0} katkestas ühenduse", "DeviceOfflineWithName": "{0} katkestas ühenduse",
"Default": "Vaikimisi", "Default": "Vaikimisi",
@@ -114,5 +114,10 @@
"Artists": "Esitajad", "Artists": "Esitajad",
"Application": "Rakendus", "Application": "Rakendus",
"AppDeviceValues": "Rakendus: {0}, seade: {1}", "AppDeviceValues": "Rakendus: {0}, seade: {1}",
"Albums": "Albumid" "Albums": "Albumid",
"UserOfflineFromDevice": "{0} katkestas ühenduse seadmega {1}",
"SubtitleDownloadFailureFromForItem": "Subtiitrite allalaadimine {0} > {1} nurjus",
"UserPolicyUpdatedWithName": "Kasutaja {0} õigusi värskendati",
"UserStoppedPlayingItemWithValues": "{0} lõpetas {1} taasesituse seadmes {2}",
"UserOnlineFromDevice": "{0} on ühendatud seadmest {1}"
} }

View File

@@ -6,7 +6,7 @@
"AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد", "AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد",
"Books": "کتاب‌ها", "Books": "کتاب‌ها",
"CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}", "CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}",
"Channels": "کانالها", "Channels": "کانالها",
"ChapterNameValue": "قسمت {0}", "ChapterNameValue": "قسمت {0}",
"Collections": "مجموعه‌ها", "Collections": "مجموعه‌ها",
"DeviceOfflineWithName": "ارتباط {0} قطع شد", "DeviceOfflineWithName": "ارتباط {0} قطع شد",
@@ -37,7 +37,7 @@
"MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد", "MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد",
"MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد", "MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد",
"MixedContent": "محتوای مخلوط", "MixedContent": "محتوای مخلوط",
"Movies": "فیلمها", "Movies": "فیلم ها",
"Music": "موسیقی", "Music": "موسیقی",
"MusicVideos": "موزیک ویدیوها", "MusicVideos": "موزیک ویدیوها",
"NameInstallFailed": "{0} نصب با مشکل مواجه شد", "NameInstallFailed": "{0} نصب با مشکل مواجه شد",

View File

@@ -15,7 +15,7 @@
"Favorites": "Favoris", "Favorites": "Favoris",
"Folders": "Dossiers", "Folders": "Dossiers",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Artistes de l'album", "HeaderAlbumArtists": "Artistes d'album",
"HeaderContinueWatching": "Continuer à regarder", "HeaderContinueWatching": "Continuer à regarder",
"HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteAlbums": "Albums favoris",
"HeaderFavoriteArtists": "Artistes préférés", "HeaderFavoriteArtists": "Artistes préférés",

View File

@@ -15,7 +15,7 @@
"Favorites": "Favoriti", "Favorites": "Favoriti",
"Folders": "Mape", "Folders": "Mape",
"Genres": "Žanrovi", "Genres": "Žanrovi",
"HeaderAlbumArtists": "Album od izvođača", "HeaderAlbumArtists": "Izvođači albuma",
"HeaderContinueWatching": "Nastavi gledati", "HeaderContinueWatching": "Nastavi gledati",
"HeaderFavoriteAlbums": "Omiljeni albumi", "HeaderFavoriteAlbums": "Omiljeni albumi",
"HeaderFavoriteArtists": "Omiljeni izvođači", "HeaderFavoriteArtists": "Omiljeni izvođači",

View File

@@ -11,11 +11,11 @@
"Collections": "Gyűjtemények", "Collections": "Gyűjtemények",
"DeviceOfflineWithName": "{0} kijelentkezett", "DeviceOfflineWithName": "{0} kijelentkezett",
"DeviceOnlineWithName": "{0} belépett", "DeviceOnlineWithName": "{0} belépett",
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet tőle: {0}", "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet innen: {0}",
"Favorites": "Kedvencek", "Favorites": "Kedvencek",
"Folders": "Könyvtárak", "Folders": "Könyvtárak",
"Genres": "Műfajok", "Genres": "Műfajok",
"HeaderAlbumArtists": "Előadó albumai", "HeaderAlbumArtists": "Album előadó(k)",
"HeaderContinueWatching": "Megtekintés folytatása", "HeaderContinueWatching": "Megtekintés folytatása",
"HeaderFavoriteAlbums": "Kedvenc albumok", "HeaderFavoriteAlbums": "Kedvenc albumok",
"HeaderFavoriteArtists": "Kedvenc előadók", "HeaderFavoriteArtists": "Kedvenc előadók",

View File

@@ -7,10 +7,10 @@
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui", "MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
"Latest": "Terbaru", "Latest": "Terbaru",
"LabelIpAddressValue": "Alamat IP: {0}", "LabelIpAddressValue": "Alamat IP: {0}",
"ItemRemovedWithName": "{0} sudah dikeluarkan dari pustaka", "ItemRemovedWithName": "{0} sudah dihapus dari pustaka",
"ItemAddedWithName": "{0} telah dimasukkan ke dalam pustaka", "ItemAddedWithName": "{0} telah dimasukkan ke dalam pustaka",
"Inherit": "Warisan", "Inherit": "Warisi",
"HomeVideos": "Video Rumah", "HomeVideos": "Video Rumahan",
"HeaderRecordingGroups": "Grup Rekaman", "HeaderRecordingGroups": "Grup Rekaman",
"HeaderNextUp": "Selanjutnya", "HeaderNextUp": "Selanjutnya",
"HeaderLiveTV": "TV Live", "HeaderLiveTV": "TV Live",
@@ -73,7 +73,7 @@
"NotificationOptionCameraImageUploaded": "Gambar kamera terunggah", "NotificationOptionCameraImageUploaded": "Gambar kamera terunggah",
"NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang", "NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang",
"NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia", "NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia",
"NewVersionIsAvailable": "Versi baru dari Jellyfin Server tersedia untuk diunduh.", "NewVersionIsAvailable": "Versi baru dari Jellyfin Server sudah tersedia untuk diunduh.",
"NameSeasonUnknown": "Musim tak diketahui", "NameSeasonUnknown": "Musim tak diketahui",
"NameSeasonNumber": "Musim {0}", "NameSeasonNumber": "Musim {0}",
"NameInstallFailed": "{0} penginstalan gagal", "NameInstallFailed": "{0} penginstalan gagal",
@@ -117,5 +117,7 @@
"TaskCleanActivityLog": "Bersihkan Log Aktivitas", "TaskCleanActivityLog": "Bersihkan Log Aktivitas",
"Undefined": "Tidak terdefinisi", "Undefined": "Tidak terdefinisi",
"Forced": "Dipaksa", "Forced": "Dipaksa",
"Default": "Bawaan" "Default": "Bawaan",
"TaskOptimizeDatabaseDescription": "Rapihkan basis data dan membersihkan ruang kosong. Menjalankan tugas ini setelah memindai pustaka atau melakukan perubahan lain yang menyiratkan modifikasi basis data dapat meningkatkan kinerja.",
"TaskOptimizeDatabase": "Optimalkan basis data"
} }

View File

@@ -15,7 +15,7 @@
"Favorites": "Preferiti", "Favorites": "Preferiti",
"Folders": "Cartelle", "Folders": "Cartelle",
"Genres": "Generi", "Genres": "Generi",
"HeaderAlbumArtists": "Artisti dell'Album", "HeaderAlbumArtists": "Artisti dell'album",
"HeaderContinueWatching": "Continua a guardare", "HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album Preferiti", "HeaderFavoriteAlbums": "Album Preferiti",
"HeaderFavoriteArtists": "Artisti Preferiti", "HeaderFavoriteArtists": "Artisti Preferiti",

View File

@@ -11,11 +11,11 @@
"Collections": "コレクション", "Collections": "コレクション",
"DeviceOfflineWithName": "{0} が切断されました", "DeviceOfflineWithName": "{0} が切断されました",
"DeviceOnlineWithName": "{0} が接続されました", "DeviceOnlineWithName": "{0} が接続されました",
"FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました", "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0} によって失敗しました",
"Favorites": "お気に入り", "Favorites": "お気に入り",
"Folders": "フォルダー", "Folders": "フォルダー",
"Genres": "ジャンル", "Genres": "ジャンル",
"HeaderAlbumArtists": "アーティストのアルバム", "HeaderAlbumArtists": "アルバムアーティスト",
"HeaderContinueWatching": "視聴を続ける", "HeaderContinueWatching": "視聴を続ける",
"HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteAlbums": "お気に入りのアルバム",
"HeaderFavoriteArtists": "お気に入りのアーティスト", "HeaderFavoriteArtists": "お気に入りのアーティスト",

View File

@@ -15,7 +15,7 @@
"Favorites": "Tañdaulylar", "Favorites": "Tañdaulylar",
"Folders": "Qaltalar", "Folders": "Qaltalar",
"Genres": "Janrlar", "Genres": "Janrlar",
"HeaderAlbumArtists": "Oryndauşynyñ älbomy", "HeaderAlbumArtists": "Älbom oryndauşylary",
"HeaderContinueWatching": "Qaraudy jalğastyru", "HeaderContinueWatching": "Qaraudy jalğastyru",
"HeaderFavoriteAlbums": "Tañdauly älbomdar", "HeaderFavoriteAlbums": "Tañdauly älbomdar",
"HeaderFavoriteArtists": "Tañdauly oryndauşylar", "HeaderFavoriteArtists": "Tañdauly oryndauşylar",

View File

@@ -1,7 +1,7 @@
{ {
"Albums": "Albumai", "Albums": "Albumai",
"AppDeviceValues": "Programa: {0}, Įrenginys: {1}", "AppDeviceValues": "Programa: {0}, Įrenginys: {1}",
"Application": "Programa", "Application": "Programėlė",
"Artists": "Atlikėjai", "Artists": "Atlikėjai",
"AuthenticationSucceededWithUserName": "{0} sėkmingai autentifikuota", "AuthenticationSucceededWithUserName": "{0} sėkmingai autentifikuota",
"Books": "Knygos", "Books": "Knygos",

View File

@@ -5,7 +5,7 @@
"PluginUninstalledWithName": "{0} беше успешно деинсталирано", "PluginUninstalledWithName": "{0} беше успешно деинсталирано",
"PluginInstalledWithName": "{0} беше успешно инсталирано", "PluginInstalledWithName": "{0} беше успешно инсталирано",
"Plugin": "Додатоци", "Plugin": "Додатоци",
"Playlists": "Листи", "Playlists": "Плејлисти",
"Photos": "Слики", "Photos": "Слики",
"NotificationOptionVideoPlaybackStopped": "Видео стопирано", "NotificationOptionVideoPlaybackStopped": "Видео стопирано",
"NotificationOptionVideoPlayback": "Видео пуштено", "NotificationOptionVideoPlayback": "Видео пуштено",
@@ -50,7 +50,7 @@
"HeaderFavoriteEpisodes": "Омилени Епизоди", "HeaderFavoriteEpisodes": "Омилени Епизоди",
"HeaderFavoriteArtists": "Омилени Изведувачи", "HeaderFavoriteArtists": "Омилени Изведувачи",
"HeaderFavoriteAlbums": "Омилени Албуми", "HeaderFavoriteAlbums": "Омилени Албуми",
"HeaderContinueWatching": "Продолжи со гледање", "HeaderContinueWatching": "Продолжи со Гледање",
"HeaderAlbumArtists": "Изведувачи од Албуми", "HeaderAlbumArtists": "Изведувачи од Албуми",
"Genres": "Жанрови", "Genres": "Жанрови",
"Folders": "Папки", "Folders": "Папки",
@@ -97,5 +97,8 @@
"TasksChannelsCategory": "Интернет Канали", "TasksChannelsCategory": "Интернет Канали",
"TasksApplicationCategory": "Апликација", "TasksApplicationCategory": "Апликација",
"TasksLibraryCategory": "Библиотека", "TasksLibraryCategory": "Библиотека",
"TasksMaintenanceCategory": "Одржување" "TasksMaintenanceCategory": "Одржување",
"Undefined": "Недефинирано",
"Forced": "Принудно",
"Default": "Зададено"
} }

View File

@@ -0,0 +1,14 @@
{
"Books": "Номууд",
"HeaderNextUp": "Дараах",
"HeaderContinueWatching": "Үргэлжлүүлэн үзэх",
"Songs": "Дуунууд",
"Playlists": "Тоглуулах жагсаалт",
"Movies": "Кино",
"Latest": "Сүүлийн үеийн",
"Genres": "Төрөл зүйл",
"Favorites": "Дуртай",
"Collections": "Багц",
"Artists": "Зураачуд",
"Albums": "Цомгууд"
}

View File

@@ -37,9 +37,9 @@
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini", "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini",
"MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini", "MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini",
"MixedContent": "Kandungan campuran", "MixedContent": "Kandungan campuran",
"Movies": "Filem", "Movies": "Filem-filem",
"Music": "Muzik", "Music": "Muzik",
"MusicVideos": "Muzik video", "MusicVideos": "",
"NameInstallFailed": "{0} pemasangan gagal", "NameInstallFailed": "{0} pemasangan gagal",
"NameSeasonNumber": "Musim {0}", "NameSeasonNumber": "Musim {0}",
"NameSeasonUnknown": "Musim Tidak Diketahui", "NameSeasonUnknown": "Musim Tidak Diketahui",
@@ -53,43 +53,43 @@
"NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah", "NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah",
"NotificationOptionPluginError": "Kegagalan plugin", "NotificationOptionPluginError": "Kegagalan plugin",
"NotificationOptionPluginInstalled": "Plugin telah dipasang", "NotificationOptionPluginInstalled": "Plugin telah dipasang",
"NotificationOptionPluginUninstalled": "Plugin uninstalled", "NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang",
"NotificationOptionPluginUpdateInstalled": "Plugin update installed", "NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang",
"NotificationOptionServerRestartRequired": "Server restart required", "NotificationOptionServerRestartRequired": "",
"NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionTaskFailed": "Kegagalan tugas berjadual",
"NotificationOptionUserLockedOut": "User locked out", "NotificationOptionUserLockedOut": "Pengguna telah dikunci",
"NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlayback": "Ulangmain video bermula",
"NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan", "NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan",
"Photos": "Gambar-gambar", "Photos": "Gambar-gambar",
"Playlists": "Senarai main", "Playlists": "Senarai main",
"Plugin": "Plugin", "Plugin": "Plugin",
"PluginInstalledWithName": "{0} was installed", "PluginInstalledWithName": "{0} telah dipasang",
"PluginUninstalledWithName": "{0} was uninstalled", "PluginUninstalledWithName": "{0} telah dinyahpasang",
"PluginUpdatedWithName": "{0} was updated", "PluginUpdatedWithName": "{0} telah dikemaskini",
"ProviderValue": "Provider: {0}", "ProviderValue": "Pembekal: {0}",
"ScheduledTaskFailedWithName": "{0} gagal", "ScheduledTaskFailedWithName": "{0} gagal",
"ScheduledTaskStartedWithName": "{0} bermula", "ScheduledTaskStartedWithName": "{0} bermula",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "ServerNameNeedsToBeRestarted": "{0} perlu di ulangmula",
"Shows": "Series", "Shows": "Tayangan",
"Songs": "Lagu-lagu", "Songs": "Lagu-lagu",
"StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.", "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}", "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}",
"Sync": "Sync", "Sync": "",
"System": "Sistem", "System": "Sistem",
"TvShows": "TV Shows", "TvShows": "Tayangan TV",
"User": "User", "User": "Pengguna",
"UserCreatedWithName": "User {0} has been created", "UserCreatedWithName": "Pengguna {0} telah diwujudkan",
"UserDeletedWithName": "User {0} has been deleted", "UserDeletedWithName": "Pengguna {0} telah dipadamkan",
"UserDownloadingItemWithValues": "{0} is downloading {1}", "UserDownloadingItemWithValues": "{0} sedang memuat turun {1}",
"UserLockedOutWithName": "Pengguna {0} telah dikunci", "UserLockedOutWithName": "Pengguna {0} telah dikunci",
"UserOfflineFromDevice": "{0} telah terputus dari {1}", "UserOfflineFromDevice": "{0} telah terputus dari {1}",
"UserOnlineFromDevice": "{0} berada dalam talian dari {1}", "UserOnlineFromDevice": "{0} berada dalam talian dari {1}",
"UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}", "UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}",
"UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}", "UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}",
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", "UserStartedPlayingItemWithValues": "{0} sedang dimainkan {1} pada {2}",
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "UserStoppedPlayingItemWithValues": "{0} telah tamat dimainkan {1} pada {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library", "ValueHasBeenAddedToLibrary": "{0} telah ditambah ke media library anda",
"ValueSpecialEpisodeName": "Khas - {0}", "ValueSpecialEpisodeName": "Khas - {0}",
"VersionNumber": "Versi {0}", "VersionNumber": "Versi {0}",
"TaskCleanActivityLog": "Log Aktiviti Bersih", "TaskCleanActivityLog": "Log Aktiviti Bersih",

View File

@@ -69,7 +69,7 @@
"UserDeletedWithName": "प्रयोगकर्ता {0} हटाइएको छ", "UserDeletedWithName": "प्रयोगकर्ता {0} हटाइएको छ",
"UserCreatedWithName": "प्रयोगकर्ता {0} सिर्जना गरिएको छ", "UserCreatedWithName": "प्रयोगकर्ता {0} सिर्जना गरिएको छ",
"User": "प्रयोगकर्ता", "User": "प्रयोगकर्ता",
"PluginInstalledWithName": "", "PluginInstalledWithName": "{0} सभएको थियो",
"StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।", "StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।",
"Songs": "गीतहरू", "Songs": "गीतहरू",
"Shows": "शोहरू", "Shows": "शोहरू",

View File

@@ -15,7 +15,7 @@
"Favorites": "Favorieten", "Favorites": "Favorieten",
"Folders": "Mappen", "Folders": "Mappen",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Artiests Album", "HeaderAlbumArtists": "Album Artiesten",
"HeaderContinueWatching": "Kijken hervatten", "HeaderContinueWatching": "Kijken hervatten",
"HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteAlbums": "Favoriete albums",
"HeaderFavoriteArtists": "Favoriete artiesten", "HeaderFavoriteArtists": "Favoriete artiesten",

View File

@@ -24,7 +24,7 @@
"TasksLibraryCategory": "ਲਾਇਬ੍ਰੇਰੀ", "TasksLibraryCategory": "ਲਾਇਬ੍ਰੇਰੀ",
"TasksMaintenanceCategory": "ਰੱਖ-ਰਖਾਅ", "TasksMaintenanceCategory": "ਰੱਖ-ਰਖਾਅ",
"VersionNumber": "ਵਰਜਨ {0}", "VersionNumber": "ਵਰਜਨ {0}",
"ValueSpecialEpisodeName": "ਵਿਸ਼ੇਸ਼ - {0}", "ValueSpecialEpisodeName": "ਖਾਸ - {0}",
"ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ", "ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ",
"UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ", "UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ",
"UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ", "UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ",
@@ -43,8 +43,8 @@
"Sync": "ਸਿੰਕ", "Sync": "ਸਿੰਕ",
"SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾ toਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ", "SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾ toਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ",
"StartupEmbyServerIsLoading": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ. ਕਿਰਪਾ ਕਰਕੇ ਜਲਦੀ ਹੀ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ.", "StartupEmbyServerIsLoading": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ. ਕਿਰਪਾ ਕਰਕੇ ਜਲਦੀ ਹੀ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ.",
"Songs": "ਗਾਣੇ", "Songs": "ਗਾਣੇ",
"Shows": "ਸ਼ੋਅਜ਼", "Shows": "ਸ਼ੋਅ",
"ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ", "ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ",
"ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ", "ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ",
"ScheduledTaskFailedWithName": "{0} ਅਸਫਲ", "ScheduledTaskFailedWithName": "{0} ਅਸਫਲ",
@@ -53,7 +53,7 @@
"PluginUninstalledWithName": "{0} ਅਣਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ ਸੀ", "PluginUninstalledWithName": "{0} ਅਣਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ ਸੀ",
"PluginInstalledWithName": "{0} ਲਗਾਇਆ ਗਿਆ ਸੀ", "PluginInstalledWithName": "{0} ਲਗਾਇਆ ਗਿਆ ਸੀ",
"Plugin": "ਪਲੱਗਇਨ", "Plugin": "ਪਲੱਗਇਨ",
"Playlists": "ਪਲੇਲਿਸਟਸ", "Playlists": "ਪਲੇਸੂਚੀਆਂ",
"Photos": "ਫੋਟੋਆਂ", "Photos": "ਫੋਟੋਆਂ",
"NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ", "NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ",
"NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ", "NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ",
@@ -102,13 +102,13 @@
"HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ", "HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ",
"Genres": "ਸ਼ੈਲੀਆਂ", "Genres": "ਸ਼ੈਲੀਆਂ",
"Forced": "ਮਜਬੂਰ", "Forced": "ਮਜਬੂਰ",
"Folders": "ਫੋਲਡਰ", "Folders": "ਫੋਲਡਰ",
"Favorites": "ਮਨਪਸੰਦ", "Favorites": "ਮਨਪਸੰਦ",
"FailedLoginAttemptWithUserName": "ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ {0}", "FailedLoginAttemptWithUserName": "ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ {0}",
"DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ", "DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ",
"DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ", "DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ",
"Default": "ਮੂਲ", "Default": "ਡਿਫੌਲਟ",
"Collections": "ਸੰਗ੍ਰਹਿ", "Collections": "ਸੰਗ੍ਰਹਿ",
"ChapterNameValue": "ਅਧਿਆਇ {0}", "ChapterNameValue": "ਅਧਿਆਇ {0}",
"Channels": "ਚੈਨਲ", "Channels": "ਚੈਨਲ",
"CameraImageUploadedFrom": "ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ {0}", "CameraImageUploadedFrom": "ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ {0}",

View File

@@ -15,7 +15,7 @@
"Favorites": "Ulubione", "Favorites": "Ulubione",
"Folders": "Foldery", "Folders": "Foldery",
"Genres": "Gatunki", "Genres": "Gatunki",
"HeaderAlbumArtists": "Album artysty", "HeaderAlbumArtists": "Wykonawcy albumów",
"HeaderContinueWatching": "Kontynuuj odtwarzanie", "HeaderContinueWatching": "Kontynuuj odtwarzanie",
"HeaderFavoriteAlbums": "Ulubione albumy", "HeaderFavoriteAlbums": "Ulubione albumy",
"HeaderFavoriteArtists": "Ulubieni wykonawcy", "HeaderFavoriteArtists": "Ulubieni wykonawcy",
@@ -47,7 +47,7 @@
"NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji", "NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji",
"NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację", "NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację",
"NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki", "NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki",
"NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane", "NotificationOptionAudioPlaybackStopped": "Odtwarzanie dźwięku zatrzymane",
"NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego", "NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego",
"NotificationOptionInstallationFailed": "Nieudana instalacja", "NotificationOptionInstallationFailed": "Nieudana instalacja",
"NotificationOptionNewLibraryContent": "Dodano nową zawartość", "NotificationOptionNewLibraryContent": "Dodano nową zawartość",
@@ -98,7 +98,7 @@
"TaskRefreshChannels": "Odśwież kanały", "TaskRefreshChannels": "Odśwież kanały",
"TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.", "TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.",
"TaskCleanTranscode": "Wyczyść folder transkodowania", "TaskCleanTranscode": "Wyczyść folder transkodowania",
"TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów które są skonfigurowane do automatycznej aktualizacji.", "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów, które są skonfigurowane do automatycznej aktualizacji.",
"TaskUpdatePlugins": "Aktualizuj pluginy", "TaskUpdatePlugins": "Aktualizuj pluginy",
"TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.", "TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.",
"TaskRefreshPeople": "Odśwież obsadę", "TaskRefreshPeople": "Odśwież obsadę",

View File

@@ -1,5 +1,7 @@
{ {
"Books": "Libros", "Books": "Libros",
"AuthenticationSucceededWithUserName": "{0} autentificado correctamente", "AuthenticationSucceededWithUserName": "{0} autentificado correctamente",
"Artists": "Artistas" "Artists": "Artistas",
"Songs": "Shantees",
"Albums": "Ships"
} }

View File

@@ -15,7 +15,7 @@
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Folders": "Pastas", "Folders": "Pastas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas do Álbum", "HeaderAlbumArtists": "Álbum do Artista",
"HeaderContinueWatching": "Continuar a Ver", "HeaderContinueWatching": "Continuar a Ver",
"HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteArtists": "Artistas Favoritos", "HeaderFavoriteArtists": "Artistas Favoritos",

View File

@@ -74,7 +74,7 @@
"HeaderFavoriteArtists": "Artiști Favoriți", "HeaderFavoriteArtists": "Artiști Favoriți",
"HeaderFavoriteAlbums": "Albume Favorite", "HeaderFavoriteAlbums": "Albume Favorite",
"HeaderContinueWatching": "Vizionează în continuare", "HeaderContinueWatching": "Vizionează în continuare",
"HeaderAlbumArtists": "Album Artiști", "HeaderAlbumArtists": "Albume Artiști",
"Genres": "Genuri", "Genres": "Genuri",
"Folders": "Dosare", "Folders": "Dosare",
"Favorites": "Favorite", "Favorites": "Favorite",

View File

@@ -74,7 +74,7 @@
"NameSeasonUnknown": "Sezon i panjohur", "NameSeasonUnknown": "Sezon i panjohur",
"NameSeasonNumber": "Sezoni {0}", "NameSeasonNumber": "Sezoni {0}",
"NameInstallFailed": "Instalimi i {0} dështoi", "NameInstallFailed": "Instalimi i {0} dështoi",
"MusicVideos": "Video muzikore", "MusicVideos": "Video Muzikore",
"Music": "Muzikë", "Music": "Muzikë",
"Movies": "Filmat", "Movies": "Filmat",
"MixedContent": "Përmbajtje e përzier", "MixedContent": "Përmbajtje e përzier",
@@ -96,7 +96,7 @@
"HeaderFavoriteArtists": "Artistët e preferuar", "HeaderFavoriteArtists": "Artistët e preferuar",
"HeaderFavoriteAlbums": "Albumet e preferuar", "HeaderFavoriteAlbums": "Albumet e preferuar",
"HeaderContinueWatching": "Vazhdo të shikosh", "HeaderContinueWatching": "Vazhdo të shikosh",
"HeaderAlbumArtists": "Artistët e Albumeve", "HeaderAlbumArtists": "Artistët e albumeve",
"Genres": "Zhanret", "Genres": "Zhanret",
"Folders": "Skedarët", "Folders": "Skedarët",
"Favorites": "Të preferuarat", "Favorites": "Të preferuarat",

View File

@@ -50,7 +50,7 @@
"NameSeasonUnknown": "Непозната сезона", "NameSeasonUnknown": "Непозната сезона",
"NameSeasonNumber": "Сезона {0}", "NameSeasonNumber": "Сезона {0}",
"NameInstallFailed": "Инсталација {0} није успела", "NameInstallFailed": "Инсталација {0} није успела",
"MusicVideos": "Музички спотови", "MusicVideos": "Музички видео",
"Music": "Музика", "Music": "Музика",
"Movies": "Филмови", "Movies": "Филмови",
"MixedContent": "Мешовит садржај", "MixedContent": "Мешовит садржај",
@@ -64,7 +64,7 @@
"ItemRemovedWithName": "{0} уклоњено из библиотеке", "ItemRemovedWithName": "{0} уклоњено из библиотеке",
"ItemAddedWithName": "{0} додато у библиотеку", "ItemAddedWithName": "{0} додато у библиотеку",
"Inherit": "Наследи", "Inherit": "Наследи",
"HomeVideos": "Кућни видео", "HomeVideos": "Кућни Видео",
"HeaderRecordingGroups": "Групе снимања", "HeaderRecordingGroups": "Групе снимања",
"HeaderNextUp": "Следи", "HeaderNextUp": "Следи",
"HeaderLiveTV": "ТВ уживо", "HeaderLiveTV": "ТВ уживо",
@@ -117,5 +117,6 @@
"TaskCleanActivityLog": "Очисти историју активности", "TaskCleanActivityLog": "Очисти историју активности",
"Undefined": "Недефинисано", "Undefined": "Недефинисано",
"Forced": "Принудно", "Forced": "Принудно",
"Default": "Подразумевано" "Default": "Подразумевано",
"TaskOptimizeDatabase": "Оптимизуј датабазу"
} }

View File

@@ -15,7 +15,7 @@
"Favorites": "Favoriter", "Favorites": "Favoriter",
"Folders": "Mappar", "Folders": "Mappar",
"Genres": "Genrer", "Genres": "Genrer",
"HeaderAlbumArtists": "Artistens album", "HeaderAlbumArtists": "Albumsartister",
"HeaderContinueWatching": "Fortsätt kolla", "HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister", "HeaderFavoriteArtists": "Favoritartister",
@@ -96,8 +96,8 @@
"TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter", "TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.", "TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
"TaskRefreshChannels": "Uppdatera kanaler", "TaskRefreshChannels": "Uppdatera kanaler",
"TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.", "TaskCleanTranscodeDescription": "Raderar omkodningsfiler som är mer än en dag gamla.",
"TaskCleanTranscode": "Töm transkodningskatalog", "TaskCleanTranscode": "Töm omkodningskatalog",
"TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.", "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
"TaskUpdatePlugins": "Uppdatera insticksprogram", "TaskUpdatePlugins": "Uppdatera insticksprogram",
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.", "TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",

View File

@@ -85,7 +85,7 @@
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்", "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
"HeaderFavoriteAlbums": "பிடித்த ஆல்பங்கள்", "HeaderFavoriteAlbums": "பிடித்த ஆல்பங்கள்",
"HeaderContinueWatching": "தொடர்ந்து பார்", "HeaderContinueWatching": "தொடர்ந்து பார்",
"HeaderAlbumArtists": "இசைக் கலைஞர்கள்", "HeaderAlbumArtists": "கலைஞரின் ஆல்பம்",
"Genres": "வகைகள்", "Genres": "வகைகள்",
"Favorites": "பிடித்தவை", "Favorites": "பிடித்தவை",
"ChapterNameValue": "அத்தியாயம் {0}", "ChapterNameValue": "அத்தியாயம் {0}",

View File

@@ -1 +1,23 @@
{} {
"ValueSpecialEpisodeName": "ప్రత్యేక - {0}",
"Sync": "సమకాలీకరించు",
"Songs": "పాటలు",
"Shows": "ప్రదర్శనలు",
"Playlists": "ప్లేజాబితాలు",
"Photos": "ఫోటోలు",
"MusicVideos": "మ్యూజిక్ వీడియోలు",
"Music": "సంగీతం",
"Movies": "సినిమాలు",
"HeaderContinueWatching": "చూడటం కొనసాగించండి",
"HeaderAlbumArtists": "ఆల్బమ్ కళాకారులు",
"Genres": "శైలులు",
"Forced": "బలవంతంగా",
"Folders": "ఫోల్డర్లు",
"Favorites": "ఇష్టమైనవి",
"Default": "డిఫాల్ట్",
"Collections": "సేకరణలు",
"Channels": "ఛానెల్‌లు",
"Books": "పుస్తకాలు",
"Artists": "కళాకారులు",
"Albums": "ఆల్బమ్‌లు"
}

View File

@@ -16,7 +16,7 @@
"HeaderFavoriteArtists": "Улюблені виконавці", "HeaderFavoriteArtists": "Улюблені виконавці",
"HeaderFavoriteAlbums": "Улюблені альбоми", "HeaderFavoriteAlbums": "Улюблені альбоми",
"HeaderContinueWatching": "Продовжити перегляд", "HeaderContinueWatching": "Продовжити перегляд",
"HeaderAlbumArtists": "Виконавці альбомів", "HeaderAlbumArtists": "Виконавці альбому",
"Genres": "Жанри", "Genres": "Жанри",
"Folders": "Каталоги", "Folders": "Каталоги",
"Favorites": "Улюблені", "Favorites": "Улюблені",

View File

@@ -3,7 +3,7 @@
"Favorites": "Yêu Thích", "Favorites": "Yêu Thích",
"Folders": "Thư Mục", "Folders": "Thư Mục",
"Genres": "Thể Loại", "Genres": "Thể Loại",
"HeaderAlbumArtists": "Album Nghệ sĩ", "HeaderAlbumArtists": "Album nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp", "HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp", "HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim", "Movies": "Phim",
@@ -95,7 +95,7 @@
"ItemRemovedWithName": "{0} đã xóa khỏi thư viện", "ItemRemovedWithName": "{0} đã xóa khỏi thư viện",
"ItemAddedWithName": "{0} được thêm vào thư viện", "ItemAddedWithName": "{0} được thêm vào thư viện",
"Inherit": "Thừa hưởng", "Inherit": "Thừa hưởng",
"HomeVideos": "Video nhà", "HomeVideos": "Video Nhà",
"HeaderRecordingGroups": "Nhóm Ghi Video", "HeaderRecordingGroups": "Nhóm Ghi Video",
"HeaderNextUp": "Tiếp Theo", "HeaderNextUp": "Tiếp Theo",
"HeaderFavoriteSongs": "Bài Hát Yêu Thích", "HeaderFavoriteSongs": "Bài Hát Yêu Thích",
@@ -103,7 +103,7 @@
"HeaderFavoriteEpisodes": "Tập Phim Yêu Thích", "HeaderFavoriteEpisodes": "Tập Phim Yêu Thích",
"HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích", "HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích",
"HeaderFavoriteAlbums": "Album Ưa Thích", "HeaderFavoriteAlbums": "Album Ưa Thích",
"FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}", "FailedLoginAttemptWithUserName": "Đăng nhập không thành công thử từ {0}",
"DeviceOnlineWithName": "{0} đã kết nối", "DeviceOnlineWithName": "{0} đã kết nối",
"DeviceOfflineWithName": "{0} đã ngắt kết nối", "DeviceOfflineWithName": "{0} đã ngắt kết nối",
"ChapterNameValue": "Phân Cảnh {0}", "ChapterNameValue": "Phân Cảnh {0}",

View File

@@ -11,7 +11,7 @@
"Collections": "合集", "Collections": "合集",
"DeviceOfflineWithName": "{0} 已断开", "DeviceOfflineWithName": "{0} 已断开",
"DeviceOnlineWithName": "{0} 已连接", "DeviceOnlineWithName": "{0} 已连接",
"FailedLoginAttemptWithUserName": "来自 {0} 的失败登入", "FailedLoginAttemptWithUserName": " {0} 尝试登录失败",
"Favorites": "我的最爱", "Favorites": "我的最爱",
"Folders": "文件夹", "Folders": "文件夹",
"Genres": "风格", "Genres": "风格",

View File

@@ -373,60 +373,75 @@ namespace Emby.Server.Implementations.Localization
public IEnumerable<LocalizationOption> GetLocalizationOptions() public IEnumerable<LocalizationOption> GetLocalizationOptions()
{ {
yield return new LocalizationOption("Afrikaans", "af"); yield return new LocalizationOption("Afrikaans", "af");
yield return new LocalizationOption("Arabic", "ar"); yield return new LocalizationOption("العربية", "ar");
yield return new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG"); yield return new LocalizationOption("Беларуская", "be");
yield return new LocalizationOption("Catalan", "ca"); yield return new LocalizationOption("Български", "bg-BG");
yield return new LocalizationOption("Chinese (Hong Kong)", "zh-HK"); yield return new LocalizationOption("বাংলা (বাংলাদেশ)", "bn");
yield return new LocalizationOption("Chinese Simplified", "zh-CN"); yield return new LocalizationOption("Català", "ca");
yield return new LocalizationOption("Chinese Traditional", "zh-TW"); yield return new LocalizationOption("Čeština", "cs");
yield return new LocalizationOption("Croatian", "hr"); yield return new LocalizationOption("Cymraeg", "cy");
yield return new LocalizationOption("Czech", "cs"); yield return new LocalizationOption("Dansk", "da");
yield return new LocalizationOption("Danish", "da"); yield return new LocalizationOption("Deutsch", "de");
yield return new LocalizationOption("Dutch", "nl");
yield return new LocalizationOption("English (United Kingdom)", "en-GB"); yield return new LocalizationOption("English (United Kingdom)", "en-GB");
yield return new LocalizationOption("English (United States)", "en-US"); yield return new LocalizationOption("English", "en-US");
yield return new LocalizationOption("Ελληνικά", "el");
yield return new LocalizationOption("Esperanto", "eo"); yield return new LocalizationOption("Esperanto", "eo");
yield return new LocalizationOption("Estonian", "et"); yield return new LocalizationOption("Español", "es");
yield return new LocalizationOption("Finnish", "fi"); yield return new LocalizationOption("Español americano", "es_419");
yield return new LocalizationOption("French", "fr"); yield return new LocalizationOption("Español (Argentina)", "es-AR");
yield return new LocalizationOption("French (Canada)", "fr-CA"); yield return new LocalizationOption("Español (Dominicana)", "es_DO");
yield return new LocalizationOption("German", "de"); yield return new LocalizationOption("Español (México)", "es-MX");
yield return new LocalizationOption("Greek", "el"); yield return new LocalizationOption("Eesti", "et");
yield return new LocalizationOption("Hebrew", "he"); yield return new LocalizationOption("فارسی", "fa");
yield return new LocalizationOption("Hungarian", "hu"); yield return new LocalizationOption("Suomi", "fi");
yield return new LocalizationOption("Icelandic", "is"); yield return new LocalizationOption("Filipino", "fil");
yield return new LocalizationOption("Indonesian", "id"); yield return new LocalizationOption("Français", "fr");
yield return new LocalizationOption("Italian", "it"); yield return new LocalizationOption("Français (Canada)", "fr-CA");
yield return new LocalizationOption("Japanese", "ja"); yield return new LocalizationOption("Galego", "gl");
yield return new LocalizationOption("Kazakh", "kk"); yield return new LocalizationOption("Schwiizerdütsch", "gsw");
yield return new LocalizationOption("Korean", "ko"); yield return new LocalizationOption("עִבְרִית", "he");
yield return new LocalizationOption("Latvian", "lv"); yield return new LocalizationOption("हिन्दी", "hi");
yield return new LocalizationOption("Lithuanian", "lt-LT"); yield return new LocalizationOption("Hrvatski", "hr");
yield return new LocalizationOption("Malay", "ms"); yield return new LocalizationOption("Magyar", "hu");
yield return new LocalizationOption("Malayalam", "ml"); yield return new LocalizationOption("Bahasa Indonesia", "id");
yield return new LocalizationOption("Norwegian Bokmål", "nb"); yield return new LocalizationOption("Íslenska", "is");
yield return new LocalizationOption("Norwegian Nynorsk", "nn"); yield return new LocalizationOption("Italiano", "it");
yield return new LocalizationOption("Persian", "fa"); yield return new LocalizationOption("日本語", "ja");
yield return new LocalizationOption("Polish", "pl"); yield return new LocalizationOption("Qazaqşa", "kk");
yield return new LocalizationOption("Portuguese", "pt"); yield return new LocalizationOption("한국어", "ko");
yield return new LocalizationOption("Portuguese (Brazil)", "pt-BR"); yield return new LocalizationOption("Lietuvių", "lt");
yield return new LocalizationOption("Portuguese (Portugal)", "pt-PT"); yield return new LocalizationOption("Latviešu", "lv");
yield return new LocalizationOption("Romanian", "ro"); yield return new LocalizationOption("Македонски", "mk");
yield return new LocalizationOption("Russian", "ru"); yield return new LocalizationOption("മലയാളം", "ml");
yield return new LocalizationOption("Serbian", "sr"); yield return new LocalizationOption("मराठी", "mr");
yield return new LocalizationOption("Slovak", "sk"); yield return new LocalizationOption("Bahasa Melayu", "ms");
yield return new LocalizationOption("Slovenian (Slovenia)", "sl-SI"); yield return new LocalizationOption("Norsk bokmål", "nb");
yield return new LocalizationOption("Spanish", "es"); yield return new LocalizationOption("नेपाली", "ne");
yield return new LocalizationOption("Spanish (Argentina)", "es-AR"); yield return new LocalizationOption("Nederlands", "nl");
yield return new LocalizationOption("Spanish (Latin America)", "es-419"); yield return new LocalizationOption("Norsk nynorsk", "nn");
yield return new LocalizationOption("Spanish (Mexico)", "es-MX"); yield return new LocalizationOption("ਪੰਜਾਬੀ", "pa");
yield return new LocalizationOption("Swedish", "sv"); yield return new LocalizationOption("Polski", "pl");
yield return new LocalizationOption("Swiss German", "gsw"); yield return new LocalizationOption("Pirate", "pr");
yield return new LocalizationOption("Tamil", "ta"); yield return new LocalizationOption("Português", "pt");
yield return new LocalizationOption("Telugu", "te"); yield return new LocalizationOption("Português (Brasil)", "pt-BR");
yield return new LocalizationOption("Turkish", "tr"); yield return new LocalizationOption("Português (Portugal)", "pt-PT");
yield return new LocalizationOption("Românește", "ro");
yield return new LocalizationOption("Русский", "ru");
yield return new LocalizationOption("Slovenčina", "sk");
yield return new LocalizationOption("Slovenščina", "sl-SI");
yield return new LocalizationOption("Shqip", "sq");
yield return new LocalizationOption("Српски", "sr");
yield return new LocalizationOption("Svenska", "sv");
yield return new LocalizationOption("தமிழ்", "ta");
yield return new LocalizationOption("తెలుగు", "te");
yield return new LocalizationOption("ภาษาไทย", "th");
yield return new LocalizationOption("Türkçe", "tr");
yield return new LocalizationOption("Українська", "uk");
yield return new LocalizationOption("اُردُو", "ur_PK");
yield return new LocalizationOption("Tiếng Việt", "vi"); yield return new LocalizationOption("Tiếng Việt", "vi");
yield return new LocalizationOption("Ukrainian", "uk"); yield return new LocalizationOption("汉语 (简化字)", "zh-CN");
yield return new LocalizationOption("漢語 (繁体字)", "zh-TW");
yield return new LocalizationOption("廣東話 (香港)", "zh-HK");
} }
} }
} }

View File

@@ -527,7 +527,7 @@ namespace Emby.Server.Implementations.Playlists
var relativeUri = folderUri.MakeRelativeUri(fileAbsoluteUri); var relativeUri = folderUri.MakeRelativeUri(fileAbsoluteUri);
string relativePath = Uri.UnescapeDataString(relativeUri.ToString()); string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
if (fileAbsoluteUri.Scheme.Equals("file", StringComparison.CurrentCultureIgnoreCase)) if (fileAbsoluteUri.Scheme.Equals("file", StringComparison.OrdinalIgnoreCase))
{ {
relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
} }

View File

@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns> /// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y) public int Compare(BaseItem? x, BaseItem? y)
{ {
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>

View File

@@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns> /// <returns>System.Int32.</returns>
public int Compare(BaseItem? x, BaseItem? y) public int Compare(BaseItem? x, BaseItem? y)
{ {
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>
@@ -35,9 +35,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
private static string? GetValue(BaseItem? x) private static string? GetValue(BaseItem? x)
{ {
var audio = x as Audio; return x is Audio audio ? audio.Album : string.Empty;
return audio == null ? string.Empty : audio.Album;
} }
} }
} }

View File

@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Sorting
/// <inheritdoc /> /// <inheritdoc />
public int Compare(BaseItem? x, BaseItem? y) public int Compare(BaseItem? x, BaseItem? y)
{ {
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>

View File

@@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Sorting
throw new ArgumentNullException(nameof(y)); throw new ArgumentNullException(nameof(y));
} }
return string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase); return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
} }
} }
} }

View File

@@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>System.Int32.</returns> /// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y) public int Compare(BaseItem x, BaseItem y)
{ {
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); return string.Compare(GetValue(x), GetValue(y), StringComparison.OrdinalIgnoreCase);
} }
private static string GetValue(BaseItem item) private static string GetValue(BaseItem item)

View File

@@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Sorting
throw new ArgumentNullException(nameof(y)); throw new ArgumentNullException(nameof(y));
} }
return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase); return string.Compare(x.SortName, y.SortName, StringComparison.OrdinalIgnoreCase);
} }
} }
} }

View File

@@ -58,7 +58,7 @@ namespace Emby.Server.Implementations.Udp
_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
} }
private async Task RespondToV2Message(string messageText, EndPoint endpoint, CancellationToken cancellationToken) private async Task RespondToV2Message(EndPoint endpoint, CancellationToken cancellationToken)
{ {
string? localUrl = _config[AddressOverrideConfigKey]; string? localUrl = _config[AddressOverrideConfigKey];
if (string.IsNullOrEmpty(localUrl)) if (string.IsNullOrEmpty(localUrl))
@@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Udp
try try
{ {
await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint).ConfigureAwait(false); await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint, cancellationToken).ConfigureAwait(false);
} }
catch (SocketException ex) catch (SocketException ex)
{ {
@@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.Udp
var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes); var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes);
if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase)) if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
{ {
await RespondToV2Message(text, result.RemoteEndPoint, cancellationToken).ConfigureAwait(false); await RespondToV2Message(result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
} }
} }
catch (SocketException ex) catch (SocketException ex)

View File

@@ -1,4 +1,6 @@
using System; #pragma warning disable CA1813 // Avoid unsealed attributes
using System;
namespace Jellyfin.Api.Attributes namespace Jellyfin.Api.Attributes
{ {

View File

@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// Produces file attribute of "image/*". /// Produces file attribute of "image/*".
/// </summary> /// </summary>
public class AcceptsImageFileAttribute : AcceptsFileAttribute public sealed class AcceptsImageFileAttribute : AcceptsFileAttribute
{ {
private const string ContentType = "image/*"; private const string ContentType = "image/*";

View File

@@ -7,7 +7,7 @@ namespace Jellyfin.Api.Attributes
/// <summary> /// <summary>
/// Identifies an action that supports the HTTP GET method. /// Identifies an action that supports the HTTP GET method.
/// </summary> /// </summary>
public class HttpSubscribeAttribute : HttpMethodAttribute public sealed class HttpSubscribeAttribute : HttpMethodAttribute
{ {
private static readonly IEnumerable<string> _supportedMethods = new[] { "SUBSCRIBE" }; private static readonly IEnumerable<string> _supportedMethods = new[] { "SUBSCRIBE" };

View File

@@ -7,7 +7,7 @@ namespace Jellyfin.Api.Attributes
/// <summary> /// <summary>
/// Identifies an action that supports the HTTP GET method. /// Identifies an action that supports the HTTP GET method.
/// </summary> /// </summary>
public class HttpUnsubscribeAttribute : HttpMethodAttribute public sealed class HttpUnsubscribeAttribute : HttpMethodAttribute
{ {
private static readonly IEnumerable<string> _supportedMethods = new[] { "UNSUBSCRIBE" }; private static readonly IEnumerable<string> _supportedMethods = new[] { "UNSUBSCRIBE" };

View File

@@ -6,7 +6,7 @@ namespace Jellyfin.Api.Attributes
/// Attribute to mark a parameter as obsolete. /// Attribute to mark a parameter as obsolete.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
public class ParameterObsoleteAttribute : Attribute public sealed class ParameterObsoleteAttribute : Attribute
{ {
} }
} }

View File

@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// Produces file attribute of "image/*". /// Produces file attribute of "image/*".
/// </summary> /// </summary>
public class ProducesAudioFileAttribute : ProducesFileAttribute public sealed class ProducesAudioFileAttribute : ProducesFileAttribute
{ {
private const string ContentType = "audio/*"; private const string ContentType = "audio/*";

View File

@@ -1,4 +1,6 @@
using System; #pragma warning disable CA1813 // Avoid unsealed attributes
using System;
namespace Jellyfin.Api.Attributes namespace Jellyfin.Api.Attributes
{ {

View File

@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// Produces file attribute of "image/*". /// Produces file attribute of "image/*".
/// </summary> /// </summary>
public class ProducesImageFileAttribute : ProducesFileAttribute public sealed class ProducesImageFileAttribute : ProducesFileAttribute
{ {
private const string ContentType = "image/*"; private const string ContentType = "image/*";

View File

@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// Produces file attribute of "image/*". /// Produces file attribute of "image/*".
/// </summary> /// </summary>
public class ProducesPlaylistFileAttribute : ProducesFileAttribute public sealed class ProducesPlaylistFileAttribute : ProducesFileAttribute
{ {
private const string ContentType = "application/x-mpegURL"; private const string ContentType = "application/x-mpegURL";

View File

@@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// Produces file attribute of "video/*". /// Produces file attribute of "video/*".
/// </summary> /// </summary>
public class ProducesVideoFileAttribute : ProducesFileAttribute public sealed class ProducesVideoFileAttribute : ProducesFileAttribute
{ {
private const string ContentType = "video/*"; private const string ContentType = "video/*";

View File

@@ -1,5 +1,4 @@
using System; using System.Net.Mime;
using System.Net.Mime;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@@ -7,7 +6,6 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.ClientLogDtos; using Jellyfin.Api.Models.ClientLogDtos;
using MediaBrowser.Controller.ClientEvent; using MediaBrowser.Controller.ClientEvent;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.ClientLog;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -37,54 +35,6 @@ namespace Jellyfin.Api.Controllers
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
} }
/// <summary>
/// Post event from client.
/// </summary>
/// <param name="clientLogEventDto">The client log dto.</param>
/// <response code="204">Event logged.</response>
/// <response code="403">Event logging disabled.</response>
/// <returns>Submission status.</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult LogEvent([FromBody] ClientLogEventDto clientLogEventDto)
{
if (!_serverConfigurationManager.Configuration.AllowClientLogUpload)
{
return Forbid();
}
var (clientName, clientVersion, userId, deviceId) = GetRequestInformation();
Log(clientLogEventDto, userId, clientName, clientVersion, deviceId);
return NoContent();
}
/// <summary>
/// Bulk post events from client.
/// </summary>
/// <param name="clientLogEventDtos">The list of client log dtos.</param>
/// <response code="204">All events logged.</response>
/// <response code="403">Event logging disabled.</response>
/// <returns>Submission status.</returns>
[HttpPost("Bulk")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult LogEvents([FromBody] ClientLogEventDto[] clientLogEventDtos)
{
if (!_serverConfigurationManager.Configuration.AllowClientLogUpload)
{
return Forbid();
}
var (clientName, clientVersion, userId, deviceId) = GetRequestInformation();
foreach (var dto in clientLogEventDtos)
{
Log(dto, userId, clientName, clientVersion, deviceId);
}
return NoContent();
}
/// <summary> /// <summary>
/// Upload a document. /// Upload a document.
/// </summary> /// </summary>
@@ -111,39 +61,20 @@ namespace Jellyfin.Api.Controllers
return StatusCode(StatusCodes.Status413PayloadTooLarge, $"Payload must be less than {MaxDocumentSize:N0} bytes"); return StatusCode(StatusCodes.Status413PayloadTooLarge, $"Payload must be less than {MaxDocumentSize:N0} bytes");
} }
var (clientName, clientVersion, _, _) = GetRequestInformation(); var (clientName, clientVersion) = GetRequestInformation();
var fileName = await _clientEventLogger.WriteDocumentAsync(clientName, clientVersion, Request.Body) var fileName = await _clientEventLogger.WriteDocumentAsync(clientName, clientVersion, Request.Body)
.ConfigureAwait(false); .ConfigureAwait(false);
return Ok(new ClientLogDocumentResponseDto(fileName)); return Ok(new ClientLogDocumentResponseDto(fileName));
} }
private void Log( private (string ClientName, string ClientVersion) GetRequestInformation()
ClientLogEventDto dto,
Guid userId,
string clientName,
string clientVersion,
string deviceId)
{
_clientEventLogger.Log(new ClientLogEvent(
dto.Timestamp,
dto.Level,
userId,
clientName,
clientVersion,
deviceId,
dto.Message));
}
private (string ClientName, string ClientVersion, Guid UserId, string DeviceId) GetRequestInformation()
{ {
var clientName = ClaimHelpers.GetClient(HttpContext.User) ?? "unknown-client"; var clientName = ClaimHelpers.GetClient(HttpContext.User) ?? "unknown-client";
var clientVersion = ClaimHelpers.GetIsApiKey(HttpContext.User) var clientVersion = ClaimHelpers.GetIsApiKey(HttpContext.User)
? "apikey" ? "apikey"
: ClaimHelpers.GetVersion(HttpContext.User) ?? "unknown-version"; : ClaimHelpers.GetVersion(HttpContext.User) ?? "unknown-version";
var userId = ClaimHelpers.GetUserId(HttpContext.User) ?? Guid.Empty;
var deviceId = ClaimHelpers.GetDeviceId(HttpContext.User) ?? "unknown-device-id";
return (clientName, clientVersion, userId, deviceId); return (clientName, clientVersion);
} }
} }
} }

View File

@@ -5,8 +5,6 @@ using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
@@ -30,7 +28,6 @@ namespace Jellyfin.Api.Controllers
public class ItemLookupController : BaseJellyfinApiController public class ItemLookupController : BaseJellyfinApiController
{ {
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly IServerApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger<ItemLookupController> _logger; private readonly ILogger<ItemLookupController> _logger;
@@ -39,19 +36,16 @@ namespace Jellyfin.Api.Controllers
/// Initializes a new instance of the <see cref="ItemLookupController"/> class. /// Initializes a new instance of the <see cref="ItemLookupController"/> class.
/// </summary> /// </summary>
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{ItemLookupController}"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{ItemLookupController}"/> interface.</param>
public ItemLookupController( public ItemLookupController(
IProviderManager providerManager, IProviderManager providerManager,
IServerConfigurationManager serverConfigurationManager,
IFileSystem fileSystem, IFileSystem fileSystem,
ILibraryManager libraryManager, ILibraryManager libraryManager,
ILogger<ItemLookupController> logger) ILogger<ItemLookupController> logger)
{ {
_providerManager = providerManager; _providerManager = providerManager;
_appPaths = serverConfigurationManager.ApplicationPaths;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = logger; _logger = logger;

View File

@@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
@@ -33,6 +34,7 @@ namespace Jellyfin.Api.Controllers
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly ILogger<ItemsController> _logger; private readonly ILogger<ItemsController> _logger;
private readonly ISessionManager _sessionManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ItemsController"/> class. /// Initializes a new instance of the <see cref="ItemsController"/> class.
@@ -42,18 +44,21 @@ namespace Jellyfin.Api.Controllers
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
public ItemsController( public ItemsController(
IUserManager userManager, IUserManager userManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
ILocalizationManager localization, ILocalizationManager localization,
IDtoService dtoService, IDtoService dtoService,
ILogger<ItemsController> logger) ILogger<ItemsController> logger,
ISessionManager sessionManager)
{ {
_userManager = userManager; _userManager = userManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_localization = localization; _localization = localization;
_dtoService = dtoService; _dtoService = dtoService;
_logger = logger; _logger = logger;
_sessionManager = sessionManager;
} }
/// <summary> /// <summary>
@@ -763,6 +768,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param> /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.</param>
/// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param> /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
/// <param name="enableImages">Optional. Include image information in output.</param> /// <param name="enableImages">Optional. Include image information in output.</param>
/// <param name="excludeActiveSessions">Optional. Whether to exclude the currently active sessions.</param>
/// <response code="200">Items returned.</response> /// <response code="200">Items returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns>
[HttpGet("Users/{userId}/Items/Resume")] [HttpGet("Users/{userId}/Items/Resume")]
@@ -781,7 +787,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true) [FromQuery] bool? enableImages = true,
[FromQuery] bool excludeActiveSessions = false)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
var parentIdGuid = parentId ?? Guid.Empty; var parentIdGuid = parentId ?? Guid.Empty;
@@ -801,6 +808,15 @@ namespace Jellyfin.Api.Controllers
.ToArray(); .ToArray();
} }
var excludeItemIds = Array.Empty<Guid>();
if (excludeActiveSessions)
{
excludeItemIds = _sessionManager.Sessions
.Where(s => s.UserId == userId && s.NowPlayingItem != null)
.Select(s => s.NowPlayingItem.Id)
.ToArray();
}
var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{ {
OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }, OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
@@ -817,7 +833,8 @@ namespace Jellyfin.Api.Controllers
AncestorIds = ancestorIds, AncestorIds = ancestorIds,
IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
SearchTerm = searchTerm SearchTerm = searchTerm,
ExcludeItemIds = excludeItemIds
}); });
var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user); var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user);

View File

@@ -26,7 +26,6 @@ namespace Jellyfin.Api.Controllers
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PersonsController"/> class. /// Initializes a new instance of the <see cref="PersonsController"/> class.
@@ -34,17 +33,14 @@ namespace Jellyfin.Api.Controllers
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="userDataManager">Instance of the <see cref="IUserDataManager"/> interface.</param>
public PersonsController( public PersonsController(
ILibraryManager libraryManager, ILibraryManager libraryManager,
IDtoService dtoService, IDtoService dtoService,
IUserManager userManager, IUserManager userManager)
IUserDataManager userDataManager)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_dtoService = dtoService; _dtoService = dtoService;
_userManager = userManager; _userManager = userManager;
_userDataManager = userDataManager;
} }
/// <summary> /// <summary>

View File

@@ -28,7 +28,6 @@ namespace Jellyfin.Api.Controllers
{ {
private readonly IInstallationManager _installationManager; private readonly IInstallationManager _installationManager;
private readonly IPluginManager _pluginManager; private readonly IPluginManager _pluginManager;
private readonly IConfigurationManager _config;
private readonly JsonSerializerOptions _serializerOptions; private readonly JsonSerializerOptions _serializerOptions;
/// <summary> /// <summary>
@@ -36,16 +35,13 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param> /// <param name="installationManager">Instance of the <see cref="IInstallationManager"/> interface.</param>
/// <param name="pluginManager">Instance of the <see cref="IPluginManager"/> interface.</param> /// <param name="pluginManager">Instance of the <see cref="IPluginManager"/> interface.</param>
/// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
public PluginsController( public PluginsController(
IInstallationManager installationManager, IInstallationManager installationManager,
IPluginManager pluginManager, IPluginManager pluginManager)
IConfigurationManager config)
{ {
_installationManager = installationManager; _installationManager = installationManager;
_pluginManager = pluginManager; _pluginManager = pluginManager;
_serializerOptions = JsonDefaults.Options; _serializerOptions = JsonDefaults.Options;
_config = config;
} }
/// <summary> /// <summary>

View File

@@ -30,7 +30,6 @@ namespace Jellyfin.Api.Controllers
{ {
private readonly IProviderManager _providerManager; private readonly IProviderManager _providerManager;
private readonly IServerApplicationPaths _applicationPaths; private readonly IServerApplicationPaths _applicationPaths;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
/// <summary> /// <summary>
@@ -38,17 +37,14 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param>
/// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
public RemoteImageController( public RemoteImageController(
IProviderManager providerManager, IProviderManager providerManager,
IServerApplicationPaths applicationPaths, IServerApplicationPaths applicationPaths,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager) ILibraryManager libraryManager)
{ {
_providerManager = providerManager; _providerManager = providerManager;
_applicationPaths = applicationPaths; _applicationPaths = applicationPaths;
_httpClientFactory = httpClientFactory;
_libraryManager = libraryManager; _libraryManager = libraryManager;
} }

View File

@@ -93,7 +93,7 @@ namespace Jellyfin.Api.Controllers
NetworkConfiguration settings = _config.GetNetworkConfiguration(); NetworkConfiguration settings = _config.GetNetworkConfiguration();
settings.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess; settings.EnableRemoteAccess = startupRemoteAccessDto.EnableRemoteAccess;
settings.EnableUPnP = startupRemoteAccessDto.EnableAutomaticPortMapping; settings.EnableUPnP = startupRemoteAccessDto.EnableAutomaticPortMapping;
_config.SaveConfiguration("network", settings); _config.SaveConfiguration(NetworkConfigurationStore.StoreKey, settings);
return NoContent(); return NoContent();
} }

View File

@@ -451,7 +451,7 @@ namespace Jellyfin.Api.Controllers
if (@static.HasValue && @static.Value && state.DirectStreamProvider != null) if (@static.HasValue && @static.Value && state.DirectStreamProvider != null)
{ {
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, state.Request.StartTimeTicks, Request, _dlnaManager);
var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfo(streamingRequest.LiveStreamId); var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfo(streamingRequest.LiveStreamId);
if (liveStreamInfo == null) if (liveStreamInfo == null)
@@ -467,7 +467,7 @@ namespace Jellyfin.Api.Controllers
// Static remote stream // Static remote stream
if (@static.HasValue && @static.Value && state.InputProtocol == MediaProtocol.Http) if (@static.HasValue && @static.Value && state.InputProtocol == MediaProtocol.Http)
{ {
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager); StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, state.Request.StartTimeTicks, Request, _dlnaManager);
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false); return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false);
@@ -484,7 +484,7 @@ namespace Jellyfin.Api.Controllers
var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
var isTranscodeCached = outputPathExists && transcodingJob != null; var isTranscodeCached = outputPathExists && transcodingJob != null;
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, (@static.HasValue && @static.Value) || isTranscodeCached, startTimeTicks, Request, _dlnaManager); StreamingHelpers.AddDlnaHeaders(state, Response.Headers, (@static.HasValue && @static.Value) || isTranscodeCached, state.Request.StartTimeTicks, Request, _dlnaManager);
// Static stream // Static stream
if (@static.HasValue && @static.Value) if (@static.HasValue && @static.Value)

View File

@@ -1,71 +0,0 @@
using System;
using System.Reflection;
namespace Jellyfin.Api.Helpers
{
/// <summary>
/// A static class for copying matching properties from one object to another.
/// TODO: remove at the point when a fixed migration path has been decided upon.
/// </summary>
public static class ClassMigrationHelper
{
/// <summary>
/// Extension for 'Object' that copies the properties to a destination object.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="destination">The destination.</param>
public static void CopyProperties(this object source, object destination)
{
// If any this null throw an exception.
if (source == null || destination == null)
{
throw new ArgumentException("Source or/and Destination Objects are null");
}
// Getting the Types of the objects.
Type typeDest = destination.GetType();
Type typeSrc = source.GetType();
// Iterate the Properties of the source instance and populate them from their destination counterparts.
PropertyInfo[] srcProps = typeSrc.GetProperties();
foreach (PropertyInfo srcProp in srcProps)
{
if (!srcProp.CanRead)
{
continue;
}
var targetProperty = typeDest.GetProperty(srcProp.Name);
if (targetProperty == null)
{
continue;
}
if (!targetProperty.CanWrite)
{
continue;
}
var obj = targetProperty.GetSetMethod(true);
if (obj != null && obj.IsPrivate)
{
continue;
}
var target = targetProperty.GetSetMethod();
if (target != null && (target.Attributes & MethodAttributes.Static) != 0)
{
continue;
}
if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
{
continue;
}
// Passed all tests, lets set the value.
targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
}
}
}
}

View File

@@ -17,7 +17,6 @@ namespace Jellyfin.Api.Helpers
private readonly TranscodingJobDto? _job; private readonly TranscodingJobDto? _job;
private readonly TranscodingJobHelper? _transcodingJobHelper; private readonly TranscodingJobHelper? _transcodingJobHelper;
private readonly int _timeoutMs; private readonly int _timeoutMs;
private int _bytesWritten;
private bool _disposed; private bool _disposed;
/// <summary> /// <summary>
@@ -71,53 +70,58 @@ namespace Jellyfin.Api.Helpers
/// <inheritdoc /> /// <inheritdoc />
public override void Flush() public override void Flush()
{ {
_stream.Flush(); // Not supported
} }
/// <inheritdoc /> /// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count) public override int Read(byte[] buffer, int offset, int count)
=> Read(buffer.AsSpan(offset, count));
/// <inheritdoc />
public override int Read(Span<byte> buffer)
{ {
return _stream.Read(buffer, offset, count); int totalBytesRead = 0;
var stopwatch = Stopwatch.StartNew();
while (KeepReading(stopwatch.ElapsedMilliseconds))
{
totalBytesRead += _stream.Read(buffer);
if (totalBytesRead > 0)
{
break;
}
Thread.Sleep(50);
}
UpdateBytesWritten(totalBytesRead);
return totalBytesRead;
} }
/// <inheritdoc /> /// <inheritdoc />
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> await ReadAsync(buffer.AsMemory(offset, count), cancellationToken).ConfigureAwait(false);
/// <inheritdoc />
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{ {
int totalBytesRead = 0; int totalBytesRead = 0;
int remainingBytesToRead = count;
var stopwatch = Stopwatch.StartNew(); var stopwatch = Stopwatch.StartNew();
int newOffset = offset; while (KeepReading(stopwatch.ElapsedMilliseconds))
while (remainingBytesToRead > 0)
{ {
cancellationToken.ThrowIfCancellationRequested(); totalBytesRead += await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
int bytesRead = await _stream.ReadAsync(buffer, newOffset, remainingBytesToRead, cancellationToken).ConfigureAwait(false); if (totalBytesRead > 0)
remainingBytesToRead -= bytesRead;
newOffset += bytesRead;
if (bytesRead > 0)
{ {
_bytesWritten += bytesRead; break;
totalBytesRead += bytesRead;
if (_job != null)
{
_job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
}
} }
else
{
// If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely
if (_job?.HasExited ?? stopwatch.ElapsedMilliseconds > _timeoutMs)
{
break;
}
await Task.Delay(50, cancellationToken).ConfigureAwait(false); await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
} }
UpdateBytesWritten(totalBytesRead);
return totalBytesRead; return totalBytesRead;
} }
@@ -159,5 +163,19 @@ namespace Jellyfin.Api.Helpers
base.Dispose(disposing); base.Dispose(disposing);
} }
} }
private void UpdateBytesWritten(int totalBytesRead)
{
if (_job != null)
{
_job.BytesDownloaded += totalBytesRead;
}
}
private bool KeepReading(long elapsed)
{
// If the job is null it's a live stream and will require user action to close, but don't keep it open indefinitely
return !_job?.HasExited ?? elapsed < _timeoutMs;
}
} }
} }

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