mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-31 20:24:21 -04:00
Merge remote-tracking branch 'jellyfinorigin/master' into feature/DatabaseRefactor
This commit is contained in:
commit
feea5af2f3
6
.github/workflows/ci-codeql-analysis.yml
vendored
6
.github/workflows/ci-codeql-analysis.yml
vendored
@ -27,11 +27,11 @@ jobs:
|
|||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
|
uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
|
uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@9e8d0789d4a0fa9ceb6b1738f7e269594bdd67f0 # v3.28.9
|
uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10
|
||||||
|
8
.github/workflows/ci-compat.yml
vendored
8
.github/workflows/ci-compat.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
dotnet build Jellyfin.Server -o ./out
|
dotnet build Jellyfin.Server -o ./out
|
||||||
|
|
||||||
- name: Upload Head
|
- name: Upload Head
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||||
with:
|
with:
|
||||||
name: abi-head
|
name: abi-head
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@ -65,7 +65,7 @@ jobs:
|
|||||||
dotnet build Jellyfin.Server -o ./out
|
dotnet build Jellyfin.Server -o ./out
|
||||||
|
|
||||||
- name: Upload Head
|
- name: Upload Head
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||||
with:
|
with:
|
||||||
name: abi-base
|
name: abi-base
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@ -85,13 +85,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download abi-head
|
- name: Download abi-head
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||||
with:
|
with:
|
||||||
name: abi-head
|
name: abi-head
|
||||||
path: abi-head
|
path: abi-head
|
||||||
|
|
||||||
- name: Download abi-base
|
- name: Download abi-base
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||||
with:
|
with:
|
||||||
name: abi-base
|
name: abi-base
|
||||||
path: abi-base
|
path: abi-base
|
||||||
|
16
.github/workflows/ci-openapi.yml
vendored
16
.github/workflows/ci-openapi.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
- name: Generate openapi.json
|
- 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"
|
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
|
- name: Upload openapi.json
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@ -61,7 +61,7 @@ jobs:
|
|||||||
- name: Generate openapi.json
|
- 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"
|
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
|
- name: Upload openapi.json
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||||
with:
|
with:
|
||||||
name: openapi-base
|
name: openapi-base
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@ -80,12 +80,12 @@ jobs:
|
|||||||
- openapi-base
|
- openapi-base
|
||||||
steps:
|
steps:
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
- name: Download openapi-base
|
- name: Download openapi-base
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||||
with:
|
with:
|
||||||
name: openapi-base
|
name: openapi-base
|
||||||
path: openapi-base
|
path: openapi-base
|
||||||
@ -158,7 +158,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
@ -172,7 +172,7 @@ jobs:
|
|||||||
strip_components: 1
|
strip_components: 1
|
||||||
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||||
- name: Move openapi.json (unstable) into place
|
- name: Move openapi.json (unstable) into place
|
||||||
uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
|
uses: appleboy/ssh-action@8faa84277b88b6cd1455986f459aa66cf72bc8a3 # v1.2.1
|
||||||
with:
|
with:
|
||||||
host: "${{ secrets.REPO_HOST }}"
|
host: "${{ secrets.REPO_HOST }}"
|
||||||
username: "${{ secrets.REPO_USER }}"
|
username: "${{ secrets.REPO_USER }}"
|
||||||
@ -220,7 +220,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
@ -234,7 +234,7 @@ jobs:
|
|||||||
strip_components: 1
|
strip_components: 1
|
||||||
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||||
- name: Move openapi.json (stable) into place
|
- name: Move openapi.json (stable) into place
|
||||||
uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
|
uses: appleboy/ssh-action@8faa84277b88b6cd1455986f459aa66cf72bc8a3 # v1.2.1
|
||||||
with:
|
with:
|
||||||
host: "${{ secrets.REPO_HOST }}"
|
host: "${{ secrets.REPO_HOST }}"
|
||||||
username: "${{ secrets.REPO_USER }}"
|
username: "${{ secrets.REPO_USER }}"
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
|
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
|
||||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.2" />
|
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.2" />
|
||||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||||
<PackageVersion Include="z440.atl.core" Version="6.16.0" />
|
<PackageVersion Include="z440.atl.core" Version="6.17.0" />
|
||||||
<PackageVersion Include="TMDbLib" Version="2.2.0" />
|
<PackageVersion Include="TMDbLib" Version="2.2.0" />
|
||||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||||
|
@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
{
|
{
|
||||||
if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
|
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<BaseItem>? itemList = null;
|
List<BaseItem>? itemList = null;
|
||||||
@ -218,7 +218,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
if (item is null)
|
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))
|
if (!currentLinkedChildrenIds.Contains(id))
|
||||||
|
@ -458,6 +458,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
foreach (var child in children)
|
foreach (var child in children)
|
||||||
{
|
{
|
||||||
_itemRepository.DeleteItem(child.Id);
|
_itemRepository.DeleteItem(child.Id);
|
||||||
|
_cache.TryRemove(child.Id, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cache.TryRemove(item.Id, out _);
|
_cache.TryRemove(item.Id, out _);
|
||||||
@ -2631,15 +2632,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
episode.ParentIndexNumber = season.IndexNumber;
|
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)
|
if (episode.ParentIndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"DeviceOnlineWithName": "{0} belépett",
|
"DeviceOnlineWithName": "{0} belépett",
|
||||||
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet innen: {0}",
|
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet innen: {0}",
|
||||||
"Favorites": "Kedvencek",
|
"Favorites": "Kedvencek",
|
||||||
"Folders": "Könyvtárak",
|
"Folders": "Mappák",
|
||||||
"Genres": "Műfajok",
|
"Genres": "Műfajok",
|
||||||
"HeaderAlbumArtists": "Albumelőadók",
|
"HeaderAlbumArtists": "Albumelőadók",
|
||||||
"HeaderContinueWatching": "Megtekintés folytatása",
|
"HeaderContinueWatching": "Megtekintés folytatása",
|
||||||
|
@ -58,8 +58,8 @@
|
|||||||
"NotificationOptionServerRestartRequired": "Riavvio del server necessario",
|
"NotificationOptionServerRestartRequired": "Riavvio del server necessario",
|
||||||
"NotificationOptionTaskFailed": "Operazione pianificata fallita",
|
"NotificationOptionTaskFailed": "Operazione pianificata fallita",
|
||||||
"NotificationOptionUserLockedOut": "Utente bloccato",
|
"NotificationOptionUserLockedOut": "Utente bloccato",
|
||||||
"NotificationOptionVideoPlayback": "La riproduzione video è iniziata",
|
"NotificationOptionVideoPlayback": "Riproduzione video iniziata",
|
||||||
"NotificationOptionVideoPlaybackStopped": "La riproduzione video è stata interrotta",
|
"NotificationOptionVideoPlaybackStopped": "Riproduzione video interrotta",
|
||||||
"Photos": "Foto",
|
"Photos": "Foto",
|
||||||
"Playlists": "Playlist",
|
"Playlists": "Playlist",
|
||||||
"Plugin": "Plugin",
|
"Plugin": "Plugin",
|
||||||
|
@ -116,6 +116,7 @@ public partial class AudioNormalizationTask : IScheduledTask
|
|||||||
{
|
{
|
||||||
a.LUFS = await CalculateLUFSAsync(
|
a.LUFS = await CalculateLUFSAsync(
|
||||||
string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
|
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);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -142,7 +143,10 @@ public partial class AudioNormalizationTask : IScheduledTask
|
|||||||
continue;
|
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);
|
_itemRepository.SaveItems(tracks, cancellationToken);
|
||||||
@ -162,7 +166,7 @@ public partial class AudioNormalizationTask : IScheduledTask
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<float?> CalculateLUFSAsync(string inputArgs, CancellationToken cancellationToken)
|
private async Task<float?> CalculateLUFSAsync(string inputArgs, bool waitForExit, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var args = $"-hide_banner {inputArgs} -af ebur128=framelog=verbose -f null -";
|
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;
|
using var reader = process.StandardError;
|
||||||
|
float? lufs = null;
|
||||||
await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
|
await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
|
||||||
{
|
{
|
||||||
Match match = LUFSRegex().Match(line);
|
Match match = LUFSRegex().Match(line);
|
||||||
|
|
||||||
if (match.Success)
|
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");
|
if (lufs is null)
|
||||||
return null;
|
{
|
||||||
|
_logger.LogError("Failed to find LUFS value in output");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waitForExit)
|
||||||
|
{
|
||||||
|
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lufs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,20 +212,4 @@ public class SystemController : BaseJellyfinApiController
|
|||||||
FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||||
return File(stream, "text/plain; charset=utf-8");
|
return File(stream, "text/plain; charset=utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets wake on lan information.
|
|
||||||
/// </summary>
|
|
||||||
/// <response code="200">Information retrieved.</response>
|
|
||||||
/// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns>
|
|
||||||
[HttpGet("WakeOnLanInfo")]
|
|
||||||
[Authorize]
|
|
||||||
[Obsolete("This endpoint is obsolete.")]
|
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
||||||
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
|
|
||||||
{
|
|
||||||
var result = _networkManager.GetMacAddresses()
|
|
||||||
.Select(i => new WakeOnLanInfo(i));
|
|
||||||
return Ok(result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
|
// 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
|
// 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 ( )
|
// 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\ \-'._@+]+(?<!\s)$")]
|
||||||
private static partial Regex ValidUsernameRegex();
|
private static partial Regex ValidUsernameRegex();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -94,12 +94,6 @@ namespace MediaBrowser.Common.Net
|
|||||||
/// <returns>IP address to use, or loopback address if all else fails.</returns>
|
/// <returns>IP address to use, or loopback address if all else fails.</returns>
|
||||||
string GetBindAddress(string source, out int? port);
|
string GetBindAddress(string source, out int? port);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a list of all the MAC addresses associated with active interfaces.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>List of MAC addresses.</returns>
|
|
||||||
IReadOnlyList<PhysicalAddress> GetMacAddresses();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the address is part of the user defined LAN.
|
/// Returns true if the address is part of the user defined LAN.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1203,6 +1203,11 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.Is4K.HasValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (request.IsHD.HasValue)
|
if (request.IsHD.HasValue)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -84,6 +84,11 @@ namespace MediaBrowser.Model.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The TvMaze provider.
|
/// The TvMaze provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TvMaze = 19
|
TvMaze = 19,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The MusicBrainz recording provider.
|
||||||
|
/// </summary>
|
||||||
|
MusicBrainzRecording = 20,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,11 @@ namespace MediaBrowser.Model.Providers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A book.
|
/// A book.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Book = 13
|
Book = 13,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A music recording.
|
||||||
|
/// </summary>
|
||||||
|
Recording = 14
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
using System.Net.NetworkInformation;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.System
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides the MAC address and port for wake-on-LAN functionality.
|
|
||||||
/// </summary>
|
|
||||||
public class WakeOnLanInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="macAddress">The MAC address.</param>
|
|
||||||
public WakeOnLanInfo(PhysicalAddress macAddress) : this(macAddress.ToString())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="macAddress">The MAC address.</param>
|
|
||||||
public WakeOnLanInfo(string macAddress) : this()
|
|
||||||
{
|
|
||||||
MacAddress = macAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
|
|
||||||
/// </summary>
|
|
||||||
public WakeOnLanInfo()
|
|
||||||
{
|
|
||||||
Port = 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the MAC address of the device.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The MAC address.</value>
|
|
||||||
public string? MacAddress { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the wake-on-LAN port.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The wake-on-LAN port.</value>
|
|
||||||
public int Port { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,6 +19,7 @@ using MediaBrowser.Model.Entities;
|
|||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using static Jellyfin.Extensions.StringExtensions;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.MediaInfo
|
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<recording MBID>'
|
||||||
|
if (ufIdValue.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, ufIdValue.AsSpan().RightPart('\0').ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save extracted lyrics if they exist,
|
// Save extracted lyrics if they exist,
|
||||||
// and if the audio doesn't yet have lyrics.
|
// and if the audio doesn't yet have lyrics.
|
||||||
var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics;
|
var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz recording id.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicBrainzRecordingId : IExternalId
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string ProviderName => "MusicBrainz";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Key => MetadataProvider.MusicBrainzRecording.ToString();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ExternalIdMediaType? Type => ExternalIdMediaType.Recording;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/recording/{0}";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||||
|
}
|
@ -55,13 +55,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
|||||||
|
|
||||||
if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId)
|
if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId)
|
||||||
&& !string.IsNullOrEmpty(seriesImdbId)
|
&& !string.IsNullOrEmpty(seriesImdbId)
|
||||||
&& info.IndexNumber.HasValue
|
&& info.IndexNumber.HasValue)
|
||||||
&& info.ParentIndexNumber.HasValue)
|
|
||||||
{
|
{
|
||||||
result.HasMetadata = await _omdbProvider.FetchEpisodeData(
|
result.HasMetadata = await _omdbProvider.FetchEpisodeData(
|
||||||
result,
|
result,
|
||||||
info.IndexNumber.Value,
|
info.IndexNumber.Value,
|
||||||
info.ParentIndexNumber.Value,
|
info.ParentIndexNumber ?? 1,
|
||||||
info.GetProviderId(MetadataProvider.Imdb),
|
info.GetProviderId(MetadataProvider.Imdb),
|
||||||
seriesImdbId,
|
seriesImdbId,
|
||||||
info.MetadataLanguage,
|
info.MetadataLanguage,
|
||||||
|
@ -63,10 +63,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
return Enumerable.Empty<RemoteImageInfo>();
|
return Enumerable.Empty<RemoteImageInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var seasonNumber = episode.ParentIndexNumber;
|
var seasonNumber = episode.ParentIndexNumber ?? 1;
|
||||||
var episodeNumber = episode.IndexNumber;
|
var episodeNumber = episode.IndexNumber;
|
||||||
|
|
||||||
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
|
if (!episodeNumber.HasValue)
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<RemoteImageInfo>();
|
return Enumerable.Empty<RemoteImageInfo>();
|
||||||
}
|
}
|
||||||
@ -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
|
// 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
|
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);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
var stills = episodeResult?.Images?.Stills;
|
var stills = episodeResult?.Images?.Stills;
|
||||||
|
@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// The search query must either provide an episode number or date
|
// 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<RemoteSearchResult>();
|
return Enumerable.Empty<RemoteSearchResult>();
|
||||||
}
|
}
|
||||||
@ -96,10 +96,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
return metadataResult;
|
return metadataResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
var seasonNumber = info.ParentIndexNumber;
|
var seasonNumber = info.ParentIndexNumber ?? 1;
|
||||||
var episodeNumber = info.IndexNumber;
|
var episodeNumber = info.IndexNumber;
|
||||||
|
|
||||||
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
|
if (!episodeNumber.HasValue)
|
||||||
{
|
{
|
||||||
return metadataResult;
|
return metadataResult;
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
List<TvEpisode>? result = null;
|
List<TvEpisode>? result = null;
|
||||||
for (int? episode = startindex; episode <= endindex; episode++)
|
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)
|
if (episodeInfo is not null)
|
||||||
{
|
{
|
||||||
(result ??= new List<TvEpisode>()).Add(episodeInfo);
|
(result ??= new List<TvEpisode>()).Add(episodeInfo);
|
||||||
@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
episodeResult = await _tmdbClientManager
|
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);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ using Jellyfin.Data.Entities;
|
|||||||
using Jellyfin.Data.Entities.Security;
|
using Jellyfin.Data.Entities.Security;
|
||||||
using Jellyfin.Data.Interfaces;
|
using Jellyfin.Data.Interfaces;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations;
|
namespace Jellyfin.Server.Implementations;
|
||||||
|
@ -49,11 +49,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private bool _eventfire;
|
private bool _eventfire;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// List of all interface MAC addresses.
|
|
||||||
/// </summary>
|
|
||||||
private IReadOnlyList<PhysicalAddress> _macAddresses;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dictionary containing interface addresses and their subnets.
|
/// Dictionary containing interface addresses and their subnets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -91,7 +86,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
|||||||
_startupConfig = startupConfig;
|
_startupConfig = startupConfig;
|
||||||
_initLock = new();
|
_initLock = new();
|
||||||
_interfaces = new List<IPData>();
|
_interfaces = new List<IPData>();
|
||||||
_macAddresses = new List<PhysicalAddress>();
|
|
||||||
_publishedServerUrls = new List<PublishedServerUriOverride>();
|
_publishedServerUrls = new List<PublishedServerUriOverride>();
|
||||||
_networkEventLock = new();
|
_networkEventLock = new();
|
||||||
_remoteAddressFilter = new List<IPNetwork>();
|
_remoteAddressFilter = new List<IPNetwork>();
|
||||||
@ -215,7 +209,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate a list of all the interface ip addresses and submasks where that are in the active/unknown state.
|
/// 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.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeInterfaces()
|
private void InitializeInterfaces()
|
||||||
{
|
{
|
||||||
@ -224,7 +217,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
|||||||
_logger.LogDebug("Refreshing interfaces.");
|
_logger.LogDebug("Refreshing interfaces.");
|
||||||
|
|
||||||
var interfaces = new List<IPData>();
|
var interfaces = new List<IPData>();
|
||||||
var macAddresses = new List<PhysicalAddress>();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -236,13 +228,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ipProperties = adapter.GetIPProperties();
|
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
|
// Populate interface list
|
||||||
foreach (var info in ipProperties.UnicastAddresses)
|
foreach (var info in ipProperties.UnicastAddresses)
|
||||||
@ -302,7 +287,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
|||||||
_logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count);
|
_logger.LogDebug("Discovered {NumberOfInterfaces} interfaces.", interfaces.Count);
|
||||||
_logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
|
_logger.LogDebug("Interfaces addresses: {Addresses}", interfaces.OrderByDescending(s => s.AddressFamily == AddressFamily.InterNetwork).Select(s => s.Address.ToString()));
|
||||||
|
|
||||||
_macAddresses = macAddresses;
|
|
||||||
_interfaces = interfaces;
|
_interfaces = interfaces;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -711,13 +695,6 @@ public class NetworkManager : INetworkManager, IDisposable
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public IReadOnlyList<PhysicalAddress> GetMacAddresses()
|
|
||||||
{
|
|
||||||
// Populated in construction - so always has values.
|
|
||||||
return _macAddresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IReadOnlyList<IPData> GetLoopbacks()
|
public IReadOnlyList<IPData> GetLoopbacks()
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,10 @@ namespace Jellyfin.Server.Implementations.Tests.Users
|
|||||||
[InlineData(" ")]
|
[InlineData(" ")]
|
||||||
[InlineData("")]
|
[InlineData("")]
|
||||||
[InlineData("special characters like & $ ? are not allowed")]
|
[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)
|
public void ThrowIfInvalidUsername_WhenInvalidUsername_ThrowsArgumentException(string username)
|
||||||
{
|
{
|
||||||
Assert.Throws<ArgumentException>(() => UserManager.ThrowIfInvalidUsername(username));
|
Assert.Throws<ArgumentException>(() => UserManager.ThrowIfInvalidUsername(username));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user