mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-04 03:27:21 -05:00 
			
		
		
		
	
						commit
						c54bdb4a0a
					
				@ -28,6 +28,12 @@ jobs:
 | 
			
		||||
      inputs:
 | 
			
		||||
        script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
 | 
			
		||||
 | 
			
		||||
## Authenticate with npm registry
 | 
			
		||||
    - task: npmAuthenticate@0
 | 
			
		||||
      inputs:
 | 
			
		||||
        workingFile: ./.npmrc
 | 
			
		||||
        customEndpoint: 'jellyfin-bot for NPM'
 | 
			
		||||
 | 
			
		||||
## Generate npm api client
 | 
			
		||||
# Unstable
 | 
			
		||||
    - task: CmdLine@2
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.npmrc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
registry=https://registry.npmjs.org/
 | 
			
		||||
@jellyfin:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/
 | 
			
		||||
always-auth=true
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "HeaderLiveTV": "Live-TV",
 | 
			
		||||
    "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
 | 
			
		||||
    "NameSeasonUnknown": "Tuntematon Kausi",
 | 
			
		||||
    "NameSeasonUnknown": "Tuntematon kausi",
 | 
			
		||||
    "NameSeasonNumber": "Kausi {0}",
 | 
			
		||||
    "NameInstallFailed": "{0} asennus epäonnistui",
 | 
			
		||||
    "MusicVideos": "Musiikkivideot",
 | 
			
		||||
@ -19,23 +19,23 @@
 | 
			
		||||
    "ItemAddedWithName": "{0} lisättiin kirjastoon",
 | 
			
		||||
    "Inherit": "Periytyä",
 | 
			
		||||
    "HomeVideos": "Kotivideot",
 | 
			
		||||
    "HeaderRecordingGroups": "Nauhoiteryhmät",
 | 
			
		||||
    "HeaderRecordingGroups": "Tallennusryhmät",
 | 
			
		||||
    "HeaderNextUp": "Seuraavaksi",
 | 
			
		||||
    "HeaderFavoriteSongs": "Lempikappaleet",
 | 
			
		||||
    "HeaderFavoriteShows": "Lempisarjat",
 | 
			
		||||
    "HeaderFavoriteEpisodes": "Lempijaksot",
 | 
			
		||||
    "HeaderFavoriteArtists": "Lempiartistit",
 | 
			
		||||
    "HeaderFavoriteAlbums": "Lempialbumit",
 | 
			
		||||
    "HeaderFavoriteSongs": "Suosikkikappaleet",
 | 
			
		||||
    "HeaderFavoriteShows": "Suosikkisarjat",
 | 
			
		||||
    "HeaderFavoriteEpisodes": "Suosikkijaksot",
 | 
			
		||||
    "HeaderFavoriteArtists": "Suosikkiartistit",
 | 
			
		||||
    "HeaderFavoriteAlbums": "Suosikkialbumit",
 | 
			
		||||
    "HeaderContinueWatching": "Jatka katsomista",
 | 
			
		||||
    "HeaderAlbumArtists": "Albumin esittäjä",
 | 
			
		||||
    "HeaderAlbumArtists": "Albumin artistit",
 | 
			
		||||
    "Genres": "Tyylilajit",
 | 
			
		||||
    "Folders": "Kansiot",
 | 
			
		||||
    "Favorites": "Suosikit",
 | 
			
		||||
    "FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}",
 | 
			
		||||
    "DeviceOnlineWithName": "{0} on yhdistetty",
 | 
			
		||||
    "DeviceOfflineWithName": "{0} on katkaissut yhteytensä",
 | 
			
		||||
    "DeviceOfflineWithName": "{0} yhteys on katkaistu",
 | 
			
		||||
    "Collections": "Kokoelmat",
 | 
			
		||||
    "ChapterNameValue": "Luku: {0}",
 | 
			
		||||
    "ChapterNameValue": "Jakso: {0}",
 | 
			
		||||
    "Channels": "Kanavat",
 | 
			
		||||
    "CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}",
 | 
			
		||||
    "Books": "Kirjat",
 | 
			
		||||
@ -61,25 +61,25 @@
 | 
			
		||||
    "UserPolicyUpdatedWithName": "Käyttöoikeudet päivitetty käyttäjälle {0}",
 | 
			
		||||
    "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
 | 
			
		||||
    "UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
 | 
			
		||||
    "UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
 | 
			
		||||
    "UserOfflineFromDevice": "{0} yhteys katkaistu kohteesta {1}",
 | 
			
		||||
    "UserLockedOutWithName": "Käyttäjä {0} lukittu",
 | 
			
		||||
    "UserDownloadingItemWithValues": "{0} lataa {1}",
 | 
			
		||||
    "UserDeletedWithName": "Käyttäjä {0} poistettu",
 | 
			
		||||
    "UserCreatedWithName": "Käyttäjä {0} luotu",
 | 
			
		||||
    "TvShows": "TV-sarjat",
 | 
			
		||||
    "TvShows": "TV-ohjelmat",
 | 
			
		||||
    "Sync": "Synkronoi",
 | 
			
		||||
    "SubtitleDownloadFailureFromForItem": "Tekstitysten lataus ({0} -> {1}) epäonnistui //this string would have to be generated for each provider and movie because of finnish cases, sorry",
 | 
			
		||||
    "StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Kokeile hetken kuluttua uudelleen.",
 | 
			
		||||
    "SubtitleDownloadFailureFromForItem": "Tekstitystä ei voitu ladata osoitteesta {0} kohteelle {1}",
 | 
			
		||||
    "StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Yritä hetken kuluttua uudelleen.",
 | 
			
		||||
    "Songs": "Kappaleet",
 | 
			
		||||
    "Shows": "Sarjat",
 | 
			
		||||
    "ServerNameNeedsToBeRestarted": "{0} täytyy käynnistää uudelleen",
 | 
			
		||||
    "Shows": "Ohjelmat",
 | 
			
		||||
    "ServerNameNeedsToBeRestarted": "{0} on käynnistettävä uudelleen",
 | 
			
		||||
    "ProviderValue": "Tarjoaja: {0}",
 | 
			
		||||
    "Plugin": "Liitännäinen",
 | 
			
		||||
    "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
 | 
			
		||||
    "NotificationOptionVideoPlayback": "Videota toistetaan",
 | 
			
		||||
    "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
 | 
			
		||||
    "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
 | 
			
		||||
    "NotificationOptionServerRestartRequired": "Palvelin pitää käynnistää uudelleen",
 | 
			
		||||
    "NotificationOptionServerRestartRequired": "Palvelin on käynnistettävä uudelleen",
 | 
			
		||||
    "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
 | 
			
		||||
    "NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
 | 
			
		||||
    "NotificationOptionPluginInstalled": "Liitännäinen asennettu",
 | 
			
		||||
