diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml
index 00f7e9e6d2..1f166d10c8 100644
--- a/.github/workflows/ci-codeql-analysis.yml
+++ b/.github/workflows/ci-codeql-analysis.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '9.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
+ uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
+ uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
+ uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
diff --git a/.github/workflows/ci-compat.yml b/.github/workflows/ci-compat.yml
index 07e61024ee..ca505790cc 100644
--- a/.github/workflows/ci-compat.yml
+++ b/.github/workflows/ci-compat.yml
@@ -26,7 +26,7 @@ jobs:
dotnet build Jellyfin.Server -o ./out
- name: Upload Head
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: abi-head
retention-days: 14
@@ -65,7 +65,7 @@ jobs:
dotnet build Jellyfin.Server -o ./out
- name: Upload Head
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: abi-base
retention-days: 14
@@ -85,13 +85,13 @@ jobs:
steps:
- name: Download abi-head
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: abi-head
path: abi-head
- name: Download abi-base
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: abi-base
path: abi-base
diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml
index e82988200d..85a7a33bcd 100644
--- a/.github/workflows/ci-openapi.yml
+++ b/.github/workflows/ci-openapi.yml
@@ -27,7 +27,7 @@ jobs:
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: openapi-head
retention-days: 14
@@ -61,7 +61,7 @@ jobs:
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
with:
name: openapi-base
retention-days: 14
@@ -80,12 +80,12 @@ jobs:
- openapi-base
steps:
- name: Download openapi-head
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: openapi-head
path: openapi-head
- name: Download openapi-base
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: openapi-base
path: openapi-base
@@ -158,7 +158,7 @@ jobs:
run: |-
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
- name: Download openapi-head
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: openapi-head
path: openapi-head
@@ -172,7 +172,7 @@ jobs:
strip_components: 1
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (unstable) into place
- uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
+ uses: appleboy/ssh-action@8faa84277b88b6cd1455986f459aa66cf72bc8a3 # v1.2.1
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
@@ -220,7 +220,7 @@ jobs:
run: |-
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Download openapi-head
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
+ uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
with:
name: openapi-head
path: openapi-head
@@ -234,7 +234,7 @@ jobs:
strip_components: 1
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (stable) into place
- uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
+ uses: appleboy/ssh-action@8faa84277b88b6cd1455986f459aa66cf72bc8a3 # v1.2.1
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 999e4ba745..854c5a6df8 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -79,7 +79,7 @@
-
+
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index e414792ba0..4a0662e16a 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.Collections
{
if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
{
- throw new ArgumentException("No collection exists with the supplied Id");
+ throw new ArgumentException("No collection exists with the supplied collectionId " + collectionId);
}
List? itemList = null;
@@ -218,7 +218,7 @@ namespace Emby.Server.Implementations.Collections
if (item is null)
{
- throw new ArgumentException("No item exists with the supplied Id");
+ throw new ArgumentException("No item exists with the supplied Id " + id);
}
if (!currentLinkedChildrenIds.Contains(id))
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 3c2b622391..4fef6ea010 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -458,6 +458,7 @@ namespace Emby.Server.Implementations.Library
foreach (var child in children)
{
_itemRepository.DeleteItem(child.Id);
+ _cache.TryRemove(child.Id, out _);
}
_cache.TryRemove(item.Id, out _);
@@ -2631,15 +2632,6 @@ namespace Emby.Server.Implementations.Library
{
episode.ParentIndexNumber = season.IndexNumber;
}
- else
- {
- /*
- Anime series don't generally have a season in their file name, however,
- TVDb needs a season to correctly get the metadata.
- Hence, a null season needs to be filled with something. */
- // FIXME perhaps this would be better for TVDb parser to ask for season 1 if no season is specified
- episode.ParentIndexNumber = 1;
- }
if (episode.ParentIndexNumber.HasValue)
{
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index f205e8b64c..1a9c3ee8be 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -13,7 +13,7 @@
"DeviceOnlineWithName": "{0} belépett",
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet innen: {0}",
"Favorites": "Kedvencek",
- "Folders": "Könyvtárak",
+ "Folders": "Mappák",
"Genres": "Műfajok",
"HeaderAlbumArtists": "Albumelőadók",
"HeaderContinueWatching": "Megtekintés folytatása",
diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json
index 297b3abce7..e05afbabeb 100644
--- a/Emby.Server.Implementations/Localization/Core/it.json
+++ b/Emby.Server.Implementations/Localization/Core/it.json
@@ -58,8 +58,8 @@
"NotificationOptionServerRestartRequired": "Riavvio del server necessario",
"NotificationOptionTaskFailed": "Operazione pianificata fallita",
"NotificationOptionUserLockedOut": "Utente bloccato",
- "NotificationOptionVideoPlayback": "La riproduzione video è iniziata",
- "NotificationOptionVideoPlaybackStopped": "La riproduzione video è stata interrotta",
+ "NotificationOptionVideoPlayback": "Riproduzione video iniziata",
+ "NotificationOptionVideoPlaybackStopped": "Riproduzione video interrotta",
"Photos": "Foto",
"Playlists": "Playlist",
"Plugin": "Plugin",
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
index 031d147765..8d1d509ff7 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
@@ -116,6 +116,7 @@ public partial class AudioNormalizationTask : IScheduledTask
{
a.LUFS = await CalculateLUFSAsync(
string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
+ OperatingSystem.IsWindows(), // Wait for process to exit on Windows before we try deleting the concat file
cancellationToken).ConfigureAwait(false);
}
finally
@@ -142,7 +143,10 @@ public partial class AudioNormalizationTask : IScheduledTask
continue;
}
- t.LUFS = await CalculateLUFSAsync(string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)), cancellationToken).ConfigureAwait(false);
+ t.LUFS = await CalculateLUFSAsync(
+ string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)),
+ false,
+ cancellationToken).ConfigureAwait(false);
}
_itemRepository.SaveItems(tracks, cancellationToken);
@@ -162,7 +166,7 @@ public partial class AudioNormalizationTask : IScheduledTask
];
}
- private async Task CalculateLUFSAsync(string inputArgs, CancellationToken cancellationToken)
+ private async Task CalculateLUFSAsync(string inputArgs, bool waitForExit, CancellationToken cancellationToken)
{
var args = $"-hide_banner {inputArgs} -af ebur128=framelog=verbose -f null -";
@@ -189,18 +193,28 @@ public partial class AudioNormalizationTask : IScheduledTask
}
using var reader = process.StandardError;
+ float? lufs = null;
await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
{
Match match = LUFSRegex().Match(line);
-
if (match.Success)
{
- return float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
+ lufs = float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
+ break;
}
}
- _logger.LogError("Failed to find LUFS value in output");
- return null;
+ if (lufs is null)
+ {
+ _logger.LogError("Failed to find LUFS value in output");
+ }
+
+ if (waitForExit)
+ {
+ await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ return lufs;
}
}
}
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index 6c5ce47158..0ee11c0704 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -212,20 +212,4 @@ public class SystemController : BaseJellyfinApiController
FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
return File(stream, "text/plain; charset=utf-8");
}
-
- ///
- /// Gets wake on lan information.
- ///
- /// Information retrieved.
- /// An with the WakeOnLan infos.
- [HttpGet("WakeOnLanInfo")]
- [Authorize]
- [Obsolete("This endpoint is obsolete.")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult> GetWakeOnLanInfo()
- {
- var result = _networkManager.GetMacAddresses()
- .Select(i => new WakeOnLanInfo(i));
- return Ok(result);
- }
}
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 1939122eb0..79fa70c0b2 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -114,7 +114,7 @@ namespace Jellyfin.Server.Implementations.Users
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
- [GeneratedRegex(@"^[\w\ \-'._@+]+$")]
+ [GeneratedRegex(@"^(?!\s)[\w\ \-'._@+]+(?
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index 78a391d36d..d838144ff6 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -94,12 +94,6 @@ namespace MediaBrowser.Common.Net
/// IP address to use, or loopback address if all else fails.
string GetBindAddress(string source, out int? port);
- ///
- /// Get a list of all the MAC addresses associated with active interfaces.
- ///
- /// List of MAC addresses.
- IReadOnlyList GetMacAddresses();
-
///
/// Returns true if the address is part of the user defined LAN.
///
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index ad07a69cb6..b2f23cc11d 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1203,6 +1203,11 @@ namespace MediaBrowser.Controller.Entities
return false;
}
+ if (request.Is4K.HasValue)
+ {
+ return false;
+ }
+
if (request.IsHD.HasValue)
{
return false;
diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs
index dcc4ae88c5..65337b60fd 100644
--- a/MediaBrowser.Model/Entities/MetadataProvider.cs
+++ b/MediaBrowser.Model/Entities/MetadataProvider.cs
@@ -84,6 +84,11 @@ namespace MediaBrowser.Model.Entities
///
/// The TvMaze provider.
///
- TvMaze = 19
+ TvMaze = 19,
+
+ ///
+ /// The MusicBrainz recording provider.
+ ///
+ MusicBrainzRecording = 20,
}
}
diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs
index ef518369cc..71a131bb80 100644
--- a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs
+++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs
@@ -71,6 +71,11 @@ namespace MediaBrowser.Model.Providers
///
/// A book.
///
- Book = 13
+ Book = 13,
+
+ ///
+ /// A music recording.
+ ///
+ Recording = 14
}
}
diff --git a/MediaBrowser.Model/System/WakeOnLanInfo.cs b/MediaBrowser.Model/System/WakeOnLanInfo.cs
deleted file mode 100644
index aba19a6baf..0000000000
--- a/MediaBrowser.Model/System/WakeOnLanInfo.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System.Net.NetworkInformation;
-
-namespace MediaBrowser.Model.System
-{
- ///
- /// Provides the MAC address and port for wake-on-LAN functionality.
- ///
- public class WakeOnLanInfo
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The MAC address.
- public WakeOnLanInfo(PhysicalAddress macAddress) : this(macAddress.ToString())
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The MAC address.
- public WakeOnLanInfo(string macAddress) : this()
- {
- MacAddress = macAddress;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public WakeOnLanInfo()
- {
- Port = 9;
- }
-
- ///
- /// Gets the MAC address of the device.
- ///
- /// The MAC address.
- public string? MacAddress { get; }
-
- ///
- /// Gets or sets the wake-on-LAN port.
- ///
- /// The wake-on-LAN port.
- public int Port { get; set; }
- }
-}
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index a0481a6426..963b611515 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -19,6 +19,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
+using static Jellyfin.Extensions.StringExtensions;
namespace MediaBrowser.Providers.MediaInfo
{
@@ -400,6 +401,24 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
+ if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzRecording, out _))
+ {
+ if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_TRACKID", out var recordingMbId)
+ || track.AdditionalFields.TryGetValue("MusicBrainz Track Id", out recordingMbId))
+ && !string.IsNullOrEmpty(recordingMbId))
+ {
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, recordingMbId);
+ }
+ else if (track.AdditionalFields.TryGetValue("UFID", out var ufIdValue) && !string.IsNullOrEmpty(ufIdValue))
+ {
+ // If tagged with MB Picard, the format is 'http://musicbrainz.org\0'
+ if (ufIdValue.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase))
+ {
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, ufIdValue.AsSpan().RightPart('\0').ToString());
+ }
+ }
+ }
+
// Save extracted lyrics if they exist,
// and if the audio doesn't yet have lyrics.
var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs
new file mode 100644
index 0000000000..d2af628067
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzRecordingId.cs
@@ -0,0 +1,27 @@
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+///
+/// MusicBrainz recording id.
+///
+public class MusicBrainzRecordingId : IExternalId
+{
+ ///
+ public string ProviderName => "MusicBrainz";
+
+ ///
+ public string Key => MetadataProvider.MusicBrainzRecording.ToString();
+
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Recording;
+
+ ///
+ public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/recording/{0}";
+
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio;
+}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index d8b33a799f..ccff31ebaa 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -55,13 +55,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId)
&& !string.IsNullOrEmpty(seriesImdbId)
- && info.IndexNumber.HasValue
- && info.ParentIndexNumber.HasValue)
+ && info.IndexNumber.HasValue)
{
result.HasMetadata = await _omdbProvider.FetchEpisodeData(
result,
info.IndexNumber.Value,
- info.ParentIndexNumber.Value,
+ info.ParentIndexNumber ?? 1,
info.GetProviderId(MetadataProvider.Imdb),
seriesImdbId,
info.MetadataLanguage,
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
index d1fec7cb13..7de0e430f2 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -63,10 +63,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return Enumerable.Empty();
}
- var seasonNumber = episode.ParentIndexNumber;
+ var seasonNumber = episode.ParentIndexNumber ?? 1;
var episodeNumber = episode.IndexNumber;
- if (!seasonNumber.HasValue || !episodeNumber.HasValue)
+ if (!episodeNumber.HasValue)
{
return Enumerable.Empty();
}
@@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
var episodeResult = await _tmdbClientManager
- .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
+ .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
.ConfigureAwait(false);
var stills = episodeResult?.Images?.Stills;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
index e628abde55..c93dabb66c 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
// The search query must either provide an episode number or date
- if (!searchInfo.IndexNumber.HasValue || !searchInfo.ParentIndexNumber.HasValue)
+ if (!searchInfo.IndexNumber.HasValue)
{
return Enumerable.Empty();
}
@@ -96,10 +96,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
return metadataResult;
}
- var seasonNumber = info.ParentIndexNumber;
+ var seasonNumber = info.ParentIndexNumber ?? 1;
var episodeNumber = info.IndexNumber;
- if (!seasonNumber.HasValue || !episodeNumber.HasValue)
+ if (!episodeNumber.HasValue)
{
return metadataResult;
}
@@ -112,7 +112,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
List? result = null;
for (int? episode = startindex; episode <= endindex; episode++)
{
- var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false);
+ var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false);
if (episodeInfo is not null)
{
(result ??= new List()).Add(episodeInfo);
@@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
else
{
episodeResult = await _tmdbClientManager
- .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+ .GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
.ConfigureAwait(false);
}
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs
index c65006c7d8..024d595d58 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs
@@ -4,6 +4,8 @@ using Jellyfin.Data.Entities;
using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Interfaces;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations;
diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs
index dd01e9533b..2fbcbf79ce 100644
--- a/src/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -49,11 +49,6 @@ public class NetworkManager : INetworkManager, IDisposable
///
private bool _eventfire;
- ///
- /// List of all interface MAC addresses.
- ///
- private IReadOnlyList _macAddresses;
-
///
/// Dictionary containing interface addresses and their subnets.
///
@@ -91,7 +86,6 @@ public class NetworkManager : INetworkManager, IDisposable
_startupConfig = startupConfig;
_initLock = new();
_interfaces = new List();
- _macAddresses = new List();
_publishedServerUrls = new List();
_networkEventLock = new();
_remoteAddressFilter = new List();
@@ -215,7 +209,6 @@ public class NetworkManager : INetworkManager, IDisposable
///
/// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
- /// Generate a list of all active mac addresses that aren't loopback addresses.
///
private void InitializeInterfaces()
{
@@ -224,7 +217,6 @@ public class NetworkManager : INetworkManager, IDisposable
_logger.LogDebug("Refreshing interfaces.");
var interfaces = new List();
- var macAddresses = new List();
try
{
@@ -236,13 +228,6 @@ public class NetworkManager : INetworkManager, IDisposable
try
{
var ipProperties = adapter.GetIPProperties();
- var mac = adapter.GetPhysicalAddress();
-
- // Populate MAC list
- if (adapter.NetworkInterfaceType != NetworkInterfaceType.Loopback && !PhysicalAddress.None.Equals(mac))
- {
- macAddresses.Add(mac);
- }
// Populate interface list
foreach (var info in ipProperties.UnicastAddresses)
@@ -302,7 +287,6 @@ public class NetworkManager : INetworkManager, IDisposable
_logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count);
_logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
- _macAddresses = macAddresses;
_interfaces = interfaces;
}
}
@@ -711,13 +695,6 @@ public class NetworkManager : INetworkManager, IDisposable
return true;
}
- ///
- public IReadOnlyList GetMacAddresses()
- {
- // Populated in construction - so always has values.
- return _macAddresses;
- }
-
///
public IReadOnlyList GetLoopbacks()
{
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs
index 665afe1118..4cea53bd3d 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs
@@ -23,6 +23,10 @@ namespace Jellyfin.Server.Implementations.Tests.Users
[InlineData(" ")]
[InlineData("")]
[InlineData("special characters like & $ ? are not allowed")]
+ [InlineData("thishasaspaceontheend ")]
+ [InlineData(" thishasaspaceatthestart")]
+ [InlineData(" thishasaspaceatbothends ")]
+ [InlineData(" this has a space at both ends and inbetween ")]
public void ThrowIfInvalidUsername_WhenInvalidUsername_ThrowsArgumentException(string username)
{
Assert.Throws(() => UserManager.ThrowIfInvalidUsername(username));