mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-11 10:10:35 +01:00
Merge remote-tracking branch 'upstream/master' into network-rewrite
This commit is contained in:
@@ -22,7 +22,6 @@ using Emby.Drawing;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Notifications;
|
||||
using Emby.Photos;
|
||||
using Emby.Server.Implementations.Archiving;
|
||||
using Emby.Server.Implementations.Channels;
|
||||
using Emby.Server.Implementations.Collections;
|
||||
using Emby.Server.Implementations.Configuration;
|
||||
@@ -49,6 +48,7 @@ using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.MediaEncoding.Hls.Playlist;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Networking.Manager;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Events;
|
||||
@@ -67,6 +67,7 @@ using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Lyrics;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Notifications;
|
||||
@@ -94,12 +95,14 @@ using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Providers.Chapters;
|
||||
using MediaBrowser.Providers.Lyric;
|
||||
using MediaBrowser.Providers.Manager;
|
||||
using MediaBrowser.Providers.Plugins.Tmdb;
|
||||
using MediaBrowser.Providers.Subtitles;
|
||||
using MediaBrowser.XbmcMetadata.Providers;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -559,8 +562,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
||||
|
||||
serviceCollection.AddSingleton<IZipClient, ZipClient>();
|
||||
|
||||
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||
serviceCollection.AddSingleton(ApplicationPaths);
|
||||
|
||||
@@ -598,6 +599,7 @@ namespace Emby.Server.Implementations
|
||||
serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
|
||||
|
||||
serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
|
||||
serviceCollection.AddSingleton<ILyricManager, LyricManager>();
|
||||
|
||||
serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
|
||||
|
||||
@@ -652,6 +654,17 @@ namespace Emby.Server.Implementations
|
||||
/// <returns>A task representing the service initialization operation.</returns>
|
||||
public async Task InitializeServices()
|
||||
{
|
||||
var jellyfinDb = await Resolve<IDbContextFactory<JellyfinDb>>().CreateDbContextAsync().ConfigureAwait(false);
|
||||
await using (jellyfinDb.ConfigureAwait(false))
|
||||
{
|
||||
if ((await jellyfinDb.Database.GetPendingMigrationsAsync().ConfigureAwait(false)).Any())
|
||||
{
|
||||
Logger.LogInformation("There are pending EFCore migrations in the database. Applying... (This may take a while, do not stop Jellyfin)");
|
||||
await jellyfinDb.Database.MigrateAsync().ConfigureAwait(false);
|
||||
Logger.LogInformation("EFCore migrations applied successfully");
|
||||
}
|
||||
}
|
||||
|
||||
var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
|
||||
await localizationManager.LoadAll().ConfigureAwait(false);
|
||||
|
||||
@@ -1089,15 +1102,7 @@ namespace Emby.Server.Implementations
|
||||
return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
|
||||
}
|
||||
|
||||
// Published server ends with a /
|
||||
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
||||
{
|
||||
// Published server ends with a '/', so we need to remove it.
|
||||
return PublishedServerUrl.Trim('/');
|
||||
}
|
||||
|
||||
string smart = NetManager.GetBindInterface(request, out var port);
|
||||
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
|
||||
return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using System.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.GZip;
|
||||
|
||||
namespace Emby.Server.Implementations.Archiving
|
||||
{
|
||||
/// <summary>
|
||||
/// Class DotNetZipClient.
|
||||
/// </summary>
|
||||
public class ZipClient : IZipClient
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using var reader = GZipReader.Open(source);
|
||||
var options = new ExtractionOptions
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = overwriteExistingFiles
|
||||
};
|
||||
|
||||
Directory.CreateDirectory(targetPath);
|
||||
reader.WriteAllToDirectory(targetPath, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
|
||||
{
|
||||
using var reader = GZipReader.Open(source);
|
||||
if (reader.MoveToNextEntry())
|
||||
{
|
||||
var entry = reader.Entry;
|
||||
|
||||
var filename = entry.Key;
|
||||
if (string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
filename = defaultFileName;
|
||||
}
|
||||
|
||||
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,10 +232,10 @@ namespace Emby.Server.Implementations.Collections
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
var newList = collection.LinkedChildren.ToList();
|
||||
newList.AddRange(list);
|
||||
collection.LinkedChildren = newList.ToArray();
|
||||
|
||||
LinkedChild[] newChildren = new LinkedChild[collection.LinkedChildren.Length + list.Count];
|
||||
collection.LinkedChildren.CopyTo(newChildren, 0);
|
||||
list.CopyTo(newChildren, collection.LinkedChildren.Length);
|
||||
collection.LinkedChildren = newChildren;
|
||||
collection.UpdateRatingToItems(linkedChildrenList);
|
||||
|
||||
await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
@@ -178,7 +178,8 @@ namespace Emby.Server.Implementations.Data
|
||||
"RpuPresentFlag",
|
||||
"ElPresentFlag",
|
||||
"BlPresentFlag",
|
||||
"DvBlSignalCompatibilityId"
|
||||
"DvBlSignalCompatibilityId",
|
||||
"IsHearingImpaired"
|
||||
};
|
||||
|
||||
private static readonly string _mediaStreamSaveColumnsInsertQuery =
|
||||
@@ -349,7 +350,8 @@ namespace Emby.Server.Implementations.Data
|
||||
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
|
||||
{
|
||||
const string CreateMediaStreamsTableCommand
|
||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, IsHearingImpaired BIT NULL, PRIMARY KEY (ItemId, StreamIndex))";
|
||||
|
||||
const string CreateMediaAttachmentsTableCommand
|
||||
= "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecTag TEXT NULL, Comment TEXT NULL, Filename TEXT NULL, MIMEType TEXT NULL, PRIMARY KEY (ItemId, AttachmentIndex))";
|
||||
|
||||
@@ -572,6 +574,8 @@ namespace Emby.Server.Implementations.Data
|
||||
AddColumn(db, "MediaStreams", "ElPresentFlag", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "BlPresentFlag", "INT", existingColumnNames);
|
||||
AddColumn(db, "MediaStreams", "DvBlSignalCompatibilityId", "INT", existingColumnNames);
|
||||
|
||||
AddColumn(db, "MediaStreams", "IsHearingImpaired", "BIT", existingColumnNames);
|
||||
},
|
||||
TransactionMode);
|
||||
|
||||
@@ -2452,6 +2456,8 @@ namespace Emby.Server.Implementations.Data
|
||||
builder.Append('(');
|
||||
|
||||
builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)");
|
||||
builder.Append("+ ((CleanName = @SearchTermStartsWith COLLATE NOCASE or (OriginalTitle not null and OriginalTitle = @SearchTermStartsWith COLLATE NOCASE)) * 10)");
|
||||
|
||||
|
||||
if (query.SearchTerm.Length > 1)
|
||||
{
|
||||
@@ -3150,6 +3156,11 @@ namespace Emby.Server.Implementations.Data
|
||||
return ItemSortBy.SimilarityScore;
|
||||
}
|
||||
|
||||
if (string.Equals(name, ItemSortBy.SearchScore, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ItemSortBy.SearchScore;
|
||||
}
|
||||
|
||||
// Unknown SortBy, just sort by the SortName.
|
||||
return ItemSortBy.SortName;
|
||||
}
|
||||
@@ -3520,6 +3531,13 @@ namespace Emby.Server.Implementations.Data
|
||||
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||
}
|
||||
|
||||
if (query.MinParentAndIndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("((ParentIndexNumber=@MinParentAndIndexNumberParent and IndexNumber>=@MinParentAndIndexNumberIndex) or ParentIndexNumber>@MinParentAndIndexNumberParent)");
|
||||
statement?.TryBind("@MinParentAndIndexNumberParent", query.MinParentAndIndexNumber.Value.ParentIndexNumber);
|
||||
statement?.TryBind("@MinParentAndIndexNumberIndex", query.MinParentAndIndexNumber.Value.IndexNumber);
|
||||
}
|
||||
|
||||
if (query.MinDateCreated.HasValue)
|
||||
{
|
||||
whereClauses.Add("DateCreated>=@MinDateCreated");
|
||||
@@ -5836,6 +5854,8 @@ AND Type = @InternalPersonType)");
|
||||
statement.TryBind("@ElPresentFlag" + index, stream.ElPresentFlag);
|
||||
statement.TryBind("@BlPresentFlag" + index, stream.BlPresentFlag);
|
||||
statement.TryBind("@DvBlSignalCompatibilityId" + index, stream.DvBlSignalCompatibilityId);
|
||||
|
||||
statement.TryBind("@IsHearingImpaired" + index, stream.IsHearingImpaired);
|
||||
}
|
||||
|
||||
statement.Reset();
|
||||
@@ -6047,12 +6067,15 @@ AND Type = @InternalPersonType)");
|
||||
item.DvBlSignalCompatibilityId = dvBlSignalCompatibilityId;
|
||||
}
|
||||
|
||||
item.IsHearingImpaired = reader.GetBoolean(43);
|
||||
|
||||
if (item.Type == MediaStreamType.Subtitle)
|
||||
{
|
||||
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
|
||||
item.LocalizedDefault = _localization.GetLocalizedString("Default");
|
||||
item.LocalizedForced = _localization.GetLocalizedString("Forced");
|
||||
item.LocalizedExternal = _localization.GetLocalizedString("External");
|
||||
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
@@ -18,6 +19,7 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Lyrics;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
@@ -50,6 +52,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
|
||||
|
||||
private readonly ILyricManager _lyricManager;
|
||||
|
||||
public DtoService(
|
||||
ILogger<DtoService> logger,
|
||||
ILibraryManager libraryManager,
|
||||
@@ -59,7 +63,8 @@ namespace Emby.Server.Implementations.Dto
|
||||
IProviderManager providerManager,
|
||||
IApplicationHost appHost,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
Lazy<ILiveTvManager> livetvManagerFactory)
|
||||
Lazy<ILiveTvManager> livetvManagerFactory,
|
||||
ILyricManager lyricManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
@@ -70,6 +75,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
_appHost = appHost;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_livetvManagerFactory = livetvManagerFactory;
|
||||
_lyricManager = lyricManager;
|
||||
}
|
||||
|
||||
private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
|
||||
@@ -139,6 +145,10 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
|
||||
}
|
||||
else if (item is Audio)
|
||||
{
|
||||
dto.HasLyrics = _lyricManager.HasLyricFile(item);
|
||||
}
|
||||
|
||||
if (item is IItemByName itemByName
|
||||
&& options.ContainsField(ItemFields.ItemCounts))
|
||||
|
||||
@@ -25,14 +25,13 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
|
||||
<PackageReference Include="sharpcompress" Version="0.32.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.11" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.4" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
}
|
||||
|
||||
var collectionFolders = _libraryManager.GetCollectionFolders(item).ToList();
|
||||
var collectionFolders = _libraryManager.GetCollectionFolders(item);
|
||||
|
||||
foreach (var collectionFolder in collectionFolders)
|
||||
{
|
||||
|
||||
@@ -79,14 +79,6 @@ namespace Emby.Server.Implementations.IO
|
||||
TemporarilyIgnore(path);
|
||||
}
|
||||
|
||||
public bool IsPathLocked(string path)
|
||||
{
|
||||
// This method is not used by the core but it used by auto-organize
|
||||
|
||||
var lockedPaths = _tempIgnoredPaths.Keys.ToList();
|
||||
return lockedPaths.Any(i => _fileSystem.AreEqual(i, path) || _fileSystem.ContainsSubPath(i, path));
|
||||
}
|
||||
|
||||
public async void ReportFileSystemChangeComplete(string path, bool refreshPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
@@ -145,8 +137,7 @@ namespace Emby.Server.Implementations.IO
|
||||
.OfType<Folder>()
|
||||
.SelectMany(f => f.PhysicalLocations)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(i => i)
|
||||
.ToList();
|
||||
.OrderBy(i => i);
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
@@ -372,11 +363,8 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
var monitorPath = !IgnorePatterns.ShouldIgnore(path);
|
||||
|
||||
// Ignore certain files
|
||||
var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList();
|
||||
|
||||
// If the parent of an ignored path has a change event, ignore that too
|
||||
if (tempIgnorePaths.Any(i =>
|
||||
// Ignore certain files, If the parent of an ignored path has a change event, ignore that too
|
||||
if (_tempIgnoredPaths.Keys.Any(i =>
|
||||
{
|
||||
if (_fileSystem.AreEqual(i, path))
|
||||
{
|
||||
@@ -491,7 +479,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
lock (_activeRefreshers)
|
||||
{
|
||||
foreach (var refresher in _activeRefreshers.ToList())
|
||||
foreach (var refresher in _activeRefreshers)
|
||||
{
|
||||
refresher.Completed -= OnNewRefresherCompleted;
|
||||
refresher.Dispose();
|
||||
|
||||
@@ -665,11 +665,7 @@ namespace Emby.Server.Implementations.Library
|
||||
if (result?.Items.Count > 0)
|
||||
{
|
||||
var items = result.Items;
|
||||
foreach (var item in items)
|
||||
{
|
||||
ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
|
||||
}
|
||||
|
||||
items.RemoveAll(item => !ResolverHelper.SetInitialItemValues(item, parent, this, directoryService));
|
||||
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
|
||||
return items;
|
||||
}
|
||||
@@ -2594,9 +2590,9 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
/*
|
||||
Anime series don't generally have a season in their file name, however,
|
||||
tvdb needs a season to correctly get the metadata.
|
||||
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
|
||||
// FIXME perhaps this would be better for TVDb parser to ask for season 1 if no season is specified
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,42 +45,42 @@ namespace Emby.Server.Implementations.Library
|
||||
.ThenByDescending(x => x.IsForced && string.Equals(x.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
.ThenByDescending(x => x.IsForced)
|
||||
.ThenByDescending(x => x.IsDefault)
|
||||
.ThenByDescending(x => preferredLanguages.Contains(x.Language, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
MediaStream? stream = null;
|
||||
if (mode == SubtitlePlaybackMode.Default)
|
||||
{
|
||||
// Prefer embedded metadata over smart logic
|
||||
stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
|
||||
|
||||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||
if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
// Load subtitles according to external, forced and default flags.
|
||||
stream = sortedStreams.FirstOrDefault(x => x.IsExternal || x.IsForced || x.IsDefault);
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Smart)
|
||||
{
|
||||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||
// Only attempt to load subtitles if the audio language is not one of the user's preferred subtitle languages.
|
||||
// If no subtitles of preferred language available, use default behaviour.
|
||||
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ??
|
||||
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
|
||||
stream = sortedStreams.FirstOrDefault(x => preferredLanguages.Contains(x.Language, StringComparison.OrdinalIgnoreCase)) ??
|
||||
sortedStreams.FirstOrDefault(x => x.IsExternal || x.IsForced || x.IsDefault);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Respect forced flag.
|
||||
stream = sortedStreams.FirstOrDefault(x => x.IsForced);
|
||||
}
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Always)
|
||||
{
|
||||
// always load the most suitable full subtitles
|
||||
stream = sortedStreams.FirstOrDefault(s => !s.IsForced);
|
||||
// Always load (full/non-forced) subtitles of the user's preferred subtitle language if possible, otherwise default behaviour.
|
||||
stream = sortedStreams.FirstOrDefault(x => !x.IsForced && preferredLanguages.Contains(x.Language, StringComparison.OrdinalIgnoreCase)) ??
|
||||
sortedStreams.FirstOrDefault(x => x.IsExternal || x.IsForced || x.IsDefault);
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.OnlyForced)
|
||||
{
|
||||
// always load the most suitable full subtitles
|
||||
// Only load subtitles that are flagged forced.
|
||||
stream = sortedStreams.FirstOrDefault(x => x.IsForced);
|
||||
}
|
||||
|
||||
// load forced subs if we have found no suitable full subtitles
|
||||
stream ??= sortedStreams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
|
||||
return stream?.Index;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,9 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="parent">The parent.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="directoryService">The directory service.</param>
|
||||
/// <returns>True if initializing was successful.</returns>
|
||||
/// <exception cref="ArgumentException">Item must have a path.</exception>
|
||||
public static void SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService)
|
||||
public static bool SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService)
|
||||
{
|
||||
// This version of the below method has no ItemResolveArgs, so we have to require the path already being set
|
||||
if (string.IsNullOrEmpty(item.Path))
|
||||
@@ -44,12 +45,14 @@ namespace Emby.Server.Implementations.Library
|
||||
var fileInfo = directoryService.GetFile(item.Path);
|
||||
if (fileInfo == null)
|
||||
{
|
||||
throw new FileNotFoundException("Can't find item path.", item.Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
SetDateCreated(item, fileInfo);
|
||||
|
||||
EnsureName(item, fileInfo);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,7 +19,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Class MusicAlbumResolver.
|
||||
/// The music album resolver.
|
||||
/// </summary>
|
||||
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
|
||||
{
|
||||
@@ -82,7 +83,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check.</param>
|
||||
/// <param name="directoryService">The directory service.</param>
|
||||
/// <returns><c>true</c> if the provided path points to a music album, <c>false</c> otherwise.</returns>
|
||||
/// <returns><c>true</c> if the provided path points to a music album; otherwise, <c>false</c>.</returns>
|
||||
public bool IsMusicAlbum(string path, IDirectoryService directoryService)
|
||||
{
|
||||
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService);
|
||||
@@ -95,10 +96,19 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
|
||||
private bool IsMusicAlbum(ItemResolveArgs args)
|
||||
{
|
||||
// Args points to an album if parent is an Artist folder or it directly contains music
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
// if (args.Parent is MusicArtist) return true; // saves us from testing children twice
|
||||
// If args is a artist subfolder it's not a music album
|
||||
foreach (var subfolder in _namingOptions.ArtistSubfolders)
|
||||
{
|
||||
if (Path.GetDirectoryName(args.Path.AsSpan()).Equals(subfolder, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogDebug("Found release folder: {Path}", args.Path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If args contains music it's a music album
|
||||
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService))
|
||||
{
|
||||
return true;
|
||||
@@ -111,22 +121,23 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
/// <summary>
|
||||
/// Determine if the supplied list contains what we should consider music.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if the provided path list contains music; otherwise, <c>false</c>.</returns>
|
||||
private bool ContainsMusic(
|
||||
ICollection<FileSystemMetadata> list,
|
||||
bool allowSubfolders,
|
||||
IDirectoryService directoryService)
|
||||
{
|
||||
// check for audio files before digging down into directories
|
||||
// Check for audio files before digging down into directories
|
||||
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && AudioFileParser.IsAudioFile(fileSystemInfo.FullName, _namingOptions));
|
||||
if (foundAudioFile)
|
||||
{
|
||||
// at least one audio file exists
|
||||
// At least one audio file exists
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!allowSubfolders)
|
||||
{
|
||||
// not music since no audio file exists and we're not looking into subfolders
|
||||
// Not music since no audio file exists and we're not looking into subfolders
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Class MusicArtistResolver.
|
||||
/// The music artist resolver.
|
||||
/// </summary>
|
||||
public class MusicArtistResolver : ItemResolver<MusicArtist>
|
||||
{
|
||||
@@ -23,8 +23,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MusicArtistResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger for the created <see cref="MusicAlbumResolver"/> instances.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <param name="logger">Instance of the <see cref="MusicAlbumResolver"/> interface.</param>
|
||||
/// <param name="namingOptions">The <see cref="NamingOptions"/>.</param>
|
||||
public MusicArtistResolver(
|
||||
ILogger<MusicAlbumResolver> logger,
|
||||
NamingOptions namingOptions)
|
||||
@@ -40,10 +40,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
public override ResolverPriority Priority => ResolverPriority.Second;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the specified args.
|
||||
/// Resolves the specified resolver arguments.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>MusicArtist.</returns>
|
||||
/// <param name="args">The resolver arguments.</param>
|
||||
/// <returns>A <see cref="MusicArtist"/>.</returns>
|
||||
protected override MusicArtist Resolve(ItemResolveArgs args)
|
||||
{
|
||||
if (!args.IsDirectory)
|
||||
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
|
||||
var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// If there's a collection type and it's not music, it can't be a series
|
||||
// If there's a collection type and it's not music, it can't be a music artist
|
||||
if (!isMusicMediaFolder)
|
||||
{
|
||||
return null;
|
||||
@@ -82,14 +82,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
|
||||
var albumResolver = new MusicAlbumResolver(_logger, _namingOptions);
|
||||
|
||||
// If we contain an album assume we are an artist folder
|
||||
var directories = args.FileSystemChildren.Where(i => i.IsDirectory);
|
||||
|
||||
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
|
||||
{
|
||||
// If we contain a artist subfolder assume we are an artist folder
|
||||
foreach (var subfolder in _namingOptions.ArtistSubfolders)
|
||||
{
|
||||
if (fileSystemInfo.Name.Equals(subfolder, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Stop once we see an artist subfolder
|
||||
state.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
// If we contain a music album assume we are an artist folder
|
||||
if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, directoryService))
|
||||
{
|
||||
// stop once we see a music album
|
||||
// Stop once we see a music album
|
||||
state.Stop();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>`0.</returns>
|
||||
public override T Resolve(ItemResolveArgs args)
|
||||
protected override T Resolve(ItemResolveArgs args)
|
||||
{
|
||||
return ResolveVideo<T>(args, false);
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@ using System.Linq;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.Books
|
||||
{
|
||||
public class BookResolver : MediaBrowser.Controller.Resolvers.ItemResolver<Book>
|
||||
public class BookResolver : ItemResolver<Book>
|
||||
{
|
||||
private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" };
|
||||
|
||||
public override Book Resolve(ItemResolveArgs args)
|
||||
protected override Book Resolve(ItemResolveArgs args)
|
||||
{
|
||||
var collectionType = args.GetCollectionType();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ItemResolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of BaseItem.</typeparam>
|
||||
public abstract class ItemResolver<T> : IItemResolver
|
||||
where T : BaseItem, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the priority.
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public virtual ResolverPriority Priority => ResolverPriority.First;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the specified args.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>`0.</returns>
|
||||
protected virtual T Resolve(ItemResolveArgs args)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets initial values on the newly resolved item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="args">The args.</param>
|
||||
protected virtual void SetInitialItemValues(T item, ItemResolveArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the path.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
BaseItem IItemResolver.ResolvePath(ItemResolveArgs args)
|
||||
{
|
||||
var item = Resolve(args);
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
SetInitialItemValues(item, args);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>Video.</returns>
|
||||
public override Video Resolve(ItemResolveArgs args)
|
||||
protected override Video Resolve(ItemResolveArgs args)
|
||||
{
|
||||
var collectionType = args.GetCollectionType();
|
||||
|
||||
@@ -376,7 +376,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
|
||||
if (!justName.IsEmpty)
|
||||
{
|
||||
// check for tmdb id
|
||||
// Check for TMDb id
|
||||
var tmdbid = justName.GetAttributeValue("tmdbid");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tmdbid))
|
||||
@@ -387,7 +387,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
|
||||
if (!string.IsNullOrEmpty(item.Path))
|
||||
{
|
||||
// check for imdb id - we use full media path, as we can assume, that this will match in any use case (either id in parent dir or in file name)
|
||||
// Check for IMDb id - we use full media path, as we can assume that this will match in any use case (whether id in parent dir or in file name)
|
||||
var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(imdbid))
|
||||
|
||||
@@ -12,6 +12,7 @@ using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
|
||||
@@ -31,16 +31,18 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
// It's a boxset if the path is a directory with [playlist] in it's the name
|
||||
// TODO: Should this use Path.GetDirectoryName() instead?
|
||||
bool isBoxSet = Path.GetFileName(args.Path)
|
||||
?.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)
|
||||
?? false;
|
||||
if (isBoxSet)
|
||||
var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path));
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filename.Contains("[playlist]", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
|
||||
Name = filename.Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -51,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
return new Playlist
|
||||
{
|
||||
Path = args.Path,
|
||||
Name = Path.GetFileName(args.Path)
|
||||
Name = filename
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -60,8 +62,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
// It should have the correct collection type and a supported file extension
|
||||
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
var extension = Path.GetExtension(args.Path.AsSpan());
|
||||
if (Playlist.SupportedExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
/// <returns>Episode.</returns>
|
||||
public override Episode Resolve(ItemResolveArgs args)
|
||||
protected override Episode Resolve(ItemResolveArgs args)
|
||||
{
|
||||
var parent = args.Parent;
|
||||
|
||||
|
||||
@@ -2192,16 +2192,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
private void HandleDuplicateShowIds(List<TimerInfo> timers)
|
||||
{
|
||||
foreach (var timer in timers.Skip(1))
|
||||
// sort showings by HD channels first, then by startDate, record earliest showing possible
|
||||
foreach (var timer in timers.OrderByDescending(t => _liveTvManager.GetLiveTvChannel(t, this).IsHD).ThenBy(t => t.StartDate).Skip(1))
|
||||
{
|
||||
// TODO: Get smarter, prefer HD, etc
|
||||
|
||||
timer.Status = RecordingStatus.Cancelled;
|
||||
_timerProvider.Update(timer);
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchForDuplicateShowIds(List<TimerInfo> timers)
|
||||
private void SearchForDuplicateShowIds(IEnumerable<TimerInfo> timers)
|
||||
{
|
||||
var groups = timers.ToLookup(i => i.ShowId ?? string.Empty).ToList();
|
||||
|
||||
@@ -2219,6 +2218,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip ShowId without SubKey from duplicate removal actions - https://github.com/jellyfin/jellyfin/issues/5856
|
||||
if (group.Key.EndsWith("0000", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HandleDuplicateShowIds(groupTimers);
|
||||
}
|
||||
}
|
||||
@@ -2276,39 +2281,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (updateTimerSettings)
|
||||
{
|
||||
// Only update if not currently active - test both new timer and existing in case Id's are different
|
||||
// Id's could be different if the timer was created manually prior to series timer creation
|
||||
if (!_activeRecordings.TryGetValue(timer.Id, out _) && !_activeRecordings.TryGetValue(existingTimer.Id, out _))
|
||||
{
|
||||
UpdateExistingTimerWithNewMetadata(existingTimer, timer);
|
||||
|
||||
// Needed by ShouldCancelTimerForSeriesTimer
|
||||
timer.IsManual = existingTimer.IsManual;
|
||||
|
||||
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
|
||||
{
|
||||
existingTimer.Status = RecordingStatus.Cancelled;
|
||||
}
|
||||
else if (!existingTimer.IsManual)
|
||||
{
|
||||
existingTimer.Status = RecordingStatus.New;
|
||||
}
|
||||
|
||||
if (existingTimer.Status != RecordingStatus.Cancelled)
|
||||
{
|
||||
enabledTimersForSeries.Add(existingTimer);
|
||||
}
|
||||
|
||||
existingTimer.KeepUntil = seriesTimer.KeepUntil;
|
||||
existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
|
||||
existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
|
||||
existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
|
||||
existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
|
||||
existingTimer.Priority = seriesTimer.Priority;
|
||||
existingTimer.SeriesTimerId = seriesTimer.Id;
|
||||
|
||||
_timerProvider.Update(existingTimer);
|
||||
}
|
||||
existingTimer.KeepUntil = seriesTimer.KeepUntil;
|
||||
existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
|
||||
existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
|
||||
existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
|
||||
existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
|
||||
existingTimer.Priority = seriesTimer.Priority;
|
||||
existingTimer.SeriesTimerId = seriesTimer.Id;
|
||||
}
|
||||
|
||||
existingTimer.SeriesTimerId = seriesTimer.Id;
|
||||
|
||||
@@ -122,11 +122,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (_timers.TryAdd(item.Id, timer))
|
||||
{
|
||||
Logger.LogInformation(
|
||||
"Creating recording timer for {Id}, {Name}. Timer will fire in {Minutes} minutes",
|
||||
if (item.IsSeries)
|
||||
{
|
||||
Logger.LogInformation(
|
||||
"Creating recording timer for {Id}, {Name} {SeasonNumber}x{EpisodeNumber:D2} on channel {ChannelId}. Timer will fire in {Minutes} minutes at {StartDate}",
|
||||
item.Id,
|
||||
item.Name,
|
||||
dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
||||
item.SeasonNumber,
|
||||
item.EpisodeNumber,
|
||||
item.ChannelId,
|
||||
dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture),
|
||||
item.StartDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation(
|
||||
"Creating recording timer for {Id}, {Name} on channel {ChannelId}. Timer will fire in {Minutes} minutes at {StartDate}",
|
||||
item.Id,
|
||||
item.Name,
|
||||
item.ChannelId,
|
||||
dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture),
|
||||
item.StartDate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -166,12 +166,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
const double DesiredAspect = 2.0 / 3;
|
||||
|
||||
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect) ??
|
||||
GetProgramImage(ApiUrl, allImages, DesiredAspect);
|
||||
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect, token) ??
|
||||
GetProgramImage(ApiUrl, allImages, DesiredAspect, token);
|
||||
|
||||
const double WideAspect = 16.0 / 9;
|
||||
|
||||
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect);
|
||||
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect, token);
|
||||
|
||||
// Don't supply the same image twice
|
||||
if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
|
||||
@@ -179,7 +179,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
programEntry.ThumbImage = null;
|
||||
}
|
||||
|
||||
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect);
|
||||
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect, token);
|
||||
|
||||
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
|
||||
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
|
||||
@@ -400,7 +400,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
return info;
|
||||
}
|
||||
|
||||
private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect)
|
||||
private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect, string token)
|
||||
{
|
||||
var match = images
|
||||
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
|
||||
@@ -424,7 +424,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
}
|
||||
else
|
||||
{
|
||||
return apiUrl + "/image/" + uri;
|
||||
return apiUrl + "/image/" + uri + "?token=" + token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,6 +458,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
IReadOnlyList<string> programIds,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (programIds.Count == 0)
|
||||
{
|
||||
return Array.Empty<ShowImagesDto>();
|
||||
@@ -479,6 +481,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json)
|
||||
};
|
||||
message.Headers.TryAddWithoutValidation("token", token);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -6,9 +6,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
@@ -32,21 +32,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<XmlTvListingsProvider> _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IZipClient _zipClient;
|
||||
|
||||
public XmlTvListingsProvider(
|
||||
IServerConfigurationManager config,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILogger<XmlTvListingsProvider> logger,
|
||||
IFileSystem fileSystem,
|
||||
IZipClient zipClient)
|
||||
ILogger<XmlTvListingsProvider> logger)
|
||||
{
|
||||
_config = config;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_zipClient = zipClient;
|
||||
}
|
||||
|
||||
public string Name => "XmlTV";
|
||||
@@ -67,16 +61,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
_logger.LogInformation("xmltv path: {Path}", info.Path);
|
||||
|
||||
if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return UnzipIfNeeded(info.Path, info.Path);
|
||||
}
|
||||
|
||||
string cacheFilename = info.Id + ".xml";
|
||||
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
|
||||
|
||||
if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
|
||||
{
|
||||
return UnzipIfNeeded(info.Path, cacheFile);
|
||||
return cacheFile;
|
||||
}
|
||||
|
||||
// Must check if file exists as parent directory may not exist.
|
||||
@@ -84,95 +74,50 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
File.Delete(cacheFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
||||
}
|
||||
|
||||
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
|
||||
if (info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await using var stream = AsyncFile.OpenRead(info.Path);
|
||||
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false);
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, FileOptions.Asynchronous))
|
||||
private async Task<string> UnzipIfNeededAndCopy(string originalUrl, Stream stream, string file, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var fileStream = new FileStream(file, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||
|
||||
if (Path.GetExtension(originalUrl.AsSpan().LeftPart('?')).Equals(".gz", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var reader = new GZipStream(stream, CompressionMode.Decompress);
|
||||
await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting from gz file {File}", originalUrl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return UnzipIfNeeded(info.Path, cacheFile);
|
||||
}
|
||||
|
||||
private string UnzipIfNeeded(ReadOnlySpan<char> originalUrl, string file)
|
||||
{
|
||||
ReadOnlySpan<char> ext = Path.GetExtension(originalUrl.LeftPart('?'));
|
||||
|
||||
if (ext.Equals(".gz", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
string tempFolder = ExtractGz(file);
|
||||
return FindXmlFile(tempFolder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting from gz file {File}", file);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string tempFolder = ExtractFirstFileFromGz(file);
|
||||
return FindXmlFile(tempFolder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting from zip file {File}", file);
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private string ExtractFirstFileFromGz(string file)
|
||||
{
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
string tempFolder = GetTempFolderPath(stream);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
|
||||
|
||||
return tempFolder;
|
||||
}
|
||||
}
|
||||
|
||||
private string ExtractGz(string file)
|
||||
{
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
string tempFolder = GetTempFolderPath(stream);
|
||||
Directory.CreateDirectory(tempFolder);
|
||||
|
||||
_zipClient.ExtractAllFromGz(stream, tempFolder, true);
|
||||
|
||||
return tempFolder;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTempFolderPath(Stream stream)
|
||||
{
|
||||
#pragma warning disable CA5351
|
||||
using var md5 = MD5.Create();
|
||||
#pragma warning restore CA5351
|
||||
var checksum = Convert.ToHexString(md5.ComputeHash(stream));
|
||||
stream.Position = 0;
|
||||
return Path.Combine(_config.ApplicationPaths.TempDirectory, checksum);
|
||||
}
|
||||
|
||||
private string FindXmlFile(string directory)
|
||||
{
|
||||
return _fileSystem.GetFiles(directory, true)
|
||||
.Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase))
|
||||
.Select(i => i.FullName)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(channelId))
|
||||
@@ -213,16 +158,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
ImageUrl = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source) ? program.Icon.Source : null,
|
||||
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
|
||||
OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null,
|
||||
ImageUrl = string.IsNullOrEmpty(program.Icon?.Source) ? null : program.Icon.Source,
|
||||
HasImage = !string.IsNullOrEmpty(program.Icon?.Source),
|
||||
OfficialRating = string.IsNullOrEmpty(program.Rating?.Value) ? null : program.Rating.Value,
|
||||
CommunityRating = program.StarRating,
|
||||
SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
SeriesId = program.Episode == null ? null : program.Title?.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(program.ProgramId))
|
||||
{
|
||||
string uniqueString = (program.Title ?? string.Empty) + (episodeTitle ?? string.Empty) /*+ (p.IceTvEpisodeNumber ?? string.Empty)*/;
|
||||
string uniqueString = (program.Title ?? string.Empty) + (episodeTitle ?? string.Empty);
|
||||
|
||||
if (programInfo.SeasonNumber.HasValue)
|
||||
{
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return VerifyReturnValueOfGetSet(buffer.AsSpan(receivedBytes), "none");
|
||||
return VerifyReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), "none");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"TasksChannelsCategory": "قنوات الإنترنت",
|
||||
"TasksLibraryCategory": "مكتبة",
|
||||
"TasksMaintenanceCategory": "صيانة",
|
||||
"TaskRefreshLibraryDescription": "يفصح مكتبة الوسائط الخاصة بك بحثًا عن ملفات جديدة، ومن ثم يتحدث البيانات الوصفية.",
|
||||
"TaskRefreshLibraryDescription": "يفحص مكتبة الوسائط الخاصة بك باحثا عن ملفات جديدة، ومن ثم يتحدث البيانات الوصفية.",
|
||||
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
|
||||
"TaskRefreshChapterImagesDescription": "يُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.",
|
||||
"TaskRefreshChapterImages": "استخراج صور الفصل",
|
||||
|
||||
@@ -40,16 +40,16 @@
|
||||
"Movies": "Pel·lícules",
|
||||
"Music": "Música",
|
||||
"MusicVideos": "Vídeos Musicals",
|
||||
"NameInstallFailed": "Instalació de {0} fallida",
|
||||
"NameInstallFailed": "Instal·lació de {0} fallida",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Temporada Desconeguda",
|
||||
"NewVersionIsAvailable": "Una nova versió del Servidor Jellyfin està disponible per descarregar.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Actualització d'aplicació disponible",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Actualització d'aplicació instal·lada",
|
||||
"NotificationOptionAudioPlayback": "Reproducció d'audio iniciada",
|
||||
"NotificationOptionAudioPlaybackStopped": "Reproducció d'audio aturada",
|
||||
"NotificationOptionAudioPlayback": "Reproducció d'àudio iniciada",
|
||||
"NotificationOptionAudioPlaybackStopped": "Reproducció d'àudio aturada",
|
||||
"NotificationOptionCameraImageUploaded": "Imatge de càmera pujada",
|
||||
"NotificationOptionInstallationFailed": "Instalació fallida",
|
||||
"NotificationOptionInstallationFailed": "Instal·lació fallida",
|
||||
"NotificationOptionNewLibraryContent": "Nou contingut afegit",
|
||||
"NotificationOptionPluginError": "Un connector ha fallat",
|
||||
"NotificationOptionPluginInstalled": "Connector instal·lat",
|
||||
@@ -72,13 +72,13 @@
|
||||
"ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciat",
|
||||
"Shows": "Sèries",
|
||||
"Songs": "Cançons",
|
||||
"StartupEmbyServerIsLoading": "El Servidor d'Jellyfin està carregant. Si et plau, prova de nou en breus.",
|
||||
"StartupEmbyServerIsLoading": "El Servidor de Jellyfin està carregant. Si et plau, prova de nou ben aviat.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}",
|
||||
"Sync": "Sincronitzar",
|
||||
"System": "Sistema",
|
||||
"TvShows": "Espectacles de TV",
|
||||
"User": "User",
|
||||
"TvShows": "Sèries de TV",
|
||||
"User": "Usuari",
|
||||
"UserCreatedWithName": "S'ha creat l'usuari {0}",
|
||||
"UserDeletedWithName": "L'usuari {0} ha estat eliminat",
|
||||
"UserDownloadingItemWithValues": "{0} està descarregant {1}",
|
||||
@@ -89,12 +89,12 @@
|
||||
"UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva llibreria",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva biblioteca",
|
||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||
"VersionNumber": "Versió {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.",
|
||||
"TaskDownloadMissingSubtitles": "Descarrega els subtítols que faltin",
|
||||
"TaskRefreshChannelsDescription": "Actualitza la informació dels canals d'internet.",
|
||||
"TaskRefreshChannelsDescription": "Actualitza la informació dels canals d'Internet.",
|
||||
"TaskRefreshChannels": "Actualitza Canals",
|
||||
"TaskCleanTranscodeDescription": "Elimina els arxius temporals de transcodificacions que tinguin més d'un dia.",
|
||||
"TaskCleanTranscode": "Neteja les transcodificacions",
|
||||
@@ -110,7 +110,7 @@
|
||||
"TaskRefreshChapterImages": "Extreure les imatges dels capítols",
|
||||
"TaskCleanCacheDescription": "Elimina els arxius temporals que ja no són necessaris per al servidor.",
|
||||
"TaskCleanCache": "Elimina arxius temporals",
|
||||
"TasksChannelsCategory": "Canals d'internet",
|
||||
"TasksChannelsCategory": "Canals d'Internet",
|
||||
"TasksApplicationCategory": "Aplicació",
|
||||
"TasksLibraryCategory": "Biblioteca",
|
||||
"TasksMaintenanceCategory": "Manteniment",
|
||||
@@ -118,10 +118,11 @@
|
||||
"TaskCleanActivityLog": "Buidar Registre d'Activitat",
|
||||
"Undefined": "Indefinit",
|
||||
"Forced": "Forçat",
|
||||
"Default": "Defecto",
|
||||
"Default": "Defecte",
|
||||
"TaskOptimizeDatabaseDescription": "Compacta la base de dades i trunca l'espai lliure. Executar aquesta tasca després d’escanejar la biblioteca o fer altres canvis que impliquin modificacions a la base de dades pot millorar el rendiment.",
|
||||
"TaskOptimizeDatabase": "Optimitzar la base de dades",
|
||||
"TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot durar molt de temps.",
|
||||
"TaskKeyframeExtractor": "Extractor de fotogrames clau",
|
||||
"External": "Extern"
|
||||
"External": "Extern",
|
||||
"HearingImpaired": "Discapacitat Auditiva"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Optimalizovat databázi",
|
||||
"TaskKeyframeExtractorDescription": "Vytahuje klíčové snímky ze souborů videa za účelem vytváření přesnějších seznamů přehrávání HLS. Tento úkol může trvat velmi dlouho.",
|
||||
"TaskKeyframeExtractor": "Vytahovač klíčových snímků",
|
||||
"External": "Externí"
|
||||
"External": "Externí",
|
||||
"HearingImpaired": "Sluchově postižení"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Optimér database",
|
||||
"TaskKeyframeExtractorDescription": "Udtrækker billeder fra videofiler for at lave mere præcise HLS playlister. Denne opgave kan godt tage lang tid.",
|
||||
"TaskKeyframeExtractor": "Billedramme udtrækker",
|
||||
"External": "Ekstern"
|
||||
"External": "Ekstern",
|
||||
"HearingImpaired": "Hørehæmmet"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Datenbank optimieren",
|
||||
"TaskKeyframeExtractorDescription": "Extrahiere Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.",
|
||||
"TaskKeyframeExtractor": "Keyframe Extraktor",
|
||||
"External": "Extern"
|
||||
"External": "Extern",
|
||||
"HearingImpaired": "Hörgeschädigt"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Βελτιστοποίηση βάσης δεδομένων",
|
||||
"TaskKeyframeExtractorDescription": "Εξάγει καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς λίστες αναπαραγωγής HLS. Αυτή η διεργασία μπορεί να πάρει χρόνο.",
|
||||
"TaskKeyframeExtractor": "Εξαγωγέας βασικών καρέ βίντεο",
|
||||
"External": "Εξωτερικό"
|
||||
"External": "Εξωτερικό",
|
||||
"HearingImpaired": "Με προβλήματα ακοής"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Optimise database",
|
||||
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.",
|
||||
"TaskKeyframeExtractor": "Keyframe Extractor",
|
||||
"External": "External"
|
||||
"External": "External",
|
||||
"HearingImpaired": "Hearing Impaired"
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"HeaderLiveTV": "Live TV",
|
||||
"HeaderNextUp": "Next Up",
|
||||
"HeaderRecordingGroups": "Recording Groups",
|
||||
"HearingImpaired": "Hearing Impaired",
|
||||
"HomeVideos": "Home Videos",
|
||||
"Inherit": "Inherit",
|
||||
"ItemAddedWithName": "{0} was added to the library",
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Optimización de base de datos",
|
||||
"External": "Externo",
|
||||
"TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reprodución HLS más precisas. Esta tarea puede durar mucho tiempo.",
|
||||
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave"
|
||||
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
|
||||
"HearingImpaired": "Personas con discapacidad auditiva"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y trunca el espacio libre. Puede mejorar el rendimiento si se realiza esta tarea después de escanear la biblioteca o después de realizar otros cambios que impliquen modificar la base de datos.",
|
||||
"TaskKeyframeExtractorDescription": "Extrae los cuadros clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar un buen rato.",
|
||||
"TaskKeyframeExtractor": "Extractor de Cuadros Clave",
|
||||
"External": "Externo"
|
||||
"External": "Externo",
|
||||
"HearingImpaired": "Discapacidad Auditiva"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabaseDescription": "Optimiza y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
|
||||
"TaskKeyframeExtractorDescription": "Extrae los fotogramas clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar mucho tiempo.",
|
||||
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
|
||||
"External": "Externo"
|
||||
"External": "Externo",
|
||||
"HearingImpaired": "Discapacidad Auditiva"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,8 @@
|
||||
"UserPolicyUpdatedWithName": "Kasutaja {0} õigusi värskendati",
|
||||
"UserStoppedPlayingItemWithValues": "{0} lõpetas {1} taasesituse seadmes {2}",
|
||||
"UserOnlineFromDevice": "{0} on ühendatud seadmest {1}",
|
||||
"External": "Väline"
|
||||
"External": "Väline",
|
||||
"HearingImpaired": "Kuulmispuudega",
|
||||
"TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadreid, et luua täpsemaid HLS-i esitusloendeid. See ülesanne võib kesta pikka aega.",
|
||||
"TaskKeyframeExtractor": "Võtmekaadri ekstraktor"
|
||||
}
|
||||
|
||||
@@ -116,5 +116,12 @@
|
||||
"CameraImageUploadedFrom": "{0}-tik kamera irudi berri bat igo da",
|
||||
"AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da",
|
||||
"Application": "Aplikazioa",
|
||||
"AppDeviceValues": "App: {0}, Gailua: {1}"
|
||||
"AppDeviceValues": "App: {0}, Gailua: {1}",
|
||||
"HearingImpaired": "Entzunaldia aldatua",
|
||||
"ProviderValue": "Hornitzailea: {0}",
|
||||
"TaskKeyframeExtractorDescription": "Bideo fitxategietako fotograma gakoak ateratzen ditu HLS erreprodukzio-zerrenda zehatzagoak sortzeko. Zeregin honek denbora asko iraun dezake.",
|
||||
"HeaderRecordingGroups": "Grabaketa taldeak",
|
||||
"Inherit": "Oinordetu",
|
||||
"TaskOptimizeDatabaseDescription": "Datu-basea trinkotu eta bertatik espazioa askatzen du. Liburutegia eskaneatu ondoren edo datu-basean aldaketak egin ondoren ataza hau exekutatzeak errendimendua hobetu lezake.",
|
||||
"TaskKeyframeExtractor": "Fotograma gakoen erauzgailua"
|
||||
}
|
||||
|
||||
@@ -122,5 +122,6 @@
|
||||
"TaskOptimizeDatabase": "Optimoi tietokanta",
|
||||
"TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.",
|
||||
"TaskKeyframeExtractor": "Avainkuvien purkain",
|
||||
"External": "Ulkoinen"
|
||||
"External": "Ulkoinen",
|
||||
"HearingImpaired": "Kuulorajoitteinen"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
||||
"Books": "Livres",
|
||||
"CameraImageUploadedFrom": "Une nouvelle image de caméra a été téléchargée depuis {0}",
|
||||
"CameraImageUploadedFrom": "Une nouvelle photo a été téléversée depuis {0}",
|
||||
"Channels": "Chaînes",
|
||||
"ChapterNameValue": "Chapitre {0}",
|
||||
"Collections": "Collections",
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Optimiser la base de données",
|
||||
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
||||
"TaskKeyframeExtractor": "Extracteur d'image clé",
|
||||
"External": "Externe"
|
||||
"External": "Externe",
|
||||
"HearingImpaired": "Malentendants"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"Favorites": "Favoris",
|
||||
"Folders": "Dossiers",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Artistes d'album",
|
||||
"HeaderAlbumArtists": "Artistes de l'album",
|
||||
"HeaderContinueWatching": "Reprendre le visionnage",
|
||||
"HeaderFavoriteAlbums": "Albums favoris",
|
||||
"HeaderFavoriteArtists": "Artistes préférés",
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Optimiser la base de données",
|
||||
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
||||
"TaskKeyframeExtractor": "Extracteur d'image clé",
|
||||
"External": "Externe"
|
||||
"External": "Externe",
|
||||
"HearingImpaired": "Malentendants"
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"HeaderFavoriteEpisodes": "Episodios Favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas Favoritos",
|
||||
"HeaderFavoriteAlbums": "Álbunes Favoritos",
|
||||
"HeaderContinueWatching": "Seguir mirando",
|
||||
"HeaderContinueWatching": "Seguir vendo",
|
||||
"HeaderAlbumArtists": "Artistas do Album",
|
||||
"Genres": "Xéneros",
|
||||
"Forced": "Forzado",
|
||||
@@ -119,5 +119,9 @@
|
||||
"UserOnlineFromDevice": "{0} está en liña desde {1}",
|
||||
"UserOfflineFromDevice": "{0} desconectouse desde {1}",
|
||||
"TaskOptimizeDatabaseDescription": "Compacta e libera o espazo libre da base de datos. Executar esta tarefa logo de realizar mudanzas que impliquen modificacións da base de datos ou despois de escanear a biblioteca pode traer mellorías de desempeño.",
|
||||
"TaskOptimizeDatabase": "Optimizar base de datos"
|
||||
"TaskOptimizeDatabase": "Optimizar base de datos",
|
||||
"TaskKeyframeExtractorDescription": "Extrae fragmentos do vídeo para crear listas de reprodución HLS máis precisas. Podería levarlle bastante tempo.",
|
||||
"External": "Externo",
|
||||
"HearingImpaired": "Problemas de audición",
|
||||
"TaskKeyframeExtractor": "Extractor de fragmentos"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים.",
|
||||
"TaskKeyframeExtractorDescription": "חלץ תמונות מפתח מקבצי וידאו בכדי ליצור רשימות השמעה מדויקות יותר של HLS. משימה זו עלולה להימשך זמן רב.",
|
||||
"TaskKeyframeExtractor": "מחלץ תמונות מפתח",
|
||||
"External": "חיצוני"
|
||||
"External": "חיצוני",
|
||||
"HearingImpaired": "לקוי שמיעה"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"External": "Vanjski",
|
||||
"TaskKeyframeExtractorDescription": "Izvlačenje ključnih okvira iz videozapisa za stvaranje objektivnije HLS liste za reprodukciju. Pokretanje ovog zadatka može potrajati.",
|
||||
"TaskKeyframeExtractor": "Izvoditelj ključnog okvira",
|
||||
"TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka."
|
||||
"TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka.",
|
||||
"HearingImpaired": "Oštećen sluh"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Adatbázis optimalizálása",
|
||||
"TaskKeyframeExtractor": "Kulcskockák kibontása",
|
||||
"TaskKeyframeExtractorDescription": "Kulcskockákat bont ki a videofájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat.",
|
||||
"External": "Külső"
|
||||
"External": "Külső",
|
||||
"HearingImpaired": "Hallássérült"
|
||||
}
|
||||
|
||||
@@ -122,5 +122,6 @@
|
||||
"TaskOptimizeDatabase": "Optimalkan basis data",
|
||||
"TaskKeyframeExtractorDescription": "Ekstrak bingkai utama dari file video untuk membuat daftar putar HLS yang lebih tepat. Tugas ini dapat berjalan untuk waktu yang lama.",
|
||||
"TaskKeyframeExtractor": "Ekstraktor Bingkai Utama",
|
||||
"External": "Luar"
|
||||
"External": "Luar",
|
||||
"HearingImpaired": "Gangguan Pendengaran"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Ottimizza Database",
|
||||
"TaskKeyframeExtractor": "Estrattore di Keyframe",
|
||||
"TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo.",
|
||||
"External": "Esterno"
|
||||
"External": "Esterno",
|
||||
"HearingImpaired": "con problemi di udito"
|
||||
}
|
||||
|
||||
7
Emby.Server.Implementations/Localization/Core/jbo.json
Normal file
7
Emby.Server.Implementations/Localization/Core/jbo.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Albums": "lo albuma",
|
||||
"Artists": "lo larpra",
|
||||
"Books": "lo cukta",
|
||||
"HeaderAlbumArtists": "lo albuma larpra",
|
||||
"Playlists": "lo zgipor"
|
||||
}
|
||||
31
Emby.Server.Implementations/Localization/Core/ka.json
Normal file
31
Emby.Server.Implementations/Localization/Core/ka.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"Genres": "ჟანრები",
|
||||
"HeaderAlbumArtists": "ალბომის შემსრულებლები",
|
||||
"HeaderFavoriteAlbums": "რჩეული ალბომები",
|
||||
"TasksApplicationCategory": "აპლიკაცია",
|
||||
"Albums": "ალბომები",
|
||||
"AppDeviceValues": "აპი: {0}, მოწყობილობა: {1}",
|
||||
"Application": "აპლიკაცია",
|
||||
"Artists": "შემსრულებლები",
|
||||
"AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია",
|
||||
"Books": "წიგნები",
|
||||
"Forced": "ძალით",
|
||||
"Inherit": "მემკვიდრეობით",
|
||||
"Latest": "უახლესი",
|
||||
"Movies": "ფილმები",
|
||||
"Music": "მუსიკა",
|
||||
"Photos": "ფოტოები",
|
||||
"Playlists": "დასაკრავი სიები",
|
||||
"Plugin": "დამატება",
|
||||
"Shows": "სერიალები",
|
||||
"Songs": "სიმღერები",
|
||||
"Sync": "სინქრონიზაცია",
|
||||
"System": "სისტემა",
|
||||
"Undefined": "აღუწერელი",
|
||||
"User": "მომხმარებელი",
|
||||
"TasksMaintenanceCategory": "რემონტი",
|
||||
"TasksLibraryCategory": "ბიბლიოთეკა",
|
||||
"ChapterNameValue": "თავი {0}",
|
||||
"HeaderContinueWatching": "ყურების გაგრძელება",
|
||||
"HeaderFavoriteArtists": "რჩეული შემსრულებლები"
|
||||
}
|
||||
3
Emby.Server.Implementations/Localization/Core/km.json
Normal file
3
Emby.Server.Implementations/Localization/Core/km.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Albums": "Albums"
|
||||
}
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskKeyframeExtractorDescription": "Iš vaizdo įrašo paruošia reikšminius kadrus, kad būtų sukuriamas tikslenis HLS grojaraštis. Šios užduoties vykdymas gali ilgai užtrukti.",
|
||||
"TaskKeyframeExtractor": "Pagrindinių kadrų ištraukėjas",
|
||||
"TaskOptimizeDatabaseDescription": "Suspaudžia duomenų bazę ir atlaisvina vietą. Paleidžiant šią užduotį, po bibliotekos skenavimo arba kitų veiksmų kurie galimai modifikuoja duomenų bazė, gali pagerinti greitaveiką.",
|
||||
"External": "Išorinis"
|
||||
"External": "Išorinis",
|
||||
"HearingImpaired": "Su klausos sutrikimais"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabaseDescription": "Komprimerer database og frigjør plass. Denne prosessen kan forbedre ytelsen etter skanning av bibliotek eller andre handlinger som fører til databaseendringer.",
|
||||
"TaskKeyframeExtractorDescription": "Trekker ut nøkkelbilder fra videofiler for å skape mere nøyaktige HLS-spillelister. Denne oppgaven kan ta lang tid.",
|
||||
"TaskKeyframeExtractor": "Nøkkelbilde-uttrekker",
|
||||
"External": "Ekstern"
|
||||
"External": "Ekstern",
|
||||
"HearingImpaired": "Hørselshemmet"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"Artists": "Artiesten",
|
||||
"AuthenticationSucceededWithUserName": "{0} is succesvol geauthenticeerd",
|
||||
"Books": "Boeken",
|
||||
"CameraImageUploadedFrom": "Nieuwe camera afbeelding toegevoegd vanaf {0}",
|
||||
"CameraImageUploadedFrom": "Nieuwe camera-afbeelding toegevoegd vanaf {0}",
|
||||
"Channels": "Kanalen",
|
||||
"ChapterNameValue": "Hoofdstuk {0}",
|
||||
"Collections": "Verzamelingen",
|
||||
@@ -15,7 +15,7 @@
|
||||
"Favorites": "Favorieten",
|
||||
"Folders": "Mappen",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Album Artiesten",
|
||||
"HeaderAlbumArtists": "Albumartiesten",
|
||||
"HeaderContinueWatching": "Kijken hervatten",
|
||||
"HeaderFavoriteAlbums": "Favoriete albums",
|
||||
"HeaderFavoriteArtists": "Favoriete artiesten",
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Database optimaliseren",
|
||||
"TaskKeyframeExtractorDescription": "Haalt keyframes uit videobestanden om preciezere HLS afspeellijsten te maken. Dit kan lang duren.",
|
||||
"TaskKeyframeExtractor": "Keyframe Extractor",
|
||||
"External": "Extern"
|
||||
"External": "Extern",
|
||||
"HearingImpaired": "Slechthorend"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Otimizar base de dados",
|
||||
"TaskKeyframeExtractor": "Extrator de quadro-chave",
|
||||
"TaskKeyframeExtractorDescription": "Extrai quadros-chave de arquivos de vídeo para criar listas de reprodução HLS mais precisas. Esta tarefa pode ser executada por um longo tempo.",
|
||||
"External": "Externo"
|
||||
"External": "Externo",
|
||||
"HearingImpaired": "Deficiência Auditiva"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Otimizar base de dados",
|
||||
"TaskKeyframeExtractorDescription": "Extrai quadros-chave de ficheiros de video para criar listas de reprodução HLS mais precisas. Esta tarefa pode demorar algum tempo.",
|
||||
"TaskKeyframeExtractor": "Extrator de Quadros-chave",
|
||||
"External": "Externo"
|
||||
"External": "Externo",
|
||||
"HearingImpaired": "Surdo"
|
||||
}
|
||||
|
||||
@@ -120,5 +120,6 @@
|
||||
"TaskCleanActivityLogDescription": "Apaga itens no registro com idade acima do que é configurado.",
|
||||
"TaskOptimizeDatabase": "Otimizar base de dados",
|
||||
"TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.",
|
||||
"External": "Externo"
|
||||
"External": "Externo",
|
||||
"HearingImpaired": "Problemas auditivos"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"UserOfflineFromDevice": "{0} s-a deconectat de la {1}",
|
||||
"UserLockedOutWithName": "Utilizatorul {0} a fost blocat",
|
||||
"UserDownloadingItemWithValues": "{0} descarcă {1}",
|
||||
"UserDeletedWithName": "Utilizatorul {0} a fost eliminat",
|
||||
"UserDeletedWithName": "Utilizatorul {0} a fost șters",
|
||||
"UserCreatedWithName": "Utilizatorul {0} a fost creat",
|
||||
"User": "Utilizator",
|
||||
"TvShows": "Seriale TV",
|
||||
@@ -20,33 +20,33 @@
|
||||
"SubtitleDownloadFailureFromForItem": "Subtitrările nu au putut fi descărcate de la {0} pentru {1}",
|
||||
"StartupEmbyServerIsLoading": "Se încarcă serverul Jellyfin. Încercați din nou în scurt timp.",
|
||||
"Songs": "Melodii",
|
||||
"Shows": "Spectacole",
|
||||
"ServerNameNeedsToBeRestarted": "{0} trebuie repornit",
|
||||
"Shows": "Seriale",
|
||||
"ServerNameNeedsToBeRestarted": "{0} trebuie să fie repornit",
|
||||
"ScheduledTaskStartedWithName": "{0} pornit/ă",
|
||||
"ScheduledTaskFailedWithName": "{0} eșuat/ă",
|
||||
"ProviderValue": "Furnizor: {0}",
|
||||
"PluginUpdatedWithName": "{0} a fost actualizat/ă",
|
||||
"PluginUninstalledWithName": "{0} a fost dezinstalat",
|
||||
"PluginInstalledWithName": "{0} a fost instalat",
|
||||
"Plugin": "Plugin",
|
||||
"Playlists": "Liste redare",
|
||||
"Plugin": "Extensie",
|
||||
"Playlists": "Liste de redare",
|
||||
"Photos": "Fotografii",
|
||||
"NotificationOptionVideoPlaybackStopped": "Redarea video oprită",
|
||||
"NotificationOptionVideoPlayback": "Redare video începută",
|
||||
"NotificationOptionUserLockedOut": "Utilizatorul a fost blocat",
|
||||
"NotificationOptionTaskFailed": "Activitate programata eșuată",
|
||||
"NotificationOptionTaskFailed": "Activitate programată eșuată",
|
||||
"NotificationOptionServerRestartRequired": "Este necesară repornirea serverului",
|
||||
"NotificationOptionPluginUpdateInstalled": "Actualizare plugin instalată",
|
||||
"NotificationOptionPluginUninstalled": "Plugin dezinstalat",
|
||||
"NotificationOptionPluginInstalled": "Plugin instalat",
|
||||
"NotificationOptionPluginError": "Plugin-ul a eșuat",
|
||||
"NotificationOptionNewLibraryContent": "Adăugat conținut nou",
|
||||
"NotificationOptionInstallationFailed": "Eșec la instalare",
|
||||
"NotificationOptionCameraImageUploaded": "Încarcată imagine cameră",
|
||||
"NotificationOptionPluginUpdateInstalled": "Actualizarea extensiei este instalată",
|
||||
"NotificationOptionPluginUninstalled": "Extensie dezinstalată",
|
||||
"NotificationOptionPluginInstalled": "Extensie instalată",
|
||||
"NotificationOptionPluginError": "Eroare de extensie",
|
||||
"NotificationOptionNewLibraryContent": "A fost adăugat conținut nou",
|
||||
"NotificationOptionInstallationFailed": "Instalare eșuată",
|
||||
"NotificationOptionCameraImageUploaded": "Imagine încarcată",
|
||||
"NotificationOptionAudioPlaybackStopped": "Redare audio oprită",
|
||||
"NotificationOptionAudioPlayback": "A început redarea audio",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Actualizarea aplicației a fost instalată",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Disponibilă o actualizare a aplicației",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Este disponibilă o actualizare a aplicației",
|
||||
"NewVersionIsAvailable": "O nouă versiune a Jellyfin Server este disponibilă pentru descărcare.",
|
||||
"NameSeasonUnknown": "Sezon Necunoscut",
|
||||
"NameSeasonNumber": "Sezonul {0}",
|
||||
@@ -54,8 +54,8 @@
|
||||
"MusicVideos": "Videoclipuri muzicale",
|
||||
"Music": "Muzică",
|
||||
"Movies": "Filme",
|
||||
"MixedContent": "Conținut mixt",
|
||||
"MessageServerConfigurationUpdated": "Configurația serverului a fost actualizată",
|
||||
"MixedContent": "Conținut amestecat",
|
||||
"MessageServerConfigurationUpdated": "Configurarea serverului a fost actualizată",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Secțiunea de configurare a serverului {0} a fost acualizata",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server a fost actualizat la {0}",
|
||||
"MessageApplicationUpdated": "Jellyfin Server a fost actualizat",
|
||||
@@ -69,7 +69,7 @@
|
||||
"HeaderRecordingGroups": "Grupuri de înregistrare",
|
||||
"HeaderLiveTV": "TV în Direct",
|
||||
"HeaderFavoriteSongs": "Melodii Favorite",
|
||||
"HeaderFavoriteShows": "Spectacole Favorite",
|
||||
"HeaderFavoriteShows": "Seriale TV Favorite",
|
||||
"HeaderFavoriteEpisodes": "Episoade Favorite",
|
||||
"HeaderFavoriteArtists": "Artiști Favoriți",
|
||||
"HeaderFavoriteAlbums": "Albume Favorite",
|
||||
@@ -97,10 +97,10 @@
|
||||
"TaskRefreshChannels": "Actualizează canale",
|
||||
"TaskCleanTranscodeDescription": "Șterge fișierele de transcodare mai vechi de o zi.",
|
||||
"TaskCleanTranscode": "Curățați directorul de transcodare",
|
||||
"TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru pluginuri care sunt configurate să se actualizeze automat.",
|
||||
"TaskUpdatePlugins": "Actualizați plugin-uri",
|
||||
"TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru extensiile care sunt configurate să se actualizeze automat.",
|
||||
"TaskUpdatePlugins": "Actualizați Extensile",
|
||||
"TaskRefreshPeopleDescription": "Actualizează metadatele pentru actori și regizori din biblioteca media.",
|
||||
"TaskRefreshPeople": "Actualizează oamenii",
|
||||
"TaskRefreshPeople": "Actualizează Persoanele",
|
||||
"TaskCleanLogsDescription": "Șterge fișierele jurnal care au mai mult de {0} zile.",
|
||||
"TaskCleanLogs": "Curățare director jurnal",
|
||||
"TaskRefreshLibraryDescription": "Scanează biblioteca media pentru fișiere noi și reîmprospătează metadatele.",
|
||||
@@ -114,13 +114,14 @@
|
||||
"TasksLibraryCategory": "Librărie",
|
||||
"TasksMaintenanceCategory": "Mentenanță",
|
||||
"TaskCleanActivityLogDescription": "Șterge intrările din jurnalul de activitate mai vechi de data configurată.",
|
||||
"TaskCleanActivityLog": "Curăță Jurnalul de Activitate",
|
||||
"TaskCleanActivityLog": "Curăță Jurnalul de Activități",
|
||||
"Undefined": "Nedefinit",
|
||||
"Forced": "Forțat",
|
||||
"Default": "Implicit",
|
||||
"TaskOptimizeDatabaseDescription": "Compactează baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.",
|
||||
"TaskOptimizeDatabaseDescription": "Comprimă baza de date și trunchiază spațiul liber. Rularea acestei sarcini după scanarea bibliotecii sau după efectuarea altor modificări care implică modificări ale bazei de date poate îmbunătăți performanța.",
|
||||
"TaskOptimizeDatabase": "Optimizează baza de date",
|
||||
"TaskKeyframeExtractorDescription": "Extrage cadrele cheie din fișierele video pentru a crea liste de redare HLS mai precise. Această sarcină poate rula o perioadă lungă de timp.",
|
||||
"External": "Extern",
|
||||
"TaskKeyframeExtractor": "Extractor de cadre cheie"
|
||||
"TaskKeyframeExtractor": "Extractor de cadre cheie",
|
||||
"HearingImpaired": "Ascultare Impară"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
|
||||
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
|
||||
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
|
||||
"Sync": "Синхро",
|
||||
"Sync": "Синхронизация",
|
||||
"System": "Система",
|
||||
"TvShows": "ТВ",
|
||||
"User": "Пользователь",
|
||||
@@ -117,11 +117,12 @@
|
||||
"TaskCleanActivityLogDescription": "Удаляет записи журнала активности старше установленного возраста.",
|
||||
"TaskCleanActivityLog": "Очистка журнала активности",
|
||||
"Undefined": "Не определено",
|
||||
"Forced": "Форсир-ые",
|
||||
"Forced": "Принудительно",
|
||||
"Default": "По умолчанию",
|
||||
"TaskOptimizeDatabaseDescription": "Сжимает базу данных и вырезает свободные места. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.",
|
||||
"TaskOptimizeDatabase": "Оптимизация базы данных",
|
||||
"TaskKeyframeExtractorDescription": "Извлекаются ключевые кадры из видеофайлов для создания более точных списков плей-листов HLS. Эта задача может выполняться в течение длительного времени.",
|
||||
"TaskKeyframeExtractor": "Извлечение ключевых кадров",
|
||||
"External": "Внешние"
|
||||
"External": "Внешние",
|
||||
"HearingImpaired": "Для слабослышащих"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "Optimalizovať databázu",
|
||||
"TaskKeyframeExtractorDescription": "Extrahuje kľúčové snímky z video súborov na vytvorenie presnejších HLS playlistov. Táto úloha môže trvať dlhšiu dobu.",
|
||||
"TaskKeyframeExtractor": "Extraktor kľúčových snímkov",
|
||||
"External": "Externé"
|
||||
"External": "Externé",
|
||||
"HearingImpaired": "Sluchovo Postihnutý"
|
||||
}
|
||||
|
||||
@@ -119,5 +119,9 @@
|
||||
"Forced": "I detyruar",
|
||||
"Default": "Parazgjedhur",
|
||||
"TaskOptimizeDatabaseDescription": "Kompakton bazën e të dhënave dhe shkurton hapësirën e lirë. Drejtimi i kësaj detyre pasi skanoni bibliotekën ose bëni ndryshime të tjera që nënkuptojnë modifikime të bazës së të dhënave mund të përmirësojë performancën.",
|
||||
"TaskOptimizeDatabase": "Optimizo databazën"
|
||||
"TaskOptimizeDatabase": "Optimizo databazën",
|
||||
"TaskKeyframeExtractorDescription": "Nxjerrë kornizat kryesore nga skedarët video për të krijuar lista luajtjeje më të sakta HLS. Ky veprim mund të dojë një kohë të gjatë për tu kompletuar.",
|
||||
"TaskKeyframeExtractor": "Nxjerrës i kornizës kryesore",
|
||||
"External": "Jashtem",
|
||||
"HearingImpaired": "Dëgjimi i dëmtuar"
|
||||
}
|
||||
|
||||
@@ -122,5 +122,6 @@
|
||||
"TaskOptimizeDatabaseDescription": "Стискає базу даних та збільшує вільний простір. Виконання цього завдання після сканування медіатеки або внесення інших змін, які передбачають модифікацію бази даних може покращити продуктивність.",
|
||||
"TaskKeyframeExtractorDescription": "Витягує ключові кадри з відеофайлів для створення більш точних списків відтворення HLS. Це завдання може виконуватися протягом тривалого часу.",
|
||||
"TaskKeyframeExtractor": "Екстрактор ключових кадрів",
|
||||
"External": "Зовнішній"
|
||||
"External": "Зовнішній",
|
||||
"HearingImpaired": "З порушеннями слуху"
|
||||
}
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
"HeaderAlbumArtists": "البم کے فنکار",
|
||||
"Movies": "فلمیں",
|
||||
"HeaderFavoriteEpisodes": "پسندیدہ اقساط",
|
||||
"Collections": "مجموعہ",
|
||||
"Collections": "مجموعے",
|
||||
"Folders": "فولڈرز",
|
||||
"HeaderLiveTV": "براہ راست ٹی وی",
|
||||
"Channels": "چینلز",
|
||||
"HeaderContinueWatching": "دیکھنا جاری رکھیں",
|
||||
"Playlists": "پلے لسٹس",
|
||||
"ValueSpecialEpisodeName": "خاص - {0}",
|
||||
"Shows": "شوز",
|
||||
"ValueSpecialEpisodeName": "خصوصی - {0}",
|
||||
"Shows": "دکھاتا ہے۔",
|
||||
"Genres": "انواع",
|
||||
"Artists": "فنکار",
|
||||
"Sync": "مطابقت",
|
||||
"Photos": "تصوریں",
|
||||
"Sync": "مطابقت پذیری",
|
||||
"Photos": "تصاویر",
|
||||
"Albums": "البمز",
|
||||
"Favorites": "پسندیدہ",
|
||||
"Songs": "گانے",
|
||||
|
||||
@@ -122,5 +122,6 @@
|
||||
"TaskOptimizeDatabase": "Tối ưu hóa cơ sở dữ liệu",
|
||||
"TaskKeyframeExtractor": "Trích Xuất Khung Hình",
|
||||
"TaskKeyframeExtractorDescription": "Trích xuất khung hình chính từ các tệp video để tạo danh sách phát HLS chính xác hơn. Tác vụ này có thể chạy trong một thời gian dài.",
|
||||
"External": "Bên ngoài"
|
||||
"External": "Bên ngoài",
|
||||
"HearingImpaired": "Khiếm Thính"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskOptimizeDatabase": "优化数据库",
|
||||
"TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的HLS播放列表。这项任务可能需要很长时间。",
|
||||
"TaskKeyframeExtractor": "关键帧提取器",
|
||||
"External": "外部"
|
||||
"External": "外部",
|
||||
"HearingImpaired": "听力障碍"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@
|
||||
"TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。",
|
||||
"TaskKeyframeExtractorDescription": "提取關鍵格以創建更準確的HLS播放列表。次指示可能用時很長。",
|
||||
"TaskKeyframeExtractor": "關鍵幀提取器",
|
||||
"External": "外部"
|
||||
"External": "外部",
|
||||
"HearingImpaired": "聽力障礙"
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"MixedContent": "混合內容",
|
||||
"Movies": "電影",
|
||||
"Music": "音樂",
|
||||
"MusicVideos": "音樂錄影帶",
|
||||
"MusicVideos": "MV",
|
||||
"NameInstallFailed": "{0} 安裝失敗",
|
||||
"NameSeasonNumber": "第 {0} 季",
|
||||
"NameSeasonUnknown": "未知季數",
|
||||
@@ -122,5 +122,6 @@
|
||||
"TaskOptimizeDatabase": "最佳化資料庫",
|
||||
"TaskKeyframeExtractorDescription": "將關鍵幀從影片檔案提取出來並建立更精準的HLS播放清單。這可能需要很長時間。",
|
||||
"TaskKeyframeExtractor": "關鍵幀提取器",
|
||||
"External": "外部"
|
||||
"External": "外部",
|
||||
"HearingImpaired": "聽力障礙"
|
||||
}
|
||||
|
||||
@@ -386,6 +386,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
yield return new LocalizationOption("Español (Dominicana)", "es_DO");
|
||||
yield return new LocalizationOption("Español (México)", "es-MX");
|
||||
yield return new LocalizationOption("Eesti", "et");
|
||||
yield return new LocalizationOption("Basque", "eu");
|
||||
yield return new LocalizationOption("فارسی", "fa");
|
||||
yield return new LocalizationOption("Suomi", "fi");
|
||||
yield return new LocalizationOption("Filipino", "fil");
|
||||
@@ -433,8 +434,8 @@ namespace Emby.Server.Implementations.Localization
|
||||
yield return new LocalizationOption("Українська", "uk");
|
||||
yield return new LocalizationOption("اُردُو", "ur_PK");
|
||||
yield return new LocalizationOption("Tiếng Việt", "vi");
|
||||
yield return new LocalizationOption("汉语 (简化字)", "zh-CN");
|
||||
yield return new LocalizationOption("漢語 (繁体字)", "zh-TW");
|
||||
yield return new LocalizationOption("汉语 (简体字)", "zh-CN");
|
||||
yield return new LocalizationOption("漢語 (繁體字)", "zh-TW");
|
||||
yield return new LocalizationOption("廣東話 (香港)", "zh-HK");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -715,6 +715,7 @@ namespace Emby.Server.Implementations.Plugins
|
||||
{
|
||||
// This value is memory only - so that the web will show restart required.
|
||||
plugin.Manifest.Status = PluginStatus.Restart;
|
||||
plugin.Manifest.AutoUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -729,6 +730,7 @@ namespace Emby.Server.Implementations.Plugins
|
||||
|
||||
// This value is memory only - so that the web will show restart required.
|
||||
plugin.Manifest.Status = PluginStatus.Restart;
|
||||
plugin.Manifest.AutoUpdate = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
{
|
||||
@@ -24,15 +25,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
/// </summary>
|
||||
public class ChapterImagesTask : IScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _library manager.
|
||||
/// </summary>
|
||||
private readonly ILogger<ChapterImagesTask> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
private readonly IItemRepository _itemRepo;
|
||||
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
private readonly IEncodingManager _encodingManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILocalizationManager _localization;
|
||||
@@ -40,6 +36,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>.
|
||||
/// <param name="libraryManager">The library manager.</param>.
|
||||
/// <param name="itemRepo">The item repository.</param>
|
||||
/// <param name="appPaths">The application paths.</param>
|
||||
@@ -47,6 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
/// <param name="fileSystem">The filesystem.</param>
|
||||
/// <param name="localization">The localization manager.</param>
|
||||
public ChapterImagesTask(
|
||||
ILogger<ChapterImagesTask> logger,
|
||||
ILibraryManager libraryManager,
|
||||
IItemRepository itemRepo,
|
||||
IApplicationPaths appPaths,
|
||||
@@ -54,6 +52,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
IFileSystem fileSystem,
|
||||
ILocalizationManager localization)
|
||||
{
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
_itemRepo = itemRepo;
|
||||
_appPaths = appPaths;
|
||||
@@ -167,9 +166,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
|
||||
progress.Report(100 * percent);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
catch (ObjectDisposedException ex)
|
||||
{
|
||||
// TODO Investigate and properly fix.
|
||||
_logger.LogError(ex, "Object Disposed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
{
|
||||
private readonly ILogger<OptimizeDatabaseTask> _logger;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly JellyfinDbProvider _provider;
|
||||
private readonly IDbContextFactory<JellyfinDb> _provider;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OptimizeDatabaseTask" /> class.
|
||||
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
public OptimizeDatabaseTask(
|
||||
ILogger<OptimizeDatabaseTask> logger,
|
||||
ILocalizationManager localization,
|
||||
JellyfinDbProvider provider)
|
||||
IDbContextFactory<JellyfinDb> provider)
|
||||
{
|
||||
_logger = logger;
|
||||
_localization = localization;
|
||||
@@ -70,30 +70,31 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Optimizing and vacuuming jellyfin.db...");
|
||||
|
||||
try
|
||||
{
|
||||
using var context = _provider.CreateContext();
|
||||
if (context.Database.IsSqlite())
|
||||
var context = await _provider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (context.ConfigureAwait(false))
|
||||
{
|
||||
context.Database.ExecuteSqlRaw("PRAGMA optimize");
|
||||
context.Database.ExecuteSqlRaw("VACUUM");
|
||||
_logger.LogInformation("jellyfin.db optimized successfully!");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("This database doesn't support optimization");
|
||||
if (context.Database.IsSqlite())
|
||||
{
|
||||
await context.Database.ExecuteSqlRawAsync("PRAGMA optimize", cancellationToken).ConfigureAwait(false);
|
||||
await context.Database.ExecuteSqlRawAsync("VACUUM", cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogInformation("jellyfin.db optimized successfully!");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("This database doesn't support optimization");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error while optimizing jellyfin.db");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,6 @@ namespace Emby.Server.Implementations.TV
|
||||
AncestorWithPresentationUniqueKey = null,
|
||||
SeriesPresentationUniqueKey = seriesKey,
|
||||
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
||||
OrderBy = new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) },
|
||||
IsPlayed = true,
|
||||
Limit = 1,
|
||||
ParentIndexNumberNotEquals = 0,
|
||||
@@ -203,11 +202,10 @@ namespace Emby.Server.Implementations.TV
|
||||
}
|
||||
};
|
||||
|
||||
if (rewatching)
|
||||
{
|
||||
// find last watched by date played, not by newest episode watched
|
||||
lastQuery.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) };
|
||||
}
|
||||
// If rewatching is enabled, sort first by date played and then by season and episode numbers
|
||||
lastQuery.OrderBy = rewatching
|
||||
? new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) }
|
||||
: new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) };
|
||||
|
||||
var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault();
|
||||
|
||||
@@ -226,18 +224,16 @@ namespace Emby.Server.Implementations.TV
|
||||
DtoOptions = dtoOptions
|
||||
};
|
||||
|
||||
Episode nextEpisode;
|
||||
if (rewatching)
|
||||
// Locate the next up episode based on the last watched episode's season and episode number
|
||||
var lastWatchedParentIndexNumber = lastWatchedEpisode?.ParentIndexNumber;
|
||||
var lastWatchedIndexNumber = lastWatchedEpisode?.IndexNumberEnd ?? lastWatchedEpisode?.IndexNumber;
|
||||
if (lastWatchedParentIndexNumber.HasValue && lastWatchedIndexNumber.HasValue)
|
||||
{
|
||||
nextQuery.Limit = 2;
|
||||
// get watched episode after most recently watched
|
||||
nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().ElementAtOrDefault(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault();
|
||||
nextQuery.MinParentAndIndexNumber = (lastWatchedParentIndexNumber.Value, lastWatchedIndexNumber.Value + 1);
|
||||
}
|
||||
|
||||
var nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault();
|
||||
|
||||
if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
|
||||
{
|
||||
var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
|
||||
Reference in New Issue
Block a user