@ -104,10 +104,10 @@
 | 
			
		||||
    "TaskRefreshPeople": "Päivitä henkilöt",
 | 
			
		||||
    "TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
 | 
			
		||||
    "TaskCleanLogs": "Puhdista lokihakemisto",
 | 
			
		||||
    "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
 | 
			
		||||
    "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uudet tiedostot ja päivittää metatiedot.",
 | 
			
		||||
    "TaskRefreshLibrary": "Skannaa mediakirjasto",
 | 
			
		||||
    "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
 | 
			
		||||
    "TaskRefreshChapterImages": "Eristä lukujen kuvat",
 | 
			
		||||
    "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on jaksoja.",
 | 
			
		||||
    "TaskRefreshChapterImages": "Pura jakson kuvat",
 | 
			
		||||
    "TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
 | 
			
		||||
    "TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
 | 
			
		||||
    "TasksChannelsCategory": "Internet kanavat",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								Emby.Server.Implementations/Localization/Core/hi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Emby.Server.Implementations/Localization/Core/hi.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
{
 | 
			
		||||
    "Albums": "आल्बुम्"
 | 
			
		||||
}
 | 
			
		||||
@ -112,5 +112,5 @@
 | 
			
		||||
    "Artists": "Artistë",
 | 
			
		||||
    "Application": "Aplikacioni",
 | 
			
		||||
    "AppDeviceValues": "Aplikacioni: {0}, Pajisja: {1}",
 | 
			
		||||
    "Albums": "Albumet"
 | 
			
		||||
    "Albums": "Albume"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@
 | 
			
		||||
    "Channels": "Kanaler",
 | 
			
		||||
    "ChapterNameValue": "Kapitel {0}",
 | 
			
		||||
    "Collections": "Samlingar",
 | 
			
		||||
    "DeviceOfflineWithName": "{0} har kopplat från",
 | 
			
		||||
    "DeviceOfflineWithName": "{0} har kopplat ner",
 | 
			
		||||
    "DeviceOnlineWithName": "{0} är ansluten",
 | 
			
		||||
    "FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}",
 | 
			
		||||
    "Favorites": "Favoriter",
 | 
			
		||||
 | 
			
		||||
@ -101,7 +101,7 @@
 | 
			
		||||
    "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
 | 
			
		||||
    "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
 | 
			
		||||
    "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
 | 
			
		||||
    "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
 | 
			
		||||
    "TaskCleanTranscodeDescription": "ஒரு நாளைக்கு மேற்பட்ட பழைய டிரான்ஸ்கோட் கோப்புகளை நீக்குகிறது.",
 | 
			
		||||
    "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
 | 
			
		||||
    "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.",
 | 
			
		||||
    "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ using MediaBrowser.Controller.Dto;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -99,7 +100,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? years,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? person,
 | 
			
		||||
            [FromQuery] string? personIds,
 | 
			
		||||
            [FromQuery] string? personTypes,
 | 
			
		||||
@ -308,7 +309,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? years,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? person,
 | 
			
		||||
            [FromQuery] string? personIds,
 | 
			
		||||
            [FromQuery] string? personTypes,
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -100,7 +101,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? years,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? person,
 | 
			
		||||
            [FromQuery] string? personIds,
 | 
			
		||||
            [FromQuery] string? personTypes,
 | 
			
		||||
 | 
			
		||||
@ -109,7 +109,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            var userDataPath = Path.Combine(_serverConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username);
 | 
			
		||||
            if (user.ProfileImage != null)
 | 
			
		||||
            {
 | 
			
		||||
                _userManager.ClearProfileImage(user);
 | 
			
		||||
                await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
 | 
			
		||||
@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
			
		||||
        public ActionResult DeleteUserImage(
 | 
			
		||||
        public async Task<ActionResult> DeleteUserImage(
 | 
			
		||||
            [FromRoute, Required] Guid userId,
 | 
			
		||||
            [FromRoute, Required] ImageType imageType,
 | 
			
		||||
            [FromRoute] int? index = null)
 | 
			
		||||
@ -158,7 +158,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                _logger.LogError(e, "Error deleting user profile image:");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _userManager.ClearProfileImage(user);
 | 
			
		||||
            await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
 | 
			
		||||
            return NoContent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Controller.Playlists;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -71,7 +72,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes)
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var item = _libraryManager.GetItemById(id);
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
@ -108,7 +109,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes)
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var album = _libraryManager.GetItemById(id);
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
@ -145,7 +146,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes)
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var playlist = (Playlist)_libraryManager.GetItemById(id);
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
@ -182,7 +183,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes)
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
                ? _userManager.GetUserById(userId.Value)
 | 
			
		||||
