Bläddra i källkod

Fix all route for base url support

crobibero 4 år sedan
förälder
incheckning
858aecd409

+ 3 - 2
Jellyfin.Api/Controllers/AlbumsController.cs

@@ -17,6 +17,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The albums controller.
     /// The albums controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class AlbumsController : BaseJellyfinApiController
     public class AlbumsController : BaseJellyfinApiController
     {
     {
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
@@ -48,7 +49,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="limit">Optional. The maximum number of records to return.</param>
         /// <param name="limit">Optional. The maximum number of records to return.</param>
         /// <response code="200">Similar albums returned.</response>
         /// <response code="200">Similar albums returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar albums.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar albums.</returns>
-        [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] string albumId,
@@ -80,7 +81,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="limit">Optional. The maximum number of records to return.</param>
         /// <param name="limit">Optional. The maximum number of records to return.</param>
         /// <response code="200">Similar artists returned.</response>
         /// <response code="200">Similar artists returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar artists.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with similar artists.</returns>
-        [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] string artistId,

+ 6 - 5
Jellyfin.Api/Controllers/DashboardController.cs

@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The dashboard controller.
     /// The dashboard controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class DashboardController : BaseJellyfinApiController
     public class DashboardController : BaseJellyfinApiController
     {
     {
         private readonly ILogger<DashboardController> _logger;
         private readonly ILogger<DashboardController> _logger;
@@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">ConfigurationPages returned.</response>
         /// <response code="200">ConfigurationPages returned.</response>
         /// <response code="404">Server still loading.</response>
         /// <response code="404">Server still loading.</response>
         /// <returns>An <see cref="IEnumerable{ConfigurationPageInfo}"/> with infos about the plugins.</returns>
         /// <returns>An <see cref="IEnumerable{ConfigurationPageInfo}"/> with infos about the plugins.</returns>
-        [HttpGet("/web/ConfigurationPages")]
+        [HttpGet("web/ConfigurationPages")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<IEnumerable<ConfigurationPageInfo?>> GetConfigurationPages(
         public ActionResult<IEnumerable<ConfigurationPageInfo?>> GetConfigurationPages(
@@ -118,7 +119,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">ConfigurationPage returned.</response>
         /// <response code="200">ConfigurationPage returned.</response>
         /// <response code="404">Plugin configuration page not found.</response>
         /// <response code="404">Plugin configuration page not found.</response>
         /// <returns>The configuration page.</returns>
         /// <returns>The configuration page.</returns>
-        [HttpGet("/web/ConfigurationPage")]
+        [HttpGet("web/ConfigurationPage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult GetDashboardConfigurationPage([FromQuery] string? name)
         public ActionResult GetDashboardConfigurationPage([FromQuery] string? name)
@@ -172,7 +173,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="200">Robots.txt returned.</response>
         /// <response code="200">Robots.txt returned.</response>
         /// <returns>The robots.txt.</returns>
         /// <returns>The robots.txt.</returns>
-        [HttpGet("/robots.txt")]
+        [HttpGet("robots.txt")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ApiExplorerSettings(IgnoreApi = true)]
         [ApiExplorerSettings(IgnoreApi = true)]
         public ActionResult GetRobotsTxt()
         public ActionResult GetRobotsTxt()
@@ -187,7 +188,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Web client returned.</response>
         /// <response code="200">Web client returned.</response>
         /// <response code="404">Server does not host a web client.</response>
         /// <response code="404">Server does not host a web client.</response>
         /// <returns>The resource.</returns>
         /// <returns>The resource.</returns>
-        [HttpGet("/web/{*resourceName}")]
+        [HttpGet("web/{*resourceName}")]
         [ApiExplorerSettings(IgnoreApi = true)]
         [ApiExplorerSettings(IgnoreApi = true)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -218,7 +219,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="200">Favicon.ico returned.</response>
         /// <response code="200">Favicon.ico returned.</response>
         /// <returns>The favicon.</returns>
         /// <returns>The favicon.</returns>
-        [HttpGet("/favicon.ico")]
+        [HttpGet("favicon.ico")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ApiExplorerSettings(IgnoreApi = true)]
         [ApiExplorerSettings(IgnoreApi = true)]
         public ActionResult GetFavIcon()
         public ActionResult GetFavIcon()

+ 9 - 8
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -37,6 +37,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Dynamic hls controller.
     /// Dynamic hls controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class DynamicHlsController : BaseJellyfinApiController
     public class DynamicHlsController : BaseJellyfinApiController
     {
     {
@@ -164,8 +165,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
         /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
         /// <response code="200">Video stream returned.</response>
         /// <response code="200">Video stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
         /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
-        [HttpGet("/Videos/{itemId}/master.m3u8")]
-        [HttpHead("/Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
+        [HttpGet("Videos/{itemId}/master.m3u8")]
+        [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] Guid itemId,
@@ -334,8 +335,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
         /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
         /// <response code="200">Audio stream returned.</response>
         /// <response code="200">Audio stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
         /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
-        [HttpGet("/Audio/{itemId}/master.m3u8")]
-        [HttpHead("/Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
+        [HttpGet("Audio/{itemId}/master.m3u8")]
+        [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] Guid itemId,
@@ -503,7 +504,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <response code="200">Video stream returned.</response>
         /// <response code="200">Video stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
-        [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] Guid itemId,
@@ -668,7 +669,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <response code="200">Audio stream returned.</response>
         /// <response code="200">Audio stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
-        [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] Guid itemId,
@@ -835,7 +836,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <response code="200">Video stream returned.</response>
         /// <response code="200">Video stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
-        [HttpGet("/Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
+        [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
         [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(
@@ -1004,7 +1005,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <response code="200">Video stream returned.</response>
         /// <response code="200">Video stream returned.</response>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
-        [HttpGet("/Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
+        [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
         [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(

+ 3 - 2
Jellyfin.Api/Controllers/FilterController.cs

@@ -18,6 +18,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Filters controller.
     /// Filters controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class FilterController : BaseJellyfinApiController
     public class FilterController : BaseJellyfinApiController
     {
     {
@@ -44,7 +45,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
         /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
         /// <response code="200">Legacy filters retrieved.</response>
         /// <response code="200">Legacy filters retrieved.</response>
         /// <returns>Legacy query filters.</returns>
         /// <returns>Legacy query filters.</returns>
-        [HttpGet("/Items/Filters")]
+        [HttpGet("Items/Filters")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
         public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
@@ -133,7 +134,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="recursive">Optional. Search recursive.</param>
         /// <param name="recursive">Optional. Search recursive.</param>
         /// <response code="200">Filters retrieved.</response>
         /// <response code="200">Filters retrieved.</response>
         /// <returns>Query filters.</returns>
         /// <returns>Query filters.</returns>
-        [HttpGet("/Items/Filters2")]
+        [HttpGet("Items/Filters2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryFilters> GetQueryFilters(
         public ActionResult<QueryFilters> GetQueryFilters(
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,

+ 6 - 5
Jellyfin.Api/Controllers/HlsSegmentController.cs

@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The hls segment controller.
     /// The hls segment controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class HlsSegmentController : BaseJellyfinApiController
     public class HlsSegmentController : BaseJellyfinApiController
     {
     {
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
@@ -50,8 +51,8 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="FileStreamResult"/> containing the audio stream.</returns>
         /// <returns>A <see cref="FileStreamResult"/> containing the audio stream.</returns>
         // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
         // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
         // [Authenticated]
         // [Authenticated]
-        [HttpGet("/Audio/{itemId}/hls/{segmentId}/stream.mp3", Name = "GetHlsAudioSegmentLegacyMp3")]
-        [HttpGet("/Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
+        [HttpGet("Audio/{itemId}/hls/{segmentId}/stream.mp3", Name = "GetHlsAudioSegmentLegacyMp3")]
+        [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] string itemId, [FromRoute] string segmentId)
@@ -70,7 +71,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playlistId">The playlist id.</param>
         /// <param name="playlistId">The playlist id.</param>
         /// <response code="200">Hls video playlist returned.</response>
         /// <response code="200">Hls video playlist returned.</response>
         /// <returns>A <see cref="FileStreamResult"/> containing the playlist.</returns>
         /// <returns>A <see cref="FileStreamResult"/> containing the playlist.</returns>
-        [HttpGet("/Videos/{itemId}/hls/{playlistId}/stream.m3u8")]
+        [HttpGet("Videos/{itemId}/hls/{playlistId}/stream.m3u8")]
         [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")]
@@ -89,7 +90,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playSessionId">The play session id.</param>
         /// <param name="playSessionId">The play session id.</param>
         /// <response code="204">Encoding stopped successfully.</response>
         /// <response code="204">Encoding stopped successfully.</response>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
-        [HttpDelete("/Videos/ActiveEncodings")]
+        [HttpDelete("Videos/ActiveEncodings")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult StopEncodingProcess([FromQuery] string deviceId, [FromQuery] string playSessionId)
         public ActionResult StopEncodingProcess([FromQuery] string deviceId, [FromQuery] string playSessionId)
@@ -109,7 +110,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="FileStreamResult"/> containing the video segment.</returns>
         /// <returns>A <see cref="FileStreamResult"/> containing the video segment.</returns>
         // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
         // Can't require authentication just yet due to seeing some requests come from Chrome without full query string
         // [Authenticated]
         // [Authenticated]
-        [HttpGet("/Videos/{itemId}/hls/{playlistId}/{segmentId}.{segmentContainer}")]
+        [HttpGet("Videos/{itemId}/hls/{playlistId}/{segmentId}.{segmentContainer}")]
         [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(

+ 29 - 28
Jellyfin.Api/Controllers/ImageController.cs

@@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Image controller.
     /// Image controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class ImageController : BaseJellyfinApiController
     public class ImageController : BaseJellyfinApiController
     {
     {
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
@@ -81,8 +82,8 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Image updated.</response>
         /// <response code="204">Image updated.</response>
         /// <response code="403">User does not have permission to delete the image.</response>
         /// <response code="403">User does not have permission to delete the image.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Users/{userId}/Images/{imageType}")]
-        [HttpPost("/Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")]
+        [HttpPost("Users/{userId}/Images/{imageType}")]
+        [HttpPost("Users/{userId}/Images/{imageType}/{index?}", Name = "PostUserImage_2")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@@ -127,8 +128,8 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Image deleted.</response>
         /// <response code="204">Image deleted.</response>
         /// <response code="403">User does not have permission to delete the image.</response>
         /// <response code="403">User does not have permission to delete the image.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpDelete("/Users/{userId}/Images/{itemType}")]
-        [HttpDelete("/Users/{userId}/Images/{itemType}/{index?}", Name = "DeleteUserImage_2")]
+        [HttpDelete("Users/{userId}/Images/{itemType}")]
+        [HttpDelete("Users/{userId}/Images/{itemType}/{index?}", Name = "DeleteUserImage_2")]
         [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")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -166,8 +167,8 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Image deleted.</response>
         /// <response code="204">Image deleted.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
-        [HttpDelete("/Items/{itemId}/Images/{imageType}")]
-        [HttpDelete("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "DeleteItemImage_2")]
+        [HttpDelete("Items/{itemId}/Images/{imageType}")]
+        [HttpDelete("Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "DeleteItemImage_2")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -195,8 +196,8 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Image saved.</response>
         /// <response code="204">Image saved.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
-        [HttpPost("/Items/{itemId}/Images/{imageType}")]
-        [HttpPost("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "SetItemImage_2")]
+        [HttpPost("Items/{itemId}/Images/{imageType}")]
+        [HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "SetItemImage_2")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -230,7 +231,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Image index updated.</response>
         /// <response code="204">Image index updated.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
-        [HttpPost("/Items/{itemId}/Images/{imageType}/{imageIndex}/Index")]
+        [HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex}/Index")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -257,7 +258,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Item images returned.</response>
         /// <response code="200">Item images returned.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>The list of image infos on success, or <see cref="NotFoundResult"/> if item not found.</returns>
         /// <returns>The list of image infos on success, or <see cref="NotFoundResult"/> if item not found.</returns>
-        [HttpGet("/Items/{itemId}/Images")]
+        [HttpGet("Items/{itemId}/Images")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<IEnumerable<ImageInfo>> GetItemImageInfos([FromRoute] Guid itemId)
         public ActionResult<IEnumerable<ImageInfo>> GetItemImageInfos([FromRoute] Guid itemId)
@@ -341,10 +342,10 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         /// </returns>
-        [HttpGet("/Items/{itemId}/Images/{imageType}")]
-        [HttpHead("/Items/{itemId}/Images/{imageType}", Name = "HeadItemImage")]
-        [HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "GetItemImage_2")]
-        [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "HeadItemImage_2")]
+        [HttpGet("Items/{itemId}/Images/{imageType}")]
+        [HttpHead("Items/{itemId}/Images/{imageType}", Name = "HeadItemImage")]
+        [HttpGet("Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "GetItemImage_2")]
+        [HttpHead("Items/{itemId}/Images/{imageType}/{imageIndex?}", Name = "HeadItemImage_2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetItemImage(
         public async Task<ActionResult> GetItemImage(
@@ -421,8 +422,8 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         /// </returns>
-        [HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")]
-        [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}", Name = "HeadItemImage2")]
+        [HttpGet("Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")]
+        [HttpHead("Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}", Name = "HeadItemImage2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetItemImage2(
         public async Task<ActionResult> GetItemImage2(
@@ -499,8 +500,8 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         /// </returns>
-        [HttpGet("/Artists/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Artists/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadArtistImage")]
+        [HttpGet("Artists/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("Artists/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadArtistImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetArtistImage(
         public async Task<ActionResult> GetArtistImage(
@@ -577,8 +578,8 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         /// </returns>
-        [HttpGet("/Genres/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Genres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadGenreImage")]
+        [HttpGet("Genres/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("Genres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadGenreImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetGenreImage(
         public async Task<ActionResult> GetGenreImage(
@@ -655,8 +656,8 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         /// </returns>
-        [HttpGet("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadMusicGenreImage")]
+        [HttpGet("MusicGenres/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("MusicGenres/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadMusicGenreImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetMusicGenreImage(
         public async Task<ActionResult> GetMusicGenreImage(
@@ -733,8 +734,8 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         /// </returns>
-        [HttpGet("/Persons/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Persons/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadPersonImage")]
+        [HttpGet("Persons/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("Persons/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadPersonImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetPersonImage(
         public async Task<ActionResult> GetPersonImage(
@@ -811,8 +812,8 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         /// </returns>
-        [HttpGet("/Studios/{name}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Studios/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadStudioImage")]
+        [HttpGet("Studios/{name}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("Studios/{name}/Images/{imageType}/{imageIndex?}", Name = "HeadStudioImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetStudioImage(
         public async Task<ActionResult> GetStudioImage(
@@ -889,8 +890,8 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// A <see cref="FileStreamResult"/> containing the file stream on success,
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// or a <see cref="NotFoundResult"/> if item not found.
         /// </returns>
         /// </returns>
-        [HttpGet("/Users/{userId}/Images/{imageType}/{imageIndex?}")]
-        [HttpHead("/Users/{userId}/Images/{imageType}/{imageIndex?}", Name = "HeadUserImage")]
+        [HttpGet("Users/{userId}/Images/{imageType}/{imageIndex?}")]
+        [HttpHead("Users/{userId}/Images/{imageType}/{imageIndex?}", Name = "HeadUserImage")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> GetUserImage(
         public async Task<ActionResult> GetUserImage(

+ 8 - 7
Jellyfin.Api/Controllers/InstantMixController.cs

@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The instant mix controller.
     /// The instant mix controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class InstantMixController : BaseJellyfinApiController
     public class InstantMixController : BaseJellyfinApiController
     {
     {
@@ -59,7 +60,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <response code="200">Instant playlist returned.</response>
         /// <response code="200">Instant playlist returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
-        [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] Guid id,
@@ -96,7 +97,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <response code="200">Instant playlist returned.</response>
         /// <response code="200">Instant playlist returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
-        [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] Guid id,
@@ -133,7 +134,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <response code="200">Instant playlist returned.</response>
         /// <response code="200">Instant playlist returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
-        [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] Guid id,
@@ -170,7 +171,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <response code="200">Instant playlist returned.</response>
         /// <response code="200">Instant playlist returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
-        [HttpGet("/MusicGenres/{name}/InstantMix")]
+        [HttpGet("MusicGenres/{name}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenre(
             [FromRoute] string? name,
             [FromRoute] string? name,
@@ -206,7 +207,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <response code="200">Instant playlist returned.</response>
         /// <response code="200">Instant playlist returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
-        [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] Guid id,
@@ -243,7 +244,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <response code="200">Instant playlist returned.</response>
         /// <response code="200">Instant playlist returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
-        [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] Guid id,
@@ -280,7 +281,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
         /// <response code="200">Instant playlist returned.</response>
         /// <response code="200">Instant playlist returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
-        [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] Guid id,

+ 13 - 12
Jellyfin.Api/Controllers/ItemLookupController.cs

@@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Item lookup controller.
     /// Item lookup controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class ItemLookupController : BaseJellyfinApiController
     public class ItemLookupController : BaseJellyfinApiController
     {
     {
@@ -68,7 +69,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">External id info retrieved.</response>
         /// <response code="200">External id info retrieved.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>List of external id info.</returns>
         /// <returns>List of external id info.</returns>
-        [HttpGet("/Items/{itemId}/ExternalIdInfos")]
+        [HttpGet("Items/{itemId}/ExternalIdInfos")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -92,7 +93,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/Movie")]
+        [HttpPost("Items/RemoteSearch/Movie")]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMovieRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MovieInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMovieRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MovieInfo> query)
         {
         {
             var results = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(query, CancellationToken.None)
             var results = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(query, CancellationToken.None)
@@ -109,7 +110,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/Trailer")]
+        [HttpPost("Items/RemoteSearch/Trailer")]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetTrailerRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<TrailerInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetTrailerRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<TrailerInfo> query)
         {
         {
             var results = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(query, CancellationToken.None)
             var results = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(query, CancellationToken.None)
@@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/MusicVideo")]
+        [HttpPost("Items/RemoteSearch/MusicVideo")]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicVideoRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MusicVideoInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicVideoRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MusicVideoInfo> query)
         {
         {
             var results = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(query, CancellationToken.None)
             var results = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(query, CancellationToken.None)
@@ -143,7 +144,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/Series")]
+        [HttpPost("Items/RemoteSearch/Series")]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetSeriesRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<SeriesInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetSeriesRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<SeriesInfo> query)
         {
         {
             var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, CancellationToken.None)
             var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, CancellationToken.None)
@@ -160,7 +161,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/BoxSet")]
+        [HttpPost("Items/RemoteSearch/BoxSet")]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBoxSetRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BoxSetInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBoxSetRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BoxSetInfo> query)
         {
         {
             var results = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(query, CancellationToken.None)
             var results = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(query, CancellationToken.None)
@@ -177,7 +178,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/MusicArtist")]
+        [HttpPost("Items/RemoteSearch/MusicArtist")]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicArtistRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<ArtistInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicArtistRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<ArtistInfo> query)
         {
         {
             var results = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(query, CancellationToken.None)
             var results = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(query, CancellationToken.None)
@@ -194,7 +195,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/MusicAlbum")]
+        [HttpPost("Items/RemoteSearch/MusicAlbum")]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicAlbumRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<AlbumInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicAlbumRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<AlbumInfo> query)
         {
         {
             var results = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(query, CancellationToken.None)
             var results = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(query, CancellationToken.None)
@@ -211,7 +212,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/Person")]
+        [HttpPost("Items/RemoteSearch/Person")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetPersonRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<PersonLookupInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetPersonRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<PersonLookupInfo> query)
         {
         {
@@ -229,7 +230,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results.
         /// </returns>
         /// </returns>
-        [HttpPost("/Items/RemoteSearch/Book")]
+        [HttpPost("Items/RemoteSearch/Book")]
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBookRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BookInfo> query)
         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBookRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BookInfo> query)
         {
         {
             var results = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(query, CancellationToken.None)
             var results = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(query, CancellationToken.None)
@@ -247,7 +248,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="FileStreamResult"/> containing the images file stream.
         /// The task result contains an <see cref="FileStreamResult"/> containing the images file stream.
         /// </returns>
         /// </returns>
-        [HttpGet("/Items/RemoteSearch/Image")]
+        [HttpGet("Items/RemoteSearch/Image")]
         public async Task<ActionResult> GetRemoteSearchImage(
         public async Task<ActionResult> GetRemoteSearchImage(
             [FromQuery, Required] string imageUrl,
             [FromQuery, Required] string imageUrl,
             [FromQuery, Required] string providerName)
             [FromQuery, Required] string providerName)
@@ -291,7 +292,7 @@ namespace Jellyfin.Api.Controllers
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
         /// The task result contains an <see cref="NoContentResult"/>.
         /// The task result contains an <see cref="NoContentResult"/>.
         /// </returns>
         /// </returns>
-        [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] Guid itemId,

+ 4 - 3
Jellyfin.Api/Controllers/ItemUpdateController.cs

@@ -24,6 +24,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Item update controller.
     /// Item update controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.RequiresElevation)]
     [Authorize(Policy = Policies.RequiresElevation)]
     public class ItemUpdateController : BaseJellyfinApiController
     public class ItemUpdateController : BaseJellyfinApiController
     {
     {
@@ -63,7 +64,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Item updated.</response>
         /// <response code="204">Item updated.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
         /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
-        [HttpPost("/Items/{itemId}")]
+        [HttpPost("Items/{itemId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, BindRequired] BaseItemDto request)
         public ActionResult UpdateItem([FromRoute] Guid itemId, [FromBody, BindRequired] BaseItemDto request)
@@ -136,7 +137,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Item metadata editor returned.</response>
         /// <response code="200">Item metadata editor returned.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>An <see cref="OkResult"/> on success containing the metadata editor, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
         /// <returns>An <see cref="OkResult"/> on success containing the metadata editor, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
-        [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] Guid itemId)
@@ -190,7 +191,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Item content type updated.</response>
         /// <response code="204">Item content type updated.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
         /// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
-        [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, BindRequired] string? contentType)
         public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, BindRequired] string? contentType)

+ 4 - 3
Jellyfin.Api/Controllers/ItemsController.cs

@@ -23,6 +23,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The items controller.
     /// The items controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class ItemsController : BaseJellyfinApiController
     public class ItemsController : BaseJellyfinApiController
     {
     {
@@ -139,8 +140,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
         /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
         /// <param name="enableImages">Optional, include image information in output.</param>
         /// <param name="enableImages">Optional, include image information in output.</param>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
-        [HttpGet("/Items")]
-        [HttpGet("/Users/{uId}/Items", Name = "GetItems_2")]
+        [HttpGet("Items")]
+        [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] Guid? uId,
@@ -523,7 +524,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableImages">Optional. Include image information in output.</param>
         /// <param name="enableImages">Optional. Include image information in output.</param>
         /// <response code="200">Items returned.</response>
         /// <response code="200">Items returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns>
-        [HttpGet("/Users/{userId}/Items/Resume")]
+        [HttpGet("Users/{userId}/Items/Resume")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
         public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
             [FromRoute] Guid userId,
             [FromRoute] Guid userId,

+ 26 - 25
Jellyfin.Api/Controllers/LibraryController.cs

@@ -43,6 +43,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Library Controller.
     /// Library Controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class LibraryController : BaseJellyfinApiController
     public class LibraryController : BaseJellyfinApiController
     {
     {
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
@@ -100,7 +101,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">File stream returned.</response>
         /// <response code="200">File stream returned.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>A <see cref="FileStreamResult"/> with the original file.</returns>
         /// <returns>A <see cref="FileStreamResult"/> with the original file.</returns>
-        [HttpGet("/Items/{itemId}/File")]
+        [HttpGet("Items/{itemId}/File")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -121,7 +122,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="200">Critic reviews returned.</response>
         /// <response code="200">Critic reviews returned.</response>
         /// <returns>The list of critic reviews.</returns>
         /// <returns>The list of critic reviews.</returns>
-        [HttpGet("/Items/{itemId}/CriticReviews")]
+        [HttpGet("Items/{itemId}/CriticReviews")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Obsolete("This endpoint is obsolete.")]
         [Obsolete("This endpoint is obsolete.")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
@@ -139,7 +140,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Theme songs returned.</response>
         /// <response code="200">Theme songs returned.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>The item theme songs.</returns>
         /// <returns>The item theme songs.</returns>
-        [HttpGet("/Items/{itemId}/ThemeSongs")]
+        [HttpGet("Items/{itemId}/ThemeSongs")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -205,7 +206,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Theme videos returned.</response>
         /// <response code="200">Theme videos returned.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>The item theme videos.</returns>
         /// <returns>The item theme videos.</returns>
-        [HttpGet("/Items/{itemId}/ThemeVideos")]
+        [HttpGet("Items/{itemId}/ThemeVideos")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -271,7 +272,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Theme songs and videos returned.</response>
         /// <response code="200">Theme songs and videos returned.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>The item theme videos.</returns>
         /// <returns>The item theme videos.</returns>
-        [HttpGet("/Items/{itemId}/ThemeMedia")]
+        [HttpGet("Items/{itemId}/ThemeMedia")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<AllThemeMediaResult> GetThemeMedia(
         public ActionResult<AllThemeMediaResult> GetThemeMedia(
@@ -302,7 +303,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="204">Library scan started.</response>
         /// <response code="204">Library scan started.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpGet("/Library/Refresh")]
+        [HttpGet("Library/Refresh")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> RefreshLibrary()
         public async Task<ActionResult> RefreshLibrary()
@@ -326,7 +327,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Item deleted.</response>
         /// <response code="204">Item deleted.</response>
         /// <response code="401">Unauthorized access.</response>
         /// <response code="401">Unauthorized access.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpDelete("/Items/{itemId}")]
+        [HttpDelete("Items/{itemId}")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status401Unauthorized)]
         [ProducesResponseType(StatusCodes.Status401Unauthorized)]
@@ -356,7 +357,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Items deleted.</response>
         /// <response code="204">Items deleted.</response>
         /// <response code="401">Unauthorized access.</response>
         /// <response code="401">Unauthorized access.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpDelete("/Items")]
+        [HttpDelete("Items")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status401Unauthorized)]
         [ProducesResponseType(StatusCodes.Status401Unauthorized)]
@@ -400,7 +401,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="isFavorite">Optional. Get counts of favorite items.</param>
         /// <param name="isFavorite">Optional. Get counts of favorite items.</param>
         /// <response code="200">Item counts returned.</response>
         /// <response code="200">Item counts returned.</response>
         /// <returns>Item counts.</returns>
         /// <returns>Item counts.</returns>
-        [HttpGet("/Items/Counts")]
+        [HttpGet("Items/Counts")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<ItemCounts> GetItemCounts(
         public ActionResult<ItemCounts> GetItemCounts(
@@ -434,7 +435,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Item parents returned.</response>
         /// <response code="200">Item parents returned.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>Item parents.</returns>
         /// <returns>Item parents.</returns>
-        [HttpGet("/Items/{itemId}/Ancestors")]
+        [HttpGet("Items/{itemId}/Ancestors")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -476,7 +477,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="200">Physical paths returned.</response>
         /// <response code="200">Physical paths returned.</response>
         /// <returns>List of physical paths.</returns>
         /// <returns>List of physical paths.</returns>
-        [HttpGet("/Library/PhysicalPaths")]
+        [HttpGet("Library/PhysicalPaths")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<string>> GetPhysicalPaths()
         public ActionResult<IEnumerable<string>> GetPhysicalPaths()
@@ -491,7 +492,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="isHidden">Optional. Filter by folders that are marked hidden, or not.</param>
         /// <param name="isHidden">Optional. Filter by folders that are marked hidden, or not.</param>
         /// <response code="200">Media folders returned.</response>
         /// <response code="200">Media folders returned.</response>
         /// <returns>List of user media folders.</returns>
         /// <returns>List of user media folders.</returns>
-        [HttpGet("/Library/MediaFolders")]
+        [HttpGet("Library/MediaFolders")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden)
         public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden)
@@ -521,8 +522,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="tvdbId">The tvdbId.</param>
         /// <param name="tvdbId">The tvdbId.</param>
         /// <response code="204">Report success.</response>
         /// <response code="204">Report success.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Library/Series/Added", Name = "PostAddedSeries")]
-        [HttpPost("/Library/Series/Updated")]
+        [HttpPost("Library/Series/Added", Name = "PostAddedSeries")]
+        [HttpPost("Library/Series/Updated")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult PostUpdatedSeries([FromQuery] string? tvdbId)
         public ActionResult PostUpdatedSeries([FromQuery] string? tvdbId)
@@ -551,8 +552,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="imdbId">The imdbId.</param>
         /// <param name="imdbId">The imdbId.</param>
         /// <response code="204">Report success.</response>
         /// <response code="204">Report success.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Library/Movies/Added", Name = "PostAddedMovies")]
-        [HttpPost("/Library/Movies/Updated")]
+        [HttpPost("Library/Movies/Added", Name = "PostAddedMovies")]
+        [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] string? tmdbId, [FromRoute] string? imdbId)
@@ -593,7 +594,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="updates">A list of updated media paths.</param>
         /// <param name="updates">A list of updated media paths.</param>
         /// <response code="204">Report success.</response>
         /// <response code="204">Report success.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Library/Media/Updated")]
+        [HttpPost("Library/Media/Updated")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult PostUpdatedMedia([FromBody, BindRequired] MediaUpdateInfoDto[] updates)
         public ActionResult PostUpdatedMedia([FromBody, BindRequired] MediaUpdateInfoDto[] updates)
@@ -614,7 +615,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>A <see cref="FileResult"/> containing the media stream.</returns>
         /// <returns>A <see cref="FileResult"/> containing the media stream.</returns>
         /// <exception cref="ArgumentException">User can't download or item can't be downloaded.</exception>
         /// <exception cref="ArgumentException">User can't download or item can't be downloaded.</exception>
-        [HttpGet("/Items/{itemId}/Download")]
+        [HttpGet("Items/{itemId}/Download")]
         [Authorize(Policy = Policies.Download)]
         [Authorize(Policy = Policies.Download)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -679,12 +680,12 @@ namespace Jellyfin.Api.Controllers
         /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
         /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.</param>
         /// <response code="200">Similar items returned.</response>
         /// <response code="200">Similar items returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> containing the similar items.</returns>
-        [HttpGet("/Artists/{itemId}/Similar", Name = "GetSimilarArtists2")]
-        [HttpGet("/Items/{itemId}/Similar")]
-        [HttpGet("/Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")]
-        [HttpGet("/Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
-        [HttpGet("/Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
-        [HttpGet("/Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
+        [HttpGet("Artists/{itemId}/Similar", Name = "GetSimilarArtists2")]
+        [HttpGet("Items/{itemId}/Similar")]
+        [HttpGet("Albums/{itemId}/Similar", Name = "GetSimilarAlbums2")]
+        [HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows2")]
+        [HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies2")]
+        [HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
             [FromRoute] Guid itemId,
             [FromRoute] Guid itemId,
@@ -735,7 +736,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="isNewLibrary">Whether this is a new library.</param>
         /// <param name="isNewLibrary">Whether this is a new library.</param>
         /// <response code="200">Library options info returned.</response>
         /// <response code="200">Library options info returned.</response>
         /// <returns>Library options info.</returns>
         /// <returns>Library options info.</returns>
-        [HttpGet("/Libraries/AvailableOptions")]
+        [HttpGet("Libraries/AvailableOptions")]
         [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
         [Authorize(Policy = Policies.FirstTimeSetupOrElevated)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(
         public ActionResult<LibraryOptionsResultDto> GetLibraryOptionsInfo(

+ 6 - 5
Jellyfin.Api/Controllers/MediaInfoController.cs

@@ -34,6 +34,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The media info controller.
     /// The media info controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class MediaInfoController : BaseJellyfinApiController
     public class MediaInfoController : BaseJellyfinApiController
     {
     {
@@ -88,7 +89,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="userId">The user id.</param>
         /// <param name="userId">The user id.</param>
         /// <response code="200">Playback info returned.</response>
         /// <response code="200">Playback info returned.</response>
         /// <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] Guid? userId)
         public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery] Guid? userId)
         {
         {
@@ -116,7 +117,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
         /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
         /// <response code="200">Playback info returned.</response>
         /// <response code="200">Playback info returned.</response>
         /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
         /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
-        [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] Guid itemId,
@@ -237,7 +238,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
         /// <param name="enableDirectStream">Whether to enable direct stream. Default: true.</param>
         /// <response code="200">Media source opened.</response>
         /// <response code="200">Media source opened.</response>
         /// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
         /// <returns>A <see cref="Task"/> containing a <see cref="LiveStreamResponse"/>.</returns>
-        [HttpPost("/LiveStreams/Open")]
+        [HttpPost("LiveStreams/Open")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
         public async Task<ActionResult<LiveStreamResponse>> OpenLiveStream(
             [FromQuery] string? openToken,
             [FromQuery] string? openToken,
@@ -278,7 +279,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="liveStreamId">The livestream id.</param>
         /// <param name="liveStreamId">The livestream id.</param>
         /// <response code="204">Livestream closed.</response>
         /// <response code="204">Livestream closed.</response>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
-        [HttpPost("/LiveStreams/Close")]
+        [HttpPost("LiveStreams/Close")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult CloseLiveStream([FromQuery] string? liveStreamId)
         public ActionResult CloseLiveStream([FromQuery] string? liveStreamId)
         {
         {
@@ -293,7 +294,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Test buffer returned.</response>
         /// <response code="200">Test buffer returned.</response>
         /// <response code="400">Size has to be a numer between 0 and 10,000,000.</response>
         /// <response code="400">Size has to be a numer between 0 and 10,000,000.</response>
         /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
         /// <returns>A <see cref="FileResult"/> with specified bitrate.</returns>
-        [HttpGet("/Playback/BitrateTest")]
+        [HttpGet("Playback/BitrateTest")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [Produces(MediaTypeNames.Application.Octet)]
         [Produces(MediaTypeNames.Application.Octet)]

+ 7 - 7
Jellyfin.Api/Controllers/PackageController.cs

@@ -16,7 +16,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Package Controller.
     /// Package Controller.
     /// </summary>
     /// </summary>
-    [Route("Packages")]
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class PackageController : BaseJellyfinApiController
     public class PackageController : BaseJellyfinApiController
     {
     {
@@ -41,7 +41,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="assemblyGuid">The GUID of the associated assembly.</param>
         /// <param name="assemblyGuid">The GUID of the associated assembly.</param>
         /// <response code="200">Package retrieved.</response>
         /// <response code="200">Package retrieved.</response>
         /// <returns>A <see cref="PackageInfo"/> containing package information.</returns>
         /// <returns>A <see cref="PackageInfo"/> containing package information.</returns>
-        [HttpGet("/{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,
@@ -61,7 +61,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="200">Available packages returned.</response>
         /// <response code="200">Available packages returned.</response>
         /// <returns>An <see cref="PackageInfo"/> containing available packages information.</returns>
         /// <returns>An <see cref="PackageInfo"/> containing available packages information.</returns>
-        [HttpGet]
+        [HttpGet("Packages")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<IEnumerable<PackageInfo>> GetPackages()
         public async Task<IEnumerable<PackageInfo>> GetPackages()
         {
         {
@@ -79,7 +79,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Package found.</response>
         /// <response code="204">Package found.</response>
         /// <response code="404">Package not found.</response>
         /// <response code="404">Package not found.</response>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the package could not be found.</returns>
         /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the package could not be found.</returns>
-        [HttpPost("/Installed/{name}")]
+        [HttpPost("Packages/Installed/{name}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
@@ -111,7 +111,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="packageId">Installation Id.</param>
         /// <param name="packageId">Installation Id.</param>
         /// <response code="204">Installation cancelled.</response>
         /// <response code="204">Installation cancelled.</response>
         /// <returns>A <see cref="NoContentResult"/> on successfully cancelling a package installation.</returns>
         /// <returns>A <see cref="NoContentResult"/> on successfully cancelling a package installation.</returns>
-        [HttpDelete("/Installing/{packageId}")]
+        [HttpDelete("Packages/Installing/{packageId}")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult CancelPackageInstallation(
         public ActionResult CancelPackageInstallation(
@@ -126,7 +126,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="200">Package repositories returned.</response>
         /// <response code="200">Package repositories returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the list of package repositories.</returns>
         /// <returns>An <see cref="OkResult"/> containing the list of package repositories.</returns>
-        [HttpGet("/Repositories")]
+        [HttpGet("Repositories")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<RepositoryInfo>> GetRepositories()
         public ActionResult<IEnumerable<RepositoryInfo>> GetRepositories()
@@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="repositoryInfos">The list of package repositories.</param>
         /// <param name="repositoryInfos">The list of package repositories.</param>
         /// <response code="204">Package repositories saved.</response>
         /// <response code="204">Package repositories saved.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpOptions("/Repositories")]
+        [HttpOptions("Repositories")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos)
         public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos)

+ 10 - 9
Jellyfin.Api/Controllers/PlaystateController.cs

@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Playstate controller.
     /// Playstate controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class PlaystateController : BaseJellyfinApiController
     public class PlaystateController : BaseJellyfinApiController
     {
     {
@@ -67,7 +68,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="datePlayed">Optional. The date the item was played.</param>
         /// <param name="datePlayed">Optional. The date the item was played.</param>
         /// <response code="200">Item marked as played.</response>
         /// <response code="200">Item marked as played.</response>
         /// <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}/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] Guid userId,
@@ -93,7 +94,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="itemId">Item id.</param>
         /// <response code="200">Item marked as unplayed.</response>
         /// <response code="200">Item marked as unplayed.</response>
         /// <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}/PlayedItem/{itemId}")]
+        [HttpDelete("Users/{userId}/PlayedItem/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
         public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
         {
         {
@@ -115,7 +116,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playbackStartInfo">The playback start info.</param>
         /// <param name="playbackStartInfo">The playback start info.</param>
         /// <response code="204">Playback start recorded.</response>
         /// <response code="204">Playback start recorded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/Playing")]
+        [HttpPost("Sessions/Playing")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> ReportPlaybackStart([FromBody] PlaybackStartInfo playbackStartInfo)
         public async Task<ActionResult> ReportPlaybackStart([FromBody] PlaybackStartInfo playbackStartInfo)
         {
         {
@@ -131,7 +132,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playbackProgressInfo">The playback progress info.</param>
         /// <param name="playbackProgressInfo">The playback progress info.</param>
         /// <response code="204">Playback progress recorded.</response>
         /// <response code="204">Playback progress recorded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/Playing/Progress")]
+        [HttpPost("Sessions/Playing/Progress")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> ReportPlaybackProgress([FromBody] PlaybackProgressInfo playbackProgressInfo)
         public async Task<ActionResult> ReportPlaybackProgress([FromBody] PlaybackProgressInfo playbackProgressInfo)
         {
         {
@@ -147,7 +148,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playSessionId">Playback session id.</param>
         /// <param name="playSessionId">Playback session id.</param>
         /// <response code="204">Playback session pinged.</response>
         /// <response code="204">Playback session pinged.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/Playing/Ping")]
+        [HttpPost("Sessions/Playing/Ping")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult PingPlaybackSession([FromQuery] string playSessionId)
         public ActionResult PingPlaybackSession([FromQuery] string playSessionId)
         {
         {
@@ -161,7 +162,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playbackStopInfo">The playback stop info.</param>
         /// <param name="playbackStopInfo">The playback stop info.</param>
         /// <response code="204">Playback stop recorded.</response>
         /// <response code="204">Playback stop recorded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/Playing/Stopped")]
+        [HttpPost("Sessions/Playing/Stopped")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> ReportPlaybackStopped([FromBody] PlaybackStopInfo playbackStopInfo)
         public async Task<ActionResult> ReportPlaybackStopped([FromBody] PlaybackStopInfo playbackStopInfo)
         {
         {
@@ -190,7 +191,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="canSeek">Indicates if the client can seek.</param>
         /// <param name="canSeek">Indicates if the client can seek.</param>
         /// <response code="204">Play start recorded.</response>
         /// <response code="204">Play start recorded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Users/{userId}/PlayingItems/{itemId}")]
+        [HttpPost("Users/{userId}/PlayingItems/{itemId}")]
         [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(
@@ -240,7 +241,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="isMuted">Indicates if the player is muted.</param>
         /// <param name="isMuted">Indicates if the player is muted.</param>
         /// <response code="204">Play progress recorded.</response>
         /// <response code="204">Play progress recorded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Users/{userId}/PlayingItems/{itemId}/Progress")]
+        [HttpPost("Users/{userId}/PlayingItems/{itemId}/Progress")]
         [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(
@@ -292,7 +293,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playSessionId">The play session id.</param>
         /// <param name="playSessionId">The play session id.</param>
         /// <response code="204">Playback stop recorded.</response>
         /// <response code="204">Playback stop recorded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpDelete("/Users/{userId}/PlayingItems/{itemId}")]
+        [HttpDelete("Users/{userId}/PlayingItems/{itemId}")]
         [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(

+ 1 - 1
Jellyfin.Api/Controllers/PluginsController.cs

@@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>Not Implemented.</returns>
         /// <returns>Not Implemented.</returns>
         /// <exception cref="NotImplementedException">This endpoint is not implemented.</exception>
         /// <exception cref="NotImplementedException">This endpoint is not implemented.</exception>
         [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] string? name)
         {
         {

+ 17 - 16
Jellyfin.Api/Controllers/SessionController.cs

@@ -23,6 +23,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The session controller.
     /// The session controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class SessionController : BaseJellyfinApiController
     public class SessionController : BaseJellyfinApiController
     {
     {
         private readonly ISessionManager _sessionManager;
         private readonly ISessionManager _sessionManager;
@@ -57,7 +58,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="activeWithinSeconds">Optional. Filter by sessions that were active in the last n seconds.</param>
         /// <param name="activeWithinSeconds">Optional. Filter by sessions that were active in the last n seconds.</param>
         /// <response code="200">List of sessions returned.</response>
         /// <response code="200">List of sessions returned.</response>
         /// <returns>An <see cref="IEnumerable{SessionInfo}"/> with the available sessions.</returns>
         /// <returns>An <see cref="IEnumerable{SessionInfo}"/> with the available sessions.</returns>
-        [HttpGet("/Sessions")]
+        [HttpGet("Sessions")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<SessionInfo>> GetSessions(
         public ActionResult<IEnumerable<SessionInfo>> GetSessions(
@@ -120,7 +121,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemName">The name of the item.</param>
         /// <param name="itemName">The name of the item.</param>
         /// <response code="204">Instruction sent to session.</response>
         /// <response code="204">Instruction sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/Viewing")]
+        [HttpPost("Sessions/{sessionId}/Viewing")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult DisplayContent(
         public ActionResult DisplayContent(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -154,7 +155,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playRequest">The <see cref="PlayRequest"/>.</param>
         /// <param name="playRequest">The <see cref="PlayRequest"/>.</param>
         /// <response code="204">Instruction sent to session.</response>
         /// <response code="204">Instruction sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/Playing")]
+        [HttpPost("Sessions/{sessionId}/Playing")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult Play(
         public ActionResult Play(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -188,7 +189,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="playstateRequest">The <see cref="PlaystateRequest"/>.</param>
         /// <param name="playstateRequest">The <see cref="PlaystateRequest"/>.</param>
         /// <response code="204">Playstate command sent to session.</response>
         /// <response code="204">Playstate command sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/Playing/{command}")]
+        [HttpPost("Sessions/{sessionId}/Playing/{command}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult SendPlaystateCommand(
         public ActionResult SendPlaystateCommand(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -210,7 +211,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="command">The command to send.</param>
         /// <param name="command">The command to send.</param>
         /// <response code="204">System command sent to session.</response>
         /// <response code="204">System command sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/System/{command}")]
+        [HttpPost("Sessions/{sessionId}/System/{command}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult SendSystemCommand(
         public ActionResult SendSystemCommand(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -241,7 +242,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="command">The command to send.</param>
         /// <param name="command">The command to send.</param>
         /// <response code="204">General command sent to session.</response>
         /// <response code="204">General command sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/Command/{command}")]
+        [HttpPost("Sessions/{sessionId}/Command/{command}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult SendGeneralCommand(
         public ActionResult SendGeneralCommand(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -267,7 +268,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="command">The <see cref="GeneralCommand"/>.</param>
         /// <param name="command">The <see cref="GeneralCommand"/>.</param>
         /// <response code="204">Full general command sent to session.</response>
         /// <response code="204">Full general command sent to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/Command")]
+        [HttpPost("Sessions/{sessionId}/Command")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult SendFullGeneralCommand(
         public ActionResult SendFullGeneralCommand(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -300,7 +301,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="timeoutMs">The message timeout. If omitted the user will have to confirm viewing the message.</param>
         /// <param name="timeoutMs">The message timeout. If omitted the user will have to confirm viewing the message.</param>
         /// <response code="204">Message sent.</response>
         /// <response code="204">Message sent.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/Message")]
+        [HttpPost("Sessions/{sessionId}/Message")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult SendMessageCommand(
         public ActionResult SendMessageCommand(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -327,7 +328,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="userId">The user id.</param>
         /// <param name="userId">The user id.</param>
         /// <response code="204">User added to session.</response>
         /// <response code="204">User added to session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/{sessionId}/User/{userId}")]
+        [HttpPost("Sessions/{sessionId}/User/{userId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult AddUserToSession(
         public ActionResult AddUserToSession(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -344,7 +345,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="userId">The user id.</param>
         /// <param name="userId">The user id.</param>
         /// <response code="204">User removed from session.</response>
         /// <response code="204">User removed from session.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpDelete("/Sessions/{sessionId}/User/{userId}")]
+        [HttpDelete("Sessions/{sessionId}/User/{userId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult RemoveUserFromSession(
         public ActionResult RemoveUserFromSession(
             [FromRoute] string? sessionId,
             [FromRoute] string? sessionId,
@@ -365,7 +366,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param>
         /// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param>
         /// <response code="204">Capabilities posted.</response>
         /// <response code="204">Capabilities posted.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/Capabilities")]
+        [HttpPost("Sessions/Capabilities")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult PostCapabilities(
         public ActionResult PostCapabilities(
             [FromQuery] string? id,
             [FromQuery] string? id,
@@ -398,7 +399,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="capabilities">The <see cref="ClientCapabilities"/>.</param>
         /// <param name="capabilities">The <see cref="ClientCapabilities"/>.</param>
         /// <response code="204">Capabilities updated.</response>
         /// <response code="204">Capabilities updated.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/Capabilities/Full")]
+        [HttpPost("Sessions/Capabilities/Full")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult PostFullCapabilities(
         public ActionResult PostFullCapabilities(
             [FromQuery] string? id,
             [FromQuery] string? id,
@@ -421,7 +422,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">The item id.</param>
         /// <param name="itemId">The item id.</param>
         /// <response code="204">Session reported to server.</response>
         /// <response code="204">Session reported to server.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/Viewing")]
+        [HttpPost("Sessions/Viewing")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult ReportViewing(
         public ActionResult ReportViewing(
             [FromQuery] string? sessionId,
             [FromQuery] string? sessionId,
@@ -438,7 +439,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="204">Session end reported to server.</response>
         /// <response code="204">Session end reported to server.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Sessions/Logout")]
+        [HttpPost("Sessions/Logout")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult ReportSessionEnded()
         public ActionResult ReportSessionEnded()
         {
         {
@@ -453,7 +454,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="200">Auth providers retrieved.</response>
         /// <response code="200">Auth providers retrieved.</response>
         /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the auth providers.</returns>
         /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the auth providers.</returns>
-        [HttpGet("/Auth/Providers")]
+        [HttpGet("Auth/Providers")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<NameIdPair>> GetAuthProviders()
         public ActionResult<IEnumerable<NameIdPair>> GetAuthProviders()
         {
         {
@@ -465,7 +466,7 @@ namespace Jellyfin.Api.Controllers
         /// </summary>
         /// </summary>
         /// <response code="200">Password reset providers retrieved.</response>
         /// <response code="200">Password reset providers retrieved.</response>
         /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the password reset providers.</returns>
         /// <returns>An <see cref="IEnumerable{NameIdPair}"/> with the password reset providers.</returns>
-        [HttpGet("/Auto/PasswordResetProviders")]
+        [HttpGet("Auto/PasswordResetProviders")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<NameIdPair>> GetPasswordResetProviders()
         public ActionResult<IEnumerable<NameIdPair>> GetPasswordResetProviders()
         {
         {

+ 8 - 7
Jellyfin.Api/Controllers/SubtitleController.cs

@@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// Subtitle controller.
     /// Subtitle controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class SubtitleController : BaseJellyfinApiController
     public class SubtitleController : BaseJellyfinApiController
     {
     {
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
@@ -80,7 +81,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="204">Subtitle deleted.</response>
         /// <response code="204">Subtitle deleted.</response>
         /// <response code="404">Item not found.</response>
         /// <response code="404">Item not found.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpDelete("/Videos/{itemId}/Subtitles/{index}")]
+        [HttpDelete("Videos/{itemId}/Subtitles/{index}")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -107,7 +108,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="isPerfectMatch">Optional. Only show subtitles which are a perfect match.</param>
         /// <param name="isPerfectMatch">Optional. Only show subtitles which are a perfect match.</param>
         /// <response code="200">Subtitles retrieved.</response>
         /// <response code="200">Subtitles retrieved.</response>
         /// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns>
         /// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns>
-        [HttpGet("/Items/{itemId}/RemoteSearch/Subtitles/{language}")]
+        [HttpGet("Items/{itemId}/RemoteSearch/Subtitles/{language}")]
         [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(
@@ -127,7 +128,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="subtitleId">The subtitle id.</param>
         /// <param name="subtitleId">The subtitle id.</param>
         /// <response code="204">Subtitle downloaded.</response>
         /// <response code="204">Subtitle downloaded.</response>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
         /// <returns>A <see cref="NoContentResult"/>.</returns>
-        [HttpPost("/Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")]
+        [HttpPost("Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> DownloadRemoteSubtitles(
         public async Task<ActionResult> DownloadRemoteSubtitles(
@@ -157,7 +158,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="id">The item id.</param>
         /// <param name="id">The item id.</param>
         /// <response code="200">File returned.</response>
         /// <response code="200">File returned.</response>
         /// <returns>A <see cref="FileStreamResult"/> with the subtitle file.</returns>
         /// <returns>A <see cref="FileStreamResult"/> with the subtitle file.</returns>
-        [HttpGet("/Providers/Subtitles/Subtitles/{id}")]
+        [HttpGet("Providers/Subtitles/Subtitles/{id}")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Produces(MediaTypeNames.Application.Octet)]
         [Produces(MediaTypeNames.Application.Octet)]
@@ -181,8 +182,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="startPositionTicks">Optional. The start position of the subtitle in ticks.</param>
         /// <param name="startPositionTicks">Optional. The start position of the subtitle in ticks.</param>
         /// <response code="200">File returned.</response>
         /// <response code="200">File returned.</response>
         /// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
         /// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
-        [HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
-        [HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks?}/Stream.{format}", Name = "GetSubtitle_2")]
+        [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
+        [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks?}/Stream.{format}", Name = "GetSubtitle_2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetSubtitle(
         public async Task<ActionResult> GetSubtitle(
             [FromRoute, Required] Guid itemId,
             [FromRoute, Required] Guid itemId,
@@ -247,7 +248,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="segmentLength">The subtitle segment length.</param>
         /// <param name="segmentLength">The subtitle segment length.</param>
         /// <response code="200">Subtitle playlist retrieved.</response>
         /// <response code="200">Subtitle playlist retrieved.</response>
         /// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns>
         /// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns>
-        [HttpGet("/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")]
+        [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [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")]

+ 2 - 1
Jellyfin.Api/Controllers/SuggestionsController.cs

@@ -16,6 +16,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The suggestions controller.
     /// The suggestions controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class SuggestionsController : BaseJellyfinApiController
     public class SuggestionsController : BaseJellyfinApiController
     {
     {
         private readonly IDtoService _dtoService;
         private readonly IDtoService _dtoService;
@@ -49,7 +50,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableTotalRecordCount">Whether to enable the total record count.</param>
         /// <param name="enableTotalRecordCount">Whether to enable the total record count.</param>
         /// <response code="200">Suggestions returned.</response>
         /// <response code="200">Suggestions returned.</response>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the suggestions.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the suggestions.</returns>
-        [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] Guid userId,

+ 1 - 1
Jellyfin.Api/Controllers/TrailersController.cs

@@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
         /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
         /// <param name="enableImages">Optional, include image information in output.</param>
         /// <param name="enableImages">Optional, include image information in output.</param>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the trailers.</returns>
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the trailers.</returns>
-        [HttpGet("/Trailers")]
+        [HttpGet]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
         public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,

+ 5 - 4
Jellyfin.Api/Controllers/UniversalAudioController.cs

@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The universal audio controller.
     /// The universal audio controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class UniversalAudioController : BaseJellyfinApiController
     public class UniversalAudioController : BaseJellyfinApiController
     {
     {
         private readonly IAuthorizationContext _authorizationContext;
         private readonly IAuthorizationContext _authorizationContext;
@@ -68,10 +69,10 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Audio stream returned.</response>
         /// <response code="200">Audio stream returned.</response>
         /// <response code="302">Redirected to remote audio stream.</response>
         /// <response code="302">Redirected to remote audio stream.</response>
         /// <returns>A <see cref="Task"/> containing the audio file.</returns>
         /// <returns>A <see cref="Task"/> containing the audio file.</returns>
-        [HttpGet("/Audio/{itemId}/universal")]
-        [HttpGet("/Audio/{itemId}/{universal=universal}.{container?}", Name = "GetUniversalAudioStream_2")]
-        [HttpHead("/Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
-        [HttpHead("/Audio/{itemId}/{universal=universal}.{container?}", Name = "HeadUniversalAudioStream_2")]
+        [HttpGet("Audio/{itemId}/universal")]
+        [HttpGet("Audio/{itemId}/{universal=universal}.{container?}", Name = "GetUniversalAudioStream_2")]
+        [HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
+        [HttpHead("Audio/{itemId}/{universal=universal}.{container?}", Name = "HeadUniversalAudioStream_2")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status302Found)]
         [ProducesResponseType(StatusCodes.Status302Found)]

+ 1 - 1
Jellyfin.Api/Controllers/UserController.cs

@@ -450,7 +450,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="request">The create user by name request body.</param>
         /// <param name="request">The create user by name request body.</param>
         /// <response code="200">User created.</response>
         /// <response code="200">User created.</response>
         /// <returns>An <see cref="UserDto"/> of the new user.</returns>
         /// <returns>An <see cref="UserDto"/> of the new user.</returns>
-        [HttpPost("/Users/New")]
+        [HttpPost("New")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<UserDto>> CreateUserByName([FromBody] CreateUserByName request)
         public async Task<ActionResult<UserDto>> CreateUserByName([FromBody] CreateUserByName request)

+ 11 - 10
Jellyfin.Api/Controllers/UserLibraryController.cs

@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// User library controller.
     /// User library controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class UserLibraryController : BaseJellyfinApiController
     public class UserLibraryController : BaseJellyfinApiController
     {
     {
@@ -67,7 +68,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="itemId">Item id.</param>
         /// <response code="200">Item returned.</response>
         /// <response code="200">Item returned.</response>
         /// <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] Guid userId, [FromRoute] Guid itemId)
         {
         {
@@ -90,7 +91,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="userId">User id.</param>
         /// <param name="userId">User id.</param>
         /// <response code="200">Root folder returned.</response>
         /// <response code="200">Root folder returned.</response>
         /// <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] Guid userId)
         {
         {
@@ -107,7 +108,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="itemId">Item id.</param>
         /// <response code="200">Intros returned.</response>
         /// <response code="200">Intros returned.</response>
         /// <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] Guid userId, [FromRoute] Guid itemId)
         {
         {
@@ -135,7 +136,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="itemId">Item id.</param>
         /// <response code="200">Item marked as favorite.</response>
         /// <response code="200">Item marked as favorite.</response>
         /// <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] Guid userId, [FromRoute] Guid itemId)
         {
         {
@@ -149,7 +150,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="itemId">Item id.</param>
         /// <response code="200">Item unmarked as favorite.</response>
         /// <response code="200">Item unmarked as favorite.</response>
         /// <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] Guid userId, [FromRoute] Guid itemId)
         {
         {
@@ -163,7 +164,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="itemId">Item id.</param>
         /// <response code="200">Personal rating removed.</response>
         /// <response code="200">Personal rating removed.</response>
         /// <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] Guid userId, [FromRoute] Guid itemId)
         {
         {
@@ -178,7 +179,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="likes">Whether this <see cref="UpdateUserItemRating" /> is likes.</param>
         /// <param name="likes">Whether this <see cref="UpdateUserItemRating" /> is likes.</param>
         /// <response code="200">Item rating updated.</response>
         /// <response code="200">Item rating updated.</response>
         /// <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] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool? likes)
         {
         {
@@ -192,7 +193,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="itemId">Item id.</param>
         /// <response code="200">An <see cref="OkResult"/> containing the item's local trailers.</response>
         /// <response code="200">An <see cref="OkResult"/> containing the item's local trailers.</response>
         /// <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] Guid userId, [FromRoute] Guid itemId)
         {
         {
@@ -227,7 +228,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="itemId">Item id.</param>
         /// <param name="itemId">Item id.</param>
         /// <response code="200">Special features returned.</response>
         /// <response code="200">Special features returned.</response>
         /// <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] Guid userId, [FromRoute] Guid itemId)
         {
         {
@@ -260,7 +261,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="groupItems">Whether or not to group items into a parent container.</param>
         /// <param name="groupItems">Whether or not to group items into a parent container.</param>
         /// <response code="200">Latest media returned.</response>
         /// <response code="200">Latest media returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the latest media.</returns>
         /// <returns>An <see cref="OkResult"/> containing the latest media.</returns>
-        [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] Guid userId,

+ 3 - 2
Jellyfin.Api/Controllers/UserViewsController.cs

@@ -21,6 +21,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// User views controller.
     /// User views controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     public class UserViewsController : BaseJellyfinApiController
     public class UserViewsController : BaseJellyfinApiController
     {
     {
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
@@ -60,7 +61,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="includeHidden">Whether or not to include hidden content.</param>
         /// <param name="includeHidden">Whether or not to include hidden content.</param>
         /// <response code="200">User views returned.</response>
         /// <response code="200">User views returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the user views.</returns>
         /// <returns>An <see cref="OkResult"/> containing the user views.</returns>
-        [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] Guid userId,
@@ -122,7 +123,7 @@ namespace Jellyfin.Api.Controllers
         /// An <see cref="OkResult"/> containing the user view grouping options
         /// An <see cref="OkResult"/> containing the user view grouping options
         /// or a <see cref="NotFoundResult"/> if user not found.
         /// or a <see cref="NotFoundResult"/> if user not found.
         /// </returns>
         /// </returns>
-        [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] Guid userId)

+ 3 - 2
Jellyfin.Api/Controllers/VideoHlsController.cs

@@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// <summary>
     /// The video hls controller.
     /// The video hls controller.
     /// </summary>
     /// </summary>
+    [Route("")]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     [Authorize(Policy = Policies.DefaultAuthorization)]
     public class VideoHlsController : BaseJellyfinApiController
     public class VideoHlsController : BaseJellyfinApiController
     {
     {
@@ -158,7 +159,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
         /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
         /// <response code="200">Hls live stream retrieved.</response>
         /// <response code="200">Hls live stream retrieved.</response>
         /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
         /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
-        [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] Guid itemId,
@@ -271,7 +272,7 @@ namespace Jellyfin.Api.Controllers
             };
             };
 
 
             var cancellationTokenSource = new CancellationTokenSource();
             var cancellationTokenSource = new CancellationTokenSource();
-            var state = await StreamingHelpers.GetStreamingState(
+            using var state = await StreamingHelpers.GetStreamingState(
                     streamingRequest,
                     streamingRequest,
                     Request,
                     Request,
                     _authContext,
                     _authContext,