mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 09:59:06 -07:00
Merge branch 'master' into network-rewrite
This commit is contained in:
commit
d8d5c86d49
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@ -22,16 +22,16 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3
|
uses: actions/setup-dotnet@aa983c550dfda0d1722b6ac6aed55724ffacc6d3 # v3.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '7.0.x'
|
dotnet-version: '7.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3
|
uses: github/codeql-action/init@f0e3dfb30302f8a0881bb509b044e0de4f6ef589 # v2.3.4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3
|
uses: github/codeql-action/autobuild@f0e3dfb30302f8a0881bb509b044e0de4f6ef589 # v2.3.4
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3
|
uses: github/codeql-action/analyze@f0e3dfb30302f8a0881bb509b044e0de4f6ef589 # v2.3.4
|
||||||
|
4
.github/workflows/openapi.yml
vendored
4
.github/workflows/openapi.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3
|
uses: actions/setup-dotnet@aa983c550dfda0d1722b6ac6aed55724ffacc6d3 # v3.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '7.0.x'
|
dotnet-version: '7.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
@ -51,7 +51,7 @@ jobs:
|
|||||||
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/${{ github.head_ref }})
|
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/${{ github.head_ref }})
|
||||||
git checkout --progress --force $ANCESTOR_REF
|
git checkout --progress --force $ANCESTOR_REF
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3
|
uses: actions/setup-dotnet@aa983c550dfda0d1722b6ac6aed55724ffacc6d3 # v3.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '7.0.x'
|
dotnet-version: '7.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
|
@ -126,6 +126,7 @@
|
|||||||
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||||
- [tbraeutigam](https://github.com/tbraeutigam)
|
- [tbraeutigam](https://github.com/tbraeutigam)
|
||||||
- [teacupx](https://github.com/teacupx)
|
- [teacupx](https://github.com/teacupx)
|
||||||
|
- [TelepathicWalrus](https://github.com/TelepathicWalrus)
|
||||||
- [Terror-Gene](https://github.com/Terror-Gene)
|
- [Terror-Gene](https://github.com/Terror-Gene)
|
||||||
- [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
|
- [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
|
||||||
- [ThibaultNocchi](https://github.com/ThibaultNocchi)
|
- [ThibaultNocchi](https://github.com/ThibaultNocchi)
|
||||||
|
@ -13,15 +13,15 @@
|
|||||||
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
||||||
<PackageVersion Include="BlurHashSharp" Version="1.2.0" />
|
<PackageVersion Include="BlurHashSharp" Version="1.2.0" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="coverlet.collector" Version="3.2.0" />
|
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
|
||||||
<PackageVersion Include="Diacritics" Version="3.3.18" />
|
<PackageVersion Include="Diacritics" Version="3.3.18" />
|
||||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||||
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.9.1" />
|
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.9.1" />
|
||||||
<PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
|
<PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
|
||||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||||
<PackageVersion Include="libse" Version="3.6.11" />
|
<PackageVersion Include="libse" Version="3.6.13" />
|
||||||
<PackageVersion Include="LrcParser" Version="2023.308.0" />
|
<PackageVersion Include="LrcParser" Version="2023.524.0" />
|
||||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
|
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.5" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.5" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
|
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
|
||||||
@ -46,20 +46,20 @@
|
|||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
||||||
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
||||||
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
||||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||||
<PackageVersion Include="NEbml" Version="0.11.0" />
|
<PackageVersion Include="NEbml" Version="0.11.0" />
|
||||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageVersion Include="PlaylistsNET" Version="1.3.1" />
|
<PackageVersion Include="PlaylistsNET" Version="1.3.2" />
|
||||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.0.0" />
|
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.0.0" />
|
||||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||||
<PackageVersion Include="prometheus-net" Version="8.0.0" />
|
<PackageVersion Include="prometheus-net" Version="8.0.0" />
|
||||||
<PackageVersion Include="Serilog.AspNetCore" Version="6.1.0" />
|
<PackageVersion Include="Serilog.AspNetCore" Version="7.0.0" />
|
||||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="3.4.0" />
|
<PackageVersion Include="Serilog.Settings.Configuration" Version="7.0.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
|
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
@ -71,7 +71,7 @@
|
|||||||
<PackageVersion Include="SkiaSharp" Version="2.88.3" />
|
<PackageVersion Include="SkiaSharp" Version="2.88.3" />
|
||||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||||
<PackageVersion Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
<PackageVersion Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||||
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.4" />
|
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.5" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.4.0" />
|
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.4.0" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@ -15,7 +17,10 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class DlnaHttpClient
|
/// <summary>
|
||||||
|
/// Http client for Dlna PlayTo function.
|
||||||
|
/// </summary>
|
||||||
|
public partial class DlnaHttpClient
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
@ -54,15 +59,30 @@ namespace Emby.Dlna.PlayTo
|
|||||||
LoadOptions.None,
|
LoadOptions.None,
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to parse response");
|
// try correcting the Xml response with common errors
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
var xmlString = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
_logger.LogDebug("Malformed response: {Content}\n", await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
// find and replace unescaped ampersands (&)
|
||||||
|
xmlString = EscapeAmpersandRegex().Replace(xmlString, "&");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// retry reading Xml
|
||||||
|
var xmlReader = new StringReader(xmlString);
|
||||||
|
return await XDocument.LoadAsync(
|
||||||
|
xmlReader,
|
||||||
|
LoadOptions.None,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (XmlException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to parse response");
|
||||||
|
_logger.LogDebug("Malformed response: {Content}\n", xmlString);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,5 +124,12 @@ namespace Emby.Dlna.PlayTo
|
|||||||
// Have to await here instead of returning the Task directly, otherwise request would be disposed too soon
|
// Have to await here instead of returning the Task directly, otherwise request would be disposed too soon
|
||||||
return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
|
return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compile-time generated regular expression for escaping ampersands.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Compiled regular expression.</returns>
|
||||||
|
[GeneratedRegex("(&(?![a-z]*;))")]
|
||||||
|
private static partial Regex EscapeAmpersandRegex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace Emby.Server.Implementations
|
|||||||
public static Dictionary<string, string?> DefaultConfiguration => new Dictionary<string, string?>
|
public static Dictionary<string, string?> DefaultConfiguration => new Dictionary<string, string?>
|
||||||
{
|
{
|
||||||
{ HostWebClientKey, bool.TrueString },
|
{ HostWebClientKey, bool.TrueString },
|
||||||
{ DefaultRedirectKey, "web/index.html" },
|
{ DefaultRedirectKey, "web/" },
|
||||||
{ FfmpegProbeSizeKey, "1G" },
|
{ FfmpegProbeSizeKey, "1G" },
|
||||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||||
{ PlaylistsAllowDuplicatesKey, bool.FalseString },
|
{ PlaylistsAllowDuplicatesKey, bool.FalseString },
|
||||||
|
@ -49,8 +49,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
private const string SaveItemCommandText =
|
private const string SaveItemCommandText =
|
||||||
@"replace into TypedBaseItems
|
@"replace into TypedBaseItems
|
||||||
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
|
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
|
||||||
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
|
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
@ -110,6 +110,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
"PrimaryVersionId",
|
"PrimaryVersionId",
|
||||||
"DateLastMediaAdded",
|
"DateLastMediaAdded",
|
||||||
"Album",
|
"Album",
|
||||||
|
"LUFS",
|
||||||
"CriticRating",
|
"CriticRating",
|
||||||
"IsVirtualItem",
|
"IsVirtualItem",
|
||||||
"SeriesName",
|
"SeriesName",
|
||||||
@ -489,6 +490,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
AddColumn(db, "TypedBaseItems", "PrimaryVersionId", "Text", existingColumnNames);
|
AddColumn(db, "TypedBaseItems", "PrimaryVersionId", "Text", existingColumnNames);
|
||||||
AddColumn(db, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
|
AddColumn(db, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
|
||||||
AddColumn(db, "TypedBaseItems", "Album", "Text", existingColumnNames);
|
AddColumn(db, "TypedBaseItems", "Album", "Text", existingColumnNames);
|
||||||
|
AddColumn(db, "TypedBaseItems", "LUFS", "Float", existingColumnNames);
|
||||||
AddColumn(db, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
|
AddColumn(db, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
|
||||||
AddColumn(db, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
|
AddColumn(db, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
|
||||||
AddColumn(db, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
|
AddColumn(db, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
|
||||||
@ -906,6 +908,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveItemStatement.TryBind("@Album", item.Album);
|
saveItemStatement.TryBind("@Album", item.Album);
|
||||||
|
saveItemStatement.TryBind("@LUFS", item.LUFS);
|
||||||
saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem);
|
saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem);
|
||||||
|
|
||||||
if (item is IHasSeries hasSeriesName)
|
if (item is IHasSeries hasSeriesName)
|
||||||
@ -1756,6 +1759,11 @@ namespace Emby.Server.Implementations.Data
|
|||||||
item.Album = album;
|
item.Album = album;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (reader.TryGetSingle(index++, out var lUFS))
|
||||||
|
{
|
||||||
|
item.LUFS = lUFS;
|
||||||
|
}
|
||||||
|
|
||||||
if (reader.TryGetSingle(index++, out var criticRating))
|
if (reader.TryGetSingle(index++, out var criticRating))
|
||||||
{
|
{
|
||||||
item.CriticRating = criticRating;
|
item.CriticRating = criticRating;
|
||||||
|
@ -906,6 +906,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
// Add audio info
|
// Add audio info
|
||||||
if (item is Audio audio)
|
if (item is Audio audio)
|
||||||
{
|
{
|
||||||
|
dto.LUFS = audio.LUFS;
|
||||||
dto.Album = audio.Album;
|
dto.Album = audio.Album;
|
||||||
if (audio.ExtraType.HasValue)
|
if (audio.ExtraType.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
{
|
{
|
||||||
if (args.IsDirectory)
|
if (args.IsDirectory)
|
||||||
{
|
{
|
||||||
// It's a boxset if the path is a directory with [playlist] in it's the name
|
// It's a boxset if the path is a directory with [playlist] in its name
|
||||||
var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path));
|
var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path));
|
||||||
if (string.IsNullOrEmpty(filename))
|
if (string.IsNullOrEmpty(filename))
|
||||||
{
|
{
|
||||||
@ -42,7 +42,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
return new Playlist
|
return new Playlist
|
||||||
{
|
{
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
Name = filename.Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
|
Name = filename.Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim(),
|
||||||
|
OpenAccess = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +54,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
return new Playlist
|
return new Playlist
|
||||||
{
|
{
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
Name = filename
|
Name = filename,
|
||||||
|
OpenAccess = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,7 +72,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
Name = Path.GetFileNameWithoutExtension(args.Path),
|
Name = Path.GetFileNameWithoutExtension(args.Path),
|
||||||
IsInMixedFolder = true,
|
IsInMixedFolder = true,
|
||||||
PlaylistMediaType = MediaType.Audio
|
PlaylistMediaType = MediaType.Audio,
|
||||||
|
OpenAccess = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
{
|
{
|
||||||
var justName = Path.GetFileName(path.AsSpan());
|
var justName = Path.GetFileName(path.AsSpan());
|
||||||
|
|
||||||
|
var imdbId = justName.GetAttributeValue("imdbid");
|
||||||
|
if (!string.IsNullOrEmpty(imdbId))
|
||||||
|
{
|
||||||
|
item.SetProviderId(MetadataProvider.Imdb, imdbId);
|
||||||
|
}
|
||||||
|
|
||||||
var tvdbId = justName.GetAttributeValue("tvdbid");
|
var tvdbId = justName.GetAttributeValue("tvdbid");
|
||||||
if (!string.IsNullOrEmpty(tvdbId))
|
if (!string.IsNullOrEmpty(tvdbId))
|
||||||
{
|
{
|
||||||
|
@ -46,10 +46,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
public Folder[] GetUserViews(UserViewQuery query)
|
public Folder[] GetUserViews(UserViewQuery query)
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(query.UserId);
|
var user = _userManager.GetUserById(query.UserId);
|
||||||
|
|
||||||
if (user is null)
|
if (user is null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("User Id specified in the query does not exist.", nameof(query));
|
throw new ArgumentException("User id specified in the query does not exist.", nameof(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
var folders = _libraryManager.GetUserRootFolder()
|
var folders = _libraryManager.GetUserRootFolder()
|
||||||
@ -58,7 +57,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var groupedFolders = new List<ICollectionFolder>();
|
var groupedFolders = new List<ICollectionFolder>();
|
||||||
|
|
||||||
var list = new List<Folder>();
|
var list = new List<Folder>();
|
||||||
|
|
||||||
foreach (var folder in folders)
|
foreach (var folder in folders)
|
||||||
@ -66,6 +64,20 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var collectionFolder = folder as ICollectionFolder;
|
var collectionFolder = folder as ICollectionFolder;
|
||||||
var folderViewType = collectionFolder?.CollectionType;
|
var folderViewType = collectionFolder?.CollectionType;
|
||||||
|
|
||||||
|
// Playlist library requires special handling because the folder only refrences user playlists
|
||||||
|
if (string.Equals(folderViewType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var items = folder.GetItemList(new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
ParentId = folder.ParentId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!items.Any(item => item.IsVisible(user)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (UserView.IsUserSpecific(folder))
|
if (UserView.IsUserSpecific(folder))
|
||||||
{
|
{
|
||||||
list.Add(_libraryManager.GetNamedView(user, folder.Name, folder.Id, folderViewType, null));
|
list.Add(_libraryManager.GetNamedView(user, folder.Name, folder.Id, folderViewType, null));
|
||||||
@ -132,14 +144,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
|
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
|
||||||
|
|
||||||
var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews);
|
var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews);
|
||||||
|
|
||||||
return list
|
return list
|
||||||
.OrderBy(i =>
|
.OrderBy(i =>
|
||||||
{
|
{
|
||||||
var index = Array.IndexOf(orders, i.Id);
|
var index = Array.IndexOf(orders, i.Id);
|
||||||
|
|
||||||
if (index == -1
|
if (index == -1
|
||||||
&& i is UserView view
|
&& i is UserView view
|
||||||
&& !view.DisplayParentId.Equals(default))
|
&& !view.DisplayParentId.Equals(default))
|
||||||
|
@ -462,10 +462,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
|
StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
|
||||||
foreach (ReadOnlySpan<char> i in programIds)
|
foreach (var i in programIds)
|
||||||
{
|
{
|
||||||
str.Append('"')
|
str.Append('"')
|
||||||
.Append(i.Slice(0, 10))
|
.Append(i[..10])
|
||||||
.Append("\",");
|
.Append("\",");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@
|
|||||||
"Forced": "জোরকরে",
|
"Forced": "জোরকরে",
|
||||||
"TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন.",
|
"TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন.",
|
||||||
"TaskCleanActivityLog": "কাজের ফাইল খালি করুন",
|
"TaskCleanActivityLog": "কাজের ফাইল খালি করুন",
|
||||||
"Default": "প্রাথমিক",
|
"Default": "ডিফল্ট",
|
||||||
"HearingImpaired": "দুর্বল শ্রবণক্ষমতাধরদের জন্য",
|
"HearingImpaired": "দুর্বল শ্রবণক্ষমতাধরদের জন্য",
|
||||||
"TaskOptimizeDatabaseDescription": "তথ্যভাণ্ডার সুবিন্যস্ত করে ও অব্যবহৃত জায়গা ছেড়ে দেয়। লাইব্রেরী স্ক্যান অথবা যেকোনো তথ্যভাণ্ডার পরিবর্তনের পর এই প্রক্রিয়া চালালে তথ্যভাণ্ডারের তথ্য প্রদান দ্রুততর হতে পারে।",
|
"TaskOptimizeDatabaseDescription": "তথ্যভাণ্ডার সুবিন্যস্ত করে ও অব্যবহৃত জায়গা ছেড়ে দেয়। লাইব্রেরী স্ক্যান অথবা যেকোনো তথ্যভাণ্ডার পরিবর্তনের পর এই প্রক্রিয়া চালালে তথ্যভাণ্ডারের তথ্য প্রদান দ্রুততর হতে পারে।",
|
||||||
"External": "বাহ্যিক",
|
"External": "বাহ্যিক",
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
"CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}",
|
"CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}",
|
||||||
"Books": "Grāmatas",
|
"Books": "Grāmatas",
|
||||||
"Artists": "Izpildītāji",
|
"Artists": "Izpildītāji",
|
||||||
"Albums": "Albūmi",
|
"Albums": "Albumi",
|
||||||
"ProviderValue": "Provider: {0}",
|
"ProviderValue": "Provider: {0}",
|
||||||
"HeaderFavoriteSongs": "Dziesmu Favorīti",
|
"HeaderFavoriteSongs": "Dziesmu Favorīti",
|
||||||
"HeaderFavoriteShows": "Raidījumu Favorīti",
|
"HeaderFavoriteShows": "Raidījumu Favorīti",
|
||||||
@ -121,5 +121,7 @@
|
|||||||
"TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Uzdevum palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.",
|
"TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Uzdevum palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.",
|
||||||
"TaskOptimizeDatabase": "Optimizēt datubāzi",
|
"TaskOptimizeDatabase": "Optimizēt datubāzi",
|
||||||
"External": "Ārējais",
|
"External": "Ārējais",
|
||||||
"HearingImpaired": "Ar dzirdes traucējumiem"
|
"HearingImpaired": "Ar dzirdes traucējumiem",
|
||||||
|
"TaskKeyframeExtractor": "Atslēgkadru Ekstraktors",
|
||||||
|
"TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs."
|
||||||
}
|
}
|
||||||
|
@ -119,5 +119,7 @@
|
|||||||
"Genres": "വിഭാഗങ്ങൾ",
|
"Genres": "വിഭാഗങ്ങൾ",
|
||||||
"Channels": "ചാനലുകൾ",
|
"Channels": "ചാനലുകൾ",
|
||||||
"TaskOptimizeDatabaseDescription": "ഡാറ്റാബേസ് ചുരുക്കുകയും സ്വതന്ത്ര ഇടം വെട്ടിച്ചുരുക്കുകയും ചെയ്യുന്നു. ലൈബ്രറി സ്കാൻ ചെയ്തതിനുശേഷം അല്ലെങ്കിൽ ഡാറ്റാബേസ് പരിഷ്ക്കരണങ്ങളെ സൂചിപ്പിക്കുന്ന മറ്റ് മാറ്റങ്ങൾ ചെയ്തതിന് ശേഷം ഈ ടാസ്ക് പ്രവർത്തിപ്പിക്കുന്നത് പ്രകടനം മെച്ചപ്പെടുത്തും.",
|
"TaskOptimizeDatabaseDescription": "ഡാറ്റാബേസ് ചുരുക്കുകയും സ്വതന്ത്ര ഇടം വെട്ടിച്ചുരുക്കുകയും ചെയ്യുന്നു. ലൈബ്രറി സ്കാൻ ചെയ്തതിനുശേഷം അല്ലെങ്കിൽ ഡാറ്റാബേസ് പരിഷ്ക്കരണങ്ങളെ സൂചിപ്പിക്കുന്ന മറ്റ് മാറ്റങ്ങൾ ചെയ്തതിന് ശേഷം ഈ ടാസ്ക് പ്രവർത്തിപ്പിക്കുന്നത് പ്രകടനം മെച്ചപ്പെടുത്തും.",
|
||||||
"TaskOptimizeDatabase": "ഡാറ്റാബേസ് ഒപ്റ്റിമൈസ് ചെയ്യുക"
|
"TaskOptimizeDatabase": "ഡാറ്റാബേസ് ഒപ്റ്റിമൈസ് ചെയ്യുക",
|
||||||
|
"HearingImpaired": "കേൾവി തകരാറുകൾ",
|
||||||
|
"External": "പുറമേയുള്ള"
|
||||||
}
|
}
|
||||||
|
@ -19,5 +19,24 @@
|
|||||||
"Channels": "ఛానెల్లు",
|
"Channels": "ఛానెల్లు",
|
||||||
"Books": "పుస్తకాలు",
|
"Books": "పుస్తకాలు",
|
||||||
"Artists": "కళాకారులు",
|
"Artists": "కళాకారులు",
|
||||||
"Albums": "ఆల్బమ్లు"
|
"Albums": "ఆల్బమ్లు",
|
||||||
|
"HearingImpaired": "వినికిడి లోపం",
|
||||||
|
"HomeVideos": "హోమ్ వీడియోలు",
|
||||||
|
"AppDeviceValues": "అప్లికేషన్ : {0}, పరికరం: {1}",
|
||||||
|
"Application": "అప్లికేషన్",
|
||||||
|
"AuthenticationSucceededWithUserName": "విజయవంతంగా ఆమోదించబడింది",
|
||||||
|
"CameraImageUploadedFrom": "{0} నుండి కొత్త కెమెరా చిత్రం అప్లోడ్ చేయబడింది",
|
||||||
|
"ChapterNameValue": "అధ్యాయం",
|
||||||
|
"DeviceOfflineWithName": "{0} డిస్కనెక్ట్ చేయబడింది",
|
||||||
|
"DeviceOnlineWithName": "{0} కనెక్ట్ చేయబడింది",
|
||||||
|
"External": "బాహ్య",
|
||||||
|
"FailedLoginAttemptWithUserName": "{0} నుండి విఫలమైన లాగిన్ ప్రయత్నం",
|
||||||
|
"HeaderFavoriteAlbums": "ఇష్టమైన ఆల్బమ్లు",
|
||||||
|
"HeaderFavoriteArtists": "ఇష్టమైన కళాకారులు",
|
||||||
|
"HeaderFavoriteEpisodes": "ఇష్టమైన ఎపిసోడ్లు",
|
||||||
|
"HeaderFavoriteShows": "ఇష్టమైన ప్రదర్శనలు",
|
||||||
|
"HeaderFavoriteSongs": "ఇష్టమైన పాటలు",
|
||||||
|
"HeaderLiveTV": "ప్రత్యక్ష TV",
|
||||||
|
"HeaderNextUp": "తదుపరి",
|
||||||
|
"HeaderRecordingGroups": "రికార్డింగ్ గుంపులు"
|
||||||
}
|
}
|
||||||
|
@ -123,5 +123,6 @@
|
|||||||
"TaskOptimizeDatabase": "Veritabanını optimize et",
|
"TaskOptimizeDatabase": "Veritabanını optimize et",
|
||||||
"TaskKeyframeExtractorDescription": "Daha hassas HLS çalma listeleri oluşturmak için video dosyalarından kareleri çıkarır. Bu görev uzun bir süre çalışabilir.",
|
"TaskKeyframeExtractorDescription": "Daha hassas HLS çalma listeleri oluşturmak için video dosyalarından kareleri çıkarır. Bu görev uzun bir süre çalışabilir.",
|
||||||
"TaskKeyframeExtractor": "Kare Ayırt Edici",
|
"TaskKeyframeExtractor": "Kare Ayırt Edici",
|
||||||
"External": "Harici"
|
"External": "Harici",
|
||||||
|
"HearingImpaired": "Duyma engelli"
|
||||||
}
|
}
|
||||||
|
@ -67,9 +67,8 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
public async Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest options)
|
public async Task<PlaylistCreationResult> CreatePlaylist(PlaylistCreationRequest options)
|
||||||
{
|
{
|
||||||
var name = options.Name;
|
var name = options.Name;
|
||||||
|
|
||||||
var folderName = _fileSystem.GetValidFilename(name);
|
var folderName = _fileSystem.GetValidFilename(name);
|
||||||
var parentFolder = GetPlaylistsFolder(Guid.Empty);
|
var parentFolder = GetPlaylistsFolder(options.UserId);
|
||||||
if (parentFolder is null)
|
if (parentFolder is null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(nameof(parentFolder));
|
throw new ArgumentException(nameof(parentFolder));
|
||||||
@ -80,7 +79,6 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
foreach (var itemId in options.ItemIdList)
|
foreach (var itemId in options.ItemIdList)
|
||||||
{
|
{
|
||||||
var item = _libraryManager.GetItemById(itemId);
|
var item = _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
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");
|
||||||
@ -121,7 +119,6 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
}
|
}
|
||||||
|
|
||||||
var user = _userManager.GetUserById(options.UserId);
|
var user = _userManager.GetUserById(options.UserId);
|
||||||
|
|
||||||
var path = Path.Combine(parentFolder.Path, folderName);
|
var path = Path.Combine(parentFolder.Path, folderName);
|
||||||
path = GetTargetPath(path);
|
path = GetTargetPath(path);
|
||||||
|
|
||||||
@ -130,7 +127,6 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
|
||||||
var playlist = new Playlist
|
var playlist = new Playlist
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
@ -140,7 +136,6 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
};
|
};
|
||||||
|
|
||||||
playlist.SetMediaType(options.MediaType);
|
playlist.SetMediaType(options.MediaType);
|
||||||
|
|
||||||
parentFolder.AddChild(playlist);
|
parentFolder.AddChild(playlist);
|
||||||
|
|
||||||
await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None)
|
await playlist.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = true }, CancellationToken.None)
|
||||||
@ -326,7 +321,8 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SavePlaylistFile(Playlist item)
|
/// <inheritdoc />
|
||||||
|
public void SavePlaylistFile(Playlist item)
|
||||||
{
|
{
|
||||||
// this is probably best done as a metadata provider
|
// this is probably best done as a metadata provider
|
||||||
// saving a file over itself will require some work to prevent this from happening when not needed
|
// saving a file over itself will require some work to prevent this from happening when not needed
|
||||||
@ -549,7 +545,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
SavePlaylistFile(playlist);
|
SavePlaylistFile(playlist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (!playlist.OpenAccess)
|
||||||
{
|
{
|
||||||
// Remove playlist if not shared
|
// Remove playlist if not shared
|
||||||
_libraryManager.DeleteItem(
|
_libraryManager.DeleteItem(
|
||||||
@ -564,20 +560,5 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async Task UpdatePlaylistAsync(Playlist playlist)
|
|
||||||
{
|
|
||||||
var currentPlaylist = (Playlist)_libraryManager.GetItemById(playlist.Id);
|
|
||||||
currentPlaylist.OwnerUserId = playlist.OwnerUserId;
|
|
||||||
currentPlaylist.Shares = playlist.Shares;
|
|
||||||
|
|
||||||
await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (currentPlaylist.IsFile)
|
|
||||||
{
|
|
||||||
SavePlaylistFile(currentPlaylist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,6 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists;
|
public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists;
|
||||||
|
|
||||||
public override bool IsVisible(User user)
|
|
||||||
{
|
|
||||||
return base.IsVisible(user) && GetChildren(user, true).Any();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
|
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
|
||||||
{
|
{
|
||||||
return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
|
return base.GetEligibleChildrenForRecursiveChildren(user).OfType<Playlist>();
|
||||||
@ -47,7 +42,6 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
|
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
|
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
|
||||||
query.Parent = null;
|
|
||||||
return LibraryManager.GetItemsResult(query);
|
return LibraryManager.GetItemsResult(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,8 +251,6 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
channel.Height = request.Height.Value;
|
channel.Height = request.Height.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Tags = request.Tags;
|
|
||||||
|
|
||||||
if (request.Taglines is not null)
|
if (request.Taglines is not null)
|
||||||
{
|
{
|
||||||
item.Tagline = request.Taglines.FirstOrDefault();
|
item.Tagline = request.Taglines.FirstOrDefault();
|
||||||
@ -276,12 +274,19 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
item.OfficialRating = request.OfficialRating;
|
item.OfficialRating = request.OfficialRating;
|
||||||
item.CustomRating = request.CustomRating;
|
item.CustomRating = request.CustomRating;
|
||||||
|
|
||||||
|
var currentTags = item.Tags;
|
||||||
|
var newTags = request.Tags;
|
||||||
|
var removedTags = currentTags.Except(newTags).ToList();
|
||||||
|
var addedTags = newTags.Except(currentTags).ToList();
|
||||||
|
item.Tags = newTags;
|
||||||
|
|
||||||
if (item is Series rseries)
|
if (item is Series rseries)
|
||||||
{
|
{
|
||||||
foreach (Season season in rseries.Children)
|
foreach (Season season in rseries.Children)
|
||||||
{
|
{
|
||||||
season.OfficialRating = request.OfficialRating;
|
season.OfficialRating = request.OfficialRating;
|
||||||
season.CustomRating = request.CustomRating;
|
season.CustomRating = request.CustomRating;
|
||||||
|
season.Tags = season.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
|
||||||
season.OnMetadataChanged();
|
season.OnMetadataChanged();
|
||||||
await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
@ -289,6 +294,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
ep.OfficialRating = request.OfficialRating;
|
ep.OfficialRating = request.OfficialRating;
|
||||||
ep.CustomRating = request.CustomRating;
|
ep.CustomRating = request.CustomRating;
|
||||||
|
ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
|
||||||
ep.OnMetadataChanged();
|
ep.OnMetadataChanged();
|
||||||
await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -300,6 +306,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
ep.OfficialRating = request.OfficialRating;
|
ep.OfficialRating = request.OfficialRating;
|
||||||
ep.CustomRating = request.CustomRating;
|
ep.CustomRating = request.CustomRating;
|
||||||
|
ep.Tags = ep.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
|
||||||
ep.OnMetadataChanged();
|
ep.OnMetadataChanged();
|
||||||
await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -310,6 +317,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
track.OfficialRating = request.OfficialRating;
|
track.OfficialRating = request.OfficialRating;
|
||||||
track.CustomRating = request.CustomRating;
|
track.CustomRating = request.CustomRating;
|
||||||
|
track.Tags = track.Tags.Concat(addedTags).Except(removedTags).Distinct().ToArray();
|
||||||
track.OnMetadataChanged();
|
track.OnMetadataChanged();
|
||||||
await track.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
await track.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -503,6 +503,7 @@ public class ItemsController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query.Parent = null;
|
||||||
result = folder.GetItems(query);
|
result = folder.GetItems(query);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -511,10 +512,12 @@ public class ItemsController : BaseJellyfinApiController
|
|||||||
result = new QueryResult<BaseItem>(itemsArray);
|
result = new QueryResult<BaseItem>(itemsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// result might include items not accessible by the user, DtoService will remove them
|
||||||
|
var accessibleItems = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user);
|
||||||
return new QueryResult<BaseItemDto>(
|
return new QueryResult<BaseItemDto>(
|
||||||
startIndex,
|
startIndex,
|
||||||
result.TotalRecordCount,
|
accessibleItems.Count,
|
||||||
_dtoService.GetBaseItemDtos(result.Items, dtoOptions, user));
|
accessibleItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -64,6 +64,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
/// <param name="userId">The user id.</param>
|
/// <param name="userId">The user id.</param>
|
||||||
/// <param name="mediaType">The media type.</param>
|
/// <param name="mediaType">The media type.</param>
|
||||||
/// <param name="createPlaylistRequest">The create playlist payload.</param>
|
/// <param name="createPlaylistRequest">The create playlist payload.</param>
|
||||||
|
/// <response code="200">Playlist created.</response>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A <see cref="Task" /> that represents the asynchronous operation to create a playlist.
|
/// A <see cref="Task" /> that represents the asynchronous operation to create a playlist.
|
||||||
/// The task result contains an <see cref="OkResult"/> indicating success.
|
/// The task result contains an <see cref="OkResult"/> indicating success.
|
||||||
@ -167,6 +168,8 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
/// <response code="404">Playlist not found.</response>
|
/// <response code="404">Playlist not found.</response>
|
||||||
/// <returns>The original playlist items.</returns>
|
/// <returns>The original playlist items.</returns>
|
||||||
[HttpGet("{playlistId}/Items")]
|
[HttpGet("{playlistId}/Items")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
|
public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
|
||||||
[FromRoute, Required] Guid playlistId,
|
[FromRoute, Required] Guid playlistId,
|
||||||
[FromQuery, Required] Guid userId,
|
[FromQuery, Required] Guid userId,
|
||||||
@ -189,9 +192,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
: _userManager.GetUserById(userId);
|
: _userManager.GetUserById(userId);
|
||||||
|
|
||||||
var items = playlist.GetManageableItems().ToArray();
|
var items = playlist.GetManageableItems().ToArray();
|
||||||
|
|
||||||
var count = items.Length;
|
var count = items.Length;
|
||||||
|
|
||||||
if (startIndex.HasValue)
|
if (startIndex.HasValue)
|
||||||
{
|
{
|
||||||
items = items.Skip(startIndex.Value).ToArray();
|
items = items.Skip(startIndex.Value).ToArray();
|
||||||
@ -207,7 +208,6 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
|
var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
|
||||||
|
|
||||||
for (int index = 0; index < dtos.Count; index++)
|
for (int index = 0; index < dtos.Count; index++)
|
||||||
{
|
{
|
||||||
dtos[index].PlaylistItemId = items[index].Item1.Id;
|
dtos[index].PlaylistItemId = items[index].Item1.Id;
|
||||||
|
@ -48,8 +48,6 @@ public class BaseUrlRedirectionMiddleware
|
|||||||
if (string.IsNullOrEmpty(localPath)
|
if (string.IsNullOrEmpty(localPath)
|
||||||
|| string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(localPath, baseUrlPrefix + "/web", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(localPath, baseUrlPrefix + "/web/", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
|| !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@ -53,13 +54,21 @@ internal class FixPlaylistOwner : IMigrationRoutine
|
|||||||
foreach (var playlist in playlists)
|
foreach (var playlist in playlists)
|
||||||
{
|
{
|
||||||
var shares = playlist.Shares;
|
var shares = playlist.Shares;
|
||||||
var firstEditShare = shares.First(x => x.CanEdit);
|
if (shares.Length > 0)
|
||||||
if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid))
|
|
||||||
{
|
{
|
||||||
playlist.OwnerUserId = guid;
|
var firstEditShare = shares.First(x => x.CanEdit);
|
||||||
playlist.Shares = shares.Where(x => x != firstEditShare).ToArray();
|
if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid))
|
||||||
|
{
|
||||||
_playlistManager.UpdatePlaylistAsync(playlist).GetAwaiter().GetResult();
|
playlist.OwnerUserId = guid;
|
||||||
|
playlist.Shares = shares.Where(x => x != firstEditShare).ToArray();
|
||||||
|
playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
_playlistManager.SavePlaylistFile(playlist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
playlist.OpenAccess = true;
|
||||||
|
playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ namespace Jellyfin.Server
|
|||||||
|
|
||||||
// This must be injected before any path related middleware.
|
// This must be injected before any path related middleware.
|
||||||
mainApp.UsePathTrim();
|
mainApp.UsePathTrim();
|
||||||
mainApp.UseStaticFiles();
|
|
||||||
if (appConfig.HostWebClient())
|
if (appConfig.HostWebClient())
|
||||||
{
|
{
|
||||||
var extensionProvider = new FileExtensionContentTypeProvider();
|
var extensionProvider = new FileExtensionContentTypeProvider();
|
||||||
@ -190,6 +190,11 @@ namespace Jellyfin.Server
|
|||||||
// subtitles octopus requires .data, .mem files.
|
// subtitles octopus requires .data, .mem files.
|
||||||
extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet);
|
extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet);
|
||||||
extensionProvider.Mappings.Add(".mem", MediaTypeNames.Application.Octet);
|
extensionProvider.Mappings.Add(".mem", MediaTypeNames.Application.Octet);
|
||||||
|
mainApp.UseDefaultFiles(new DefaultFilesOptions
|
||||||
|
{
|
||||||
|
FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
|
||||||
|
RequestPath = "/web"
|
||||||
|
});
|
||||||
mainApp.UseStaticFiles(new StaticFileOptions
|
mainApp.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
|
FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath),
|
||||||
@ -200,6 +205,7 @@ namespace Jellyfin.Server
|
|||||||
mainApp.UseRobotsRedirection();
|
mainApp.UseRobotsRedirection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mainApp.UseStaticFiles();
|
||||||
mainApp.UseAuthentication();
|
mainApp.UseAuthentication();
|
||||||
mainApp.UseJellyfinApiSwagger(_serverConfigurationManager);
|
mainApp.UseJellyfinApiSwagger(_serverConfigurationManager);
|
||||||
mainApp.UseQueryStringDecoding();
|
mainApp.UseQueryStringDecoding();
|
||||||
|
@ -128,6 +128,13 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string Album { get; set; }
|
public string Album { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the LUFS value.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The LUFS Value.</value>
|
||||||
|
[JsonIgnore]
|
||||||
|
public float LUFS { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the channel identifier.
|
/// Gets or sets the channel identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -66,10 +66,9 @@ namespace MediaBrowser.Controller.Playlists
|
|||||||
Task RemovePlaylistsAsync(Guid userId);
|
Task RemovePlaylistsAsync(Guid userId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates a playlist.
|
/// Saves a playlist.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playlist">The updated playlist.</param>
|
/// <param name="item">The playlist.</param>
|
||||||
/// <returns>Task.</returns>
|
void SavePlaylistFile(Playlist item);
|
||||||
Task UpdatePlaylistAsync(Playlist playlist);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,10 +34,13 @@ namespace MediaBrowser.Controller.Playlists
|
|||||||
public Playlist()
|
public Playlist()
|
||||||
{
|
{
|
||||||
Shares = Array.Empty<Share>();
|
Shares = Array.Empty<Share>();
|
||||||
|
OpenAccess = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid OwnerUserId { get; set; }
|
public Guid OwnerUserId { get; set; }
|
||||||
|
|
||||||
|
public bool OpenAccess { get; set; }
|
||||||
|
|
||||||
public Share[] Shares { get; set; }
|
public Share[] Shares { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
@ -233,6 +236,11 @@ namespace MediaBrowser.Controller.Playlists
|
|||||||
return base.IsVisible(user);
|
return base.IsVisible(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (OpenAccess)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var userId = user.Id;
|
var userId = user.Id;
|
||||||
if (userId.Equals(OwnerUserId))
|
if (userId.Equals(OwnerUserId))
|
||||||
{
|
{
|
||||||
|
@ -30,6 +30,8 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
|
|
||||||
public bool EnableRealtimeMonitor { get; set; }
|
public bool EnableRealtimeMonitor { get; set; }
|
||||||
|
|
||||||
|
public bool EnableLUFSScan { get; set; }
|
||||||
|
|
||||||
public bool EnableChapterImageExtraction { get; set; }
|
public bool EnableChapterImageExtraction { get; set; }
|
||||||
|
|
||||||
public bool ExtractChapterImagesDuringLibraryScan { get; set; }
|
public bool ExtractChapterImagesDuringLibraryScan { get; set; }
|
||||||
|
@ -779,6 +779,12 @@ namespace MediaBrowser.Model.Dto
|
|||||||
/// <value>The timer identifier.</value>
|
/// <value>The timer identifier.</value>
|
||||||
public string TimerId { get; set; }
|
public string TimerId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the LUFS value.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The LUFS Value.</value>
|
||||||
|
public float LUFS { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the current program.
|
/// Gets or sets the current program.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
@ -14,6 +17,7 @@ using MediaBrowser.Model.Dlna;
|
|||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using TagLib;
|
using TagLib;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.MediaInfo
|
namespace MediaBrowser.Providers.MediaInfo
|
||||||
@ -23,6 +27,10 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioFileProber
|
public class AudioFileProber
|
||||||
{
|
{
|
||||||
|
// Default LUFS value for use with the web interface, at -18db gain will be 1(no db gain).
|
||||||
|
private const float DefaultLUFSValue = -18;
|
||||||
|
|
||||||
|
private readonly ILogger<AudioFileProber> _logger;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@ -31,16 +39,19 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AudioFileProber"/> class.
|
/// Initializes a new instance of the <see cref="AudioFileProber"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
|
||||||
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
||||||
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
||||||
/// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param>
|
/// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param>
|
||||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||||
public AudioFileProber(
|
public AudioFileProber(
|
||||||
|
ILogger<AudioFileProber> logger,
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IItemRepository itemRepo,
|
IItemRepository itemRepo,
|
||||||
ILibraryManager libraryManager)
|
ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_itemRepo = itemRepo;
|
_itemRepo = itemRepo;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
@ -89,6 +100,54 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
Fetch(item, result, cancellationToken);
|
Fetch(item, result, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
||||||
|
|
||||||
|
if (libraryOptions.EnableLUFSScan)
|
||||||
|
{
|
||||||
|
string output;
|
||||||
|
using (var process = new Process()
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
|
Arguments = $"-hide_banner -i \"{path}\" -af ebur128=framelog=verbose -f null -",
|
||||||
|
RedirectStandardOutput = false,
|
||||||
|
RedirectStandardError = true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error starting ffmpeg");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
output = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
MatchCollection split = Regex.Matches(output, @"I:\s+(.*?)\s+LUFS");
|
||||||
|
|
||||||
|
if (split.Count != 0)
|
||||||
|
{
|
||||||
|
item.LUFS = float.Parse(split[0].Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.LUFS = DefaultLUFSValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.LUFS = DefaultLUFSValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("LUFS for {ItemName} is {LUFS}.", item.Name, item.LUFS);
|
||||||
|
|
||||||
return ItemUpdateType.MetadataImport;
|
return ItemUpdateType.MetadataImport;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +255,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
audio.Album = tags.Album;
|
audio.Album = tags.Album;
|
||||||
audio.IndexNumber = Convert.ToInt32(tags.Track);
|
audio.IndexNumber = Convert.ToInt32(tags.Track);
|
||||||
audio.ParentIndexNumber = Convert.ToInt32(tags.Disc);
|
audio.ParentIndexNumber = Convert.ToInt32(tags.Disc);
|
||||||
|
|
||||||
if (tags.Year != 0)
|
if (tags.Year != 0)
|
||||||
{
|
{
|
||||||
var year = Convert.ToInt32(tags.Year);
|
var year = Convert.ToInt32(tags.Year);
|
||||||
|
@ -79,7 +79,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
NamingOptions namingOptions)
|
NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger<ProbeProvider>();
|
_logger = loggerFactory.CreateLogger<ProbeProvider>();
|
||||||
_audioProber = new AudioFileProber(mediaSourceManager, mediaEncoder, itemRepo, libraryManager);
|
_audioProber = new AudioFileProber(loggerFactory.CreateLogger<AudioFileProber>(), mediaSourceManager, mediaEncoder, itemRepo, libraryManager);
|
||||||
_audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
|
_audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
|
||||||
_subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
|
_subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
|
||||||
_videoProber = new FFProbeVideoInfo(
|
_videoProber = new FFProbeVideoInfo(
|
||||||
|
Loading…
Reference in New Issue
Block a user