@ -218,7 +219,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes)
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var item = _libraryManager.GetItemById(id);
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
@ -255,7 +256,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes)
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var item = _libraryManager.GetItemById(id);
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
@ -292,7 +293,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes)
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var item = _libraryManager.GetItemById(id);
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
 | 
			
		||||
@ -185,7 +185,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] ItemFilter[] filters,
 | 
			
		||||
            [FromQuery] bool? isFavorite,
 | 
			
		||||
            [FromQuery] string? mediaTypes,
 | 
			
		||||
            [FromQuery] string? imageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] imageTypes,
 | 
			
		||||
            [FromQuery] string? sortBy,
 | 
			
		||||
            [FromQuery] bool? isPlayed,
 | 
			
		||||
            [FromQuery] string? genres,
 | 
			
		||||
@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? years,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? person,
 | 
			
		||||
            [FromQuery] string? personIds,
 | 
			
		||||
            [FromQuery] string? personTypes,
 | 
			
		||||
@ -342,7 +342,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                    PersonIds = RequestHelpers.GetGuids(personIds),
 | 
			
		||||
                    PersonTypes = RequestHelpers.Split(personTypes, ',', true),
 | 
			
		||||
                    Years = RequestHelpers.Split(years, ',', true).Select(int.Parse).ToArray(),
 | 
			
		||||
                    ImageTypes = RequestHelpers.Split(imageTypes, ',', true).Select(v => Enum.Parse<ImageType>(v, true)).ToArray(),
 | 
			
		||||
                    ImageTypes = imageTypes,
 | 
			
		||||
                    VideoTypes = RequestHelpers.Split(videoTypes, ',', true).Select(v => Enum.Parse<VideoType>(v, true)).ToArray(),
 | 
			
		||||
                    AdjacentTo = adjacentTo,
 | 
			
		||||
                    ItemIds = RequestHelpers.GetGuids(ids),
 | 
			
		||||
@ -536,7 +536,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? mediaTypes,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? excludeItemTypes,
 | 
			
		||||
            [FromQuery] string? includeItemTypes,
 | 
			
		||||
            [FromQuery] bool enableTotalRecordCount = true,
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Controller.LiveTv;
 | 
			
		||||
using MediaBrowser.Controller.Net;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.LiveTv;
 | 
			
		||||
using MediaBrowser.Model.Net;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
@ -145,7 +146,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? isDisliked,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? fields,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] string? sortBy,
 | 
			
		||||
@ -262,7 +263,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? seriesTimerId,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? fields,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] bool? isMovie,
 | 
			
		||||
@ -349,7 +350,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? seriesTimerId,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? fields,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] bool enableTotalRecordCount = true)
 | 
			
		||||
@ -560,7 +561,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? genreIds,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] string? seriesTimerId,
 | 
			
		||||
            [FromQuery] Guid? librarySeriesId,
 | 
			
		||||
@ -704,7 +705,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? isSports,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? genreIds,
 | 
			
		||||
            [FromQuery] string? fields,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Entities.Audio;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -99,7 +100,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? years,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? person,
 | 
			
		||||
            [FromQuery] string? personIds,
 | 
			
		||||
            [FromQuery] string? personTypes,
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -99,7 +100,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? years,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? person,
 | 
			
		||||
            [FromQuery] string? personIds,
 | 
			
		||||
            [FromQuery] string? personTypes,
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Controller.Playlists;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Playlists;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
@ -151,7 +152,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes)
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
 | 
			
		||||
            if (playlist == null)
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        /// <returns>The list of scheduled tasks.</returns>
 | 
			
		||||
        [HttpGet]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
			
		||||
        public IEnumerable<IScheduledTaskWorker> GetTasks(
 | 
			
		||||
        public IEnumerable<TaskInfo> GetTasks(
 | 
			
		||||
            [FromQuery] bool? isHidden,
 | 
			
		||||
            [FromQuery] bool? isEnabled)
 | 
			
		||||
        {
 | 
			
		||||
@ -57,7 +57,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                yield return task;
 | 
			
		||||
                yield return ScheduledTaskHelpers.GetTaskInfo(task);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ using MediaBrowser.Controller.Dto;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -98,7 +99,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? years,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? person,
 | 
			
		||||
            [FromQuery] string? personIds,
 | 
			
		||||
            [FromQuery] string? personTypes,
 | 
			
		||||
 | 
			
		||||
@ -150,7 +150,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] ItemFilter[] filters,
 | 
			
		||||
            [FromQuery] bool? isFavorite,
 | 
			
		||||
            [FromQuery] string? mediaTypes,
 | 
			
		||||
            [FromQuery] string? imageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] imageTypes,
 | 
			
		||||
            [FromQuery] string? sortBy,
 | 
			
		||||
            [FromQuery] bool? isPlayed,
 | 
			
		||||
            [FromQuery] string? genres,
 | 
			
		||||
