Merge remote-tracking branch 'upstream/master' into startup-complete

pull/4057/head
crobibero 4 years ago
commit 7de0fcfc94

@ -126,7 +126,6 @@ namespace Emby.Dlna
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.AppendLine("No matching device profile found. The default will need to be used."); builder.AppendLine("No matching device profile found. The default will need to be used.");
builder.AppendFormat(CultureInfo.InvariantCulture, "DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "FriendlyName:{0}", profile.FriendlyName ?? string.Empty).AppendLine(); builder.AppendFormat(CultureInfo.InvariantCulture, "FriendlyName:{0}", profile.FriendlyName ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "Manufacturer:{0}", profile.Manufacturer ?? string.Empty).AppendLine(); builder.AppendFormat(CultureInfo.InvariantCulture, "Manufacturer:{0}", profile.Manufacturer ?? string.Empty).AppendLine();
builder.AppendFormat(CultureInfo.InvariantCulture, "ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty).AppendLine(); builder.AppendFormat(CultureInfo.InvariantCulture, "ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty).AppendLine();
@ -141,17 +140,9 @@ namespace Emby.Dlna
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{ {
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
{
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
{ {
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
{ {
return false; return false;
} }
@ -159,7 +150,7 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{ {
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
{ {
return false; return false;
} }
@ -167,7 +158,7 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{ {
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
{ {
return false; return false;
} }
@ -175,7 +166,7 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{ {
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
{ {
return false; return false;
} }
@ -183,7 +174,7 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.ModelName)) if (!string.IsNullOrEmpty(profileInfo.ModelName))
{ {
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName)) if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName))
{ {
return false; return false;
} }
@ -191,7 +182,7 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{ {
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
{ {
return false; return false;
} }
@ -199,7 +190,7 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{ {
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
{ {
return false; return false;
} }
@ -207,7 +198,7 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{ {
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
{ {
return false; return false;
} }
@ -216,11 +207,11 @@ namespace Emby.Dlna
return true; return true;
} }
private bool IsRegexMatch(string input, string pattern) private bool IsRegexOrSubstringMatch(string input, string pattern)
{ {
try try
{ {
return Regex.IsMatch(input, pattern); return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
@ -511,8 +502,7 @@ namespace Emby.Dlna
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{ {
var profile = GetProfile(headers) ?? var profile = GetDefaultProfile();
GetDefaultProfile();
var serverId = _appHost.SystemId; var serverId = _appHost.SystemId;

@ -1442,10 +1442,6 @@ namespace Emby.Server.Implementations
} }
} }
public virtual void EnableLoopback(string appName)
{
}
private bool _disposed = false; private bool _disposed = false;
/// <summary> /// <summary>

@ -1,6 +1,6 @@
{ {
"Albums": "專輯", "Albums": "專輯",
"AppDeviceValues": "軟體: {0}, 裝置: {1}", "AppDeviceValues": "軟體{0},裝置:{1}",
"Application": "應用程式", "Application": "應用程式",
"Artists": "演出者", "Artists": "演出者",
"AuthenticationSucceededWithUserName": "{0} 成功授權", "AuthenticationSucceededWithUserName": "{0} 成功授權",
@ -11,7 +11,7 @@
"Collections": "合輯", "Collections": "合輯",
"DeviceOfflineWithName": "{0} 已經斷線", "DeviceOfflineWithName": "{0} 已經斷線",
"DeviceOnlineWithName": "{0} 已經連線", "DeviceOnlineWithName": "{0} 已經連線",
"FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試", "FailedLoginAttemptWithUserName": "來自使用者 {0} 的失敗登入",
"Favorites": "我的最愛", "Favorites": "我的最愛",
"Folders": "資料夾", "Folders": "資料夾",
"Genres": "風格", "Genres": "風格",
@ -28,8 +28,8 @@
"HomeVideos": "自製影片", "HomeVideos": "自製影片",
"ItemAddedWithName": "{0} 已新增至媒體庫", "ItemAddedWithName": "{0} 已新增至媒體庫",
"ItemRemovedWithName": "{0} 已從媒體庫移除", "ItemRemovedWithName": "{0} 已從媒體庫移除",
"LabelIpAddressValue": "IP 位置: {0}", "LabelIpAddressValue": "IP 位址:{0}",
"LabelRunningTimeValue": "運行時間: {0}", "LabelRunningTimeValue": "運行時間{0}",
"Latest": "最新", "Latest": "最新",
"MessageApplicationUpdated": "Jellyfin Server 已經更新", "MessageApplicationUpdated": "Jellyfin Server 已經更新",
"MessageApplicationUpdatedTo": "Jellyfin Server 已經更新至 {0}", "MessageApplicationUpdatedTo": "Jellyfin Server 已經更新至 {0}",
@ -42,18 +42,18 @@
"NameInstallFailed": "{0} 安裝失敗", "NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季", "NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知季數", "NameSeasonUnknown": "未知季數",
"NewVersionIsAvailable": "新版本的Jellyfin Server 軟體已經推出可供下載。", "NewVersionIsAvailable": "新版本的 Jellyfin Server 軟體已經可供下載。",
"NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新",
"NotificationOptionApplicationUpdateInstalled": "應用程式已更新", "NotificationOptionApplicationUpdateInstalled": "軟體更新已安裝",
"NotificationOptionAudioPlayback": "音樂開始播放", "NotificationOptionAudioPlayback": "音樂開始播放",
"NotificationOptionAudioPlaybackStopped": "音樂停止播放", "NotificationOptionAudioPlaybackStopped": "音樂停止播放",
"NotificationOptionCameraImageUploaded": "相機相片已上傳", "NotificationOptionCameraImageUploaded": "相機相片已上傳",
"NotificationOptionInstallationFailed": "安裝失敗", "NotificationOptionInstallationFailed": "安裝失敗",
"NotificationOptionNewLibraryContent": "已新增新內容", "NotificationOptionNewLibraryContent": "已新增新內容",
"NotificationOptionPluginError": "插件安裝錯誤", "NotificationOptionPluginError": "外掛安裝失敗",
"NotificationOptionPluginInstalled": "插件已安裝", "NotificationOptionPluginInstalled": "外掛已安裝",
"NotificationOptionPluginUninstalled": "插件已移除", "NotificationOptionPluginUninstalled": "外掛已移除",
"NotificationOptionPluginUpdateInstalled": "插件已更新", "NotificationOptionPluginUpdateInstalled": "外掛已更新",
"NotificationOptionServerRestartRequired": "伺服器需要重新啟動", "NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
"NotificationOptionTaskFailed": "排程任務失敗", "NotificationOptionTaskFailed": "排程任務失敗",
"NotificationOptionUserLockedOut": "使用者已鎖定", "NotificationOptionUserLockedOut": "使用者已鎖定",
@ -61,14 +61,14 @@
"NotificationOptionVideoPlaybackStopped": "影片停止播放", "NotificationOptionVideoPlaybackStopped": "影片停止播放",
"Photos": "相片", "Photos": "相片",
"Playlists": "播放清單", "Playlists": "播放清單",
"Plugin": "插件", "Plugin": "外掛",
"PluginInstalledWithName": "{0} 已安裝", "PluginInstalledWithName": "{0} 已安裝",
"PluginUninstalledWithName": "{0} 已移除", "PluginUninstalledWithName": "{0} 已移除",
"PluginUpdatedWithName": "{0} 已更新", "PluginUpdatedWithName": "{0} 已更新",
"ProviderValue": "提供商: {0}", "ProviderValue": "提供商: {0}",
"ScheduledTaskFailedWithName": "{0} 已失敗", "ScheduledTaskFailedWithName": "排程任務 {0} 已失敗",
"ScheduledTaskStartedWithName": "{0} 已開始", "ScheduledTaskStartedWithName": "排程任務 {0} 已開始",
"ServerNameNeedsToBeRestarted": "{0} 需要重新啟動", "ServerNameNeedsToBeRestarted": "伺服器 {0} 需要重新啟動",
"Shows": "節目", "Shows": "節目",
"Songs": "歌曲", "Songs": "歌曲",
"StartupEmbyServerIsLoading": "Jellyfin Server正在啟動請稍後再試一次。", "StartupEmbyServerIsLoading": "Jellyfin Server正在啟動請稍後再試一次。",
@ -78,10 +78,10 @@
"User": "使用者", "User": "使用者",
"UserCreatedWithName": "使用者 {0} 已建立", "UserCreatedWithName": "使用者 {0} 已建立",
"UserDeletedWithName": "使用者 {0} 已移除", "UserDeletedWithName": "使用者 {0} 已移除",
"UserDownloadingItemWithValues": "{0} 正在下載 {1}", "UserDownloadingItemWithValues": "使用者 {0} 正在下載 {1}",
"UserLockedOutWithName": "使用者 {0} 已鎖定", "UserLockedOutWithName": "使用者 {0} 已鎖定",
"UserOfflineFromDevice": "{0} 已從 {1} 斷線", "UserOfflineFromDevice": "使用者 {0} 已從 {1} 斷線",
"UserOnlineFromDevice": "{0} 已連線,來自 {1}", "UserOnlineFromDevice": "使用者 {0} 已從 {1} 連線",
"UserPasswordChangedWithName": "使用者 {0} 的密碼已變更", "UserPasswordChangedWithName": "使用者 {0} 的密碼已變更",
"UserPolicyUpdatedWithName": "使用者條約已更新為 {0}", "UserPolicyUpdatedWithName": "使用者條約已更新為 {0}",
"UserStartedPlayingItemWithValues": "{0}正在使用 {2} 播放 {1}", "UserStartedPlayingItemWithValues": "{0}正在使用 {2} 播放 {1}",
@ -95,23 +95,23 @@
"TaskDownloadMissingSubtitlesDescription": "在網路上透過中繼資料搜尋遺失的字幕。", "TaskDownloadMissingSubtitlesDescription": "在網路上透過中繼資料搜尋遺失的字幕。",
"TaskDownloadMissingSubtitles": "下載遺失的字幕", "TaskDownloadMissingSubtitles": "下載遺失的字幕",
"TaskRefreshChannels": "重新整理頻道", "TaskRefreshChannels": "重新整理頻道",
"TaskUpdatePlugins": "更新插件", "TaskUpdatePlugins": "更新外掛",
"TaskRefreshPeople": "重新整理人員", "TaskRefreshPeople": "重新整理人員",
"TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔。", "TaskCleanLogsDescription": "刪除超過 {0} 天的紀錄檔。",
"TaskCleanLogs": "清空紀錄資料夾", "TaskCleanLogs": "清空紀錄資料夾",
"TaskRefreshLibraryDescription": "掃描媒體庫內新的檔案並重新整理描述資料。", "TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新描述資料。",
"TaskRefreshLibrary": "掃描媒體庫", "TaskRefreshLibrary": "重新掃描媒體庫",
"TaskRefreshChapterImages": "擷取章節圖片", "TaskRefreshChapterImages": "擷取章節圖片",
"TaskCleanCacheDescription": "刪除系統長時間不需要的快取。", "TaskCleanCacheDescription": "刪除系統不需要的快取。",
"TaskCleanCache": "清除快取資料夾", "TaskCleanCache": "清除快取資料夾",
"TasksLibraryCategory": "媒體庫", "TasksLibraryCategory": "媒體庫",
"TaskRefreshChannelsDescription": "重新整理網頻道資料。", "TaskRefreshChannelsDescription": "重新整理網頻道資料。",
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。", "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
"TaskCleanTranscode": "清除轉碼資料夾", "TaskCleanTranscode": "清除轉碼資料夾",
"TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。", "TaskUpdatePluginsDescription": "為設置自動更新的外掛下載並安裝更新。",
"TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。", "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。",
"TaskRefreshChapterImagesDescription": "為有章節的視頻創建縮圖。", "TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。",
"TasksChannelsCategory": "網頻道", "TasksChannelsCategory": "網頻道",
"TasksApplicationCategory": "應用程式", "TasksApplicationCategory": "應用程式",
"TasksMaintenanceCategory": "維修" "TasksMaintenanceCategory": "維修"
} }

@ -68,12 +68,6 @@ namespace Emby.Server.Implementations.Udp
{ {
_logger.LogError(ex, "Error sending response message"); _logger.LogError(ex, "Error sending response message");
} }
var parts = messageText.Split('|');
if (parts.Length > 1)
{
_appHost.EnableLoopback(parts[1]);
}
} }
else else
{ {

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
@ -52,7 +53,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Albums/{albumId}/Similar")] [HttpGet("Albums/{albumId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums( public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
[FromRoute] string albumId, [FromRoute, Required] string albumId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] string? excludeArtistIds, [FromQuery] string? excludeArtistIds,
[FromQuery] int? limit) [FromQuery] int? limit)
@ -84,7 +85,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Artists/{artistId}/Similar")] [HttpGet("Artists/{artistId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists( public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
[FromRoute] string artistId, [FromRoute, Required] string artistId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] string? excludeArtistIds, [FromQuery] string? excludeArtistIds,
[FromQuery] int? limit) [FromQuery] int? limit)

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
@ -469,7 +470,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the artist.</returns> /// <returns>An <see cref="OkResult"/> containing the artist.</returns>
[HttpGet("{name}")] [HttpGet("{name}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetArtistByName([FromRoute] string name, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetArtistByName([FromRoute, Required] string name, [FromQuery] Guid? userId)
{ {
var dtoOptions = new DtoOptions().AddClientFields(Request); var dtoOptions = new DtoOptions().AddClientFields(Request);

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
@ -89,8 +90,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("{itemId}/stream", Name = "HeadAudioStream")] [HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetAudioStream( public async Task<ActionResult> GetAudioStream(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string? container, [FromRoute, Required] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -90,7 +91,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Channel features returned.</response> /// <response code="200">Channel features returned.</response>
/// <returns>An <see cref="OkResult"/> containing the channel features.</returns> /// <returns>An <see cref="OkResult"/> containing the channel features.</returns>
[HttpGet("{channelId}/Features")] [HttpGet("{channelId}/Features")]
public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute] string channelId) public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute, Required] string channelId)
{ {
return _channelManager.GetChannelFeatures(channelId); return _channelManager.GetChannelFeatures(channelId);
} }
@ -114,7 +115,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("{channelId}/Items")] [HttpGet("{channelId}/Items")]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannelItems( public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannelItems(
[FromRoute] Guid channelId, [FromRoute, Required] Guid channelId,
[FromQuery] Guid? folderId, [FromQuery] Guid? folderId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,

@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("{collectionId}/Items")] [HttpPost("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds) public async Task<ActionResult> AddToCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string? itemIds)
{ {
await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true); await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
return NoContent(); return NoContent();
@ -103,7 +103,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpDelete("{collectionId}/Items")] [HttpDelete("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds) public async Task<ActionResult> RemoveFromCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string? itemIds)
{ {
await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false); await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
return NoContent(); return NoContent();

@ -73,7 +73,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Configuration.</returns> /// <returns>Configuration.</returns>
[HttpGet("Configuration/{key}")] [HttpGet("Configuration/{key}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<object> GetNamedConfiguration([FromRoute] string? key) public ActionResult<object> GetNamedConfiguration([FromRoute, Required] string? key)
{ {
return _configurationManager.GetConfiguration(key); return _configurationManager.GetConfiguration(key);
} }
@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Configuration/{key}")] [HttpPost("Configuration/{key}")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string? key) public async Task<ActionResult> UpdateNamedConfiguration([FromRoute, Required] string? key)
{ {
var configurationType = _configurationManager.GetConfigurationType(key); var configurationType = _configurationManager.GetConfigurationType(key);
var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false); var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);

@ -43,7 +43,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
public ActionResult<DisplayPreferencesDto> GetDisplayPreferences( public ActionResult<DisplayPreferencesDto> GetDisplayPreferences(
[FromRoute] string? displayPreferencesId, [FromRoute, Required] string? displayPreferencesId,
[FromQuery] [Required] Guid userId, [FromQuery] [Required] Guid userId,
[FromQuery] [Required] string? client) [FromQuery] [Required] string? client)
{ {
@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
public ActionResult UpdateDisplayPreferences( public ActionResult UpdateDisplayPreferences(
[FromRoute] string? displayPreferencesId, [FromRoute, Required] string? displayPreferencesId,
[FromQuery, Required] Guid userId, [FromQuery, Required] Guid userId,
[FromQuery, Required] string? client, [FromQuery, Required] string? client,
[FromBody, Required] DisplayPreferencesDto displayPreferences) [FromBody, Required] DisplayPreferencesDto displayPreferences)

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -59,7 +60,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Profiles/{profileId}")] [HttpGet("Profiles/{profileId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<DeviceProfile> GetProfile([FromRoute] string profileId) public ActionResult<DeviceProfile> GetProfile([FromRoute, Required] string profileId)
{ {
var profile = _dlnaManager.GetProfile(profileId); var profile = _dlnaManager.GetProfile(profileId);
if (profile == null) if (profile == null)
@ -80,7 +81,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Profiles/{profileId}")] [HttpDelete("Profiles/{profileId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteProfile([FromRoute] string profileId) public ActionResult DeleteProfile([FromRoute, Required] string profileId)
{ {
var existingDeviceProfile = _dlnaManager.GetProfile(profileId); var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
if (existingDeviceProfile == null) if (existingDeviceProfile == null)
@ -117,7 +118,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Profiles/{profileId}")] [HttpPost("Profiles/{profileId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateProfile([FromRoute] string profileId, [FromBody] DeviceProfile deviceProfile) public ActionResult UpdateProfile([FromRoute, Required] string profileId, [FromBody] DeviceProfile deviceProfile)
{ {
var existingDeviceProfile = _dlnaManager.GetProfile(profileId); var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
if (existingDeviceProfile == null) if (existingDeviceProfile == null)

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Mime; using System.Net.Mime;
@ -45,7 +46,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")] [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
[Produces(MediaTypeNames.Text.Xml)] [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetDescriptionXml([FromRoute] string serverId) public ActionResult GetDescriptionXml([FromRoute, Required] string serverId)
{ {
var url = GetAbsoluteUri(); var url = GetAbsoluteUri();
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
@ -65,7 +66,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Text.Xml)] [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetContentDirectory([FromRoute] string serverId) public ActionResult GetContentDirectory([FromRoute, Required] string serverId)
{ {
return Ok(_contentDirectory.GetServiceXml()); return Ok(_contentDirectory.GetServiceXml());
} }
@ -81,7 +82,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Text.Xml)] [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId) public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
{ {
return Ok(_mediaReceiverRegistrar.GetServiceXml()); return Ok(_mediaReceiverRegistrar.GetServiceXml());
} }
@ -97,7 +98,7 @@ namespace Jellyfin.Api.Controllers
[Produces(MediaTypeNames.Text.Xml)] [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetConnectionManager([FromRoute] string serverId) public ActionResult GetConnectionManager([FromRoute, Required] string serverId)
{ {
return Ok(_connectionManager.GetServiceXml()); return Ok(_connectionManager.GetServiceXml());
} }
@ -108,7 +109,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param> /// <param name="serverId">Server UUID.</param>
/// <returns>Control response.</returns> /// <returns>Control response.</returns>
[HttpPost("{serverId}/ContentDirectory/Control")] [HttpPost("{serverId}/ContentDirectory/Control")]
public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute] string serverId) public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
{ {
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false); return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
} }
@ -119,7 +120,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param> /// <param name="serverId">Server UUID.</param>
/// <returns>Control response.</returns> /// <returns>Control response.</returns>
[HttpPost("{serverId}/ConnectionManager/Control")] [HttpPost("{serverId}/ConnectionManager/Control")]
public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute] string serverId) public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
{ {
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false); return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
} }
@ -130,7 +131,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param> /// <param name="serverId">Server UUID.</param>
/// <returns>Control response.</returns> /// <returns>Control response.</returns>
[HttpPost("{serverId}/MediaReceiverRegistrar/Control")] [HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute] string serverId) public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
{ {
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
} }
@ -185,7 +186,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Icon stream.</returns> /// <returns>Icon stream.</returns>
[HttpGet("{serverId}/icons/{fileName}")] [HttpGet("{serverId}/icons/{fileName}")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
public ActionResult GetIconId([FromRoute] string serverId, [FromRoute] string fileName) public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
{ {
return GetIconInternal(fileName); return GetIconInternal(fileName);
} }
@ -196,7 +197,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="fileName">The icon filename.</param> /// <param name="fileName">The icon filename.</param>
/// <returns>Icon stream.</returns> /// <returns>Icon stream.</returns>
[HttpGet("icons/{fileName}")] [HttpGet("icons/{fileName}")]
public ActionResult GetIcon([FromRoute] string fileName) public ActionResult GetIcon([FromRoute, Required] string fileName)
{ {
return GetIconInternal(fileName); return GetIconInternal(fileName);
} }

@ -167,8 +167,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")] [HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetMasterHlsVideoPlaylist( public async Task<ActionResult> GetMasterHlsVideoPlaylist(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string? container, [FromRoute, Required] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,
@ -334,8 +334,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")] [HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetMasterHlsAudioPlaylist( public async Task<ActionResult> GetMasterHlsAudioPlaylist(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string? container, [FromRoute, Required] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,
@ -499,8 +499,8 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Videos/{itemId}/main.m3u8")] [HttpGet("Videos/{itemId}/main.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetVariantHlsVideoPlaylist( public async Task<ActionResult> GetVariantHlsVideoPlaylist(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string? container, [FromRoute, Required] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,
@ -664,8 +664,8 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Audio/{itemId}/main.m3u8")] [HttpGet("Audio/{itemId}/main.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetVariantHlsAudioPlaylist( public async Task<ActionResult> GetVariantHlsAudioPlaylist(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string? container, [FromRoute, Required] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,
@ -832,10 +832,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetHlsVideoSegment( public async Task<ActionResult> GetHlsVideoSegment(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string playlistId, [FromRoute, Required] string playlistId,
[FromRoute] int segmentId, [FromRoute, Required] int segmentId,
[FromRoute] string container, [FromRoute, Required] string container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,
@ -1001,10 +1001,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetHlsAudioSegment( public async Task<ActionResult> GetHlsAudioSegment(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string playlistId, [FromRoute, Required] string playlistId,
[FromRoute] int segmentId, [FromRoute, Required] int segmentId,
[FromRoute] string container, [FromRoute, Required] string container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -260,7 +261,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the genre.</returns> /// <returns>An <see cref="OkResult"/> containing the genre.</returns>
[HttpGet("{genreName}")] [HttpGet("{genreName}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetGenre([FromRoute] string genreName, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{ {
var dtoOptions = new DtoOptions() var dtoOptions = new DtoOptions()
.AddClientFields(Request); .AddClientFields(Request);

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -55,7 +56,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")] [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
public ActionResult GetHlsAudioSegmentLegacy([FromRoute] string itemId, [FromRoute] string segmentId) public ActionResult GetHlsAudioSegmentLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string segmentId)
{ {
// TODO: Deprecate with new iOS app // TODO: Deprecate with new iOS app
var file = segmentId + Path.GetExtension(Request.Path); var file = segmentId + Path.GetExtension(Request.Path);
@ -75,7 +76,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
public ActionResult GetHlsPlaylistLegacy([FromRoute] string itemId, [FromRoute] string playlistId) public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId)
{ {
var file = playlistId + Path.GetExtension(Request.Path); var file = playlistId + Path.GetExtension(Request.Path);
file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file); file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
@ -114,10 +115,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
public ActionResult GetHlsVideoSegmentLegacy( public ActionResult GetHlsVideoSegmentLegacy(
[FromRoute] string itemId, [FromRoute, Required] string itemId,
[FromRoute] string playlistId, [FromRoute, Required] string playlistId,
[FromRoute] string segmentId, [FromRoute, Required] string segmentId,
[FromRoute] string segmentContainer) [FromRoute, Required] string segmentContainer)
{ {
var file = segmentId + Path.GetExtension(Request.Path); var file = segmentId + Path.GetExtension(Request.Path);
var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath(); var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -90,9 +91,9 @@ namespace Jellyfin.Api.Controllers
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> PostUserImage( public async Task<ActionResult> PostUserImage(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int? index = null) [FromRoute, Required] int? index = null)
{ {
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
{ {
@ -137,9 +138,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult DeleteUserImage( public ActionResult DeleteUserImage(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int? index = null) [FromRoute, Required] int? index = null)
{ {
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
{ {
@ -175,9 +176,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteItemImage( public async Task<ActionResult> DeleteItemImage(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -205,9 +206,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> SetItemImage( public async Task<ActionResult> SetItemImage(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -238,9 +239,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UpdateItemImageIndex( public async Task<ActionResult> UpdateItemImageIndex(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int imageIndex, [FromRoute, Required] int imageIndex,
[FromQuery] int newIndex) [FromQuery] int newIndex)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
@ -264,7 +265,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute] Guid itemId) public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -352,10 +353,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetItemImage( public async Task<ActionResult> GetItemImage(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int? maxWidth, [FromRoute, Required] int? maxWidth,
[FromRoute] int? maxHeight, [FromRoute, Required] int? maxHeight,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? quality, [FromQuery] int? quality,
@ -368,7 +369,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur, [FromQuery] int? blur,
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -430,23 +431,23 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetItemImage2( public async Task<ActionResult> GetItemImage2(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int? maxWidth, [FromRoute, Required] int? maxWidth,
[FromRoute] int? maxHeight, [FromRoute, Required] int? maxHeight,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? quality, [FromQuery] int? quality,
[FromRoute] string tag, [FromRoute, Required] string tag,
[FromQuery] bool? cropWhitespace, [FromQuery] bool? cropWhitespace,
[FromRoute] string format, [FromRoute, Required] string format,
[FromQuery] bool? addPlayedIndicator, [FromQuery] bool? addPlayedIndicator,
[FromRoute] double? percentPlayed, [FromRoute, Required] double? percentPlayed,
[FromRoute] int? unplayedCount, [FromRoute, Required] int? unplayedCount,
[FromQuery] int? blur, [FromQuery] int? blur,
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -508,14 +509,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetArtistImage( public async Task<ActionResult> GetArtistImage(
[FromRoute] string name, [FromRoute, Required] string name,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] string tag, [FromRoute, Required] string tag,
[FromRoute] string format, [FromRoute, Required] string format,
[FromRoute] int? maxWidth, [FromRoute, Required] int? maxWidth,
[FromRoute] int? maxHeight, [FromRoute, Required] int? maxHeight,
[FromRoute] double? percentPlayed, [FromRoute, Required] double? percentPlayed,
[FromRoute] int? unplayedCount, [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? quality, [FromQuery] int? quality,
@ -524,7 +525,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur, [FromQuery] int? blur,
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetArtist(name); var item = _libraryManager.GetArtist(name);
if (item == null) if (item == null)
@ -586,14 +587,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetGenreImage( public async Task<ActionResult> GetGenreImage(
[FromRoute] string name, [FromRoute, Required] string name,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] string tag, [FromRoute, Required] string tag,
[FromRoute] string format, [FromRoute, Required] string format,
[FromRoute] int? maxWidth, [FromRoute, Required] int? maxWidth,
[FromRoute] int? maxHeight, [FromRoute, Required] int? maxHeight,
[FromRoute] double? percentPlayed, [FromRoute, Required] double? percentPlayed,
[FromRoute] int? unplayedCount, [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? quality, [FromQuery] int? quality,
@ -602,7 +603,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur, [FromQuery] int? blur,
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetGenre(name); var item = _libraryManager.GetGenre(name);
if (item == null) if (item == null)
@ -664,14 +665,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetMusicGenreImage( public async Task<ActionResult> GetMusicGenreImage(
[FromRoute] string name, [FromRoute, Required] string name,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] string tag, [FromRoute, Required] string tag,
[FromRoute] string format, [FromRoute, Required] string format,
[FromRoute] int? maxWidth, [FromRoute, Required] int? maxWidth,
[FromRoute] int? maxHeight, [FromRoute, Required] int? maxHeight,
[FromRoute] double? percentPlayed, [FromRoute, Required] double? percentPlayed,
[FromRoute] int? unplayedCount, [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? quality, [FromQuery] int? quality,
@ -680,7 +681,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur, [FromQuery] int? blur,
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetMusicGenre(name); var item = _libraryManager.GetMusicGenre(name);
if (item == null) if (item == null)
@ -742,14 +743,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetPersonImage( public async Task<ActionResult> GetPersonImage(
[FromRoute] string name, [FromRoute, Required] string name,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] string tag, [FromRoute, Required] string tag,
[FromRoute] string format, [FromRoute, Required] string format,
[FromRoute] int? maxWidth, [FromRoute, Required] int? maxWidth,
[FromRoute] int? maxHeight, [FromRoute, Required] int? maxHeight,
[FromRoute] double? percentPlayed, [FromRoute, Required] double? percentPlayed,
[FromRoute] int? unplayedCount, [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? quality, [FromQuery] int? quality,
@ -758,7 +759,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur, [FromQuery] int? blur,
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetPerson(name); var item = _libraryManager.GetPerson(name);
if (item == null) if (item == null)
@ -820,14 +821,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetStudioImage( public async Task<ActionResult> GetStudioImage(
[FromRoute] string name, [FromRoute, Required] string name,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] string tag, [FromRoute, Required] string tag,
[FromRoute] string format, [FromRoute, Required] string format,
[FromRoute] int? maxWidth, [FromRoute, Required] int? maxWidth,
[FromRoute] int? maxHeight, [FromRoute, Required] int? maxHeight,
[FromRoute] double? percentPlayed, [FromRoute, Required] double? percentPlayed,
[FromRoute] int? unplayedCount, [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? quality, [FromQuery] int? quality,
@ -836,7 +837,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur, [FromQuery] int? blur,
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var item = _libraryManager.GetStudio(name); var item = _libraryManager.GetStudio(name);
if (item == null) if (item == null)
@ -898,8 +899,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetUserImage( public async Task<ActionResult> GetUserImage(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? format, [FromQuery] string? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
@ -914,7 +915,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur, [FromQuery] int? blur,
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute] int? imageIndex = null) [FromRoute, Required] int? imageIndex = null)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
if (user == null) if (user == null)

@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Songs/{id}/InstantMix")] [HttpGet("Songs/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
[FromRoute] Guid id, [FromRoute, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,
@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Albums/{id}/InstantMix")] [HttpGet("Albums/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
[FromRoute] Guid id, [FromRoute, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,
@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Playlists/{id}/InstantMix")] [HttpGet("Playlists/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
[FromRoute] Guid id, [FromRoute, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,
@ -211,7 +211,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Artists/InstantMix")] [HttpGet("Artists/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
[FromRoute] Guid id, [FromRoute, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,
@ -248,7 +248,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("MusicGenres/InstantMix")] [HttpGet("MusicGenres/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
[FromRoute] Guid id, [FromRoute, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,
@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Items/{id}/InstantMix")] [HttpGet("Items/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
[FromRoute] Guid id, [FromRoute, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? fields, [FromQuery] string? fields,

@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute] Guid itemId) public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -294,7 +294,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/RemoteSearch/Apply/{id}")] [HttpPost("Items/RemoteSearch/Apply/{id}")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult> ApplySearchCriteria( public async Task<ActionResult> ApplySearchCriteria(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromBody, Required] RemoteSearchResult searchResult, [FromBody, Required] RemoteSearchResult searchResult,
[FromQuery] bool replaceAllImages = true) [FromQuery] bool replaceAllImages = true)
{ {

@ -1,5 +1,6 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -53,7 +54,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult Post( public ActionResult Post(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None, [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
[FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None, [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
[FromQuery] bool replaceAllMetadata = false, [FromQuery] bool replaceAllMetadata = false,

@ -68,7 +68,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}")] [HttpPost("Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request) public async Task<ActionResult> UpdateItem([FromRoute, Required] Guid itemId, [FromBody, Required] BaseItemDto request)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Items/{itemId}/MetadataEditor")] [HttpGet("Items/{itemId}/MetadataEditor")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute] Guid itemId) public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}/ContentType")] [HttpPost("Items/{itemId}/ContentType")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, Required] string? contentType) public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery, Required] string? contentType)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -144,7 +145,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{uId}/Items", Name = "GetItems_2")] [HttpGet("Users/{uId}/Items", Name = "GetItems_2")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetItems( public ActionResult<QueryResult<BaseItemDto>> GetItems(
[FromRoute] Guid? uId, [FromRoute, Required] Guid? uId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] string? maxOfficialRating, [FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong, [FromQuery] bool? hasThemeSong,
@ -529,7 +530,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/Items/Resume")] [HttpGet("Users/{userId}/Items/Resume")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetResumeItems( public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? searchTerm, [FromQuery] string? searchTerm,

@ -104,7 +104,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetFile([FromRoute] Guid itemId) public ActionResult GetFile([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeSongs( public ActionResult<ThemeMediaResult> GetThemeSongs(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false) [FromQuery] bool inheritFromParent = false)
{ {
@ -210,7 +210,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeVideos( public ActionResult<ThemeMediaResult> GetThemeVideos(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false) [FromQuery] bool inheritFromParent = false)
{ {
@ -275,7 +275,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<AllThemeMediaResult> GetThemeMedia( public ActionResult<AllThemeMediaResult> GetThemeMedia(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false) [FromQuery] bool inheritFromParent = false)
{ {
@ -438,7 +438,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid? userId) public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
@ -555,7 +555,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Library/Movies/Updated")] [HttpPost("Library/Movies/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedMovies([FromRoute] string? tmdbId, [FromRoute] string? imdbId) public ActionResult PostUpdatedMovies([FromRoute, Required] string? tmdbId, [FromRoute, Required] string? imdbId)
{ {
var movies = _libraryManager.GetItemList(new InternalItemsQuery var movies = _libraryManager.GetItemList(new InternalItemsQuery
{ {
@ -618,7 +618,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.Download)] [Authorize(Policy = Policies.Download)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetDownload([FromRoute] Guid itemId) public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -687,7 +687,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems( public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] string? excludeArtistIds, [FromQuery] string? excludeArtistIds,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? limit, [FromQuery] int? limit,

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -209,7 +210,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Channels/{channelId}")] [HttpGet("Channels/{channelId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
public ActionResult<BaseItemDto> GetChannel([FromRoute] Guid channelId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{ {
var user = userId.HasValue && !userId.Equals(Guid.Empty) var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value) ? _userManager.GetUserById(userId.Value)
@ -406,7 +407,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Recordings/{recordingId}")] [HttpGet("Recordings/{recordingId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
public ActionResult<BaseItemDto> GetRecording([FromRoute] Guid recordingId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{ {
var user = userId.HasValue && !userId.Equals(Guid.Empty) var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value) ? _userManager.GetUserById(userId.Value)
@ -428,7 +429,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Tuners/{tunerId}/Reset")] [HttpPost("Tuners/{tunerId}/Reset")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
public ActionResult ResetTuner([FromRoute] string tunerId) public ActionResult ResetTuner([FromRoute, Required] string tunerId)
{ {
AssertUserCanManageLiveTv(); AssertUserCanManageLiveTv();
_liveTvManager.ResetTuner(tunerId, CancellationToken.None); _liveTvManager.ResetTuner(tunerId, CancellationToken.None);
@ -744,7 +745,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetProgram( public async Task<ActionResult<BaseItemDto>> GetProgram(
[FromRoute] string programId, [FromRoute, Required] string programId,
[FromQuery] Guid? userId) [FromQuery] Guid? userId)
{ {
var user = userId.HasValue && !userId.Equals(Guid.Empty) var user = userId.HasValue && !userId.Equals(Guid.Empty)
@ -765,7 +766,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteRecording([FromRoute] Guid recordingId) public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
{ {
AssertUserCanManageLiveTv(); AssertUserCanManageLiveTv();
@ -792,7 +793,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Timers/{timerId}")] [HttpDelete("Timers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CancelTimer([FromRoute] string timerId) public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId)
{ {
AssertUserCanManageLiveTv(); AssertUserCanManageLiveTv();
await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false); await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
@ -810,7 +811,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> UpdateTimer([FromRoute] string timerId, [FromBody] TimerInfoDto timerInfo) public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
{ {
AssertUserCanManageLiveTv(); AssertUserCanManageLiveTv();
await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
@ -844,7 +845,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute] string timerId) public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId)
{ {
var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false); var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false);
if (timer == null) if (timer == null)
@ -884,7 +885,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("SeriesTimers/{timerId}")] [HttpDelete("SeriesTimers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CancelSeriesTimer([FromRoute] string timerId) public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId)
{ {
AssertUserCanManageLiveTv(); AssertUserCanManageLiveTv();
await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false); await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
@ -902,7 +903,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> UpdateSeriesTimer([FromRoute] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo) public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
{ {
AssertUserCanManageLiveTv(); AssertUserCanManageLiveTv();
await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
@ -934,7 +935,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("This endpoint is obsolete.")] [Obsolete("This endpoint is obsolete.")]
public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute] Guid? groupId) public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid? groupId)
{ {
return NotFound(); return NotFound();
} }
@ -1176,7 +1177,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("LiveRecordings/{recordingId}/stream")] [HttpGet("LiveRecordings/{recordingId}/stream")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetLiveRecordingFile([FromRoute] string recordingId) public async Task<ActionResult> GetLiveRecordingFile([FromRoute, Required] string recordingId)
{ {
var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId); var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
@ -1206,7 +1207,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("LiveStreamFiles/{streamId}/stream.{container}")] [HttpGet("LiveStreamFiles/{streamId}/stream.{container}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetLiveStreamFile([FromRoute] string streamId, [FromRoute] string container) public async Task<ActionResult> GetLiveStreamFile([FromRoute, Required] string streamId, [FromRoute, Required] string container)
{ {
var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(streamId, CancellationToken.None).ConfigureAwait(false); var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(streamId, CancellationToken.None).ConfigureAwait(false);
if (liveStreamInfo == null) if (liveStreamInfo == null)

@ -68,7 +68,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns> /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
[HttpGet("Items/{itemId}/PlaybackInfo")] [HttpGet("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery, Required] Guid? userId) public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery, Required] Guid? userId)
{ {
return await _mediaInfoHelper.GetPlaybackInfo( return await _mediaInfoHelper.GetPlaybackInfo(
itemId, itemId,
@ -100,7 +100,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}/PlaybackInfo")] [HttpPost("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo( public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] long? maxStreamingBitrate, [FromQuery] long? maxStreamingBitrate,
[FromQuery] long? startTimeTicks, [FromQuery] long? startTimeTicks,

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -258,7 +259,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing a <see cref="BaseItemDto"/> with the music genre.</returns> /// <returns>An <see cref="OkResult"/> containing a <see cref="BaseItemDto"/> with the music genre.</returns>
[HttpGet("{genreName}")] [HttpGet("{genreName}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetMusicGenre([FromRoute] string genreName, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{ {
var dtoOptions = new DtoOptions().AddClientFields(Request); var dtoOptions = new DtoOptions().AddClientFields(Request);

@ -44,7 +44,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Packages/{name}")] [HttpGet("Packages/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PackageInfo>> GetPackageInfo( public async Task<ActionResult<PackageInfo>> GetPackageInfo(
[FromRoute] [Required] string? name, [FromRoute, Required] string? name,
[FromQuery] string? assemblyGuid) [FromQuery] string? assemblyGuid)
{ {
var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult> InstallPackage( public async Task<ActionResult> InstallPackage(
[FromRoute] [Required] string? name, [FromRoute, Required] string? name,
[FromQuery] string? assemblyGuid, [FromQuery] string? assemblyGuid,
[FromQuery] string? version) [FromQuery] string? version)
{ {
@ -115,7 +115,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CancelPackageInstallation( public ActionResult CancelPackageInstallation(
[FromRoute] [Required] Guid packageId) [FromRoute, Required] Guid packageId)
{ {
_installationManager.CancelInstallation(packageId); _installationManager.CancelInstallation(packageId);
return NoContent(); return NoContent();

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -262,7 +263,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{name}")] [HttpGet("{name}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<BaseItemDto> GetPerson([FromRoute] string name, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetPerson([FromRoute, Required] string name, [FromQuery] Guid? userId)
{ {
var dtoOptions = new DtoOptions() var dtoOptions = new DtoOptions()
.AddClientFields(Request); .AddClientFields(Request);

@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("{playlistId}/Items")] [HttpPost("{playlistId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> AddToPlaylist( public async Task<ActionResult> AddToPlaylist(
[FromRoute] Guid playlistId, [FromRoute, Required] Guid playlistId,
[FromQuery] string? ids, [FromQuery] string? ids,
[FromQuery] Guid? userId) [FromQuery] Guid? userId)
{ {
@ -103,9 +103,9 @@ namespace Jellyfin.Api.Controllers
[HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")] [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> MoveItem( public async Task<ActionResult> MoveItem(
[FromRoute] string? playlistId, [FromRoute, Required] string? playlistId,
[FromRoute] string? itemId, [FromRoute, Required] string? itemId,
[FromRoute] int newIndex) [FromRoute, Required] int newIndex)
{ {
await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false); await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
return NoContent(); return NoContent();
@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="NoContentResult"/> on success.</returns> /// <returns>An <see cref="NoContentResult"/> on success.</returns>
[HttpDelete("{playlistId}/Items")] [HttpDelete("{playlistId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds) public async Task<ActionResult> RemoveFromPlaylist([FromRoute, Required] string? playlistId, [FromQuery] string? entryIds)
{ {
await _playlistManager.RemoveFromPlaylistAsync(playlistId, RequestHelpers.Split(entryIds, ',', true)).ConfigureAwait(false); await _playlistManager.RemoveFromPlaylistAsync(playlistId, RequestHelpers.Split(entryIds, ',', true)).ConfigureAwait(false);
return NoContent(); return NoContent();
@ -143,15 +143,15 @@ namespace Jellyfin.Api.Controllers
/// <returns>The original playlist items.</returns> /// <returns>The original playlist items.</returns>
[HttpGet("{playlistId}/Items")] [HttpGet("{playlistId}/Items")]
public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems( public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
[FromRoute] Guid playlistId, [FromRoute, Required] Guid playlistId,
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute] int? startIndex, [FromRoute, Required] int? startIndex,
[FromRoute] int? limit, [FromRoute, Required] int? limit,
[FromRoute] string? fields, [FromRoute, Required] string? fields,
[FromRoute] bool? enableImages, [FromRoute, Required] bool? enableImages,
[FromRoute] bool? enableUserData, [FromRoute, Required] bool? enableUserData,
[FromRoute] int? imageTypeLimit, [FromRoute, Required] int? imageTypeLimit,
[FromRoute] string? enableImageTypes) [FromRoute, Required] string? enableImageTypes)
{ {
var playlist = (Playlist)_libraryManager.GetItemById(playlistId); var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
if (playlist == null) if (playlist == null)

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
@ -71,8 +72,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Users/{userId}/PlayedItems/{itemId}")] [HttpPost("Users/{userId}/PlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkPlayedItem( public ActionResult<UserItemDataDto> MarkPlayedItem(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] DateTime? datePlayed) [FromQuery] DateTime? datePlayed)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
@ -96,7 +97,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("Users/{userId}/PlayedItems/{itemId}")] [HttpDelete("Users/{userId}/PlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId) public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request); var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@ -195,8 +196,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackStart( public async Task<ActionResult> OnPlaybackStart(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
@ -245,8 +246,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackProgress( public async Task<ActionResult> OnPlaybackProgress(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] long? positionTicks, [FromQuery] long? positionTicks,
[FromQuery] int? audioStreamIndex, [FromQuery] int? audioStreamIndex,
@ -297,8 +298,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackStopped( public async Task<ActionResult> OnPlaybackStopped(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? nextMediaType, [FromQuery] string? nextMediaType,
[FromQuery] long? positionTicks, [FromQuery] long? positionTicks,

@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UninstallPlugin([FromRoute] Guid pluginId) public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId)
{ {
var plugin = _appHost.Plugins.FirstOrDefault(p => p.Id == pluginId); var plugin = _appHost.Plugins.FirstOrDefault(p => p.Id == pluginId);
if (plugin == null) if (plugin == null)
@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{pluginId}/Configuration")] [HttpGet("{pluginId}/Configuration")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<BasePluginConfiguration> GetPluginConfiguration([FromRoute] Guid pluginId) public ActionResult<BasePluginConfiguration> GetPluginConfiguration([FromRoute, Required] Guid pluginId)
{ {
if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin)) if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
{ {
@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("{pluginId}/Configuration")] [HttpPost("{pluginId}/Configuration")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UpdatePluginConfiguration([FromRoute] Guid pluginId) public async Task<ActionResult> UpdatePluginConfiguration([FromRoute, Required] Guid pluginId)
{ {
if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin)) if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
{ {
@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers
[Obsolete("This endpoint should not be used.")] [Obsolete("This endpoint should not be used.")]
[HttpPost("RegistrationRecords/{name}")] [HttpPost("RegistrationRecords/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute] string? name) public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute, Required] string? name)
{ {
return new MBRegistrationRecord return new MBRegistrationRecord
{ {
@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers
[Obsolete("Paid plugins are not supported")] [Obsolete("Paid plugins are not supported")]
[HttpGet("Registrations/{name}")] [HttpGet("Registrations/{name}")]
[ProducesResponseType(StatusCodes.Status501NotImplemented)] [ProducesResponseType(StatusCodes.Status501NotImplemented)]
public ActionResult GetRegistration([FromRoute] string? name) public ActionResult GetRegistration([FromRoute, Required] string? name)
{ {
// TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins, // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins,
// delete all these registration endpoints. They are only kept for compatibility. // delete all these registration endpoints. They are only kept for compatibility.

@ -70,7 +70,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<RemoteImageResult>> GetRemoteImages( public async Task<ActionResult<RemoteImageResult>> GetRemoteImages(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] ImageType? type, [FromQuery] ImageType? type,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute] Guid itemId) public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
if (item == null) if (item == null)
@ -209,7 +209,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DownloadRemoteImage( public async Task<ActionResult> DownloadRemoteImage(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery, Required] ImageType type, [FromQuery, Required] ImageType type,
[FromQuery] string? imageUrl) [FromQuery] string? imageUrl)
{ {

@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Running/{taskId}")] [HttpPost("Running/{taskId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult StartTask([FromRoute] string? taskId) public ActionResult StartTask([FromRoute, Required] string? taskId)
{ {
var task = _taskManager.ScheduledTasks.FirstOrDefault(o => var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));

@ -336,7 +336,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddUserToSession( public ActionResult AddUserToSession(
[FromRoute, Required] string? sessionId, [FromRoute, Required] string? sessionId,
[FromRoute] Guid userId) [FromRoute, Required] Guid userId)
{ {
_sessionManager.AddAdditionalUser(sessionId, userId); _sessionManager.AddAdditionalUser(sessionId, userId);
return NoContent(); return NoContent();
@ -353,8 +353,8 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RemoveUserFromSession( public ActionResult RemoveUserFromSession(
[FromRoute] string? sessionId, [FromRoute, Required] string? sessionId,
[FromRoute] Guid userId) [FromRoute, Required] Guid userId)
{ {
_sessionManager.RemoveAdditionalUser(sessionId, userId); _sessionManager.RemoveAdditionalUser(sessionId, userId);
return NoContent(); return NoContent();

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
@ -259,7 +260,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the studio.</returns> /// <returns>An <see cref="OkResult"/> containing the studio.</returns>
[HttpGet("{name}")] [HttpGet("{name}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetStudio([FromRoute] string name, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetStudio([FromRoute, Required] string name, [FromQuery] Guid? userId)
{ {
var dtoOptions = new DtoOptions().AddClientFields(Request); var dtoOptions = new DtoOptions().AddClientFields(Request);

@ -86,8 +86,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Task> DeleteSubtitle( public ActionResult<Task> DeleteSubtitle(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] int index) [FromRoute, Required] int index)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles( public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute, Required] string? language, [FromRoute, Required] string? language,
[FromQuery] bool? isPerfectMatch) [FromQuery] bool? isPerfectMatch)
{ {
@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> DownloadRemoteSubtitles( public async Task<ActionResult> DownloadRemoteSubtitles(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute, Required] string? subtitleId) [FromRoute, Required] string? subtitleId)
{ {
var video = (Video)_libraryManager.GetItemById(itemId); var video = (Video)_libraryManager.GetItemById(itemId);
@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? endPositionTicks, [FromQuery] long? endPositionTicks,
[FromQuery] bool copyTimestamps = false, [FromQuery] bool copyTimestamps = false,
[FromQuery] bool addVttTimeMap = false, [FromQuery] bool addVttTimeMap = false,
[FromRoute] long startPositionTicks = 0) [FromRoute, Required] long startPositionTicks = 0)
{ {
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase)) if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
{ {
@ -253,9 +253,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetSubtitlePlaylist( public async Task<ActionResult> GetSubtitlePlaylist(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] int index, [FromRoute, Required] int index,
[FromRoute] string? mediaSourceId, [FromRoute, Required] string? mediaSourceId,
[FromQuery, Required] int segmentLength) [FromQuery, Required] int segmentLength)
{ {
var item = (Video)_libraryManager.GetItemById(itemId); var item = (Video)_libraryManager.GetItemById(itemId);

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
@ -53,7 +54,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/Suggestions")] [HttpGet("Users/{userId}/Suggestions")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSuggestions( public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromQuery] string? mediaType, [FromQuery] string? mediaType,
[FromQuery] string? type, [FromQuery] string? type,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -92,8 +93,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status302Found)] [ProducesResponseType(StatusCodes.Status302Found)]
public async Task<ActionResult> GetUniversalAudioStream( public async Task<ActionResult> GetUniversalAudioStream(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string? container, [FromRoute, Required] string? container,
[FromQuery] string? mediaSourceId, [FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId, [FromQuery] string? deviceId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,

@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.IgnoreParentalControl)] [Authorize(Policy = Policies.IgnoreParentalControl)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<UserDto> GetUserById([FromRoute] Guid userId) public ActionResult<UserDto> GetUserById([FromRoute, Required] Guid userId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteUser([FromRoute] Guid userId) public ActionResult DeleteUser([FromRoute, Required] Guid userId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
_sessionManager.RevokeUserTokens(user.Id, null); _sessionManager.RevokeUserTokens(user.Id, null);
@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UpdateUserPassword( public async Task<ActionResult> UpdateUserPassword(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromBody] UpdateUserPassword request) [FromBody] UpdateUserPassword request)
{ {
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateUserEasyPassword( public ActionResult UpdateUserEasyPassword(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromBody] UpdateUserEasyPassword request) [FromBody] UpdateUserEasyPassword request)
{ {
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
@ -365,7 +365,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult> UpdateUser( public async Task<ActionResult> UpdateUser(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromBody] UserDto updateUser) [FromBody] UserDto updateUser)
{ {
if (updateUser == null) if (updateUser == null)
@ -409,7 +409,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult UpdateUserPolicy( public ActionResult UpdateUserPolicy(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromBody] UserPolicy newPolicy) [FromBody] UserPolicy newPolicy)
{ {
if (newPolicy == null) if (newPolicy == null)
@ -464,7 +464,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult UpdateUserConfiguration( public ActionResult UpdateUserConfiguration(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromBody] UserConfiguration userConfig) [FromBody] UserConfiguration userConfig)
{ {
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false)) if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false))

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -70,7 +71,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the d item.</returns> /// <returns>An <see cref="OkResult"/> containing the d item.</returns>
[HttpGet("Users/{userId}/Items/{itemId}")] [HttpGet("Users/{userId}/Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute] Guid userId, [FromRoute] Guid itemId) public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
@ -93,7 +94,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns> /// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns>
[HttpGet("Users/{userId}/Items/Root")] [HttpGet("Users/{userId}/Items/Root")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetRootFolder([FromRoute] Guid userId) public ActionResult<BaseItemDto> GetRootFolder([FromRoute, Required] Guid userId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
var item = _libraryManager.GetUserRootFolder(); var item = _libraryManager.GetUserRootFolder();
@ -110,7 +111,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the intros to play.</returns> /// <returns>An <see cref="OkResult"/> containing the intros to play.</returns>
[HttpGet("Users/{userId}/Items/{itemId}/Intros")] [HttpGet("Users/{userId}/Items/{itemId}/Intros")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute] Guid userId, [FromRoute] Guid itemId) public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
@ -138,7 +139,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpPost("Users/{userId}/FavoriteItems/{itemId}")] [HttpPost("Users/{userId}/FavoriteItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId) public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
return MarkFavorite(userId, itemId, true); return MarkFavorite(userId, itemId, true);
} }
@ -152,7 +153,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("Users/{userId}/FavoriteItems/{itemId}")] [HttpDelete("Users/{userId}/FavoriteItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId) public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
return MarkFavorite(userId, itemId, false); return MarkFavorite(userId, itemId, false);
} }
@ -166,7 +167,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("Users/{userId}/Items/{itemId}/Rating")] [HttpDelete("Users/{userId}/Items/{itemId}/Rating")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId) public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
return UpdateUserItemRatingInternal(userId, itemId, null); return UpdateUserItemRatingInternal(userId, itemId, null);
} }
@ -181,7 +182,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpPost("Users/{userId}/Items/{itemId}/Rating")] [HttpPost("Users/{userId}/Items/{itemId}/Rating")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool? likes) public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes)
{ {
return UpdateUserItemRatingInternal(userId, itemId, likes); return UpdateUserItemRatingInternal(userId, itemId, likes);
} }
@ -195,7 +196,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>The items local trailers.</returns> /// <returns>The items local trailers.</returns>
[HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")] [HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute] Guid userId, [FromRoute] Guid itemId) public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
@ -230,7 +231,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the special features.</returns> /// <returns>An <see cref="OkResult"/> containing the special features.</returns>
[HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")] [HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute] Guid userId, [FromRoute] Guid itemId) public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
@ -264,7 +265,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/Items/Latest")] [HttpGet("Users/{userId}/Items/Latest")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia( public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? includeItemTypes, [FromQuery] string? includeItemTypes,

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/Views")] [HttpGet("Users/{userId}/Views")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetUserViews( public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
[FromRoute] Guid userId, [FromRoute, Required] Guid userId,
[FromQuery] bool? includeExternalContent, [FromQuery] bool? includeExternalContent,
[FromQuery] string? presetViews, [FromQuery] string? presetViews,
[FromQuery] bool includeHidden = false) [FromQuery] bool includeHidden = false)
@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/GroupingOptions")] [HttpGet("Users/{userId}/GroupingOptions")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute] Guid userId) public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute, Required] Guid userId)
{ {
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);
if (user == null) if (user == null)

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
@ -162,7 +163,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Videos/{itemId}/live.m3u8")] [HttpGet("Videos/{itemId}/live.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetLiveHlsStream( public async Task<ActionResult> GetLiveHlsStream(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] string? container, [FromQuery] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,

@ -115,7 +115,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{itemId}/AdditionalParts")] [HttpGet("{itemId}/AdditionalParts")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute] Guid itemId, [FromQuery] Guid? userId) public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{ {
var user = userId.HasValue && !userId.Equals(Guid.Empty) var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value) ? _userManager.GetUserById(userId.Value)
@ -162,7 +162,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteAlternateSources([FromRoute] Guid itemId) public async Task<ActionResult> DeleteAlternateSources([FromRoute, Required] Guid itemId)
{ {
var video = (Video)_libraryManager.GetItemById(itemId); var video = (Video)_libraryManager.GetItemById(itemId);
@ -331,8 +331,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("{itemId}/stream", Name = "HeadVideoStream")] [HttpHead("{itemId}/stream", Name = "HeadVideoStream")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetVideoStream( public async Task<ActionResult> GetVideoStream(
[FromRoute] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute] string? container, [FromRoute, Required] string? container,
[FromQuery] bool? @static, [FromQuery] bool? @static,
[FromQuery] string? @params, [FromQuery] string? @params,
[FromQuery] string? tag, [FromQuery] string? tag,

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
@ -179,7 +180,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{year}")] [HttpGet("{year}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<BaseItemDto> GetYear([FromRoute] int year, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetYear([FromRoute, Required] int year, [FromQuery] Guid? userId)
{ {
var item = _libraryManager.GetYear(year); var item = _libraryManager.GetYear(year);
if (item == null) if (item == null)

@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
@ -123,10 +123,9 @@ namespace Jellyfin.Api.Helpers
state.Dispose(); state.Dispose();
} }
var memoryStream = new MemoryStream(); await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None)
await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false); .WriteToAsync(httpContext.Response.Body, CancellationToken.None).ConfigureAwait(false);
memoryStream.Position = 0; return new FileStreamResult(httpContext.Response.Body, contentType);
return new FileStreamResult(memoryStream, contentType);
} }
finally finally
{ {

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;

@ -1,6 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using System.Linq; using System.Linq;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -9,7 +8,7 @@ using Microsoft.EntityFrameworkCore;
namespace Jellyfin.Server.Implementations namespace Jellyfin.Server.Implementations
{ {
/// <inheritdoc/> /// <inheritdoc/>
public partial class JellyfinDb : DbContext public class JellyfinDb : DbContext
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JellyfinDb"/> class. /// Initializes a new instance of the <see cref="JellyfinDb"/> class.
@ -138,47 +137,20 @@ namespace Jellyfin.Server.Implementations
return base.SaveChanges(); return base.SaveChanges();
} }
/// <inheritdoc/>
public override void Dispose()
{
foreach (var entry in ChangeTracker.Entries())
{
entry.State = EntityState.Detached;
}
GC.SuppressFinalize(this);
base.Dispose();
}
/// <inheritdoc />
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
CustomInit(optionsBuilder);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
OnModelCreatingImpl(modelBuilder);
modelBuilder.HasDefaultSchema("jellyfin"); modelBuilder.HasDefaultSchema("jellyfin");
/*modelBuilder.Entity<Artwork>().HasIndex(t => t.Kind); modelBuilder.Entity<DisplayPreferences>()
.HasIndex(entity => entity.UserId)
modelBuilder.Entity<Genre>().HasIndex(t => t.Name) .IsUnique(false);
.IsUnique();
modelBuilder.Entity<LibraryItem>().HasIndex(t => t.UrlId) modelBuilder.Entity<DisplayPreferences>()
.IsUnique();*/ .HasIndex(entity => new { entity.UserId, entity.Client })
.IsUnique();
OnModelCreatedImpl(modelBuilder);
} }
partial void CustomInit(DbContextOptionsBuilder optionsBuilder);
partial void OnModelCreatingImpl(ModelBuilder modelBuilder);
partial void OnModelCreatedImpl(ModelBuilder modelBuilder);
} }
} }

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

@ -0,0 +1,51 @@
#pragma warning disable CS1591
#pragma warning disable SA1601
using Microsoft.EntityFrameworkCore.Migrations;
namespace Jellyfin.Server.Implementations.Migrations
{
public partial class FixDisplayPreferencesIndex : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_DisplayPreferences_UserId",
schema: "jellyfin",
table: "DisplayPreferences");
migrationBuilder.CreateIndex(
name: "IX_DisplayPreferences_UserId",
schema: "jellyfin",
table: "DisplayPreferences",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_DisplayPreferences_UserId_Client",
schema: "jellyfin",
table: "DisplayPreferences",
columns: new[] { "UserId", "Client" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_DisplayPreferences_UserId",
schema: "jellyfin",
table: "DisplayPreferences");
migrationBuilder.DropIndex(
name: "IX_DisplayPreferences_UserId_Client",
schema: "jellyfin",
table: "DisplayPreferences");
migrationBuilder.CreateIndex(
name: "IX_DisplayPreferences_UserId",
schema: "jellyfin",
table: "DisplayPreferences",
column: "UserId",
unique: true);
}
}
}

@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasDefaultSchema("jellyfin") .HasDefaultSchema("jellyfin")
.HasAnnotation("ProductVersion", "3.1.6"); .HasAnnotation("ProductVersion", "3.1.7");
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
{ {
@ -136,7 +136,9 @@ namespace Jellyfin.Server.Implementations.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("UserId") b.HasIndex("UserId");
b.HasIndex("UserId", "Client")
.IsUnique(); .IsUnique();
b.ToTable("DisplayPreferences"); b.ToTable("DisplayPreferences");

@ -54,7 +54,7 @@
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" /> <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.3" /> <PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.3" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.3" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" /> <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" />
</ItemGroup> </ItemGroup>

@ -114,8 +114,6 @@ namespace MediaBrowser.Controller
/// <exception cref="NotSupportedException"><see cref="CanLaunchWebBrowser"/> is false.</exception> /// <exception cref="NotSupportedException"><see cref="CanLaunchWebBrowser"/> is false.</exception>
void LaunchUrl(string url); void LaunchUrl(string url);
void EnableLoopback(string appName);
IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo(); IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo();
string ExpandVirtualPath(string path); string ExpandVirtualPath(string path);

@ -37,12 +37,6 @@ namespace MediaBrowser.Model.Dlna
/// <value>The model description.</value> /// <value>The model description.</value>
public string ModelDescription { get; set; } public string ModelDescription { get; set; }
/// <summary>
/// Gets or sets the device description.
/// </summary>
/// <value>The device description.</value>
public string DeviceDescription { get; set; }
/// <summary> /// <summary>
/// Gets or sets the model URL. /// Gets or sets the model URL.
/// </summary> /// </summary>

@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
item.ParentIndexNumber = info.ParentIndexNumber; item.ParentIndexNumber = info.ParentIndexNumber;
item.IndexNumberEnd = info.IndexNumberEnd; item.IndexNumberEnd = info.IndexNumberEnd;
if (response.External_Ids.Tvdb_Id > 0) if (response.External_Ids != null && response.External_Ids.Tvdb_Id > 0)
{ {
item.SetProviderId(MetadataProvider.Tvdb, response.External_Ids.Tvdb_Id.Value.ToString(CultureInfo.InvariantCulture)); item.SetProviderId(MetadataProvider.Tvdb, response.External_Ids.Tvdb_Id.Value.ToString(CultureInfo.InvariantCulture));
} }

@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
result.Item.Overview = seasonInfo.Overview; result.Item.Overview = seasonInfo.Overview;
if (seasonInfo.External_Ids.Tvdb_Id > 0) if (seasonInfo.External_Ids != null && seasonInfo.External_Ids.Tvdb_Id > 0)
{ {
result.Item.SetProviderId(MetadataProvider.Tvdb, seasonInfo.External_Ids.Tvdb_Id.Value.ToString(CultureInfo.InvariantCulture)); result.Item.SetProviderId(MetadataProvider.Tvdb, seasonInfo.External_Ids.Tvdb_Id.Value.ToString(CultureInfo.InvariantCulture));
} }

@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
remoteResult.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture)); remoteResult.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture));
remoteResult.SetProviderId(MetadataProvider.Imdb, obj.External_Ids.Imdb_Id); remoteResult.SetProviderId(MetadataProvider.Imdb, obj.External_Ids.Imdb_Id);
if (obj.External_Ids.Tvdb_Id > 0) if (obj.External_Ids != null && obj.External_Ids.Tvdb_Id > 0)
{ {
remoteResult.SetProviderId(MetadataProvider.Tvdb, obj.External_Ids.Tvdb_Id.Value.ToString(_usCulture)); remoteResult.SetProviderId(MetadataProvider.Tvdb, obj.External_Ids.Tvdb_Id.Value.ToString(_usCulture));
} }

Loading…
Cancel
Save