@ -159,7 +159,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? years,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] string? person,
 | 
			
		||||
            [FromQuery] string? personIds,
 | 
			
		||||
            [FromQuery] string? personTypes,
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ using MediaBrowser.Controller.Entities.TV;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Controller.TV;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? parentId,
 | 
			
		||||
            [FromQuery] bool? enableImges,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] bool enableTotalRecordCount = true)
 | 
			
		||||
        {
 | 
			
		||||
@ -134,7 +135,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? parentId,
 | 
			
		||||
            [FromQuery] bool? enableImges,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] bool? enableUserData)
 | 
			
		||||
        {
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
@ -206,7 +207,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] int? limit,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] string? sortBy)
 | 
			
		||||
        {
 | 
			
		||||
@ -325,7 +326,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? adjacentTo,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] bool? enableUserData)
 | 
			
		||||
        {
 | 
			
		||||
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
 | 
			
		||||
 | 
			
		||||
@ -381,17 +381,13 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
 | 
			
		||||
            var user = _userManager.GetUserById(userId);
 | 
			
		||||
 | 
			
		||||
            if (string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal))
 | 
			
		||||
            {
 | 
			
		||||
                await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
 | 
			
		||||
                _userManager.UpdateConfiguration(user.Id, updateUser.Configuration);
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal))
 | 
			
		||||
            {
 | 
			
		||||
                await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false);
 | 
			
		||||
                _userManager.UpdateConfiguration(updateUser.Id, updateUser.Configuration);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await _userManager.UpdateConfigurationAsync(user.Id, updateUser.Configuration).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            return NoContent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -409,7 +405,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
			
		||||
        public ActionResult UpdateUserPolicy(
 | 
			
		||||
        public async Task<ActionResult> UpdateUserPolicy(
 | 
			
		||||
            [FromRoute, Required] Guid userId,
 | 
			
		||||
            [FromBody] UserPolicy newPolicy)
 | 
			
		||||
        {
 | 
			
		||||
@ -447,7 +443,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                _sessionManager.RevokeUserTokens(user.Id, currentToken);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _userManager.UpdatePolicy(userId, newPolicy);
 | 
			
		||||
            await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            return NoContent();
 | 
			
		||||
        }
 | 
			
		||||
@ -464,7 +460,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
        [Authorize(Policy = Policies.DefaultAuthorization)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
        [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
			
		||||
        public ActionResult UpdateUserConfiguration(
 | 
			
		||||
        public async Task<ActionResult> UpdateUserConfiguration(
 | 
			
		||||
            [FromRoute, Required] Guid userId,
 | 
			
		||||
            [FromBody] UserConfiguration userConfig)
 | 
			
		||||
        {
 | 
			
		||||
@ -473,7 +469,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
                return Forbid("User configuration update not allowed");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _userManager.UpdateConfiguration(userId, userConfig);
 | 
			
		||||
            await _userManager.UpdateConfigurationAsync(userId, userConfig).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            return NoContent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -272,7 +272,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] bool? isPlayed,
 | 
			
		||||
            [FromQuery] bool? enableImages,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int limit = 20,
 | 
			
		||||
            [FromQuery] bool groupItems = true)
 | 
			
		||||
 | 
			
		||||
@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
 | 
			
		||||
            var baseUrlParam = string.Format(
 | 
			
		||||
                CultureInfo.InvariantCulture,
 | 
			
		||||
                "\"hls{0}\"",
 | 
			
		||||
                "\"hls/{0}/\"",
 | 
			
		||||
                Path.GetFileNameWithoutExtension(outputPath));
 | 
			
		||||
 | 
			
		||||
            return string.Format(
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ using MediaBrowser.Controller.Dto;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Model.Dto;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
@ -77,7 +78,7 @@ namespace Jellyfin.Api.Controllers
 | 
			
		||||
            [FromQuery] string? sortBy,
 | 
			
		||||
            [FromQuery] bool? enableUserData,
 | 
			
		||||
            [FromQuery] int? imageTypeLimit,
 | 
			
		||||
            [FromQuery] string? enableImageTypes,
 | 
			
		||||
            [FromQuery] ImageType[] enableImageTypes,
 | 
			
		||||
            [FromQuery] Guid? userId,
 | 
			
		||||
            [FromQuery] bool recursive = true,
 | 
			
		||||
            [FromQuery] bool? enableImages = true)
 | 
			
		||||
 | 
			
		||||
@ -126,7 +126,7 @@ namespace Jellyfin.Api.Extensions
 | 
			
		||||
            bool? enableImages,
 | 
			
		||||
            bool? enableUserData,
 | 
			
		||||
            int? imageTypeLimit,
 | 
			
		||||
            string? enableImageTypes)
 | 
			
		||||
            ImageType[] enableImageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            dtoOptions.EnableImages = enableImages ?? true;
 | 
			
		||||
 | 
			
		||||
@ -140,11 +140,9 @@ namespace Jellyfin.Api.Extensions
 | 
			
		||||
                dtoOptions.EnableUserData = enableUserData.Value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(enableImageTypes))
 | 
			
		||||
            if (enableImageTypes.Length != 0)
 | 
			
		||||
            {
 | 
			
		||||
                dtoOptions.ImageTypes = enableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
 | 
			
		||||
                    .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true))
 | 
			
		||||
                    .ToArray();
 | 
			
		||||
                dtoOptions.ImageTypes = enableImageTypes;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return dtoOptions;
 | 
			
		||||
 | 
			
		||||
@ -155,7 +155,7 @@ namespace Jellyfin.Api.Helpers
 | 
			
		||||
                return new FileContentResult(Array.Empty<byte>(), MimeTypes.GetMimeType("playlist.m3u8"));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var totalBitrate = state.OutputAudioBitrate ?? 0 + state.OutputVideoBitrate ?? 0;
 | 
			
		||||
            var totalBitrate = (state.OutputAudioBitrate ?? 0) + (state.OutputVideoBitrate ?? 0);
 | 
			
		||||
 | 
			
		||||
            var builder = new StringBuilder();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ using Jellyfin.Data.Enums;
 | 
			
		||||
using MediaBrowser.Common.Extensions;
 | 
			
		||||
using MediaBrowser.Controller.Net;
 | 
			
		||||
using MediaBrowser.Controller.Session;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Querying;
 | 
			
		||||
using Microsoft.AspNetCore.Http;
 | 
			
		||||
 | 
			
		||||
@ -161,5 +162,32 @@ namespace Jellyfin.Api.Helpers
 | 
			
		||||
                .Select(i => i!.Value)
 | 
			
		||||
                .ToArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the item fields.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="imageTypes">The image types string.</param>
 | 
			
		||||
        /// <returns>IEnumerable{ItemFields}.</returns>
 | 
			
		||||
        internal static ImageType[] GetImageTypes(string? imageTypes)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrEmpty(imageTypes))
 | 
			
		||||
            {
 | 
			
		||||
                return Array.Empty<ImageType>();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Split(imageTypes, ',', true)
 | 
			
		||||
                .Select(v =>
 | 
			
		||||
                {
 | 
			
		||||
                    if (Enum.TryParse(v, true, out ImageType value))
 | 
			
		||||
                    {
 | 
			
		||||
                        return (ImageType?)value;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return null;
 | 
			
		||||
                })
 | 
			
		||||
                .Where(i => i.HasValue)
 | 
			
		||||
                .Select(i => i!.Value)
 | 
			
		||||
                .ToArray();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,8 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
using MediaBrowser.Common.Json.Converters;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
 | 
			
		||||
namespace Jellyfin.Api.Models.LiveTvDtos
 | 
			
		||||
{
 | 
			
		||||
@ -137,7 +141,9 @@ namespace Jellyfin.Api.Models.LiveTvDtos
 | 
			
		||||
        /// Gets or sets the image types to include in the output.
 | 
			
		||||
        /// Optional.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string? EnableImageTypes { get; set; }
 | 
			
		||||
        [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
 | 
			
		||||
        [SuppressMessage("Microsoft.Performance", "CA1819:ReturnArrays", MessageId = "EnableImageTypes", Justification = "Imported from ServiceStack")]
 | 
			
		||||
        public ImageType[] EnableImageTypes { get; set; } = Array.Empty<ImageType>();
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets include user data.
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
#pragma warning disable CA1307
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
@ -48,6 +49,8 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
        private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider;
 | 
			
		||||
        private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
 | 
			
		||||
 | 
			
		||||
        private readonly IDictionary<Guid, User> _users;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="UserManager"/> class.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@ -81,38 +84,28 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
            _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
 | 
			
		||||
            _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
 | 
			
		||||
            _defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
 | 
			
		||||
 | 
			
		||||
            _users = new ConcurrentDictionary<Guid, User>();
 | 
			
		||||
            using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            foreach (var user in dbContext.Users
 | 
			
		||||
                .Include(user => user.Permissions)
 | 
			
		||||
                .Include(user => user.Preferences)
 | 
			
		||||
                .Include(user => user.AccessSchedules)
 | 
			
		||||
                .Include(user => user.ProfileImage)
 | 
			
		||||
                .AsEnumerable())
 | 
			
		||||
            {
 | 
			
		||||
                _users.Add(user.Id, user);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public IEnumerable<User> Users
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
                return dbContext.Users
 | 
			
		||||
                    .Include(user => user.Permissions)
 | 
			
		||||
                    .Include(user => user.Preferences)
 | 
			
		||||
                    .Include(user => user.AccessSchedules)
 | 
			
		||||
                    .Include(user => user.ProfileImage)
 | 
			
		||||
                    .ToList();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        public IEnumerable<User> Users => _users.Values;
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public IEnumerable<Guid> UsersIds
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
                return dbContext.Users
 | 
			
		||||
                    .AsQueryable()
 | 
			
		||||
                    .Select(user => user.Id)
 | 
			
		||||
                    .ToList();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        public IEnumerable<Guid> UsersIds => _users.Keys;
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public User? GetUserById(Guid id)
 | 
			
		||||
@ -122,13 +115,8 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
                throw new ArgumentException("Guid can't be empty", nameof(id));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            return dbContext.Users
 | 
			
		||||
                .Include(user => user.Permissions)
 | 
			
		||||
                .Include(user => user.Preferences)
 | 
			
		||||
                .Include(user => user.AccessSchedules)
 | 
			
		||||
                .Include(user => user.ProfileImage)
 | 
			
		||||
                .FirstOrDefault(user => user.Id == id);
 | 
			
		||||
            _users.TryGetValue(id, out var user);
 | 
			
		||||
            return user;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
@ -139,14 +127,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
                throw new ArgumentException("Invalid username", nameof(name));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            return dbContext.Users
 | 
			
		||||
                .Include(user => user.Permissions)
 | 
			
		||||
                .Include(user => user.Preferences)
 | 
			
		||||
                .Include(user => user.AccessSchedules)
 | 
			
		||||
                .Include(user => user.ProfileImage)
 | 
			
		||||
                .AsEnumerable()
 | 
			
		||||
                .FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
 | 
			
		||||
            return _users.Values.FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
@ -205,13 +186,17 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
                ? await dbContext.Users.AsQueryable().Select(u => u.InternalId).MaxAsync().ConfigureAwait(false)
 | 
			
		||||
                : 0;
 | 
			
		||||
 | 
			
		||||
            return new User(
 | 
			
		||||
            var user = new User(
 | 
			
		||||
                name,
 | 
			
		||||
                _defaultAuthenticationProvider.GetType().FullName,
 | 
			
		||||
                _defaultPasswordResetProvider.GetType().FullName)
 | 
			
		||||
            {
 | 
			
		||||
                InternalId = max + 1
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            _users.Add(user.Id, user);
 | 
			
		||||
 | 
			
		||||
            return user;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
@ -237,28 +222,12 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public void DeleteUser(Guid userId)
 | 
			
		||||
        {
 | 
			
		||||
            using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            var user = dbContext.Users
 | 
			
		||||
                .Include(u => u.Permissions)
 | 
			
		||||
                .Include(u => u.Preferences)
 | 
			
		||||
                .Include(u => u.AccessSchedules)
 | 
			
		||||
                .Include(u => u.ProfileImage)
 | 
			
		||||
                .FirstOrDefault(u => u.Id == userId);
 | 
			
		||||
            if (user == null)
 | 
			
		||||
            if (!_users.TryGetValue(userId, out var user))
 | 
			
		||||
            {
 | 
			
		||||
                throw new ResourceNotFoundException(nameof(userId));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (dbContext.Users.Find(user.Id) == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentException(string.Format(
 | 
			
		||||
                    CultureInfo.InvariantCulture,
 | 
			
		||||
                    "The user cannot be deleted because there is no user with the Name {0} and Id {1}.",
 | 
			
		||||
                    user.Username,
 | 
			
		||||
                    user.Id));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (dbContext.Users.Count() == 1)
 | 
			
		||||
            if (_users.Count == 1)
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidOperationException(string.Format(
 | 
			
		||||
                    CultureInfo.InvariantCulture,
 | 
			
		||||
@ -277,6 +246,8 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
                    nameof(userId));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
 | 
			
		||||
            // Clear all entities related to the user from the database.
 | 
			
		||||
            if (user.ProfileImage != null)
 | 
			
		||||
            {
 | 
			
		||||
@ -288,6 +259,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
            dbContext.RemoveRange(user.AccessSchedules);
 | 
			
		||||
            dbContext.Users.Remove(user);
 | 
			
		||||
            dbContext.SaveChanges();
 | 
			
		||||
            _users.Remove(userId);
 | 
			
		||||
 | 
			
		||||
            _eventManager.Publish(new UserDeletedEventArgs(user));
 | 
			
		||||
        }
 | 
			
		||||
@ -460,11 +432,9 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
                    // the authentication provider might have created it
 | 
			
		||||
                    user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
 | 
			
		||||
 | 
			
		||||
                    if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
 | 
			
		||||
                    if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy && user != null)
 | 
			
		||||
                    {
 | 
			
		||||
                        UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy());
 | 
			
		||||
 | 
			
		||||
                        await UpdateUserAsync(user).ConfigureAwait(false);
 | 
			
		||||
                        await UpdatePolicyAsync(user.Id, hasNewUserPolicy.GetNewUserPolicy()).ConfigureAwait(false);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -589,9 +559,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
        public async Task InitializeAsync()
 | 
			
		||||
        {
 | 
			
		||||
            // TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
 | 
			
		||||
            await using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
 | 
			
		||||
            if (await dbContext.Users.AsQueryable().AnyAsync().ConfigureAwait(false))
 | 
			
		||||
            if (_users.Any())
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@ -604,6 +572,7 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
 | 
			
		||||
            _logger.LogWarning("No users, creating one with username {UserName}", defaultName);
 | 
			
		||||
 | 
			
		||||
            await using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
 | 
			
		||||
            newUser.SetPermission(PermissionKind.IsAdministrator, true);
 | 
			
		||||
            newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
 | 
			
		||||
@ -644,9 +613,9 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public void UpdateConfiguration(Guid userId, UserConfiguration config)
 | 
			
		||||
        public async Task UpdateConfigurationAsync(Guid userId, UserConfiguration config)
 | 
			
		||||
        {
 | 
			
		||||
            using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            await using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            var user = dbContext.Users
 | 
			
		||||
                           .Include(u => u.Permissions)
 | 
			
		||||
                           .Include(u => u.Preferences)
 | 
			
		||||
@ -673,13 +642,13 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
            user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
 | 
			
		||||
 | 
			
		||||
            dbContext.Update(user);
 | 
			
		||||
            dbContext.SaveChanges();
 | 
			
		||||
            await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public void UpdatePolicy(Guid userId, UserPolicy policy)
 | 
			
		||||
        public async Task UpdatePolicyAsync(Guid userId, UserPolicy policy)
 | 
			
		||||
        {
 | 
			
		||||
            using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            await using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            var user = dbContext.Users
 | 
			
		||||
                           .Include(u => u.Permissions)
 | 
			
		||||
                           .Include(u => u.Preferences)
 | 
			
		||||
@ -744,15 +713,16 @@ namespace Jellyfin.Server.Implementations.Users
 | 
			
		||||
            user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
 | 
			
		||||
 | 
			
		||||
            dbContext.Update(user);
 | 
			
		||||
            dbContext.SaveChanges();
 | 
			
		||||
            await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public void ClearProfileImage(User user)
 | 
			
		||||
        public async Task ClearProfileImageAsync(User user)
 | 
			
		||||
        {
 | 
			
		||||
            using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            await using var dbContext = _dbProvider.CreateContext();
 | 
			
		||||
            dbContext.Remove(user.ProfileImage);
 | 
			
		||||
            dbContext.SaveChanges();
 | 
			
		||||
            await dbContext.SaveChangesAsync().ConfigureAwait(false);
 | 
			
		||||
            user.ProfileImage = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static bool IsValidUsername(string name)
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.9" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.9" />
 | 
			
		||||
    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.9" />
 | 
			
		||||
    <PackageReference Include="prometheus-net" Version="3.6.0" />
 | 
			
		||||
    <PackageReference Include="prometheus-net" Version="4.0.0" />
 | 
			
		||||
    <PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
 | 
			
		||||
    <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
 | 
			
		||||
    <PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,53 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace MediaBrowser.Common.Json.Converters
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Convert comma delimited string to array of type.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="T">Type to convert to.</typeparam>
 | 
			
		||||
    public class JsonCommaDelimitedArrayConverter<T> : JsonConverter<T[]>
 | 
			
		||||
    {
 | 
			
		||||
        private readonly TypeConverter _typeConverter;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="JsonCommaDelimitedArrayConverter{T}"/> class.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public JsonCommaDelimitedArrayConverter()
 | 
			
		||||
        {
 | 
			
		||||
            _typeConverter = TypeDescriptor.GetConverter(typeof(T));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            if (reader.TokenType == JsonTokenType.String)
 | 
			
		||||
            {
 | 
			
		||||
                var stringEntries = reader.GetString()?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
 | 
			
		||||
                if (stringEntries == null || stringEntries.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    return Array.Empty<T>();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var entries = new T[stringEntries.Length];
 | 
			
		||||
                for (var i = 0; i < stringEntries.Length; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    entries[i] = (T)_typeConverter.ConvertFrom(stringEntries[i].Trim());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return entries;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return JsonSerializer.Deserialize<T[]>(ref reader, options);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            JsonSerializer.Serialize(writer, value, options);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
 | 
			
		||||
namespace MediaBrowser.Common.Json.Converters
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Json comma delimited array converter factory.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <remarks>
 | 
			
		||||
    /// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow.
 | 
			
		||||
    /// </remarks>
 | 
			
		||||
    public class JsonCommaDelimitedArrayConverterFactory : JsonConverterFactory
 | 
			
		||||
    {
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public override bool CanConvert(Type typeToConvert)
 | 
			
		||||
        {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc />
 | 
			
		||||
        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
 | 
			
		||||
        {
 | 
			
		||||
            var structType = typeToConvert.GetElementType();
 | 
			
		||||
            return (JsonConverter)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -158,7 +158,8 @@ namespace MediaBrowser.Controller.Library
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="userId">The user's Id.</param>
 | 
			
		||||
        /// <param name="config">The request containing the new user configuration.</param>
 | 
			
		||||
        void UpdateConfiguration(Guid userId, UserConfiguration config);
 | 
			
		||||
        /// <returns>A task representing the update.</returns>
 | 
			
		||||
        Task UpdateConfigurationAsync(Guid userId, UserConfiguration config);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// This method updates the user's policy.
 | 
			
		||||
@ -167,12 +168,14 @@ namespace MediaBrowser.Controller.Library
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="userId">The user's Id.</param>
 | 
			
		||||
        /// <param name="policy">The request containing the new user policy.</param>
 | 
			
		||||
        void UpdatePolicy(Guid userId, UserPolicy policy);
 | 
			
		||||
        /// <returns>A task representing the update.</returns>
 | 
			
		||||
        Task UpdatePolicyAsync(Guid userId, UserPolicy policy);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Clears the user's profile image.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="user">The user.</param>
 | 
			
		||||
        void ClearProfileImage(User user);
 | 
			
		||||
        /// <returns>A task representing the clearing of the profile image.</returns>
 | 
			
		||||
        Task ClearProfileImageAsync(User user);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1380,24 +1380,40 @@ namespace MediaBrowser.Controller.MediaEncoding
 | 
			
		||||
 | 
			
		||||
        public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream)
 | 
			
		||||
        {
 | 
			
		||||
            if (audioStream == null)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (request.AudioBitRate.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                // Don't encode any higher than this
 | 
			
		||||
                return Math.Min(384000, request.AudioBitRate.Value);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
            // Empty bitrate area is not allow on iOS
 | 
			
		||||
            // Default audio bitrate to 128K if it is not being requested
 | 
			
		||||
            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 | 
			
		||||
            return 128000;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int? GetAudioBitrateParam(int? audioBitRate, MediaStream audioStream)
 | 
			
		||||
        {
 | 
			
		||||
            if (audioStream == null)
 | 
			
		||||
            {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (audioBitRate.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                // Don't encode any higher than this
 | 
			
		||||
                return Math.Min(384000, audioBitRate.Value);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
            // Empty bitrate area is not allow on iOS
 | 
			
		||||
            // Default audio bitrate to 128K if it is not being requested
 | 
			
		||||
            // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
 | 
			
		||||
            return 128000;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions, bool isHls)
 | 
			
		||||
 | 
			
		||||
@ -666,6 +666,16 @@ namespace MediaBrowser.MediaEncoding.Probing
 | 
			
		||||
                stream.AverageFrameRate = GetFrameRate(streamInfo.AverageFrameRate);
 | 
			
		||||
                stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate);
 | 
			
		||||
 | 
			
		||||
                // Interlaced video streams in Matroska containers return the field rate instead of the frame rate
 | 
			
		||||
                // as both the average and real frame rate, so we half the returned frame rates to get the correct values
 | 
			
		||||
                //
 | 
			
		||||
                // https://gitlab.com/mbunkus/mkvtoolnix/-/wikis/Wrong-frame-rate-displayed
 | 
			
		||||
                if (stream.IsInterlaced && formatInfo.FormatName.Contains("matroska", StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
                {
 | 
			
		||||
                    stream.AverageFrameRate /= 2;
 | 
			
		||||
                    stream.RealFrameRate /= 2;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (isAudio || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) ||
 | 
			
		||||
                    string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@
 | 
			
		||||
    <PackageReference Include="xunit" Version="2.4.1" />
 | 
			
		||||
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
 | 
			
		||||
    <PackageReference Include="coverlet.collector" Version="1.3.0" />
 | 
			
		||||
    <PackageReference Include="Moq" Version="4.14.6" />
 | 
			
		||||
    <PackageReference Include="Moq" Version="4.14.7" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
 | 
			
		||||
  <!-- Code Analyzers -->
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,92 @@
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
using Jellyfin.Common.Tests.Models;
 | 
			
		||||
using MediaBrowser.Model.Session;
 | 
			
		||||
using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Jellyfin.Common.Tests.Json
 | 
			
		||||
{
 | 
			
		||||
    public static class JsonCommaDelimitedArrayTests
 | 
			
		||||
    {
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public static void Deserialize_String_Valid_Success()
 | 
			
		||||
        {
 | 
			
		||||
            var desiredValue = new GenericBodyModel<string>
 | 
			
		||||
            {
 | 
			
		||||
                Value = new[] { "a", "b", "c" }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var options = new JsonSerializerOptions();
 | 
			
		||||
            var value = JsonSerializer.Deserialize<GenericBodyModel<string>>(@"{ ""Value"": ""a,b,c"" }", options);
 | 
			
		||||
            Assert.Equal(desiredValue.Value, value?.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public static void Deserialize_String_Space_Valid_Success()
 | 
			
		||||
        {
 | 
			
		||||
            var desiredValue = new GenericBodyModel<string>
 | 
			
		||||
            {
 | 
			
		||||
                Value = new[] { "a", "b", "c" }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var options = new JsonSerializerOptions();
 | 
			
		||||
            var value = JsonSerializer.Deserialize<GenericBodyModel<string>>(@"{ ""Value"": ""a, b, c"" }", options);
 | 
			
		||||
            Assert.Equal(desiredValue.Value, value?.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public static void Deserialize_GenericCommandType_Valid_Success()
 | 
			
		||||
        {
 | 
			
		||||
            var desiredValue = new GenericBodyModel<GeneralCommandType>
 | 
			
		||||
            {
 | 
			
		||||
                Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var options = new JsonSerializerOptions();
 | 
			
		||||
            options.Converters.Add(new JsonStringEnumConverter());
 | 
			
		||||
            var value = JsonSerializer.Deserialize<GenericBodyModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options);
 | 
			
		||||
            Assert.Equal(desiredValue.Value, value?.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public static void Deserialize_GenericCommandType_Space_Valid_Success()
 | 
			
		||||
        {
 | 
			
		||||
            var desiredValue = new GenericBodyModel<GeneralCommandType>
 | 
			
		||||
            {
 | 
			
		||||
                Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var options = new JsonSerializerOptions();
 | 
			
		||||
            options.Converters.Add(new JsonStringEnumConverter());
 | 
			
		||||
            var value = JsonSerializer.Deserialize<GenericBodyModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options);
 | 
			
		||||
            Assert.Equal(desiredValue.Value, value?.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public static void Deserialize_String_Array_Valid_Success()
 | 
			
		||||
        {
 | 
			
		||||
            var desiredValue = new GenericBodyModel<string>
 | 
			
		||||
            {
 | 
			
		||||
                Value = new[] { "a", "b", "c" }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var options = new JsonSerializerOptions();
 | 
			
		||||
            var value = JsonSerializer.Deserialize<GenericBodyModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", options);
 | 
			
		||||
            Assert.Equal(desiredValue.Value, value?.Value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
        public static void Deserialize_GenericCommandType_Array_Valid_Success()
 | 
			
		||||
        {
 | 
			
		||||
            var desiredValue = new GenericBodyModel<GeneralCommandType>
 | 
			
		||||
            {
 | 
			
		||||
                Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var options = new JsonSerializerOptions();
 | 
			
		||||
            options.Converters.Add(new JsonStringEnumConverter());
 | 
			
		||||
            var value = JsonSerializer.Deserialize<GenericBodyModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options);
 | 
			
		||||
            Assert.Equal(desiredValue.Value, value?.Value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
using MediaBrowser.Common.Json.Converters;
 | 
			
		||||
 | 
			
		||||
namespace Jellyfin.Common.Tests.Models
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// The generic body model.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="T">The value type.</typeparam>
 | 
			
		||||
    public class GenericBodyModel<T>
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the value.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [SuppressMessage("Microsoft.Performance", "CA1819:Properties should not return arrays", MessageId = "Value", Justification = "Imported from ServiceStack")]
 | 
			
		||||
        [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
 | 
			
		||||
        public T[] Value { get; set; } = default!;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -17,7 +17,7 @@
 | 
			
		||||
    <PackageReference Include="AutoFixture" Version="4.13.0" />
 | 
			
		||||
    <PackageReference Include="AutoFixture.AutoMoq" Version="4.13.0" />
 | 
			
		||||
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
 | 
			
		||||
    <PackageReference Include="Moq" Version="4.14.6" />
 | 
			
		||||
    <PackageReference Include="Moq" Version="4.14.7" />
 | 
			
		||||
    <PackageReference Include="xunit" Version="2.4.1" />
 | 
			
		||||
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
 | 
			
		||||
    <PackageReference Include="coverlet.collector" Version="1.3.0" />
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user