mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 18:08:53 -07:00
Merge remote-tracking branch 'origin/master' into hwa
This commit is contained in:
commit
728a5988b3
@ -39,6 +39,14 @@ jobs:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
|
||||
displayName: Set release version (stable)
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
|
||||
- script: './bump-version $(JellyfinVersion)'
|
||||
displayName: Bump internal version (stable)
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
|
||||
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
|
||||
displayName: 'Build Dockerfile'
|
||||
|
||||
@ -80,6 +88,14 @@ jobs:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
|
||||
displayName: Set release version (stable)
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
|
||||
- script: './bump-version $(JellyfinVersion)'
|
||||
displayName: Bump internal version (stable)
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download OpenAPI Spec'
|
||||
inputs:
|
||||
@ -127,6 +143,10 @@ jobs:
|
||||
displayName: Set release version (stable)
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
|
||||
- script: './bump-version $(JellyfinVersion)'
|
||||
displayName: Bump internal version (stable)
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
|
||||
- task: Docker@2
|
||||
displayName: 'Push Unstable Image'
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||
|
@ -1 +0,0 @@
|
||||
../fedora/Makefile
|
@ -150,6 +150,7 @@
|
||||
- [ianjazz246](https://github.com/ianjazz246)
|
||||
- [peterspenler](https://github.com/peterspenler)
|
||||
- [MBR-0001](https://github.com/MBR-0001)
|
||||
- [jonas-resch](https://github.com/jonas-resch)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
@ -18,11 +18,8 @@ using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
@ -30,12 +27,7 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Book = MediaBrowser.Controller.Entities.Book;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
||||
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
@ -539,7 +531,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
User = user,
|
||||
Recursive = true,
|
||||
IsMissing = false,
|
||||
ExcludeItemTypes = new[] { nameof(Book) },
|
||||
ExcludeItemTypes = new[] { BaseItemKind.Book },
|
||||
IsFolder = isFolder,
|
||||
MediaTypes = mediaTypes,
|
||||
DtoOptions = GetDtoOptions()
|
||||
@ -619,7 +611,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
Limit = limit,
|
||||
StartIndex = startIndex,
|
||||
IsVirtualItem = false,
|
||||
ExcludeItemTypes = new[] { nameof(Book) },
|
||||
ExcludeItemTypes = new[] { BaseItemKind.Book },
|
||||
IsPlaceHolder = false,
|
||||
DtoOptions = GetDtoOptions(),
|
||||
OrderBy = GetOrderBy(sort, folder.IsPreSorted)
|
||||
@ -644,7 +636,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
StartIndex = startIndex,
|
||||
Limit = limit,
|
||||
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||
OrderBy = GetOrderBy(sort, false)
|
||||
};
|
||||
|
||||
@ -675,23 +667,23 @@ namespace Emby.Dlna.ContentDirectory
|
||||
switch (stubType)
|
||||
{
|
||||
case StubType.Latest:
|
||||
return GetLatest(item, query, nameof(Audio));
|
||||
return GetLatest(item, query, BaseItemKind.Audio);
|
||||
case StubType.Playlists:
|
||||
return GetMusicPlaylists(query);
|
||||
case StubType.Albums:
|
||||
return GetChildrenOfItem(item, query, nameof(MusicAlbum));
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum);
|
||||
case StubType.Artists:
|
||||
return GetMusicArtists(item, query);
|
||||
case StubType.AlbumArtists:
|
||||
return GetMusicAlbumArtists(item, query);
|
||||
case StubType.FavoriteAlbums:
|
||||
return GetChildrenOfItem(item, query, nameof(MusicAlbum), true);
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true);
|
||||
case StubType.FavoriteArtists:
|
||||
return GetFavoriteArtists(item, query);
|
||||
case StubType.FavoriteSongs:
|
||||
return GetChildrenOfItem(item, query, nameof(Audio), true);
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.Audio, true);
|
||||
case StubType.Songs:
|
||||
return GetChildrenOfItem(item, query, nameof(Audio));
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.Audio);
|
||||
case StubType.Genres:
|
||||
return GetMusicGenres(item, query);
|
||||
}
|
||||
@ -746,13 +738,13 @@ namespace Emby.Dlna.ContentDirectory
|
||||
case StubType.ContinueWatching:
|
||||
return GetMovieContinueWatching(item, query);
|
||||
case StubType.Latest:
|
||||
return GetLatest(item, query, nameof(Movie));
|
||||
return GetLatest(item, query, BaseItemKind.Movie);
|
||||
case StubType.Movies:
|
||||
return GetChildrenOfItem(item, query, nameof(Movie));
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.Movie);
|
||||
case StubType.Collections:
|
||||
return GetMovieCollections(query);
|
||||
case StubType.Favorites:
|
||||
return GetChildrenOfItem(item, query, nameof(Movie), true);
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.Movie, true);
|
||||
case StubType.Genres:
|
||||
return GetGenres(item, query);
|
||||
}
|
||||
@ -831,13 +823,13 @@ namespace Emby.Dlna.ContentDirectory
|
||||
case StubType.NextUp:
|
||||
return GetNextUp(item, query);
|
||||
case StubType.Latest:
|
||||
return GetLatest(item, query, nameof(Episode));
|
||||
return GetLatest(item, query, BaseItemKind.Episode);
|
||||
case StubType.Series:
|
||||
return GetChildrenOfItem(item, query, nameof(Series));
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.Series);
|
||||
case StubType.FavoriteSeries:
|
||||
return GetChildrenOfItem(item, query, nameof(Series), true);
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.Series, true);
|
||||
case StubType.FavoriteEpisodes:
|
||||
return GetChildrenOfItem(item, query, nameof(Episode), true);
|
||||
return GetChildrenOfItem(item, query, BaseItemKind.Episode, true);
|
||||
case StubType.Genres:
|
||||
return GetGenres(item, query);
|
||||
}
|
||||
@ -898,7 +890,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query)
|
||||
{
|
||||
query.Recursive = true;
|
||||
query.IncludeItemTypes = new[] { nameof(BoxSet) };
|
||||
query.IncludeItemTypes = new[] { BaseItemKind.BoxSet };
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
|
||||
@ -913,7 +905,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
/// <param name="itemType">The item type.</param>
|
||||
/// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param>
|
||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, string itemType, bool isFavorite = false)
|
||||
private QueryResult<ServerItem> GetChildrenOfItem(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType, bool isFavorite = false)
|
||||
{
|
||||
query.Recursive = true;
|
||||
query.Parent = parent;
|
||||
@ -1013,7 +1005,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query)
|
||||
{
|
||||
query.Parent = null;
|
||||
query.IncludeItemTypes = new[] { nameof(Playlist) };
|
||||
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
|
||||
query.Recursive = true;
|
||||
|
||||
var result = _libraryManager.GetItemsResult(query);
|
||||
@ -1052,7 +1044,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
/// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
|
||||
/// <param name="itemType">The item type.</param>
|
||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, string itemType)
|
||||
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType)
|
||||
{
|
||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||
|
||||
@ -1086,7 +1078,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
Recursive = true,
|
||||
ArtistIds = new[] { item.Id },
|
||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||
Limit = limit,
|
||||
StartIndex = startIndex,
|
||||
DtoOptions = GetDtoOptions(),
|
||||
@ -1115,8 +1107,8 @@ namespace Emby.Dlna.ContentDirectory
|
||||
GenreIds = new[] { item.Id },
|
||||
IncludeItemTypes = new[]
|
||||
{
|
||||
nameof(Movie),
|
||||
nameof(Series)
|
||||
BaseItemKind.Movie,
|
||||
BaseItemKind.Series
|
||||
},
|
||||
Limit = limit,
|
||||
StartIndex = startIndex,
|
||||
@ -1144,7 +1136,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
Recursive = true,
|
||||
GenreIds = new[] { item.Id },
|
||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||
Limit = limit,
|
||||
StartIndex = startIndex,
|
||||
DtoOptions = GetDtoOptions(),
|
||||
|
@ -5,7 +5,6 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -124,7 +124,7 @@ namespace Emby.Dlna.Main
|
||||
config);
|
||||
Current = this;
|
||||
|
||||
var netConfig = config.GetConfiguration<NetworkConfiguration>("network");
|
||||
var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
|
||||
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
|
||||
|
||||
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
|
||||
|
@ -14,6 +14,7 @@ namespace Emby.Naming.AudioBook
|
||||
public class AudioBookListResolver
|
||||
{
|
||||
private readonly NamingOptions _options;
|
||||
private readonly AudioBookResolver _audioBookResolver;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
|
||||
@ -22,6 +23,7 @@ namespace Emby.Naming.AudioBook
|
||||
public AudioBookListResolver(NamingOptions options)
|
||||
{
|
||||
_options = options;
|
||||
_audioBookResolver = new AudioBookResolver(_options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -31,21 +33,19 @@ namespace Emby.Naming.AudioBook
|
||||
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
|
||||
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||
{
|
||||
var audioBookResolver = new AudioBookResolver(_options);
|
||||
|
||||
// File with empty fullname will be sorted out here.
|
||||
var audiobookFileInfos = files
|
||||
.Select(i => audioBookResolver.Resolve(i.FullName))
|
||||
.Select(i => _audioBookResolver.Resolve(i.FullName))
|
||||
.OfType<AudioBookFileInfo>()
|
||||
.ToList();
|
||||
|
||||
var stackResult = new StackResolver(_options)
|
||||
.ResolveAudioBooks(audiobookFileInfos);
|
||||
var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos);
|
||||
|
||||
foreach (var stack in stackResult)
|
||||
{
|
||||
var stackFiles = stack.Files
|
||||
.Select(i => audioBookResolver.Resolve(i))
|
||||
.Select(i => _audioBookResolver.Resolve(i))
|
||||
.OfType<AudioBookFileInfo>()
|
||||
.ToList();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace Emby.Naming.AudioBook
|
||||
{
|
||||
@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
// Check supported extensions
|
||||
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
if (!_options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma warning disable CA1819
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Video;
|
||||
@ -124,11 +125,11 @@ namespace Emby.Naming.Common
|
||||
token: "DSR")
|
||||
};
|
||||
|
||||
VideoFileStackingExpressions = new[]
|
||||
VideoFileStackingRules = new[]
|
||||
{
|
||||
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
||||
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
||||
"(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$"
|
||||
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true),
|
||||
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false),
|
||||
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false)
|
||||
};
|
||||
|
||||
CleanDateTimes = new[]
|
||||
@ -403,6 +404,12 @@ namespace Emby.Naming.Common
|
||||
|
||||
VideoExtraRules = new[]
|
||||
{
|
||||
new ExtraRule(
|
||||
ExtraType.Trailer,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"trailers",
|
||||
MediaType.Video),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.Trailer,
|
||||
ExtraRuleType.Filename,
|
||||
@ -469,6 +476,12 @@ namespace Emby.Naming.Common
|
||||
"theme",
|
||||
MediaType.Audio),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.ThemeSong,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"theme-music",
|
||||
MediaType.Audio),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.Scene,
|
||||
ExtraRuleType.Suffix,
|
||||
@ -563,7 +576,7 @@ namespace Emby.Naming.Common
|
||||
ExtraType.Unknown,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"extras",
|
||||
MediaType.Video),
|
||||
MediaType.Video)
|
||||
};
|
||||
|
||||
Format3DRules = new[]
|
||||
@ -675,9 +688,29 @@ namespace Emby.Naming.Common
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
AllExtrasTypesFolderNames = new Dictionary<string, ExtraType>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["trailers"] = ExtraType.Trailer,
|
||||
["theme-music"] = ExtraType.ThemeSong,
|
||||
["backdrops"] = ExtraType.ThemeVideo,
|
||||
["extras"] = ExtraType.Unknown,
|
||||
["behind the scenes"] = ExtraType.BehindTheScenes,
|
||||
["deleted scenes"] = ExtraType.DeletedScene,
|
||||
["interviews"] = ExtraType.Interview,
|
||||
["scenes"] = ExtraType.Scene,
|
||||
["samples"] = ExtraType.Sample,
|
||||
["shorts"] = ExtraType.Clip,
|
||||
["featurettes"] = ExtraType.Clip
|
||||
};
|
||||
|
||||
Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the folder name to extra types mapping.
|
||||
/// </summary>
|
||||
public Dictionary<string, ExtraType> AllExtrasTypesFolderNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets list of audio file extensions.
|
||||
/// </summary>
|
||||
@ -759,9 +792,9 @@ namespace Emby.Naming.Common
|
||||
public Format3DRule[] Format3DRules { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets list of raw video file-stacking expressions strings.
|
||||
/// Gets the file stacking rules.
|
||||
/// </summary>
|
||||
public string[] VideoFileStackingExpressions { get; set; }
|
||||
public FileStackRule[] VideoFileStackingRules { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets list of raw clean DateTimes regular expressions strings.
|
||||
@ -783,11 +816,6 @@ namespace Emby.Naming.Common
|
||||
/// </summary>
|
||||
public ExtraRule[] VideoExtraRules { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets list of video file-stack regular expressions.
|
||||
/// </summary>
|
||||
public Regex[] VideoFileStackingRegexes { get; private set; } = Array.Empty<Regex>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets list of clean datetime regular expressions.
|
||||
/// </summary>
|
||||
@ -813,7 +841,6 @@ namespace Emby.Naming.Common
|
||||
/// </summary>
|
||||
public void Compile()
|
||||
{
|
||||
VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray();
|
||||
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
|
||||
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
|
||||
EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace Emby.Naming.Subtitles
|
||||
{
|
||||
@ -34,7 +35,7 @@ namespace Emby.Naming.Subtitles
|
||||
}
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -42,11 +43,11 @@ namespace Emby.Naming.Subtitles
|
||||
var flags = GetFlags(path);
|
||||
var info = new SubtitleInfo(
|
||||
path,
|
||||
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)),
|
||||
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)));
|
||||
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)),
|
||||
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase)
|
||||
&& !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase))
|
||||
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparison.OrdinalIgnoreCase)
|
||||
&& !_options.SubtitleForcedFlags.Contains(i, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
// Should have a name, language and file extension
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace Emby.Naming.TV
|
||||
{
|
||||
@ -48,7 +48,7 @@ namespace Emby.Naming.TV
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
// Check supported extensions
|
||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// It's not supported. Check stub extensions
|
||||
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Globalization;
|
||||
using Emby.Naming.Common;
|
||||
|
||||
namespace Emby.Naming.TV
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Audio;
|
||||
using Emby.Naming.Common;
|
||||
@ -9,45 +11,27 @@ namespace Emby.Naming.Video
|
||||
/// <summary>
|
||||
/// Resolve if file is extra for video.
|
||||
/// </summary>
|
||||
public class ExtraResolver
|
||||
public static class ExtraResolver
|
||||
{
|
||||
private static readonly char[] _digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
private readonly NamingOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExtraResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options"><see cref="NamingOptions"/> object containing VideoExtraRules and passed to <see cref="AudioFileParser"/> and <see cref="VideoResolver"/>.</param>
|
||||
public ExtraResolver(NamingOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to resolve if file is extra.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to file.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
||||
public ExtraResult GetExtraInfo(string path)
|
||||
public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions)
|
||||
{
|
||||
var result = new ExtraResult();
|
||||
|
||||
for (var i = 0; i < _options.VideoExtraRules.Length; i++)
|
||||
for (var i = 0; i < namingOptions.VideoExtraRules.Length; i++)
|
||||
{
|
||||
var rule = _options.VideoExtraRules[i];
|
||||
if (rule.MediaType == MediaType.Audio)
|
||||
var rule = namingOptions.VideoExtraRules[i];
|
||||
if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions))
|
||||
|| (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions)))
|
||||
{
|
||||
if (!AudioFileParser.IsAudioFile(path, _options))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (rule.MediaType == MediaType.Video)
|
||||
{
|
||||
if (!VideoResolver.IsVideoFile(path, _options))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var pathSpan = path.AsSpan();
|
||||
@ -76,9 +60,9 @@ namespace Emby.Naming.Video
|
||||
{
|
||||
var filename = Path.GetFileName(path);
|
||||
|
||||
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
|
||||
var isMatch = Regex.IsMatch(filename, rule.Token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
if (regex.IsMatch(filename))
|
||||
if (isMatch)
|
||||
{
|
||||
result.ExtraType = rule.ExtraType;
|
||||
result.Rule = rule;
|
||||
@ -102,5 +86,66 @@ namespace Emby.Naming.Video
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds extras matching the video info.
|
||||
/// </summary>
|
||||
/// <param name="files">The list of file video infos.</param>
|
||||
/// <param name="videoInfo">The video to compare against.</param>
|
||||
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
|
||||
/// <returns>A list of video extras for [videoInfo].</returns>
|
||||
public static IReadOnlyList<VideoFileInfo> GetExtras(IReadOnlyList<VideoInfo> files, VideoFileInfo videoInfo, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
var parentDir = videoInfo.IsDirectory ? videoInfo.Path : Path.GetDirectoryName(videoInfo.Path.AsSpan());
|
||||
|
||||
var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(videoInfo.FileNameWithoutExtension, videoFlagDelimiters);
|
||||
var trimmedVideoInfoName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters);
|
||||
|
||||
var result = new List<VideoFileInfo>();
|
||||
for (var pos = files.Count - 1; pos >= 0; pos--)
|
||||
{
|
||||
var current = files[pos];
|
||||
// ignore non-extras and multi-file (can this happen?)
|
||||
if (current.ExtraType == null || current.Files.Count > 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var currentFile = current.Files[0];
|
||||
var trimmedCurrentFileName = TrimFilenameDelimiters(currentFile.Name, videoFlagDelimiters);
|
||||
|
||||
// first check filenames
|
||||
bool isValid = StartsWith(trimmedCurrentFileName, trimmedFileNameWithoutExtension)
|
||||
|| (StartsWith(trimmedCurrentFileName, trimmedVideoInfoName) && currentFile.Year == videoInfo.Year);
|
||||
|
||||
// then by directory
|
||||
if (!isValid)
|
||||
{
|
||||
// When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
|
||||
var currentParentDir = currentFile.ExtraRule?.RuleType == ExtraRuleType.DirectoryName
|
||||
? Path.GetDirectoryName(Path.GetDirectoryName(currentFile.Path.AsSpan()))
|
||||
: Path.GetDirectoryName(currentFile.Path.AsSpan());
|
||||
|
||||
isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
result.Add(currentFile);
|
||||
}
|
||||
}
|
||||
|
||||
return result.OrderBy(r => r.Path).ToArray();
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
|
||||
}
|
||||
|
||||
private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName)
|
||||
{
|
||||
return !baseName.IsEmpty && fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
@ -12,25 +12,30 @@ namespace Emby.Naming.Video
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileStack"/> class.
|
||||
/// </summary>
|
||||
public FileStack()
|
||||
/// <param name="name">The stack name.</param>
|
||||
/// <param name="isDirectory">Whether the stack files are directories.</param>
|
||||
/// <param name="files">The stack files.</param>
|
||||
public FileStack(string name, bool isDirectory, IReadOnlyList<string> files)
|
||||
{
|
||||
Files = new List<string>();
|
||||
Name = name;
|
||||
IsDirectoryStack = isDirectory;
|
||||
Files = files;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets name of file stack.
|
||||
/// Gets the name of file stack.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets list of paths in stack.
|
||||
/// Gets the list of paths in stack.
|
||||
/// </summary>
|
||||
public List<string> Files { get; set; }
|
||||
public IReadOnlyList<string> Files { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether stack is directory stack.
|
||||
/// Gets a value indicating whether stack is directory stack.
|
||||
/// </summary>
|
||||
public bool IsDirectoryStack { get; set; }
|
||||
public bool IsDirectoryStack { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to determine if path is in the stack.
|
||||
@ -40,12 +45,12 @@ namespace Emby.Naming.Video
|
||||
/// <returns>True if file is in the stack.</returns>
|
||||
public bool ContainsFile(string file, bool isDirectory)
|
||||
{
|
||||
if (IsDirectoryStack == isDirectory)
|
||||
if (string.IsNullOrEmpty(file))
|
||||
{
|
||||
return Files.Contains(file, StringComparer.OrdinalIgnoreCase);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return IsDirectoryStack == isDirectory && Files.Contains(file, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
48
Emby.Naming/Video/FileStackRule.cs
Normal file
48
Emby.Naming/Video/FileStackRule.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Emby.Naming.Video;
|
||||
|
||||
/// <summary>
|
||||
/// Regex based rule for file stacking (eg. disc1, disc2).
|
||||
/// </summary>
|
||||
public class FileStackRule
|
||||
{
|
||||
private readonly Regex _tokenRegex;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileStackRule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="token">Token.</param>
|
||||
/// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param>
|
||||
public FileStackRule(string token, bool isNumerical)
|
||||
{
|
||||
_tokenRegex = new Regex(token, RegexOptions.IgnoreCase);
|
||||
IsNumerical = isNumerical;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the rule uses numerical or alphabetical numbering.
|
||||
/// </summary>
|
||||
public bool IsNumerical { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Match the input against the rule regex.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="result">The part type and number or <c>null</c>.</param>
|
||||
/// <returns>A value indicating whether the input matched the rule.</returns>
|
||||
public bool Match(string input, [NotNullWhen(true)] out (string StackName, string PartType, string PartNumber)? result)
|
||||
{
|
||||
result = null;
|
||||
var match = _tokenRegex.Match(input);
|
||||
if (!match.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var partType = match.Groups["parttype"].Success ? match.Groups["parttype"].Value : "unknown";
|
||||
result = (match.Groups["filename"].Value, partType, match.Groups["number"].Value);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.AudioBook;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Model.IO;
|
||||
@ -12,37 +11,28 @@ namespace Emby.Naming.Video
|
||||
/// <summary>
|
||||
/// Resolve <see cref="FileStack"/> from list of paths.
|
||||
/// </summary>
|
||||
public class StackResolver
|
||||
public static class StackResolver
|
||||
{
|
||||
private readonly NamingOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StackResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options"><see cref="NamingOptions"/> object containing VideoFileStackingRegexes and passes options to <see cref="VideoResolver"/>.</param>
|
||||
public StackResolver(NamingOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves only directories from paths.
|
||||
/// </summary>
|
||||
/// <param name="files">List of paths.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
||||
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
|
||||
public static IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files, NamingOptions namingOptions)
|
||||
{
|
||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
|
||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }), namingOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves only files from paths.
|
||||
/// </summary>
|
||||
/// <param name="files">List of paths.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
|
||||
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
|
||||
public static IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files, NamingOptions namingOptions)
|
||||
{
|
||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
|
||||
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }), namingOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -50,7 +40,7 @@ namespace Emby.Naming.Video
|
||||
/// </summary>
|
||||
/// <param name="files">List of paths.</param>
|
||||
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
||||
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
|
||||
public static IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<AudioBookFileInfo> files)
|
||||
{
|
||||
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
|
||||
|
||||
@ -60,19 +50,13 @@ namespace Emby.Naming.Video
|
||||
{
|
||||
foreach (var file in directory)
|
||||
{
|
||||
var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
|
||||
stack.Files.Add(file.Path);
|
||||
var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { file.Path });
|
||||
yield return stack;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
|
||||
foreach (var file in directory)
|
||||
{
|
||||
stack.Files.Add(file.Path);
|
||||
}
|
||||
|
||||
var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray());
|
||||
yield return stack;
|
||||
}
|
||||
}
|
||||
@ -82,158 +66,91 @@ namespace Emby.Naming.Video
|
||||
/// Resolves videos from paths.
|
||||
/// </summary>
|
||||
/// <param name="files">List of paths.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
|
||||
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||
public static IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions)
|
||||
{
|
||||
var list = files
|
||||
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options))
|
||||
.OrderBy(i => i.FullName)
|
||||
.ToList();
|
||||
var potentialFiles = files
|
||||
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
|
||||
.OrderBy(i => i.FullName);
|
||||
|
||||
var expressions = _options.VideoFileStackingRegexes;
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
var potentialStacks = new Dictionary<string, StackMetadata>();
|
||||
foreach (var file in potentialFiles)
|
||||
{
|
||||
var offset = 0;
|
||||
|
||||
var file1 = list[i];
|
||||
|
||||
var expressionIndex = 0;
|
||||
while (expressionIndex < expressions.Length)
|
||||
var name = file.Name;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
var exp = expressions[expressionIndex];
|
||||
var stack = new FileStack();
|
||||
name = Path.GetFileName(file.FullName);
|
||||
}
|
||||
|
||||
// (Title)(Volume)(Ignore)(Extension)
|
||||
var match1 = FindMatch(file1, exp, offset);
|
||||
|
||||
if (match1.Success)
|
||||
for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++)
|
||||
{
|
||||
var rule = namingOptions.VideoFileStackingRules[i];
|
||||
if (!rule.Match(name, out var stackParsingResult))
|
||||
{
|
||||
var title1 = match1.Groups["title"].Value;
|
||||
var volume1 = match1.Groups["volume"].Value;
|
||||
var ignore1 = match1.Groups["ignore"].Value;
|
||||
var extension1 = match1.Groups["extension"].Value;
|
||||
continue;
|
||||
}
|
||||
|
||||
var j = i + 1;
|
||||
while (j < list.Count)
|
||||
var stackName = stackParsingResult.Value.StackName;
|
||||
var partNumber = stackParsingResult.Value.PartNumber;
|
||||
var partType = stackParsingResult.Value.PartType;
|
||||
|
||||
if (!potentialStacks.TryGetValue(stackName, out var stackResult))
|
||||
{
|
||||
stackResult = new StackMetadata(file.IsDirectory, rule.IsNumerical, partType);
|
||||
potentialStacks[stackName] = stackResult;
|
||||
}
|
||||
|
||||
if (stackResult.Parts.Count > 0)
|
||||
{
|
||||
if (stackResult.IsDirectory != file.IsDirectory
|
||||
|| !string.Equals(partType, stackResult.PartType, StringComparison.OrdinalIgnoreCase)
|
||||
|| stackResult.ContainsPart(partNumber))
|
||||
{
|
||||
var file2 = list[j];
|
||||
|
||||
if (file1.IsDirectory != file2.IsDirectory)
|
||||
{
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// (Title)(Volume)(Ignore)(Extension)
|
||||
var match2 = FindMatch(file2, exp, offset);
|
||||
|
||||
if (match2.Success)
|
||||
{
|
||||
var title2 = match2.Groups[1].Value;
|
||||
var volume2 = match2.Groups[2].Value;
|
||||
var ignore2 = match2.Groups[3].Value;
|
||||
var extension2 = match2.Groups[4].Value;
|
||||
|
||||
if (string.Equals(title1, title2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!string.Equals(volume1, volume2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(extension1, extension2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (stack.Files.Count == 0)
|
||||
{
|
||||
stack.Name = title1 + ignore1;
|
||||
stack.IsDirectoryStack = file1.IsDirectory;
|
||||
stack.Files.Add(file1.FullName);
|
||||
}
|
||||
|
||||
stack.Files.Add(file2.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sequel
|
||||
offset = 0;
|
||||
expressionIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!string.Equals(ignore1, ignore2, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// False positive, try again with offset
|
||||
offset = match1.Groups[3].Index;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extension mismatch
|
||||
offset = 0;
|
||||
expressionIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Title mismatch
|
||||
offset = 0;
|
||||
expressionIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No match 2, next expression
|
||||
offset = 0;
|
||||
expressionIndex++;
|
||||
break;
|
||||
}
|
||||
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (j == list.Count)
|
||||
if (rule.IsNumerical != stackResult.IsNumerical)
|
||||
{
|
||||
expressionIndex = expressions.Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No match 1
|
||||
offset = 0;
|
||||
expressionIndex++;
|
||||
}
|
||||
|
||||
if (stack.Files.Count > 1)
|
||||
{
|
||||
yield return stack;
|
||||
i += stack.Files.Count - 1;
|
||||
break;
|
||||
}
|
||||
stackResult.Parts.Add(partNumber, file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetRegexInput(FileSystemMetadata file)
|
||||
{
|
||||
// For directories, dummy up an extension otherwise the expressions will fail
|
||||
var input = !file.IsDirectory
|
||||
? file.FullName
|
||||
: file.FullName + ".mkv";
|
||||
|
||||
return Path.GetFileName(input);
|
||||
}
|
||||
|
||||
private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
|
||||
{
|
||||
var regexInput = GetRegexInput(input);
|
||||
|
||||
if (offset < 0 || offset >= regexInput.Length)
|
||||
foreach (var (fileName, stack) in potentialStacks)
|
||||
{
|
||||
return Match.Empty;
|
||||
if (stack.Parts.Count < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return new FileStack(fileName, stack.IsDirectory, stack.Parts.Select(kv => kv.Value.FullName).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
private class StackMetadata
|
||||
{
|
||||
public StackMetadata(bool isDirectory, bool isNumerical, string partType)
|
||||
{
|
||||
Parts = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
|
||||
IsDirectory = isDirectory;
|
||||
IsNumerical = isNumerical;
|
||||
PartType = partType;
|
||||
}
|
||||
|
||||
return regex.Match(regexInput, offset);
|
||||
public Dictionary<string, FileSystemMetadata> Parts { get; }
|
||||
|
||||
public bool IsDirectory { get; }
|
||||
|
||||
public bool IsNumerical { get; }
|
||||
|
||||
public string PartType { get; }
|
||||
|
||||
public bool ContainsPart(string partNumber) => Parts.ContainsKey(partNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Jellyfin.Extensions;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
@ -28,7 +28,7 @@ namespace Emby.Naming.Video
|
||||
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
@ -17,7 +18,6 @@ namespace Emby.Naming.Video
|
||||
Name = name;
|
||||
|
||||
Files = Array.Empty<VideoFileInfo>();
|
||||
Extras = Array.Empty<VideoFileInfo>();
|
||||
AlternateVersions = Array.Empty<VideoFileInfo>();
|
||||
}
|
||||
|
||||
@ -39,16 +39,15 @@ namespace Emby.Naming.Video
|
||||
/// <value>The files.</value>
|
||||
public IReadOnlyList<VideoFileInfo> Files { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extras.
|
||||
/// </summary>
|
||||
/// <value>The extras.</value>
|
||||
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alternate versions.
|
||||
/// </summary>
|
||||
/// <value>The alternate versions.</value>
|
||||
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extra type.
|
||||
/// </summary>
|
||||
public ExtraType? ExtraType { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
@ -17,29 +16,38 @@ namespace Emby.Naming.Video
|
||||
/// <summary>
|
||||
/// Resolves alternative versions and extras from list of video files.
|
||||
/// </summary>
|
||||
/// <param name="files">List of related video files.</param>
|
||||
/// <param name="videoInfos">List of related video files.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
|
||||
/// <param name="parseName">Whether to parse the name or use the filename.</param>
|
||||
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
|
||||
public static IEnumerable<VideoInfo> Resolve(IEnumerable<FileSystemMetadata> files, NamingOptions namingOptions, bool supportMultiVersion = true)
|
||||
public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true)
|
||||
{
|
||||
var videoInfos = files
|
||||
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, namingOptions))
|
||||
.OfType<VideoFileInfo>()
|
||||
.ToList();
|
||||
|
||||
// Filter out all extras, otherwise they could cause stacks to not be resolved
|
||||
// See the unit test TestStackedWithTrailer
|
||||
var nonExtras = videoInfos
|
||||
.Where(i => i.ExtraType == null)
|
||||
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
|
||||
|
||||
var stackResult = new StackResolver(namingOptions)
|
||||
.Resolve(nonExtras).ToList();
|
||||
var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList();
|
||||
|
||||
var remainingFiles = videoInfos
|
||||
.Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory)))
|
||||
.ToList();
|
||||
var remainingFiles = new List<VideoFileInfo>();
|
||||
var standaloneMedia = new List<VideoFileInfo>();
|
||||
|
||||
for (var i = 0; i < videoInfos.Count; i++)
|
||||
{
|
||||
var current = videoInfos[i];
|
||||
if (stackResult.Any(s => s.ContainsFile(current.Path, current.IsDirectory)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
remainingFiles.Add(current);
|
||||
if (current.ExtraType == null)
|
||||
{
|
||||
standaloneMedia.Add(current);
|
||||
}
|
||||
}
|
||||
|
||||
var list = new List<VideoInfo>();
|
||||
|
||||
@ -47,27 +55,15 @@ namespace Emby.Naming.Video
|
||||
{
|
||||
var info = new VideoInfo(stack.Name)
|
||||
{
|
||||
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions))
|
||||
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName))
|
||||
.OfType<VideoFileInfo>()
|
||||
.ToList()
|
||||
};
|
||||
|
||||
info.Year = info.Files[0].Year;
|
||||
|
||||
var extras = ExtractExtras(remainingFiles, stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0].AsSpan()), namingOptions.VideoFlagDelimiters);
|
||||
|
||||
if (extras.Count > 0)
|
||||
{
|
||||
info.Extras = extras;
|
||||
}
|
||||
|
||||
list.Add(info);
|
||||
}
|
||||
|
||||
var standaloneMedia = remainingFiles
|
||||
.Where(i => i.ExtraType == null)
|
||||
.ToList();
|
||||
|
||||
foreach (var media in standaloneMedia)
|
||||
{
|
||||
var info = new VideoInfo(media.Name) { Files = new[] { media } };
|
||||
@ -75,10 +71,6 @@ namespace Emby.Naming.Video
|
||||
info.Year = info.Files[0].Year;
|
||||
|
||||
remainingFiles.Remove(media);
|
||||
var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
|
||||
|
||||
info.Extras = extras;
|
||||
|
||||
list.Add(info);
|
||||
}
|
||||
|
||||
@ -87,58 +79,12 @@ namespace Emby.Naming.Video
|
||||
list = GetVideosGroupedByVersion(list, namingOptions);
|
||||
}
|
||||
|
||||
// If there's only one resolved video, use the folder name as well to find extras
|
||||
if (list.Count == 1)
|
||||
{
|
||||
var info = list[0];
|
||||
var videoPath = list[0].Files[0].Path;
|
||||
var parentPath = Path.GetDirectoryName(videoPath.AsSpan());
|
||||
|
||||
if (!parentPath.IsEmpty)
|
||||
{
|
||||
var folderName = Path.GetFileName(parentPath);
|
||||
if (!folderName.IsEmpty)
|
||||
{
|
||||
var extras = ExtractExtras(remainingFiles, folderName, namingOptions.VideoFlagDelimiters);
|
||||
extras.AddRange(info.Extras);
|
||||
info.Extras = extras;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the extras that are just based on file name as well
|
||||
var extrasByFileName = remainingFiles
|
||||
.Where(i => i.ExtraRule != null && i.ExtraRule.RuleType == ExtraRuleType.Filename)
|
||||
.ToList();
|
||||
|
||||
remainingFiles = remainingFiles
|
||||
.Except(extrasByFileName)
|
||||
.ToList();
|
||||
|
||||
extrasByFileName.AddRange(info.Extras);
|
||||
info.Extras = extrasByFileName;
|
||||
}
|
||||
|
||||
// If there's only one video, accept all trailers
|
||||
// Be lenient because people use all kinds of mishmash conventions with trailers.
|
||||
if (list.Count == 1)
|
||||
{
|
||||
var trailers = remainingFiles
|
||||
.Where(i => i.ExtraType == ExtraType.Trailer)
|
||||
.ToList();
|
||||
|
||||
trailers.AddRange(list[0].Extras);
|
||||
list[0].Extras = trailers;
|
||||
|
||||
remainingFiles = remainingFiles
|
||||
.Except(trailers)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Whatever files are left, just add them
|
||||
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
|
||||
{
|
||||
Files = new[] { i },
|
||||
Year = i.Year
|
||||
Year = i.Year,
|
||||
ExtraType = i.ExtraType
|
||||
}));
|
||||
|
||||
return list;
|
||||
@ -162,6 +108,11 @@ namespace Emby.Naming.Video
|
||||
for (var i = 0; i < videos.Count; i++)
|
||||
{
|
||||
var video = videos[i];
|
||||
if (video.ExtraType != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
|
||||
{
|
||||
return videos;
|
||||
@ -178,17 +129,14 @@ namespace Emby.Naming.Video
|
||||
|
||||
var alternateVersionsLen = videos.Count - 1;
|
||||
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
|
||||
var extras = new List<VideoFileInfo>(list[0].Extras);
|
||||
for (int i = 0; i < alternateVersionsLen; i++)
|
||||
{
|
||||
var video = videos[i + 1];
|
||||
alternateVersions[i] = video.Files[0];
|
||||
extras.AddRange(video.Extras);
|
||||
}
|
||||
|
||||
list[0].AlternateVersions = alternateVersions;
|
||||
list[0].Name = folderName.ToString();
|
||||
list[0].Extras = extras;
|
||||
|
||||
return list;
|
||||
}
|
||||
@ -230,7 +178,7 @@ namespace Emby.Naming.Video
|
||||
var tmpTestFilename = testFilename.ToString();
|
||||
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
|
||||
{
|
||||
tmpTestFilename = cleanName.Trim().ToString();
|
||||
tmpTestFilename = cleanName.Trim();
|
||||
}
|
||||
|
||||
// The CleanStringParser should have removed common keywords etc.
|
||||
@ -238,67 +186,5 @@ namespace Emby.Naming.Video
|
||||
|| testFilename[0] == '-'
|
||||
|| Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
|
||||
}
|
||||
|
||||
private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName, ReadOnlySpan<char> trimmedBaseName)
|
||||
{
|
||||
if (baseName.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase)
|
||||
|| (!trimmedBaseName.IsEmpty && fileName.StartsWith(trimmedBaseName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds similar filenames to that of [baseName] and removes any matches from [remainingFiles].
|
||||
/// </summary>
|
||||
/// <param name="remainingFiles">The list of remaining filenames.</param>
|
||||
/// <param name="baseName">The base name to use for the comparison.</param>
|
||||
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
|
||||
/// <returns>A list of video extras for [baseName].</returns>
|
||||
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> baseName, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
return ExtractExtras(remainingFiles, baseName, ReadOnlySpan<char>.Empty, videoFlagDelimiters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds similar filenames to that of [firstBaseName] and [secondBaseName] and removes any matches from [remainingFiles].
|
||||
/// </summary>
|
||||
/// <param name="remainingFiles">The list of remaining filenames.</param>
|
||||
/// <param name="firstBaseName">The first base name to use for the comparison.</param>
|
||||
/// <param name="secondBaseName">The second base name to use for the comparison.</param>
|
||||
/// <param name="videoFlagDelimiters">The video flag delimiters.</param>
|
||||
/// <returns>A list of video extras for [firstBaseName] and [secondBaseName].</returns>
|
||||
private static List<VideoFileInfo> ExtractExtras(IList<VideoFileInfo> remainingFiles, ReadOnlySpan<char> firstBaseName, ReadOnlySpan<char> secondBaseName, ReadOnlySpan<char> videoFlagDelimiters)
|
||||
{
|
||||
var trimmedFirstBaseName = TrimFilenameDelimiters(firstBaseName, videoFlagDelimiters);
|
||||
var trimmedSecondBaseName = TrimFilenameDelimiters(secondBaseName, videoFlagDelimiters);
|
||||
|
||||
var result = new List<VideoFileInfo>();
|
||||
for (var pos = remainingFiles.Count - 1; pos >= 0; pos--)
|
||||
{
|
||||
var file = remainingFiles[pos];
|
||||
if (file.ExtraType == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var filename = file.FileNameWithoutExtension;
|
||||
if (StartsWith(filename, firstBaseName, trimmedFirstBaseName)
|
||||
|| StartsWith(filename, secondBaseName, trimmedSecondBaseName))
|
||||
{
|
||||
result.Add(file);
|
||||
remainingFiles.RemoveAt(pos);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,11 @@ namespace Emby.Naming.Video
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
/// <param name="parseName">Whether to parse the name or use the filename.</param>
|
||||
/// <returns>VideoFileInfo.</returns>
|
||||
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions)
|
||||
public static VideoFileInfo? ResolveDirectory(string? path, NamingOptions namingOptions, bool parseName = true)
|
||||
{
|
||||
return Resolve(path, true, namingOptions);
|
||||
return Resolve(path, true, namingOptions, parseName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -74,7 +75,7 @@ namespace Emby.Naming.Video
|
||||
|
||||
var format3DResult = Format3DParser.Parse(path, namingOptions);
|
||||
|
||||
var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path);
|
||||
var extraResult = ExtraResolver.GetExtraInfo(path, namingOptions);
|
||||
|
||||
var name = Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@ -104,7 +105,7 @@ namespace Emby.Notifications
|
||||
|
||||
var type = entry.Type;
|
||||
|
||||
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparer.OrdinalIgnoreCase))
|
||||
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -60,7 +61,7 @@ namespace Emby.Photos
|
||||
item.SetImagePath(ImageType.Primary, item.Path);
|
||||
|
||||
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
|
||||
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparer.OrdinalIgnoreCase))
|
||||
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
{
|
||||
return _configurations.GetOrAdd(
|
||||
key,
|
||||
(k, configurationManager) =>
|
||||
static (k, configurationManager) =>
|
||||
{
|
||||
var file = configurationManager.GetConfigurationFile(k);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace Emby.Server.Implementations.AppBase
|
||||
|
@ -313,22 +313,6 @@ namespace Emby.Server.Implementations
|
||||
? Environment.MachineName
|
||||
: ConfigurationManager.Configuration.ServerName;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary function to migration network settings out of system.xml and into network.xml.
|
||||
/// TODO: remove at the point when a fixed migration path has been decided upon.
|
||||
/// </summary>
|
||||
private void MigrateNetworkConfiguration()
|
||||
{
|
||||
string path = Path.Combine(ConfigurationManager.CommonApplicationPaths.ConfigurationDirectoryPath, "network.xml");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
var networkSettings = new NetworkConfiguration();
|
||||
ClassMigrationHelper.CopyProperties(ConfigurationManager.Configuration, networkSettings);
|
||||
_xmlSerializer.SerializeToFile(networkSettings, path);
|
||||
Logger.LogDebug("Successfully migrated network settings.");
|
||||
}
|
||||
}
|
||||
|
||||
public string ExpandVirtualPath(string path)
|
||||
{
|
||||
var appPaths = ApplicationPaths;
|
||||
@ -513,8 +497,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||
|
||||
// Have to migrate settings here as migration subsystem not yet initialised.
|
||||
MigrateNetworkConfiguration();
|
||||
NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
|
||||
|
||||
// Initialize runtime stat collection
|
||||
|
@ -1,11 +1,8 @@
|
||||
using System.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using SharpCompress.Archives.SevenZip;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.GZip;
|
||||
using SharpCompress.Readers.Zip;
|
||||
|
||||
namespace Emby.Server.Implementations.Archiving
|
||||
{
|
||||
@ -14,55 +11,6 @@ namespace Emby.Server.Implementations.Archiving
|
||||
/// </summary>
|
||||
public class ZipClient : IZipClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts all.
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">The source file.</param>
|
||||
/// <param name="targetPath">The target path.</param>
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using var fileStream = File.OpenRead(sourceFile);
|
||||
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="targetPath">The target path.</param>
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using var reader = ReaderFactory.Open(source);
|
||||
var options = new ExtractionOptions
|
||||
{
|
||||
ExtractFullPath = true
|
||||
};
|
||||
|
||||
if (overwriteExistingFiles)
|
||||
{
|
||||
options.Overwrite = true;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(targetPath);
|
||||
reader.WriteAllToDirectory(targetPath, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using var reader = ZipReader.Open(source);
|
||||
var options = new ExtractionOptions
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = overwriteExistingFiles
|
||||
};
|
||||
|
||||
Directory.CreateDirectory(targetPath);
|
||||
reader.WriteAllToDirectory(targetPath, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
@ -94,69 +42,5 @@ namespace Emby.Server.Implementations.Archiving
|
||||
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all from7z.
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">The source file.</param>
|
||||
/// <param name="targetPath">The target path.</param>
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using var fileStream = File.OpenRead(sourceFile);
|
||||
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all from7z.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="targetPath">The target path.</param>
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using var archive = SevenZipArchive.Open(source);
|
||||
using var reader = archive.ExtractAllEntries();
|
||||
var options = new ExtractionOptions
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = overwriteExistingFiles
|
||||
};
|
||||
|
||||
Directory.CreateDirectory(targetPath);
|
||||
reader.WriteAllToDirectory(targetPath, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all from tar.
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">The source file.</param>
|
||||
/// <param name="targetPath">The target path.</param>
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using var fileStream = File.OpenRead(sourceFile);
|
||||
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all from tar.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="targetPath">The target path.</param>
|
||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||
{
|
||||
using var archive = TarArchive.Open(source);
|
||||
using var reader = archive.ExtractAllEntries();
|
||||
var options = new ExtractionOptions
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = overwriteExistingFiles
|
||||
};
|
||||
|
||||
Directory.CreateDirectory(targetPath);
|
||||
reader.WriteAllToDirectory(targetPath, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
@ -179,7 +180,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
try
|
||||
{
|
||||
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
|
||||
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
||||
&& hasAttributes.Attributes.Contains("Recordings", StringComparison.OrdinalIgnoreCase)) == val;
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -541,7 +542,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
return _libraryManager.GetItemIds(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(Channel) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Channel },
|
||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
||||
}).Select(i => GetChannelFeatures(i)).ToArray();
|
||||
}
|
||||
@ -1135,7 +1136,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
if (!info.IsLiveStream)
|
||||
{
|
||||
if (item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
|
||||
if (item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
|
||||
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
||||
@ -1144,7 +1145,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
|
||||
if (!item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
|
||||
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(Channel) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Channel },
|
||||
ExcludeItemIds = installedChannelIds.ToArray()
|
||||
});
|
||||
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Jellyfin.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
@ -194,7 +194,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
protected void AddColumn(IDatabaseConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
|
||||
{
|
||||
if (existingColumnNames.Contains(columnName, StringComparer.OrdinalIgnoreCase))
|
||||
if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private readonly ItemFields[] _allItemFields = Enum.GetValues<ItemFields>();
|
||||
|
||||
private static readonly string[] _retriveItemColumns =
|
||||
private static readonly string[] _retrieveItemColumns =
|
||||
{
|
||||
"type",
|
||||
"data",
|
||||
@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Data
|
||||
"OwnerId"
|
||||
};
|
||||
|
||||
private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid";
|
||||
private static readonly string _retrieveItemColumnsSelectQuery = $"select {string.Join(',', _retrieveItemColumns)} from TypedBaseItems where guid = @guid";
|
||||
|
||||
private static readonly string[] _mediaStreamSaveColumns =
|
||||
{
|
||||
@ -196,57 +196,56 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private static readonly string _mediaAttachmentInsertPrefix;
|
||||
|
||||
private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly BaseItemKind[] _programTypes = new[]
|
||||
{
|
||||
"Program",
|
||||
"TvChannel",
|
||||
"LiveTvProgram",
|
||||
"LiveTvTvChannel"
|
||||
BaseItemKind.Program,
|
||||
BaseItemKind.TvChannel,
|
||||
BaseItemKind.LiveTvProgram,
|
||||
BaseItemKind.LiveTvChannel
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly BaseItemKind[] _programExcludeParentTypes = new[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"MusicAlbum",
|
||||
"MusicArtist",
|
||||
"PhotoAlbum"
|
||||
BaseItemKind.Series,
|
||||
BaseItemKind.Season,
|
||||
BaseItemKind.MusicAlbum,
|
||||
BaseItemKind.MusicArtist,
|
||||
BaseItemKind.PhotoAlbum
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly BaseItemKind[] _serviceTypes = new[]
|
||||
{
|
||||
"TvChannel",
|
||||
"LiveTvTvChannel"
|
||||
BaseItemKind.TvChannel,
|
||||
BaseItemKind.LiveTvChannel
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly BaseItemKind[] _startDateTypes = new[]
|
||||
{
|
||||
"Program",
|
||||
"LiveTvProgram"
|
||||
BaseItemKind.Program,
|
||||
BaseItemKind.LiveTvProgram
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly BaseItemKind[] _seriesTypes = new[]
|
||||
{
|
||||
"Book",
|
||||
"AudioBook",
|
||||
"Episode",
|
||||
"Season"
|
||||
BaseItemKind.Book,
|
||||
BaseItemKind.AudioBook,
|
||||
BaseItemKind.Episode,
|
||||
BaseItemKind.Season
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly BaseItemKind[] _artistExcludeParentTypes = new[]
|
||||
{
|
||||
"Series",
|
||||
"Season",
|
||||
"PhotoAlbum"
|
||||
BaseItemKind.Series,
|
||||
BaseItemKind.Season,
|
||||
BaseItemKind.PhotoAlbum
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly BaseItemKind[] _artistsTypes = new[]
|
||||
{
|
||||
"Audio",
|
||||
"MusicAlbum",
|
||||
"MusicVideo",
|
||||
"AudioBook",
|
||||
"AudioPodcast"
|
||||
BaseItemKind.Audio,
|
||||
BaseItemKind.MusicAlbum,
|
||||
BaseItemKind.MusicVideo,
|
||||
BaseItemKind.AudioBook
|
||||
};
|
||||
|
||||
private static readonly Type[] _knownTypes =
|
||||
@ -285,6 +284,43 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private readonly Dictionary<string, string> _types = GetTypeMapDictionary();
|
||||
|
||||
private static readonly Dictionary<BaseItemKind, string> _baseItemKindNames = new()
|
||||
{
|
||||
{ BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
|
||||
{ BaseItemKind.Audio, typeof(Audio).FullName },
|
||||
{ BaseItemKind.AudioBook, typeof(AudioBook).FullName },
|
||||
{ BaseItemKind.BasePluginFolder, typeof(BasePluginFolder).FullName },
|
||||
{ BaseItemKind.Book, typeof(Book).FullName },
|
||||
{ BaseItemKind.BoxSet, typeof(BoxSet).FullName },
|
||||
{ BaseItemKind.Channel, typeof(Channel).FullName },
|
||||
{ BaseItemKind.CollectionFolder, typeof(CollectionFolder).FullName },
|
||||
{ BaseItemKind.Episode, typeof(Episode).FullName },
|
||||
{ BaseItemKind.Folder, typeof(Folder).FullName },
|
||||
{ BaseItemKind.Genre, typeof(Genre).FullName },
|
||||
{ BaseItemKind.Movie, typeof(Movie).FullName },
|
||||
{ BaseItemKind.LiveTvChannel, typeof(LiveTvChannel).FullName },
|
||||
{ BaseItemKind.LiveTvProgram, typeof(LiveTvProgram).FullName },
|
||||
{ BaseItemKind.MusicAlbum, typeof(MusicAlbum).FullName },
|
||||
{ BaseItemKind.MusicArtist, typeof(MusicArtist).FullName },
|
||||
{ BaseItemKind.MusicGenre, typeof(MusicGenre).FullName },
|
||||
{ BaseItemKind.MusicVideo, typeof(MusicVideo).FullName },
|
||||
{ BaseItemKind.Person, typeof(Person).FullName },
|
||||
{ BaseItemKind.Photo, typeof(Photo).FullName },
|
||||
{ BaseItemKind.PhotoAlbum, typeof(PhotoAlbum).FullName },
|
||||
{ BaseItemKind.Playlist, typeof(Playlist).FullName },
|
||||
{ BaseItemKind.PlaylistsFolder, typeof(PlaylistsFolder).FullName },
|
||||
{ BaseItemKind.Season, typeof(Season).FullName },
|
||||
{ BaseItemKind.Series, typeof(Series).FullName },
|
||||
{ BaseItemKind.Studio, typeof(Studio).FullName },
|
||||
{ BaseItemKind.Trailer, typeof(Trailer).FullName },
|
||||
{ BaseItemKind.TvChannel, typeof(LiveTvChannel).FullName },
|
||||
{ BaseItemKind.TvProgram, typeof(LiveTvProgram).FullName },
|
||||
{ BaseItemKind.UserRootFolder, typeof(UserRootFolder).FullName },
|
||||
{ BaseItemKind.UserView, typeof(UserView).FullName },
|
||||
{ BaseItemKind.Video, typeof(Video).FullName },
|
||||
{ BaseItemKind.Year, typeof(Year).FullName }
|
||||
};
|
||||
|
||||
static SqliteItemRepository()
|
||||
{
|
||||
var queryPrefixText = new StringBuilder();
|
||||
@ -1320,7 +1356,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
using (var statement = PrepareStatement(connection, _retriveItemColumnsSelectQuery))
|
||||
using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery))
|
||||
{
|
||||
statement.TryBind("@guid", id);
|
||||
|
||||
@ -2212,7 +2248,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private bool HasProgramAttributes(InternalItemsQuery query)
|
||||
{
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -2227,7 +2263,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private bool HasServiceName(InternalItemsQuery query)
|
||||
{
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -2242,7 +2278,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private bool HasStartDate(InternalItemsQuery query)
|
||||
{
|
||||
if (_programExcludeParentTypes.Contains(query.ParentType))
|
||||
if (query.ParentType != null && _programExcludeParentTypes.Contains(query.ParentType.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -2262,7 +2298,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return true;
|
||||
}
|
||||
|
||||
return query.IncludeItemTypes.Contains("Episode", StringComparer.OrdinalIgnoreCase);
|
||||
return query.IncludeItemTypes.Contains(BaseItemKind.Episode);
|
||||
}
|
||||
|
||||
private bool HasTrailerTypes(InternalItemsQuery query)
|
||||
@ -2272,12 +2308,12 @@ namespace Emby.Server.Implementations.Data
|
||||
return true;
|
||||
}
|
||||
|
||||
return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase);
|
||||
return query.IncludeItemTypes.Contains(BaseItemKind.Trailer);
|
||||
}
|
||||
|
||||
private bool HasArtistFields(InternalItemsQuery query)
|
||||
{
|
||||
if (_artistExcludeParentTypes.Contains(query.ParentType))
|
||||
if (query.ParentType != null && _artistExcludeParentTypes.Contains(query.ParentType.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -2292,7 +2328,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private bool HasSeriesFields(InternalItemsQuery query)
|
||||
{
|
||||
if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase))
|
||||
if (query.ParentType == BaseItemKind.PhotoAlbum)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -2630,7 +2666,7 @@ namespace Emby.Server.Implementations.Data
|
||||
query.Limit = query.Limit.Value + 4;
|
||||
}
|
||||
|
||||
var columns = _retriveItemColumns.ToList();
|
||||
var columns = _retrieveItemColumns.ToList();
|
||||
SetFinalColumnsToSelect(query, columns);
|
||||
var commandTextBuilder = new StringBuilder("select ", 1024)
|
||||
.AppendJoin(',', columns)
|
||||
@ -2821,7 +2857,7 @@ namespace Emby.Server.Implementations.Data
|
||||
query.Limit = query.Limit.Value + 4;
|
||||
}
|
||||
|
||||
var columns = _retriveItemColumns.ToList();
|
||||
var columns = _retrieveItemColumns.ToList();
|
||||
SetFinalColumnsToSelect(query, columns);
|
||||
var commandTextBuilder = new StringBuilder("select ", 512)
|
||||
.AppendJoin(',', columns)
|
||||
@ -3487,8 +3523,8 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.IsMovie == true)
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 0
|
||||
|| query.IncludeItemTypes.Contains(nameof(Movie))
|
||||
|| query.IncludeItemTypes.Contains(nameof(Trailer)))
|
||||
|| query.IncludeItemTypes.Contains(BaseItemKind.Movie)
|
||||
|| query.IncludeItemTypes.Contains(BaseItemKind.Trailer))
|
||||
{
|
||||
whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
||||
}
|
||||
@ -3563,31 +3599,81 @@ namespace Emby.Server.Implementations.Data
|
||||
statement?.TryBind("@IsFolder", query.IsFolder);
|
||||
}
|
||||
|
||||
var includeTypes = query.IncludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray();
|
||||
var includeTypes = query.IncludeItemTypes;
|
||||
// Only specify excluded types if no included types are specified
|
||||
if (includeTypes.Length == 0)
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
var excludeTypes = query.ExcludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray();
|
||||
var excludeTypes = query.ExcludeItemTypes;
|
||||
if (excludeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type<>@type");
|
||||
statement?.TryBind("@type", excludeTypes[0]);
|
||||
if (_baseItemKindNames.TryGetValue(excludeTypes[0], out var excludeTypeName))
|
||||
{
|
||||
whereClauses.Add("type<>@type");
|
||||
statement?.TryBind("@type", excludeTypeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeTypes[0]);
|
||||
}
|
||||
}
|
||||
else if (excludeTypes.Length > 1)
|
||||
{
|
||||
var inClause = string.Join(',', excludeTypes.Select(i => "'" + i + "'"));
|
||||
whereClauses.Add($"type not in ({inClause})");
|
||||
var whereBuilder = new StringBuilder("type not in (");
|
||||
foreach (var excludeType in excludeTypes)
|
||||
{
|
||||
if (_baseItemKindNames.TryGetValue(excludeType, out var baseItemKindName))
|
||||
{
|
||||
whereBuilder
|
||||
.Append('\'')
|
||||
.Append(baseItemKindName)
|
||||
.Append("',");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", excludeType);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trailing comma.
|
||||
whereBuilder.Length--;
|
||||
whereBuilder.Append(')');
|
||||
whereClauses.Add(whereBuilder.ToString());
|
||||
}
|
||||
}
|
||||
else if (includeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type=@type");
|
||||
statement?.TryBind("@type", includeTypes[0]);
|
||||
if (_baseItemKindNames.TryGetValue(includeTypes[0], out var includeTypeName))
|
||||
{
|
||||
whereClauses.Add("type=@type");
|
||||
statement?.TryBind("@type", includeTypeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeTypes[0]);
|
||||
}
|
||||
}
|
||||
else if (includeTypes.Length > 1)
|
||||
{
|
||||
var inClause = string.Join(',', includeTypes.Select(i => "'" + i + "'"));
|
||||
whereClauses.Add($"type in ({inClause})");
|
||||
var whereBuilder = new StringBuilder("type in (");
|
||||
foreach (var includeType in includeTypes)
|
||||
{
|
||||
if (_baseItemKindNames.TryGetValue(includeType, out var baseItemKindName))
|
||||
{
|
||||
whereBuilder
|
||||
.Append('\'')
|
||||
.Append(baseItemKindName)
|
||||
.Append("',");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("Undefined BaseItemKind to Type mapping: {BaseItemKind}", includeType);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trailing comma.
|
||||
whereBuilder.Length--;
|
||||
whereBuilder.Append(')');
|
||||
whereClauses.Add(whereBuilder.ToString());
|
||||
}
|
||||
|
||||
if (query.ChannelIds.Count == 1)
|
||||
@ -3911,7 +3997,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.IsPlayed.HasValue)
|
||||
{
|
||||
// We should probably figure this out for all folders, but for right now, this is the only place where we need it
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(Series), StringComparison.OrdinalIgnoreCase))
|
||||
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.Series)
|
||||
{
|
||||
if (query.IsPlayed.Value)
|
||||
{
|
||||
@ -4761,27 +4847,27 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var list = new List<string>();
|
||||
|
||||
if (IsTypeInQuery(nameof(Person), query))
|
||||
if (IsTypeInQuery(BaseItemKind.Person, query))
|
||||
{
|
||||
list.Add(typeof(Person).FullName);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(nameof(Genre), query))
|
||||
if (IsTypeInQuery(BaseItemKind.Genre, query))
|
||||
{
|
||||
list.Add(typeof(Genre).FullName);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(nameof(MusicGenre), query))
|
||||
if (IsTypeInQuery(BaseItemKind.MusicGenre, query))
|
||||
{
|
||||
list.Add(typeof(MusicGenre).FullName);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(nameof(MusicArtist), query))
|
||||
if (IsTypeInQuery(BaseItemKind.MusicArtist, query))
|
||||
{
|
||||
list.Add(typeof(MusicArtist).FullName);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(nameof(Studio), query))
|
||||
if (IsTypeInQuery(BaseItemKind.Studio, query))
|
||||
{
|
||||
list.Add(typeof(Studio).FullName);
|
||||
}
|
||||
@ -4789,14 +4875,14 @@ namespace Emby.Server.Implementations.Data
|
||||
return list;
|
||||
}
|
||||
|
||||
private bool IsTypeInQuery(string type, InternalItemsQuery query)
|
||||
private bool IsTypeInQuery(BaseItemKind type, InternalItemsQuery query)
|
||||
{
|
||||
if (query.ExcludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase))
|
||||
if (query.ExcludeItemTypes.Contains(type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type, StringComparer.OrdinalIgnoreCase);
|
||||
return query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(type);
|
||||
}
|
||||
|
||||
private string GetCleanValue(string value)
|
||||
@ -4836,12 +4922,12 @@ namespace Emby.Server.Implementations.Data
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.IncludeItemTypes.Contains(nameof(Episode), StringComparer.OrdinalIgnoreCase)
|
||||
|| query.IncludeItemTypes.Contains(nameof(Video), StringComparer.OrdinalIgnoreCase)
|
||||
|| query.IncludeItemTypes.Contains(nameof(Movie), StringComparer.OrdinalIgnoreCase)
|
||||
|| query.IncludeItemTypes.Contains(nameof(MusicVideo), StringComparer.OrdinalIgnoreCase)
|
||||
|| query.IncludeItemTypes.Contains(nameof(Series), StringComparer.OrdinalIgnoreCase)
|
||||
|| query.IncludeItemTypes.Contains(nameof(Season), StringComparer.OrdinalIgnoreCase))
|
||||
if (query.IncludeItemTypes.Contains(BaseItemKind.Episode)
|
||||
|| query.IncludeItemTypes.Contains(BaseItemKind.Video)
|
||||
|| query.IncludeItemTypes.Contains(BaseItemKind.Movie)
|
||||
|| query.IncludeItemTypes.Contains(BaseItemKind.MusicVideo)
|
||||
|| query.IncludeItemTypes.Contains(BaseItemKind.Series)
|
||||
|| query.IncludeItemTypes.Contains(BaseItemKind.Season))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -4890,22 +4976,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
return dict;
|
||||
}
|
||||
|
||||
private string MapIncludeItemTypes(string value)
|
||||
{
|
||||
if (_types.TryGetValue(value, out string result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (IsValidType(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
Logger.LogWarning("Unknown item type: {ItemType}", value);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void DeleteItem(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
@ -5351,7 +5421,7 @@ AND Type = @InternalPersonType)");
|
||||
stringBuilder.Clear();
|
||||
}
|
||||
|
||||
List<string> columns = _retriveItemColumns.ToList();
|
||||
List<string> columns = _retrieveItemColumns.ToList();
|
||||
// Unfortunately we need to add it to columns to ensure the order of the columns in the select
|
||||
if (!string.IsNullOrEmpty(itemCountColumns))
|
||||
{
|
||||
@ -5569,7 +5639,7 @@ AND Type = @InternalPersonType)");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ItemCounts GetItemCounts(IReadOnlyList<ResultSetValue> reader, int countStartColumn, string[] typesToCount)
|
||||
private static ItemCounts GetItemCounts(IReadOnlyList<ResultSetValue> reader, int countStartColumn, BaseItemKind[] typesToCount)
|
||||
{
|
||||
var counts = new ItemCounts();
|
||||
|
||||
|
@ -7,9 +7,9 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
@ -294,7 +294,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
path = path.TrimStart('.');
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fileExtensionContainer = path;
|
||||
}
|
||||
@ -470,7 +470,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||
Name = item.Album,
|
||||
Limit = 1
|
||||
});
|
||||
@ -1404,44 +1404,27 @@ namespace Emby.Server.Implementations.Dto
|
||||
return null;
|
||||
}
|
||||
|
||||
ImageDimensions size;
|
||||
|
||||
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
||||
|
||||
if (defaultAspectRatio > 0)
|
||||
{
|
||||
return defaultAspectRatio;
|
||||
}
|
||||
|
||||
if (!imageInfo.IsLocalFile)
|
||||
{
|
||||
return null;
|
||||
return item.GetDefaultPrimaryImageAspectRatio();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
||||
|
||||
if (size.Width <= 0 || size.Height <= 0)
|
||||
var size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
||||
var width = size.Width;
|
||||
var height = size.Height;
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
return null;
|
||||
return (double)width / height;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
|
||||
return null;
|
||||
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
|
||||
}
|
||||
|
||||
var width = size.Width;
|
||||
var height = size.Height;
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (double)width / height;
|
||||
return item.GetDefaultPrimaryImageAspectRatio();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
|
||||
<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.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.1" />
|
||||
<PackageReference Include="Mono.Nat" Version="3.0.2" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
||||
<PackageReference Include="sharpcompress" Version="0.30.1" />
|
||||
|
@ -13,7 +13,6 @@ using Jellyfin.Networking.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Mono.Nat;
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
|
@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
|
||||
WebSocketMessage<object>? stub;
|
||||
long bytesConsumed = 0;
|
||||
long bytesConsumed;
|
||||
try
|
||||
{
|
||||
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
|
||||
|
@ -28,35 +28,35 @@ namespace Emby.Server.Implementations.Images
|
||||
var view = (CollectionFolder)item;
|
||||
var viewType = view.CollectionType;
|
||||
|
||||
string[] includeItemTypes;
|
||||
BaseItemKind[] includeItemTypes;
|
||||
|
||||
if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
|
||||
{
|
||||
includeItemTypes = new string[] { "Movie" };
|
||||
includeItemTypes = new[] { BaseItemKind.Movie };
|
||||
}
|
||||
else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
|
||||
{
|
||||
includeItemTypes = new string[] { "Series" };
|
||||
includeItemTypes = new[] { BaseItemKind.Series };
|
||||
}
|
||||
else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
|
||||
{
|
||||
includeItemTypes = new string[] { "MusicAlbum" };
|
||||
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
|
||||
}
|
||||
else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
|
||||
{
|
||||
includeItemTypes = new string[] { "Book", "AudioBook" };
|
||||
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
|
||||
}
|
||||
else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
|
||||
{
|
||||
includeItemTypes = new string[] { "BoxSet" };
|
||||
includeItemTypes = new[] { BaseItemKind.BoxSet };
|
||||
}
|
||||
else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
|
||||
{
|
||||
includeItemTypes = new string[] { "Video", "Photo" };
|
||||
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
|
||||
}
|
||||
else
|
||||
{
|
||||
includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" };
|
||||
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series };
|
||||
}
|
||||
|
||||
var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);
|
||||
|
@ -6,6 +6,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@ -34,14 +36,14 @@ namespace Emby.Server.Implementations.Images
|
||||
var view = (UserView)item;
|
||||
|
||||
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
|
||||
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
|
||||
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var result = view.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
|
||||
CollapseBoxSetItems = false,
|
||||
Recursive = recursive,
|
||||
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" },
|
||||
ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person },
|
||||
DtoOptions = new DtoOptions(false)
|
||||
});
|
||||
|
||||
|
@ -8,8 +8,6 @@ using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@ -43,7 +41,7 @@ namespace Emby.Server.Implementations.Images
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
Genres = new[] { item.Name },
|
||||
IncludeItemTypes = new[] { nameof(Series), nameof(Movie) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie },
|
||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||
Limit = 4,
|
||||
Recursive = true,
|
||||
|
@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Images
|
||||
Genres = new[] { item.Name },
|
||||
IncludeItemTypes = new[]
|
||||
{
|
||||
nameof(MusicAlbum),
|
||||
nameof(MusicVideo),
|
||||
nameof(Audio)
|
||||
BaseItemKind.MusicAlbum,
|
||||
BaseItemKind.MusicVideo,
|
||||
BaseItemKind.Audio
|
||||
},
|
||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||
Limit = 4,
|
||||
|
@ -54,20 +54,10 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (parent != null)
|
||||
{
|
||||
// Ignore trailer folders but allow it at the collection level
|
||||
if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)
|
||||
&& !(parent is AggregateFolder)
|
||||
&& !(parent is UserRootFolder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
// Ignore extras folders but allow it at the collection level
|
||||
if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename)
|
||||
&& parent is not AggregateFolder
|
||||
&& parent is not UserRootFolder)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -11,11 +11,9 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Naming.Audio;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.TV;
|
||||
using Emby.Naming.Video;
|
||||
using Emby.Server.Implementations.Library.Resolvers;
|
||||
using Emby.Server.Implementations.Library.Validators;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Emby.Server.Implementations.ScheduledTasks;
|
||||
@ -533,8 +531,8 @@ namespace Emby.Server.Implementations.Library
|
||||
return key.GetMD5();
|
||||
}
|
||||
|
||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
|
||||
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
|
||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, IDirectoryService directoryService = null)
|
||||
=> ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent);
|
||||
|
||||
private BaseItem ResolvePath(
|
||||
FileSystemMetadata fileInfo,
|
||||
@ -654,7 +652,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return !args.ContainsFileSystemEntryByName(".ignore");
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType)
|
||||
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType = null)
|
||||
{
|
||||
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
|
||||
}
|
||||
@ -677,7 +675,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var result = resolver.ResolveMultiple(parent, fileList, collectionType, directoryService);
|
||||
|
||||
if (result != null && result.Items.Count > 0)
|
||||
if (result?.Items.Count > 0)
|
||||
{
|
||||
var items = new List<BaseItem>();
|
||||
items.AddRange(result.Items);
|
||||
@ -965,7 +963,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var existing = GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(MusicArtist) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
|
||||
Name = name,
|
||||
DtoOptions = options
|
||||
}).Cast<MusicArtist>()
|
||||
@ -2685,89 +2683,105 @@ namespace Emby.Server.Implementations.Library
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
public IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
var namingOptions = _namingOptions;
|
||||
|
||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||
.Where(i => string.Equals(i.Name, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false))
|
||||
.ToList();
|
||||
|
||||
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
|
||||
|
||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (currentVideo != null)
|
||||
var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
|
||||
if (ownerVideoInfo == null)
|
||||
{
|
||||
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType == ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
||||
yield break;
|
||||
}
|
||||
|
||||
var resolvers = new IItemResolver[]
|
||||
var count = fileSystemChildren.Count;
|
||||
var files = new List<VideoFileInfo>();
|
||||
var nonVideoFiles = new List<FileSystemMetadata>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
new GenericVideoResolver<Trailer>(_namingOptions)
|
||||
};
|
||||
|
||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
|
||||
.OfType<Trailer>()
|
||||
.Select(video =>
|
||||
var current = fileSystemChildren[i];
|
||||
if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
if (GetItemById(video.Id) is Trailer dbItem)
|
||||
var filesInSubFolder = _fileSystem.GetFiles(current.FullName, _namingOptions.VideoFileExtensions, false, false);
|
||||
foreach (var file in filesInSubFolder)
|
||||
{
|
||||
video = dbItem;
|
||||
var videoInfo = VideoResolver.Resolve(file.FullName, file.IsDirectory, _namingOptions);
|
||||
if (videoInfo == null)
|
||||
{
|
||||
nonVideoFiles.Add(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
files.Add(videoInfo);
|
||||
}
|
||||
}
|
||||
else if (!current.IsDirectory)
|
||||
{
|
||||
var videoInfo = VideoResolver.Resolve(current.FullName, current.IsDirectory, _namingOptions);
|
||||
if (videoInfo == null)
|
||||
{
|
||||
nonVideoFiles.Add(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
video.ParentId = Guid.Empty;
|
||||
video.OwnerId = owner.Id;
|
||||
video.ExtraType = ExtraType.Trailer;
|
||||
video.TrailerTypes = new[] { TrailerType.LocalTrailer };
|
||||
|
||||
return video;
|
||||
|
||||
// Sort them so that the list can be easily compared for changes
|
||||
}).OrderBy(i => i.Path);
|
||||
}
|
||||
|
||||
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
var namingOptions = _namingOptions;
|
||||
|
||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||
.Where(i => BaseItem.AllExtrasTypesFolderNames.ContainsKey(i.Name ?? string.Empty))
|
||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, namingOptions.VideoFileExtensions, false, false))
|
||||
.ToList();
|
||||
|
||||
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
|
||||
|
||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (currentVideo != null)
|
||||
{
|
||||
files.AddRange(currentVideo.Extras.Where(i => i.ExtraType != ExtraType.Trailer).Select(i => _fileSystem.GetFileInfo(i.Path)));
|
||||
files.Add(videoInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
|
||||
.OfType<Video>()
|
||||
.Select(video =>
|
||||
if (files.Count == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var videos = VideoListResolver.Resolve(files, _namingOptions);
|
||||
// owner video info cannot be null as that implies it has no path
|
||||
var extras = ExtraResolver.GetExtras(videos, ownerVideoInfo, _namingOptions.VideoFlagDelimiters);
|
||||
for (var i = 0; i < extras.Count; i++)
|
||||
{
|
||||
var currentExtra = extras[i];
|
||||
var resolved = ResolvePath(_fileSystem.GetFileInfo(currentExtra.Path), null, directoryService);
|
||||
if (resolved is not Video video)
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var dbItem = GetItemById(video.Id) as Video;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
video = dbItem;
|
||||
}
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
if (GetItemById(resolved.Id) is Video dbItem)
|
||||
{
|
||||
video = dbItem;
|
||||
}
|
||||
|
||||
video.ParentId = Guid.Empty;
|
||||
video.OwnerId = owner.Id;
|
||||
video.ExtraType = currentExtra.ExtraType;
|
||||
video.ParentId = Guid.Empty;
|
||||
video.OwnerId = owner.Id;
|
||||
yield return video;
|
||||
}
|
||||
|
||||
SetExtraTypeFromFilename(video);
|
||||
// TODO: theme songs must be handled "manually" (but should we?) since they aren't video files
|
||||
for (var i = 0; i < nonVideoFiles.Count; i++)
|
||||
{
|
||||
var current = nonVideoFiles[i];
|
||||
var extraInfo = ExtraResolver.GetExtraInfo(current.FullName, _namingOptions);
|
||||
if (extraInfo.ExtraType != ExtraType.ThemeSong)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return video;
|
||||
var resolved = ResolvePath(current, null, directoryService);
|
||||
if (resolved is not Audio themeSong)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sort them so that the list can be easily compared for changes
|
||||
}).OrderBy(i => i.Path);
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
if (GetItemById(themeSong.Id) is Audio dbItem)
|
||||
{
|
||||
themeSong = dbItem;
|
||||
}
|
||||
|
||||
themeSong.ExtraType = ExtraType.ThemeSong;
|
||||
themeSong.OwnerId = owner.Id;
|
||||
themeSong.ParentId = Guid.Empty;
|
||||
|
||||
yield return themeSong;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
|
||||
@ -2817,15 +2831,6 @@ namespace Emby.Server.Implementations.Library
|
||||
return path;
|
||||
}
|
||||
|
||||
private void SetExtraTypeFromFilename(Video item)
|
||||
{
|
||||
var resolver = new ExtraResolver(_namingOptions);
|
||||
|
||||
var result = resolver.GetExtraInfo(item.Path);
|
||||
|
||||
item.ExtraType = result.ExtraType;
|
||||
}
|
||||
|
||||
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
||||
{
|
||||
return _itemRepository.GetPeople(query);
|
||||
@ -2932,11 +2937,12 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||
|
||||
var existingNameCount = 1; // first numbered name will be 2
|
||||
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||
while (Directory.Exists(virtualFolderPath))
|
||||
{
|
||||
name += "1";
|
||||
virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||
existingNameCount++;
|
||||
virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
|
||||
}
|
||||
|
||||
var mediaPathInfos = options.PathInfos;
|
||||
|
@ -6,6 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
@ -64,18 +65,18 @@ namespace Emby.Server.Implementations.Library
|
||||
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, StringComparer.OrdinalIgnoreCase))
|
||||
if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
|
||||
stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Smart)
|
||||
{
|
||||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
||||
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
|
||||
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase)) ??
|
||||
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
|
||||
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ??
|
||||
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
else if (mode == SubtitlePlaybackMode.Always)
|
||||
@ -136,9 +137,9 @@ namespace Emby.Server.Implementations.Library
|
||||
else if (mode == SubtitlePlaybackMode.Smart)
|
||||
{
|
||||
// Prefer smart logic over embedded metadata
|
||||
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparer.OrdinalIgnoreCase))
|
||||
if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase))
|
||||
filteredStreams = streams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library
|
||||
var genres = item
|
||||
.GetRecursiveChildren(user, new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(Audio) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Audio },
|
||||
DtoOptions = dtoOptions
|
||||
})
|
||||
.Cast<Audio>()
|
||||
@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(Audio) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Audio },
|
||||
|
||||
GenreIds = genreIds.ToArray(),
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <param name="attribute">The attrib.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
|
||||
public static string? GetAttributeValue(this string str, string attribute)
|
||||
public static string? GetAttributeValue(this ReadOnlySpan<char> str, ReadOnlySpan<char> attribute)
|
||||
{
|
||||
if (str.Length == 0)
|
||||
{
|
||||
@ -28,17 +28,31 @@ namespace Emby.Server.Implementations.Library
|
||||
throw new ArgumentException("String can't be empty.", nameof(attribute));
|
||||
}
|
||||
|
||||
string srch = "[" + attribute + "=";
|
||||
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
|
||||
if (start != -1)
|
||||
var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// Must be at least 3 characters after the attribute =, ], any character.
|
||||
var maxIndex = str.Length - attribute.Length - 3;
|
||||
while (attributeIndex > -1 && attributeIndex < maxIndex)
|
||||
{
|
||||
start += srch.Length;
|
||||
int end = str.IndexOf(']', start);
|
||||
return str.Substring(start, end - start);
|
||||
var attributeEnd = attributeIndex + attribute.Length;
|
||||
if (attributeIndex > 0
|
||||
&& str[attributeIndex - 1] == '['
|
||||
&& str[attributeEnd] == '=')
|
||||
{
|
||||
var closingIndex = str[attributeEnd..].IndexOf(']');
|
||||
// Must be at least 1 character before the closing bracket.
|
||||
if (closingIndex > 1)
|
||||
{
|
||||
return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
str = str[attributeEnd..];
|
||||
attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// for imdbid we also accept pattern matching
|
||||
if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
|
||||
if (attribute.Equals("imdbid", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
|
||||
return match ? imdbId.ToString() : null;
|
||||
|
@ -4,12 +4,10 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
|
@ -49,120 +49,71 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
|
||||
where TVideoType : Video, new()
|
||||
{
|
||||
var namingOptions = NamingOptions;
|
||||
VideoFileInfo videoInfo = null;
|
||||
VideoType? videoType = null;
|
||||
|
||||
// If the path is a file check for a matching extensions
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
TVideoType video = null;
|
||||
VideoFileInfo videoInfo = null;
|
||||
|
||||
// Loop through each child file/folder and see if we find a video
|
||||
foreach (var child in args.FileSystemChildren)
|
||||
{
|
||||
var filename = child.Name;
|
||||
|
||||
if (child.IsDirectory)
|
||||
{
|
||||
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
|
||||
{
|
||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
||||
|
||||
if (videoInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
video = new TVideoType
|
||||
{
|
||||
Path = args.Path,
|
||||
VideoType = VideoType.Dvd,
|
||||
ProductionYear = videoInfo.Year
|
||||
};
|
||||
break;
|
||||
videoType = VideoType.Dvd;
|
||||
}
|
||||
|
||||
if (IsBluRayDirectory(filename))
|
||||
else if (IsBluRayDirectory(filename))
|
||||
{
|
||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
||||
|
||||
if (videoInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
video = new TVideoType
|
||||
{
|
||||
Path = args.Path,
|
||||
VideoType = VideoType.BluRay,
|
||||
ProductionYear = videoInfo.Year
|
||||
};
|
||||
break;
|
||||
videoType = VideoType.BluRay;
|
||||
}
|
||||
}
|
||||
else if (IsDvdFile(filename))
|
||||
{
|
||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
||||
|
||||
if (videoInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
video = new TVideoType
|
||||
{
|
||||
Path = args.Path,
|
||||
VideoType = VideoType.Dvd,
|
||||
ProductionYear = videoInfo.Year
|
||||
};
|
||||
break;
|
||||
videoType = VideoType.Dvd;
|
||||
}
|
||||
|
||||
if (videoType == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, NamingOptions, parseName);
|
||||
break;
|
||||
}
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
video.Name = parseName ?
|
||||
videoInfo.Name :
|
||||
Path.GetFileName(args.Path);
|
||||
|
||||
Set3DFormat(video, videoInfo);
|
||||
}
|
||||
|
||||
return video;
|
||||
}
|
||||
else
|
||||
{
|
||||
var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
|
||||
|
||||
if (videoInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (VideoResolver.IsVideoFile(args.Path, NamingOptions) || videoInfo.IsStub)
|
||||
{
|
||||
var path = args.Path;
|
||||
|
||||
var video = new TVideoType
|
||||
{
|
||||
Path = path,
|
||||
IsInMixedFolder = true,
|
||||
ProductionYear = videoInfo.Year
|
||||
};
|
||||
|
||||
SetVideoType(video, videoInfo);
|
||||
|
||||
video.Name = parseName ?
|
||||
videoInfo.Name :
|
||||
Path.GetFileNameWithoutExtension(args.Path);
|
||||
|
||||
Set3DFormat(video, videoInfo);
|
||||
|
||||
return video;
|
||||
}
|
||||
videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName);
|
||||
}
|
||||
|
||||
return null;
|
||||
if (videoInfo == null || (!videoInfo.IsStub && !VideoResolver.IsVideoFile(args.Path, NamingOptions)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var video = new TVideoType
|
||||
{
|
||||
Name = videoInfo.Name,
|
||||
Path = args.Path,
|
||||
ProductionYear = videoInfo.Year,
|
||||
ExtraType = videoInfo.ExtraType
|
||||
};
|
||||
|
||||
if (videoType.HasValue)
|
||||
{
|
||||
video.VideoType = videoType.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetVideoType(video, videoInfo);
|
||||
}
|
||||
|
||||
Set3DFormat(video, videoInfo);
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
|
||||
@ -207,8 +158,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
// use disc-utils, both DVDs and BDs use UDF filesystem
|
||||
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
|
||||
using (UdfReader udfReader = new UdfReader(videoFileStream))
|
||||
{
|
||||
UdfReader udfReader = new UdfReader(videoFileStream);
|
||||
if (udfReader.DirectoryExists("VIDEO_TS"))
|
||||
{
|
||||
video.IsoType = IsoType.Dvd;
|
||||
|
@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
|
||||
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
|
||||
if (extension != null && _validExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
if (extension != null && _validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// It's a book
|
||||
return new Book
|
||||
|
@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
private static void SetProviderIdFromPath(BaseItem item)
|
||||
{
|
||||
// we need to only look at the name of this actual item (not parents)
|
||||
var justName = Path.GetFileName(item.Path);
|
||||
var justName = Path.GetFileName(item.Path.AsSpan());
|
||||
|
||||
var id = justName.GetAttributeValue("tmdbid");
|
||||
|
||||
|
@ -26,7 +26,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
|
||||
{
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly StackResolver _stackResolver;
|
||||
|
||||
private string[] _validCollectionTypes = new[]
|
||||
{
|
||||
@ -46,7 +45,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
: base(namingOptions)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
_stackResolver = new StackResolver(NamingOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -62,7 +60,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
string collectionType,
|
||||
IDirectoryService directoryService)
|
||||
{
|
||||
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
|
||||
var result = ResolveMultipleInternal(parent, files, collectionType);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
@ -92,16 +90,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
return null;
|
||||
}
|
||||
|
||||
Video movie = null;
|
||||
var files = args.GetActualFileSystemChildren().ToList();
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
movie = FindMovie<Video>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(collectionType))
|
||||
@ -118,17 +117,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
return null;
|
||||
}
|
||||
|
||||
{
|
||||
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||
}
|
||||
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
// ignore extras
|
||||
return movie?.ExtraType == null ? movie : null;
|
||||
}
|
||||
|
||||
// Handle owned items
|
||||
@ -169,6 +167,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
item = ResolveVideo<Video>(args, false);
|
||||
}
|
||||
|
||||
// Ignore extras
|
||||
if (item?.ExtraType != null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
item.IsInMixedFolder = true;
|
||||
@ -180,8 +184,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
private MultiItemResolverResult ResolveMultipleInternal(
|
||||
Folder parent,
|
||||
List<FileSystemMetadata> files,
|
||||
string collectionType,
|
||||
IDirectoryService directoryService)
|
||||
string collectionType)
|
||||
{
|
||||
if (IsInvalid(parent, collectionType))
|
||||
{
|
||||
@ -190,13 +193,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ResolveVideos<MusicVideo>(parent, files, directoryService, true, collectionType, false);
|
||||
return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false);
|
||||
return ResolveVideos<Video>(parent, files, false, collectionType, false);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(collectionType))
|
||||
@ -204,7 +207,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
// Owned items should just use the plain video type
|
||||
if (parent == null)
|
||||
{
|
||||
return ResolveVideos<Video>(parent, files, directoryService, false, collectionType, false);
|
||||
return ResolveVideos<Video>(parent, files, false, collectionType, false);
|
||||
}
|
||||
|
||||
if (parent is Series || parent.GetParents().OfType<Series>().Any())
|
||||
@ -212,12 +215,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
return null;
|
||||
}
|
||||
|
||||
return ResolveVideos<Movie>(parent, files, directoryService, false, collectionType, true);
|
||||
return ResolveVideos<Movie>(parent, files, false, collectionType, true);
|
||||
}
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ResolveVideos<Movie>(parent, files, directoryService, true, collectionType, true);
|
||||
return ResolveVideos<Movie>(parent, files, true, collectionType, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -226,21 +229,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
private MultiItemResolverResult ResolveVideos<T>(
|
||||
Folder parent,
|
||||
IEnumerable<FileSystemMetadata> fileSystemEntries,
|
||||
IDirectoryService directoryService,
|
||||
bool suppportMultiEditions,
|
||||
bool supportMultiEditions,
|
||||
string collectionType,
|
||||
bool parseName)
|
||||
where T : Video, new()
|
||||
{
|
||||
var files = new List<FileSystemMetadata>();
|
||||
var videos = new List<BaseItem>();
|
||||
var leftOver = new List<FileSystemMetadata>();
|
||||
var hasCollectionType = !string.IsNullOrEmpty(collectionType);
|
||||
|
||||
// Loop through each child file/folder and see if we find a video
|
||||
foreach (var child in fileSystemEntries)
|
||||
{
|
||||
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
|
||||
if (string.IsNullOrEmpty(collectionType))
|
||||
if (!hasCollectionType)
|
||||
{
|
||||
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(child.Name, "season.nfo", StringComparison.OrdinalIgnoreCase))
|
||||
@ -259,29 +261,39 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
}
|
||||
}
|
||||
|
||||
var resolverResult = VideoListResolver.Resolve(files, NamingOptions, suppportMultiEditions).ToList();
|
||||
var videoInfos = files
|
||||
.Select(i => VideoResolver.Resolve(i.FullName, i.IsDirectory, NamingOptions, parseName))
|
||||
.Where(f => f != null)
|
||||
.ToList();
|
||||
|
||||
var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName);
|
||||
|
||||
var result = new MultiItemResolverResult
|
||||
{
|
||||
ExtraFiles = leftOver,
|
||||
Items = videos
|
||||
ExtraFiles = leftOver
|
||||
};
|
||||
|
||||
var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent);
|
||||
var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true;
|
||||
|
||||
foreach (var video in resolverResult)
|
||||
{
|
||||
var firstVideo = video.Files[0];
|
||||
var path = firstVideo.Path;
|
||||
if (video.ExtraType != null)
|
||||
{
|
||||
result.ExtraFiles.Add(files.Find(f => string.Equals(f.FullName, path, StringComparison.OrdinalIgnoreCase)));
|
||||
continue;
|
||||
}
|
||||
|
||||
var additionalParts = video.Files.Count > 1 ? video.Files.Skip(1).Select(i => i.Path).ToArray() : Array.Empty<string>();
|
||||
|
||||
var videoItem = new T
|
||||
{
|
||||
Path = video.Files[0].Path,
|
||||
Path = path,
|
||||
IsInMixedFolder = isInMixedFolder,
|
||||
ProductionYear = video.Year,
|
||||
Name = parseName ?
|
||||
video.Name :
|
||||
Path.GetFileNameWithoutExtension(video.Files[0].Path),
|
||||
AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToArray(),
|
||||
Name = parseName ? video.Name : firstVideo.Name,
|
||||
AdditionalParts = additionalParts,
|
||||
LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
|
||||
};
|
||||
|
||||
@ -299,21 +311,34 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
private static bool IsIgnored(string filename)
|
||||
{
|
||||
// Ignore samples
|
||||
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase);
|
||||
Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
return m.Success;
|
||||
}
|
||||
|
||||
private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file)
|
||||
private static bool ContainsFile(IReadOnlyList<VideoInfo> result, FileSystemMetadata file)
|
||||
{
|
||||
return result.Any(i => ContainsFile(i, file));
|
||||
}
|
||||
for (var i = 0; i < result.Count; i++)
|
||||
{
|
||||
var current = result[i];
|
||||
for (var j = 0; j < current.Files.Count; j++)
|
||||
{
|
||||
if (ContainsFile(current.Files[j], file))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ContainsFile(VideoInfo result, FileSystemMetadata file)
|
||||
{
|
||||
return result.Files.Any(i => ContainsFile(i, file)) ||
|
||||
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
|
||||
result.Extras.Any(i => ContainsFile(i, file));
|
||||
for (var j = 0; j < current.AlternateVersions.Count; j++)
|
||||
{
|
||||
if (ContainsFile(current.AlternateVersions[j], file))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
|
||||
@ -342,9 +367,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
if (item is Movie || item is MusicVideo)
|
||||
{
|
||||
// We need to only look at the name of this actual item (not parents)
|
||||
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
|
||||
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.ContainingFolderPath.AsSpan());
|
||||
|
||||
if (!string.IsNullOrEmpty(justName))
|
||||
if (!justName.IsEmpty)
|
||||
{
|
||||
// check for tmdb id
|
||||
var tmdbid = justName.GetAttributeValue("tmdbid");
|
||||
@ -358,7 +383,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 (wither id in parent dir or in file name)
|
||||
var imdbid = item.Path.GetAttributeValue("imdbid");
|
||||
var imdbid = item.Path.AsSpan().GetAttributeValue("imdbid");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(imdbid))
|
||||
{
|
||||
@ -431,7 +456,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
// TODO: Allow GetMultiDiscMovie in here
|
||||
const bool SupportsMultiVersion = true;
|
||||
|
||||
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
|
||||
var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
|
||||
new MultiItemResolverResult();
|
||||
|
||||
if (result.Items.Count == 1)
|
||||
@ -510,7 +535,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = _stackResolver.ResolveDirectories(folderPaths).ToList();
|
||||
var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList();
|
||||
|
||||
if (result.Count != 1)
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -109,7 +110,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
}
|
||||
|
||||
string extension = Path.GetExtension(path).TrimStart('.');
|
||||
return imageProcessor.SupportedInputFormats.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||
return imageProcessor.SupportedInputFormats.Contains(extension, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
@ -57,10 +58,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
|
||||
// Check if this is a music playlist file
|
||||
// It should have the correct collection type and a supported file extension
|
||||
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var extension = Path.GetExtension(args.Path);
|
||||
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Playlist
|
||||
{
|
||||
|
@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@ -45,34 +44,36 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
|
||||
// If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
|
||||
// Also handle flat tv folders
|
||||
if ((season != null ||
|
||||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
||||
args.HasParent<Series>())
|
||||
&& (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name)))
|
||||
if (season != null ||
|
||||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
||||
args.HasParent<Series>())
|
||||
{
|
||||
var episode = ResolveVideo<Episode>(args, false);
|
||||
|
||||
if (episode != null)
|
||||
// Ignore extras
|
||||
if (episode == null || episode.ExtraType != null)
|
||||
{
|
||||
var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (series != null)
|
||||
{
|
||||
episode.SeriesId = series.Id;
|
||||
episode.SeriesName = series.Name;
|
||||
}
|
||||
var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
|
||||
|
||||
if (season != null)
|
||||
{
|
||||
episode.SeasonId = season.Id;
|
||||
episode.SeasonName = season.Name;
|
||||
}
|
||||
if (series != null)
|
||||
{
|
||||
episode.SeriesId = series.Id;
|
||||
episode.SeriesName = series.Name;
|
||||
}
|
||||
|
||||
// Assume season 1 if there's no season folder and a season number could not be determined
|
||||
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
|
||||
{
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
if (season != null)
|
||||
{
|
||||
episode.SeasonId = season.Id;
|
||||
episode.SeasonName = season.Name;
|
||||
}
|
||||
|
||||
// Assume season 1 if there's no season folder and a season number could not be determined
|
||||
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
|
||||
{
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
|
||||
return episode;
|
||||
|
@ -5,12 +5,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.TV;
|
||||
using Emby.Naming.Video;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
@ -185,13 +182,42 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
/// <param name="path">The path.</param>
|
||||
private static void SetProviderIdFromPath(Series item, string path)
|
||||
{
|
||||
var justName = Path.GetFileName(path);
|
||||
var justName = Path.GetFileName(path.AsSpan());
|
||||
|
||||
var id = justName.GetAttributeValue("tvdbid");
|
||||
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
var tvdbId = justName.GetAttributeValue("tvdbid");
|
||||
if (!string.IsNullOrEmpty(tvdbId))
|
||||
{
|
||||
item.SetProviderId(MetadataProvider.Tvdb, id);
|
||||
item.SetProviderId(MetadataProvider.Tvdb, tvdbId);
|
||||
}
|
||||
|
||||
var tvmazeId = justName.GetAttributeValue("tvmazeid");
|
||||
if (!string.IsNullOrEmpty(tvmazeId))
|
||||
{
|
||||
item.SetProviderId(MetadataProvider.TvMaze, tvmazeId);
|
||||
}
|
||||
|
||||
var tmdbId = justName.GetAttributeValue("tmdbid");
|
||||
if (!string.IsNullOrEmpty(tmdbId))
|
||||
{
|
||||
item.SetProviderId(MetadataProvider.Tmdb, tmdbId);
|
||||
}
|
||||
|
||||
var anidbId = justName.GetAttributeValue("anidbid");
|
||||
if (!string.IsNullOrEmpty(anidbId))
|
||||
{
|
||||
item.SetProviderId("AniDB", anidbId);
|
||||
}
|
||||
|
||||
var aniListId = justName.GetAttributeValue("anilistid");
|
||||
if (!string.IsNullOrEmpty(aniListId))
|
||||
{
|
||||
item.SetProviderId("AniList", aniListId);
|
||||
}
|
||||
|
||||
var aniSearchId = justName.GetAttributeValue("anisearchid");
|
||||
if (!string.IsNullOrEmpty(aniSearchId))
|
||||
{
|
||||
item.SetProviderId("AniSearch", aniSearchId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,9 @@ using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Search;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
using Person = MediaBrowser.Controller.Entities.Person;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
@ -59,9 +56,9 @@ namespace Emby.Server.Implementations.Library
|
||||
};
|
||||
}
|
||||
|
||||
private static void AddIfMissing(List<string> list, string value)
|
||||
private static void AddIfMissing(List<BaseItemKind> list, BaseItemKind value)
|
||||
{
|
||||
if (!list.Contains(value, StringComparer.OrdinalIgnoreCase))
|
||||
if (!list.Contains(value))
|
||||
{
|
||||
list.Add(value);
|
||||
}
|
||||
@ -86,63 +83,63 @@ namespace Emby.Server.Implementations.Library
|
||||
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
||||
|
||||
var excludeItemTypes = query.ExcludeItemTypes.ToList();
|
||||
var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<string>()).ToList();
|
||||
var includeItemTypes = (query.IncludeItemTypes ?? Array.Empty<BaseItemKind>()).ToList();
|
||||
|
||||
excludeItemTypes.Add(nameof(Year));
|
||||
excludeItemTypes.Add(nameof(Folder));
|
||||
excludeItemTypes.Add(BaseItemKind.Year);
|
||||
excludeItemTypes.Add(BaseItemKind.Folder);
|
||||
|
||||
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase)))
|
||||
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Genre)))
|
||||
{
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, nameof(Genre));
|
||||
AddIfMissing(includeItemTypes, nameof(MusicGenre));
|
||||
AddIfMissing(includeItemTypes, BaseItemKind.Genre);
|
||||
AddIfMissing(includeItemTypes, BaseItemKind.MusicGenre);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, nameof(Genre));
|
||||
AddIfMissing(excludeItemTypes, nameof(MusicGenre));
|
||||
AddIfMissing(excludeItemTypes, BaseItemKind.Genre);
|
||||
AddIfMissing(excludeItemTypes, BaseItemKind.MusicGenre);
|
||||
}
|
||||
|
||||
if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase)))
|
||||
if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Person)))
|
||||
{
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, nameof(Person));
|
||||
AddIfMissing(includeItemTypes, BaseItemKind.Person);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, nameof(Person));
|
||||
AddIfMissing(excludeItemTypes, BaseItemKind.Person);
|
||||
}
|
||||
|
||||
if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Studio", StringComparer.OrdinalIgnoreCase)))
|
||||
if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.Studio)))
|
||||
{
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, nameof(Studio));
|
||||
AddIfMissing(includeItemTypes, BaseItemKind.Studio);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, nameof(Studio));
|
||||
AddIfMissing(excludeItemTypes, BaseItemKind.Studio);
|
||||
}
|
||||
|
||||
if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains("MusicArtist", StringComparer.OrdinalIgnoreCase)))
|
||||
if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains(BaseItemKind.MusicArtist)))
|
||||
{
|
||||
if (!query.IncludeMedia)
|
||||
{
|
||||
AddIfMissing(includeItemTypes, nameof(MusicArtist));
|
||||
AddIfMissing(includeItemTypes, BaseItemKind.MusicArtist);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(excludeItemTypes, nameof(MusicArtist));
|
||||
AddIfMissing(excludeItemTypes, BaseItemKind.MusicArtist);
|
||||
}
|
||||
|
||||
AddIfMissing(excludeItemTypes, nameof(CollectionFolder));
|
||||
AddIfMissing(excludeItemTypes, nameof(Folder));
|
||||
AddIfMissing(excludeItemTypes, BaseItemKind.CollectionFolder);
|
||||
AddIfMissing(excludeItemTypes, BaseItemKind.Folder);
|
||||
var mediaTypes = query.MediaTypes.ToList();
|
||||
|
||||
if (includeItemTypes.Count > 0)
|
||||
@ -183,7 +180,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
List<BaseItem> mediaItems;
|
||||
|
||||
if (searchQuery.IncludeItemTypes.Length == 1 && string.Equals(searchQuery.IncludeItemTypes[0], "MusicArtist", StringComparison.OrdinalIgnoreCase))
|
||||
if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist)
|
||||
{
|
||||
if (!searchQuery.ParentId.Equals(Guid.Empty))
|
||||
{
|
||||
@ -192,7 +189,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
searchQuery.ParentId = Guid.Empty;
|
||||
searchQuery.IncludeItemsByName = true;
|
||||
searchQuery.IncludeItemTypes = Array.Empty<string>();
|
||||
searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
|
||||
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList();
|
||||
}
|
||||
else
|
||||
|
@ -8,11 +8,11 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Channels;
|
||||
@ -20,8 +20,6 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Library;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
using Person = MediaBrowser.Controller.Entities.Person;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Library
|
||||
continue;
|
||||
}
|
||||
|
||||
if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
list.Add(GetUserView(folder, folderViewType, string.Empty));
|
||||
}
|
||||
@ -180,7 +178,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
if (!presetViews.Contains(viewType, StringComparer.OrdinalIgnoreCase))
|
||||
if (!presetViews.Contains(viewType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (Folder)parents[0];
|
||||
}
|
||||
@ -300,11 +298,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
includeItemTypes = new string[] { "Movie" };
|
||||
includeItemTypes = new[] { BaseItemKind.Movie };
|
||||
}
|
||||
else if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
includeItemTypes = new string[] { "Episode" };
|
||||
includeItemTypes = new[] { BaseItemKind.Episode };
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -344,13 +342,13 @@ namespace Emby.Server.Implementations.Library
|
||||
var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0
|
||||
? new[]
|
||||
{
|
||||
nameof(Person),
|
||||
nameof(Studio),
|
||||
nameof(Year),
|
||||
nameof(MusicGenre),
|
||||
nameof(Genre)
|
||||
BaseItemKind.Person,
|
||||
BaseItemKind.Studio,
|
||||
BaseItemKind.Year,
|
||||
BaseItemKind.MusicGenre,
|
||||
BaseItemKind.Genre
|
||||
}
|
||||
: Array.Empty<string>();
|
||||
: Array.Empty<BaseItemKind>();
|
||||
|
||||
var query = new InternalItemsQuery(user)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
|
||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(MusicArtist) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
|
||||
IsDeadArtist = true,
|
||||
IsLocked = false
|
||||
}).Cast<MusicArtist>().ToList();
|
||||
|
@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
var movies = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
MediaTypes = new string[] { MediaType.Video },
|
||||
IncludeItemTypes = new[] { nameof(Movie) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Movie },
|
||||
IsVirtualItem = false,
|
||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
|
||||
Parent = library,
|
||||
@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
|
||||
var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(BoxSet) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.BoxSet },
|
||||
CollapseBoxSetItems = false,
|
||||
Recursive = true
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
|
||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(Person) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Person },
|
||||
IsDeadPerson = true,
|
||||
IsLocked = false
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||
|
||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(Studio) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Studio },
|
||||
IsDeadStudio = true,
|
||||
IsLocked = false
|
||||
});
|
||||
|
@ -17,6 +17,7 @@ using System.Xml;
|
||||
using Emby.Server.Implementations.Library;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
foreach (var virtualFolder in virtualFolders)
|
||||
{
|
||||
if (!virtualFolder.Locations.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
if (!virtualFolder.Locations.Contains(path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -891,7 +892,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
throw new ArgumentNullException(nameof(tunerHostId));
|
||||
}
|
||||
|
||||
return info.EnabledTuners.Contains(tunerHostId, StringComparer.OrdinalIgnoreCase);
|
||||
return info.EnabledTuners.Contains(tunerHostId, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
@ -1778,7 +1779,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
Limit = 1,
|
||||
ExternalId = timer.ProgramId,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
@ -2137,7 +2138,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
Limit = 1,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
{
|
||||
@ -2332,7 +2333,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
var deletes = _timerProvider.GetAll()
|
||||
.Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
|
||||
.Where(i => !allTimerIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
|
||||
.Where(i => !allTimerIds.Contains(i.Id, StringComparison.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
|
||||
.Where(i => deleteStatuses.Contains(i.Status))
|
||||
.ToList();
|
||||
|
||||
@ -2352,7 +2353,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
ExternalSeriesId = seriesTimer.SeriesId,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
{
|
||||
@ -2387,7 +2388,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
channel = _libraryManager.GetItemList(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvChannel) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||
ItemIds = new[] { parent.ChannelId },
|
||||
DtoOptions = new DtoOptions()
|
||||
}).FirstOrDefault() as LiveTvChannel;
|
||||
@ -2446,7 +2447,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
channel = _libraryManager.GetItemList(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvChannel) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||
ItemIds = new[] { programInfo.ChannelId },
|
||||
DtoOptions = new DtoOptions()
|
||||
}).FirstOrDefault() as LiveTvChannel;
|
||||
@ -2511,7 +2512,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
var seriesIds = _libraryManager.GetItemIds(
|
||||
new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(Series) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Series },
|
||||
Name = program.Name
|
||||
}).ToArray();
|
||||
|
||||
@ -2524,7 +2525,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var result = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
||||
ParentIndexNumber = program.SeasonNumber.Value,
|
||||
IndexNumber = program.EpisodeNumber.Value,
|
||||
AncestorIds = seriesIds,
|
||||
@ -2621,7 +2622,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (newDevicesOnly)
|
||||
{
|
||||
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase))
|
||||
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Net.Mime;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@ -101,11 +101,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
}
|
||||
};
|
||||
|
||||
var requestString = JsonSerializer.Serialize(requestList, _jsonOptions);
|
||||
_logger.LogDebug("Request string for schedules is: {RequestString}", requestString);
|
||||
_logger.LogDebug("Request string for schedules is: {@RequestString}", requestList);
|
||||
|
||||
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/schedules");
|
||||
options.Content = new StringContent(requestString, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
options.Content = JsonContent.Create(requestList, options: _jsonOptions);
|
||||
options.Headers.TryAddWithoutValidation("token", token);
|
||||
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
|
||||
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
@ -121,8 +120,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
|
||||
|
||||
var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
|
||||
programRequestOptions.Content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(programIds, _jsonOptions));
|
||||
programRequestOptions.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
|
||||
programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions);
|
||||
|
||||
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
|
||||
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
@ -243,19 +241,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
if (programInfo.AudioProperties.Count != 0)
|
||||
{
|
||||
if (programInfo.AudioProperties.Contains("atmos", StringComparer.OrdinalIgnoreCase))
|
||||
if (programInfo.AudioProperties.Contains("atmos", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
audioType = ProgramAudio.Atmos;
|
||||
}
|
||||
else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparer.OrdinalIgnoreCase))
|
||||
else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
audioType = ProgramAudio.DolbyDigital;
|
||||
}
|
||||
else if (programInfo.AudioProperties.Contains("dd", StringComparer.OrdinalIgnoreCase))
|
||||
else if (programInfo.AudioProperties.Contains("dd", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
audioType = ProgramAudio.DolbyDigital;
|
||||
}
|
||||
else if (programInfo.AudioProperties.Contains("stereo", StringComparer.OrdinalIgnoreCase))
|
||||
else if (programInfo.AudioProperties.Contains("stereo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
audioType = ProgramAudio.Stereo;
|
||||
}
|
||||
@ -317,8 +315,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
if (programInfo.VideoProperties != null)
|
||||
{
|
||||
info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
|
||||
info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
|
||||
info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparison.OrdinalIgnoreCase);
|
||||
info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (details.ContentRating != null && details.ContentRating.Count > 0)
|
||||
@ -327,7 +325,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
.Replace("--", "-", StringComparison.Ordinal);
|
||||
|
||||
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
|
||||
if (invalid.Contains(info.OfficialRating, StringComparer.OrdinalIgnoreCase))
|
||||
if (invalid.Contains(info.OfficialRating, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
info.OfficialRating = null;
|
||||
}
|
||||
@ -389,9 +387,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
if (details.Genres != null)
|
||||
{
|
||||
info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
|
||||
info.IsNews = details.Genres.Contains("news", StringComparer.OrdinalIgnoreCase);
|
||||
info.IsNews = details.Genres.Contains("news", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (info.Genres.Contains("children", StringComparer.OrdinalIgnoreCase))
|
||||
if (info.Genres.Contains("children", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
info.IsKids = true;
|
||||
}
|
||||
|
@ -190,10 +190,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
IsSeries = program.Episode != null,
|
||||
IsRepeat = program.IsPreviouslyShown && !program.IsNew,
|
||||
IsPremiere = program.Premiere != null,
|
||||
IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
|
||||
IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
|
||||
IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
|
||||
IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
|
||||
IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||
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,
|
||||
|
@ -7,12 +7,12 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Dto;
|
||||
@ -161,7 +161,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(Series) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Series },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||
@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
ExternalSeriesId = programSeriesId,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
@ -255,7 +255,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(Series) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Series },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||
@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(Series) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.Series },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
@ -309,7 +309,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
ExternalSeriesId = programSeriesId,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
|
@ -33,8 +33,6 @@ using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
@ -191,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
IsKids = query.IsKids,
|
||||
IsSports = query.IsSports,
|
||||
IsSeries = query.IsSeries,
|
||||
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||
TopParentIds = new[] { topFolder.Id },
|
||||
IsFavorite = query.IsFavorite,
|
||||
IsLiked = query.IsLiked,
|
||||
@ -810,7 +808,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var internalQuery = new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
MinEndDate = query.MinEndDate,
|
||||
MinStartDate = query.MinStartDate,
|
||||
MaxEndDate = query.MaxEndDate,
|
||||
@ -874,7 +872,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var internalQuery = new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
IsAiring = query.IsAiring,
|
||||
HasAired = query.HasAired,
|
||||
IsNews = query.IsNews,
|
||||
@ -1085,8 +1083,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (cleanDatabase)
|
||||
{
|
||||
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { nameof(LiveTvChannel) }, progress, cancellationToken);
|
||||
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { nameof(LiveTvProgram) }, progress, cancellationToken);
|
||||
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { BaseItemKind.LiveTvChannel }, progress, cancellationToken);
|
||||
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { BaseItemKind.LiveTvProgram }, progress, cancellationToken);
|
||||
}
|
||||
|
||||
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
|
||||
@ -1177,7 +1175,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
ChannelIds = new Guid[] { currentChannel.Id },
|
||||
DtoOptions = new DtoOptions(true)
|
||||
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
|
||||
@ -1261,7 +1259,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return new Tuple<List<Guid>, List<Guid>>(channels, programs);
|
||||
}
|
||||
|
||||
private void CleanDatabaseInternal(Guid[] currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
private void CleanDatabaseInternal(Guid[] currentIdList, BaseItemKind[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
|
||||
{
|
||||
@ -1328,25 +1326,25 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
.Select(i => i.Id)
|
||||
.ToList();
|
||||
|
||||
var excludeItemTypes = new List<string>();
|
||||
var excludeItemTypes = new List<BaseItemKind>();
|
||||
|
||||
if (folderIds.Count == 0)
|
||||
{
|
||||
return new QueryResult<BaseItem>();
|
||||
}
|
||||
|
||||
var includeItemTypes = new List<string>();
|
||||
var includeItemTypes = new List<BaseItemKind>();
|
||||
var genres = new List<string>();
|
||||
|
||||
if (query.IsMovie.HasValue)
|
||||
{
|
||||
if (query.IsMovie.Value)
|
||||
{
|
||||
includeItemTypes.Add(nameof(Movie));
|
||||
includeItemTypes.Add(BaseItemKind.Movie);
|
||||
}
|
||||
else
|
||||
{
|
||||
excludeItemTypes.Add(nameof(Movie));
|
||||
excludeItemTypes.Add(BaseItemKind.Movie);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1354,11 +1352,11 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
if (query.IsSeries.Value)
|
||||
{
|
||||
includeItemTypes.Add(nameof(Episode));
|
||||
includeItemTypes.Add(BaseItemKind.Episode);
|
||||
}
|
||||
else
|
||||
{
|
||||
excludeItemTypes.Add(nameof(Episode));
|
||||
excludeItemTypes.Add(BaseItemKind.Episode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1878,7 +1876,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
ChannelIds = channelIds,
|
||||
MaxStartDate = now,
|
||||
MinEndDate = now,
|
||||
|
@ -3,7 +3,6 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@ -85,7 +84,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
Logger.LogInformation("Opening HDHR UDP Live stream from {Host}", uri.Host);
|
||||
|
||||
var remoteAddress = IPAddress.Parse(uri.Host);
|
||||
IPAddress localAddress = null;
|
||||
IPAddress localAddress;
|
||||
using (var tcpClient = new TcpClient())
|
||||
{
|
||||
try
|
||||
|
@ -10,6 +10,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
@ -119,7 +120,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
var extension = Path.GetExtension(mediaSource.Path) ?? string.Empty;
|
||||
|
||||
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
|
||||
}
|
||||
|
@ -11,11 +11,11 @@
|
||||
"Collections": "التجميعات",
|
||||
"DeviceOfflineWithName": "قُطِع الاتصال ب{0}",
|
||||
"DeviceOnlineWithName": "{0} متصل",
|
||||
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
|
||||
"FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فشلت من {0}",
|
||||
"Favorites": "مفضلات",
|
||||
"Folders": "المجلدات",
|
||||
"Genres": "التضنيفات",
|
||||
"HeaderAlbumArtists": "ألبوم الفنان",
|
||||
"HeaderAlbumArtists": "فناني الألبوم",
|
||||
"HeaderContinueWatching": "استمر بالمشاهدة",
|
||||
"HeaderFavoriteAlbums": "الألبومات المفضلة",
|
||||
"HeaderFavoriteArtists": "الفنانون المفضلون",
|
||||
|
@ -10,5 +10,49 @@
|
||||
"AuthenticationSucceededWithUserName": "{0} wedi’i ddilysu’n llwyddiannus",
|
||||
"Artists": "Artistiaid",
|
||||
"AppDeviceValues": "Ap: {0}, Dyfais: {1}",
|
||||
"Albums": "Albwmau"
|
||||
"Albums": "Albwmau",
|
||||
"Genres": "Genres",
|
||||
"Folders": "Ffolderi",
|
||||
"Favorites": "Ffefrynnau",
|
||||
"LabelRunningTimeValue": "Amser rhedeg: {0}",
|
||||
"TaskOptimizeDatabase": "Cronfa ddata Optimeiddio",
|
||||
"TaskRefreshChannels": "Adnewyddu Sianeli",
|
||||
"TaskRefreshPeople": "Adnewyddu Pobl",
|
||||
"TasksChannelsCategory": "Sianeli Internet",
|
||||
"VersionNumber": "Fersiwn {0}",
|
||||
"ScheduledTaskStartedWithName": "{0} wedi dechrau",
|
||||
"ScheduledTaskFailedWithName": "{0} wedi methu",
|
||||
"ProviderValue": "Darparwr: {0}",
|
||||
"NotificationOptionInstallationFailed": "Fethu Gosod",
|
||||
"NameSeasonUnknown": "Tymor Anhysbys",
|
||||
"NameSeasonNumber": "Tymor {0}",
|
||||
"MusicVideos": "Fideos Cerddoriaeth",
|
||||
"MixedContent": "Cynnwys amrywiol",
|
||||
"HomeVideos": "Fideos Cartref",
|
||||
"HeaderNextUp": "Nesaf i Fyny",
|
||||
"HeaderFavoriteArtists": "Ffefryn Artistiaid",
|
||||
"HeaderFavoriteAlbums": "Ffefryn Albwmau",
|
||||
"HeaderContinueWatching": "Parhewch i Weithio",
|
||||
"TasksApplicationCategory": "Rhaglen",
|
||||
"TasksLibraryCategory": "Llyfrgell",
|
||||
"TasksMaintenanceCategory": "Cynnal a Chadw",
|
||||
"System": "System",
|
||||
"Plugin": "Ategyn",
|
||||
"Music": "Cerddoriaeth",
|
||||
"Latest": "Diweddaraf",
|
||||
"Inherit": "Etifeddu",
|
||||
"Forced": "Orfodi",
|
||||
"Application": "Rhaglen",
|
||||
"HeaderAlbumArtists": "Artistiaid albwm",
|
||||
"Sync": "Cysoni",
|
||||
"Songs": "Caneuon",
|
||||
"Shows": "Rhaglenni",
|
||||
"Playlists": "Rhestri Chwarae",
|
||||
"Photos": "Lluniau",
|
||||
"ValueSpecialEpisodeName": "Arbennig - {0}",
|
||||
"Movies": "Ffilmiau",
|
||||
"Undefined": "Heb ddiffiniad",
|
||||
"TvShows": "Rhaglenni teledu",
|
||||
"HeaderLiveTV": "Teledu Byw",
|
||||
"User": "Defnyddiwr"
|
||||
}
|
||||
|
@ -104,7 +104,7 @@
|
||||
"TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
|
||||
"TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn",
|
||||
"TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
|
||||
"TaskRefreshChapterImages": "Eltiri Ĉapitraj Bildojn",
|
||||
"TaskRefreshChapterImages": "Eltiri Ĉapitrajn Bildojn",
|
||||
"TaskCleanCache": "Malplenigi Staplan Katalogon",
|
||||
"TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon",
|
||||
"PluginUpdatedWithName": "{0} estis ĝisdatigita",
|
||||
|
@ -15,8 +15,8 @@
|
||||
"Favorites": "Favoritos",
|
||||
"Folders": "Carpetas",
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artista del álbum",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderAlbumArtists": "Artistas del álbum",
|
||||
"HeaderContinueWatching": "Seguir viendo",
|
||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
||||
|
@ -15,7 +15,7 @@
|
||||
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
||||
"HeaderFavoriteShows": "Programas favoritos",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderAlbumArtists": "Artistas del álbum",
|
||||
"HeaderAlbumArtists": "Artistas de álbum",
|
||||
"Genres": "Géneros",
|
||||
"Folders": "Carpetas",
|
||||
"Favorites": "Favoritos",
|
||||
@ -29,7 +29,7 @@
|
||||
"TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
|
||||
"TaskRefreshChannels": "Actualizar canales",
|
||||
"TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
|
||||
"TaskCleanTranscode": "Limpiar directorio de transcodificado",
|
||||
"TaskCleanTranscode": "Limpiar el directorio de transcodificaciones",
|
||||
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.",
|
||||
"TaskUpdatePlugins": "Actualizar complementos",
|
||||
"TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.",
|
||||
@ -91,7 +91,7 @@
|
||||
"NameSeasonUnknown": "Temporada desconocida",
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameInstallFailed": "Falló la instalación de {0}",
|
||||
"MusicVideos": "Videos Musicales",
|
||||
"MusicVideos": "Videos musicales",
|
||||
"Music": "Música",
|
||||
"MixedContent": "Contenido mezclado",
|
||||
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
|
||||
@ -105,7 +105,7 @@
|
||||
"Inherit": "Heredar",
|
||||
"HomeVideos": "Videos caseros",
|
||||
"HeaderRecordingGroups": "Grupos de grabación",
|
||||
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}",
|
||||
"FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}",
|
||||
"DeviceOnlineWithName": "{0} está conectado",
|
||||
"DeviceOfflineWithName": "{0} se ha desconectado",
|
||||
"ChapterNameValue": "Capítulo {0}",
|
||||
@ -114,10 +114,10 @@
|
||||
"Application": "Aplicación",
|
||||
"AppDeviceValues": "App: {0}, Dispositivo: {1}",
|
||||
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
|
||||
"TaskCleanActivityLog": "Limpiar Registro de Actividades",
|
||||
"TaskCleanActivityLog": "Limpiar registro de actividades",
|
||||
"Undefined": "Sin definir",
|
||||
"Forced": "Forzado",
|
||||
"Default": "Por defecto",
|
||||
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y restaura el espacio libre. Ejecutar esta tarea después de actualizar las librerías o realizar otros cambios que impliquen modificar las bases de datos puede mejorar la performance.",
|
||||
"TaskOptimizeDatabase": "Optimización de base de datos"
|
||||
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
|
||||
"TaskOptimizeDatabase": "Optimizar base de datos"
|
||||
}
|
||||
|
@ -102,7 +102,7 @@
|
||||
"Forced": "Sunnitud",
|
||||
"Folders": "Kaustad",
|
||||
"Favorites": "Lemmikud",
|
||||
"FailedLoginAttemptWithUserName": "Ebaõnnestunud sisselogimiskatse kasutajalt {0}",
|
||||
"FailedLoginAttemptWithUserName": "{0} - sisselogimine nurjus",
|
||||
"DeviceOnlineWithName": "{0} on ühendatud",
|
||||
"DeviceOfflineWithName": "{0} katkestas ühenduse",
|
||||
"Default": "Vaikimisi",
|
||||
|
@ -6,7 +6,7 @@
|
||||
"AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد",
|
||||
"Books": "کتابها",
|
||||
"CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}",
|
||||
"Channels": "کانالها",
|
||||
"Channels": "کانالها",
|
||||
"ChapterNameValue": "قسمت {0}",
|
||||
"Collections": "مجموعهها",
|
||||
"DeviceOfflineWithName": "ارتباط {0} قطع شد",
|
||||
@ -37,7 +37,7 @@
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد",
|
||||
"MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد",
|
||||
"MixedContent": "محتوای مخلوط",
|
||||
"Movies": "فیلمها",
|
||||
"Movies": "فیلم ها",
|
||||
"Music": "موسیقی",
|
||||
"MusicVideos": "موزیک ویدیوها",
|
||||
"NameInstallFailed": "{0} نصب با مشکل مواجه شد",
|
||||
@ -118,5 +118,6 @@
|
||||
"Default": "پیشفرض",
|
||||
"TaskCleanActivityLogDescription": "ورودیهای قدیمیتر از سن تنظیم شده در سیاهه فعالیت را حذف میکند.",
|
||||
"TaskCleanActivityLog": "پاکسازی سیاهه فعالیت",
|
||||
"Undefined": "تعریف نشده"
|
||||
"Undefined": "تعریف نشده",
|
||||
"TaskOptimizeDatabase": "بهینه سازی پایگاه داده"
|
||||
}
|
||||
|
@ -117,5 +117,7 @@
|
||||
"TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas luma sa nakatakda na edad.",
|
||||
"Default": "Default",
|
||||
"Undefined": "Hindi tiyak",
|
||||
"Forced": "Sapilitan"
|
||||
"Forced": "Sapilitan",
|
||||
"TaskOptimizeDatabaseDescription": "Iko-compact ang database at ita-truncate ang free space. Ang pagpapatakbo ng gawaing ito pagkatapos ng pag-scan sa library o paggawa ng iba pang mga pagbabago na nagpapahiwatig ng mga pagbabago sa database ay maaaring magpa-improve ng performance.",
|
||||
"TaskOptimizeDatabase": "I-optimize ang database"
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
"Favorites": "Favoriti",
|
||||
"Folders": "Mape",
|
||||
"Genres": "Žanrovi",
|
||||
"HeaderAlbumArtists": "Album od izvođača",
|
||||
"HeaderAlbumArtists": "Izvođači albuma",
|
||||
"HeaderContinueWatching": "Nastavi gledati",
|
||||
"HeaderFavoriteAlbums": "Omiljeni albumi",
|
||||
"HeaderFavoriteArtists": "Omiljeni izvođači",
|
||||
|
@ -11,12 +11,12 @@
|
||||
"Collections": "コレクション",
|
||||
"DeviceOfflineWithName": "{0} が切断されました",
|
||||
"DeviceOnlineWithName": "{0} が接続されました",
|
||||
"FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました",
|
||||
"FailedLoginAttemptWithUserName": "ログインを試行しましたが {0} によって失敗しました",
|
||||
"Favorites": "お気に入り",
|
||||
"Folders": "フォルダー",
|
||||
"Genres": "ジャンル",
|
||||
"HeaderAlbumArtists": "アーティストのアルバム",
|
||||
"HeaderContinueWatching": "視聴を続ける",
|
||||
"HeaderAlbumArtists": "アルバムアーティスト",
|
||||
"HeaderContinueWatching": "続きを見る",
|
||||
"HeaderFavoriteAlbums": "お気に入りのアルバム",
|
||||
"HeaderFavoriteArtists": "お気に入りのアーティスト",
|
||||
"HeaderFavoriteEpisodes": "お気に入りのエピソード",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Albums": "Albumai",
|
||||
"AppDeviceValues": "Programa: {0}, Įrenginys: {1}",
|
||||
"Application": "Programa",
|
||||
"Application": "Programėlė",
|
||||
"Artists": "Atlikėjai",
|
||||
"AuthenticationSucceededWithUserName": "{0} sėkmingai autentifikuota",
|
||||
"Books": "Knygos",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"PluginUninstalledWithName": "{0} беше успешно деинсталирано",
|
||||
"PluginInstalledWithName": "{0} беше успешно инсталирано",
|
||||
"Plugin": "Додатоци",
|
||||
"Playlists": "Листи",
|
||||
"Playlists": "Плејлисти",
|
||||
"Photos": "Слики",
|
||||
"NotificationOptionVideoPlaybackStopped": "Видео стопирано",
|
||||
"NotificationOptionVideoPlayback": "Видео пуштено",
|
||||
@ -97,5 +97,8 @@
|
||||
"TasksChannelsCategory": "Интернет Канали",
|
||||
"TasksApplicationCategory": "Апликација",
|
||||
"TasksLibraryCategory": "Библиотека",
|
||||
"TasksMaintenanceCategory": "Одржување"
|
||||
"TasksMaintenanceCategory": "Одржување",
|
||||
"Undefined": "Недефинирано",
|
||||
"Forced": "Принудно",
|
||||
"Default": "Зададено"
|
||||
}
|
||||
|
@ -9,5 +9,6 @@
|
||||
"Genres": "Төрөл зүйл",
|
||||
"Favorites": "Дуртай",
|
||||
"Collections": "Багц",
|
||||
"Artists": "Жүжигчин"
|
||||
"Artists": "Зураачуд",
|
||||
"Albums": "Цомгууд"
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"Albums": "Album-album",
|
||||
"AppDeviceValues": "Apl: {0}, Peranti: {1}",
|
||||
"Application": "Aplikasi",
|
||||
"Artists": "Artis",
|
||||
"Artists": "Artis-artis",
|
||||
"AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
|
||||
"Books": "Buku-buku",
|
||||
"CameraImageUploadedFrom": "Gambar baharu telah dimuat naik melalui {0}",
|
||||
@ -37,9 +37,9 @@
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini",
|
||||
"MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini",
|
||||
"MixedContent": "Kandungan campuran",
|
||||
"Movies": "Filem",
|
||||
"Movies": "Filem-filem",
|
||||
"Music": "Muzik",
|
||||
"MusicVideos": "Muzik video",
|
||||
"MusicVideos": "Video muzik",
|
||||
"NameInstallFailed": "{0} pemasangan gagal",
|
||||
"NameSeasonNumber": "Musim {0}",
|
||||
"NameSeasonUnknown": "Musim Tidak Diketahui",
|
||||
@ -53,43 +53,43 @@
|
||||
"NotificationOptionNewLibraryContent": "Kandungan baru telah ditambah",
|
||||
"NotificationOptionPluginError": "Kegagalan plugin",
|
||||
"NotificationOptionPluginInstalled": "Plugin telah dipasang",
|
||||
"NotificationOptionPluginUninstalled": "Plugin uninstalled",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin update installed",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||
"NotificationOptionUserLockedOut": "User locked out",
|
||||
"NotificationOptionVideoPlayback": "Video playback started",
|
||||
"NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang",
|
||||
"NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang",
|
||||
"NotificationOptionServerRestartRequired": "",
|
||||
"NotificationOptionTaskFailed": "Kegagalan tugas berjadual",
|
||||
"NotificationOptionUserLockedOut": "Pengguna telah dikunci",
|
||||
"NotificationOptionVideoPlayback": "Ulangmain video bermula",
|
||||
"NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan",
|
||||
"Photos": "Gambar-gambar",
|
||||
"Playlists": "Senarai main",
|
||||
"Plugin": "Plugin",
|
||||
"PluginInstalledWithName": "{0} was installed",
|
||||
"PluginUninstalledWithName": "{0} was uninstalled",
|
||||
"PluginUpdatedWithName": "{0} was updated",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"PluginInstalledWithName": "{0} telah dipasang",
|
||||
"PluginUninstalledWithName": "{0} telah dinyahpasang",
|
||||
"PluginUpdatedWithName": "{0} telah dikemaskini",
|
||||
"ProviderValue": "Pembekal: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} gagal",
|
||||
"ScheduledTaskStartedWithName": "{0} bermula",
|
||||
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
|
||||
"Shows": "Series",
|
||||
"ServerNameNeedsToBeRestarted": "{0} perlu di ulangmula",
|
||||
"Shows": "Tayangan",
|
||||
"Songs": "Lagu-lagu",
|
||||
"StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}",
|
||||
"Sync": "Sync",
|
||||
"Sync": "Segerak",
|
||||
"System": "Sistem",
|
||||
"TvShows": "TV Shows",
|
||||
"User": "User",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserDeletedWithName": "User {0} has been deleted",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"TvShows": "Tayangan TV",
|
||||
"User": "Pengguna",
|
||||
"UserCreatedWithName": "Pengguna {0} telah diwujudkan",
|
||||
"UserDeletedWithName": "Pengguna {0} telah dipadamkan",
|
||||
"UserDownloadingItemWithValues": "{0} sedang memuat turun {1}",
|
||||
"UserLockedOutWithName": "Pengguna {0} telah dikunci",
|
||||
"UserOfflineFromDevice": "{0} telah terputus dari {1}",
|
||||
"UserOnlineFromDevice": "{0} berada dalam talian dari {1}",
|
||||
"UserPasswordChangedWithName": "Kata laluan telah ditukar bagi pengguna {0}",
|
||||
"UserPolicyUpdatedWithName": "Dasar pengguna telah dikemas kini untuk {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"UserStartedPlayingItemWithValues": "{0} sedang dimainkan {1} pada {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} telah tamat dimainkan {1} pada {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} telah ditambah ke media library anda",
|
||||
"ValueSpecialEpisodeName": "Khas - {0}",
|
||||
"VersionNumber": "Versi {0}",
|
||||
"TaskCleanActivityLog": "Log Aktiviti Bersih",
|
||||
|
@ -69,7 +69,7 @@
|
||||
"UserDeletedWithName": "प्रयोगकर्ता {0} हटाइएको छ",
|
||||
"UserCreatedWithName": "प्रयोगकर्ता {0} सिर्जना गरिएको छ",
|
||||
"User": "प्रयोगकर्ता",
|
||||
"PluginInstalledWithName": "",
|
||||
"PluginInstalledWithName": "{0} सभएको थियो",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।",
|
||||
"Songs": "गीतहरू",
|
||||
"Shows": "शोहरू",
|
||||
|
@ -15,7 +15,7 @@
|
||||
"Favorites": "Favorieten",
|
||||
"Folders": "Mappen",
|
||||
"Genres": "Genres",
|
||||
"HeaderAlbumArtists": "Artiests Album",
|
||||
"HeaderAlbumArtists": "Album Artiesten",
|
||||
"HeaderContinueWatching": "Kijken hervatten",
|
||||
"HeaderFavoriteAlbums": "Favoriete albums",
|
||||
"HeaderFavoriteArtists": "Favoriete artiesten",
|
||||
|
@ -24,7 +24,7 @@
|
||||
"TasksLibraryCategory": "ਲਾਇਬ੍ਰੇਰੀ",
|
||||
"TasksMaintenanceCategory": "ਰੱਖ-ਰਖਾਅ",
|
||||
"VersionNumber": "ਵਰਜਨ {0}",
|
||||
"ValueSpecialEpisodeName": "ਵਿਸ਼ੇਸ਼ - {0}",
|
||||
"ValueSpecialEpisodeName": "ਖਾਸ - {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ",
|
||||
"UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ",
|
||||
@ -43,8 +43,8 @@
|
||||
"Sync": "ਸਿੰਕ",
|
||||
"SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾ toਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ",
|
||||
"StartupEmbyServerIsLoading": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ. ਕਿਰਪਾ ਕਰਕੇ ਜਲਦੀ ਹੀ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ.",
|
||||
"Songs": "ਗਾਣੇ",
|
||||
"Shows": "ਸ਼ੋਅਜ਼",
|
||||
"Songs": "ਗਾਣੇਂ",
|
||||
"Shows": "ਸ਼ੋਅ",
|
||||
"ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ",
|
||||
"ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ",
|
||||
"ScheduledTaskFailedWithName": "{0} ਅਸਫਲ",
|
||||
@ -53,7 +53,7 @@
|
||||
"PluginUninstalledWithName": "{0} ਅਣਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ ਸੀ",
|
||||
"PluginInstalledWithName": "{0} ਲਗਾਇਆ ਗਿਆ ਸੀ",
|
||||
"Plugin": "ਪਲੱਗਇਨ",
|
||||
"Playlists": "ਪਲੇਲਿਸਟਸ",
|
||||
"Playlists": "ਪਲੇਸੂਚੀਆਂ",
|
||||
"Photos": "ਫੋਟੋਆਂ",
|
||||
"NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ",
|
||||
"NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ",
|
||||
@ -102,13 +102,13 @@
|
||||
"HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ",
|
||||
"Genres": "ਸ਼ੈਲੀਆਂ",
|
||||
"Forced": "ਮਜਬੂਰ",
|
||||
"Folders": "ਫੋਲਡਰ",
|
||||
"Folders": "ਫੋਲਡਰਸ",
|
||||
"Favorites": "ਮਨਪਸੰਦ",
|
||||
"FailedLoginAttemptWithUserName": "ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ {0}",
|
||||
"DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ",
|
||||
"DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ",
|
||||
"Default": "ਮੂਲ",
|
||||
"Collections": "ਸੰਗ੍ਰਹਿ",
|
||||
"Default": "ਡਿਫੌਲਟ",
|
||||
"Collections": "ਸੰਗ੍ਰਹਿਣ",
|
||||
"ChapterNameValue": "ਅਧਿਆਇ {0}",
|
||||
"Channels": "ਚੈਨਲ",
|
||||
"CameraImageUploadedFrom": "ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ {0}",
|
||||
|
@ -15,7 +15,7 @@
|
||||
"Favorites": "Ulubione",
|
||||
"Folders": "Foldery",
|
||||
"Genres": "Gatunki",
|
||||
"HeaderAlbumArtists": "Album artysty",
|
||||
"HeaderAlbumArtists": "Wykonawcy albumów",
|
||||
"HeaderContinueWatching": "Kontynuuj odtwarzanie",
|
||||
"HeaderFavoriteAlbums": "Ulubione albumy",
|
||||
"HeaderFavoriteArtists": "Ulubieni wykonawcy",
|
||||
@ -47,7 +47,7 @@
|
||||
"NotificationOptionApplicationUpdateAvailable": "Dostępna aktualizacja aplikacji",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Zaktualizowano aplikację",
|
||||
"NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki",
|
||||
"NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane",
|
||||
"NotificationOptionAudioPlaybackStopped": "Odtwarzanie dźwięku zatrzymane",
|
||||
"NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia przenośnego",
|
||||
"NotificationOptionInstallationFailed": "Nieudana instalacja",
|
||||
"NotificationOptionNewLibraryContent": "Dodano nową zawartość",
|
||||
@ -98,7 +98,7 @@
|
||||
"TaskRefreshChannels": "Odśwież kanały",
|
||||
"TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.",
|
||||
"TaskCleanTranscode": "Wyczyść folder transkodowania",
|
||||
"TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów które są skonfigurowane do automatycznej aktualizacji.",
|
||||
"TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów, które są skonfigurowane do automatycznej aktualizacji.",
|
||||
"TaskUpdatePlugins": "Aktualizuj pluginy",
|
||||
"TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.",
|
||||
"TaskRefreshPeople": "Odśwież obsadę",
|
||||
|
@ -1,5 +1,7 @@
|
||||
{
|
||||
"Books": "Libros",
|
||||
"AuthenticationSucceededWithUserName": "{0} autentificado correctamente",
|
||||
"Artists": "Artistas"
|
||||
"Artists": "Artistas",
|
||||
"Songs": "Shantees",
|
||||
"Albums": "Ships"
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
"HeaderFavoriteEpisodes": "Episódios Favoritos",
|
||||
"HeaderFavoriteShows": "Séries Favoritas",
|
||||
"HeaderContinueWatching": "Continuar assistindo",
|
||||
"HeaderAlbumArtists": "Discos do Artista",
|
||||
"HeaderAlbumArtists": "Artistas do Álbum",
|
||||
"Genres": "Gêneros",
|
||||
"Folders": "Diretórios",
|
||||
"Favorites": "Favoritos",
|
||||
|
@ -74,7 +74,7 @@
|
||||
"HeaderFavoriteArtists": "Artiști Favoriți",
|
||||
"HeaderFavoriteAlbums": "Albume Favorite",
|
||||
"HeaderContinueWatching": "Vizionează în continuare",
|
||||
"HeaderAlbumArtists": "Album Artiști",
|
||||
"HeaderAlbumArtists": "Albume Artiști",
|
||||
"Genres": "Genuri",
|
||||
"Folders": "Dosare",
|
||||
"Favorites": "Favorite",
|
||||
|
@ -74,7 +74,7 @@
|
||||
"NameSeasonUnknown": "Sezon i panjohur",
|
||||
"NameSeasonNumber": "Sezoni {0}",
|
||||
"NameInstallFailed": "Instalimi i {0} dështoi",
|
||||
"MusicVideos": "Video muzikore",
|
||||
"MusicVideos": "Video Muzikore",
|
||||
"Music": "Muzikë",
|
||||
"Movies": "Filmat",
|
||||
"MixedContent": "Përmbajtje e përzier",
|
||||
@ -96,7 +96,7 @@
|
||||
"HeaderFavoriteArtists": "Artistët e preferuar",
|
||||
"HeaderFavoriteAlbums": "Albumet e preferuar",
|
||||
"HeaderContinueWatching": "Vazhdo të shikosh",
|
||||
"HeaderAlbumArtists": "Artistët e Albumeve",
|
||||
"HeaderAlbumArtists": "Artistët e albumeve",
|
||||
"Genres": "Zhanret",
|
||||
"Folders": "Skedarët",
|
||||
"Favorites": "Të preferuarat",
|
||||
|
@ -50,7 +50,7 @@
|
||||
"NameSeasonUnknown": "Непозната сезона",
|
||||
"NameSeasonNumber": "Сезона {0}",
|
||||
"NameInstallFailed": "Инсталација {0} није успела",
|
||||
"MusicVideos": "Музички спотови",
|
||||
"MusicVideos": "Музички видео",
|
||||
"Music": "Музика",
|
||||
"Movies": "Филмови",
|
||||
"MixedContent": "Мешовит садржај",
|
||||
@ -64,7 +64,7 @@
|
||||
"ItemRemovedWithName": "{0} уклоњено из библиотеке",
|
||||
"ItemAddedWithName": "{0} додато у библиотеку",
|
||||
"Inherit": "Наследи",
|
||||
"HomeVideos": "Кућни видео",
|
||||
"HomeVideos": "Кућни Видео",
|
||||
"HeaderRecordingGroups": "Групе снимања",
|
||||
"HeaderNextUp": "Следи",
|
||||
"HeaderLiveTV": "ТВ уживо",
|
||||
@ -117,5 +117,6 @@
|
||||
"TaskCleanActivityLog": "Очисти историју активности",
|
||||
"Undefined": "Недефинисано",
|
||||
"Forced": "Принудно",
|
||||
"Default": "Подразумевано"
|
||||
"Default": "Подразумевано",
|
||||
"TaskOptimizeDatabase": "Оптимизуј датабазу"
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
"Favorites": "Favoriter",
|
||||
"Folders": "Mappar",
|
||||
"Genres": "Genrer",
|
||||
"HeaderAlbumArtists": "Artistens album",
|
||||
"HeaderAlbumArtists": "Albumsartister",
|
||||
"HeaderContinueWatching": "Fortsätt kolla",
|
||||
"HeaderFavoriteAlbums": "Favoritalbum",
|
||||
"HeaderFavoriteArtists": "Favoritartister",
|
||||
@ -96,8 +96,8 @@
|
||||
"TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
|
||||
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
|
||||
"TaskRefreshChannels": "Uppdatera kanaler",
|
||||
"TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
|
||||
"TaskCleanTranscode": "Töm transkodningskatalog",
|
||||
"TaskCleanTranscodeDescription": "Raderar omkodningsfiler som är mer än en dag gamla.",
|
||||
"TaskCleanTranscode": "Töm omkodningskatalog",
|
||||
"TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
|
||||
"TaskUpdatePlugins": "Uppdatera insticksprogram",
|
||||
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
|
||||
|
@ -21,7 +21,7 @@
|
||||
"Inherit": "மரபுரிமையாகப் பெறு",
|
||||
"HeaderRecordingGroups": "பதிவு குழுக்கள்",
|
||||
"Folders": "கோப்புறைகள்",
|
||||
"FailedLoginAttemptWithUserName": "{0} இல் இருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது",
|
||||
"FailedLoginAttemptWithUserName": "{0} இன் உள்நுழைவு முயற்சி தோல்வியடைந்தது",
|
||||
"DeviceOnlineWithName": "{0} இணைக்கப்பட்டது",
|
||||
"DeviceOfflineWithName": "{0} துண்டிக்கப்பட்டது",
|
||||
"Collections": "தொகுப்புகள்",
|
||||
@ -85,7 +85,7 @@
|
||||
"HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
|
||||
"HeaderFavoriteAlbums": "பிடித்த ஆல்பங்கள்",
|
||||
"HeaderContinueWatching": "தொடர்ந்து பார்",
|
||||
"HeaderAlbumArtists": "இசைக் கலைஞர்கள்",
|
||||
"HeaderAlbumArtists": "கலைஞரின் ஆல்பம்",
|
||||
"Genres": "வகைகள்",
|
||||
"Favorites": "பிடித்தவை",
|
||||
"ChapterNameValue": "அத்தியாயம் {0}",
|
||||
|
@ -1 +1,23 @@
|
||||
{}
|
||||
{
|
||||
"ValueSpecialEpisodeName": "ప్రత్యేక - {0}",
|
||||
"Sync": "సమకాలీకరించు",
|
||||
"Songs": "పాటలు",
|
||||
"Shows": "ప్రదర్శనలు",
|
||||
"Playlists": "ప్లేజాబితాలు",
|
||||
"Photos": "ఫోటోలు",
|
||||
"MusicVideos": "మ్యూజిక్ వీడియోలు",
|
||||
"Music": "సంగీతం",
|
||||
"Movies": "సినిమాలు",
|
||||
"HeaderContinueWatching": "చూడటం కొనసాగించండి",
|
||||
"HeaderAlbumArtists": "ఆల్బమ్ కళాకారులు",
|
||||
"Genres": "శైలులు",
|
||||
"Forced": "బలవంతంగా",
|
||||
"Folders": "ఫోల్డర్లు",
|
||||
"Favorites": "ఇష్టమైనవి",
|
||||
"Default": "డిఫాల్ట్",
|
||||
"Collections": "సేకరణలు",
|
||||
"Channels": "ఛానెల్లు",
|
||||
"Books": "పుస్తకాలు",
|
||||
"Artists": "కళాకారులు",
|
||||
"Albums": "ఆల్బమ్లు"
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"Favorites": "Yêu Thích",
|
||||
"Folders": "Thư Mục",
|
||||
"Genres": "Thể Loại",
|
||||
"HeaderAlbumArtists": "Album Nghệ sĩ",
|
||||
"HeaderAlbumArtists": "Album nghệ sĩ",
|
||||
"HeaderContinueWatching": "Xem Tiếp",
|
||||
"HeaderLiveTV": "TV Trực Tiếp",
|
||||
"Movies": "Phim",
|
||||
@ -103,7 +103,7 @@
|
||||
"HeaderFavoriteEpisodes": "Tập Phim Yêu Thích",
|
||||
"HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích",
|
||||
"HeaderFavoriteAlbums": "Album Ưa Thích",
|
||||
"FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}",
|
||||
"FailedLoginAttemptWithUserName": "Đăng nhập không thành công thử từ {0}",
|
||||
"DeviceOnlineWithName": "{0} đã kết nối",
|
||||
"DeviceOfflineWithName": "{0} đã ngắt kết nối",
|
||||
"ChapterNameValue": "Phân Cảnh {0}",
|
||||
|
@ -11,7 +11,7 @@
|
||||
"Collections": "合集",
|
||||
"DeviceOfflineWithName": "{0} 已断开",
|
||||
"DeviceOnlineWithName": "{0} 已连接",
|
||||
"FailedLoginAttemptWithUserName": "来自 {0} 的失败登入",
|
||||
"FailedLoginAttemptWithUserName": "从 {0} 尝试登录失败",
|
||||
"Favorites": "我的最爱",
|
||||
"Folders": "文件夹",
|
||||
"Genres": "风格",
|
||||
|
@ -1 +1,29 @@
|
||||
{}
|
||||
{
|
||||
"TasksApplicationCategory": "Ukusetshenziswa",
|
||||
"TasksLibraryCategory": "Umtapo",
|
||||
"TasksMaintenanceCategory": "Ukunakekela",
|
||||
"User": "Umsebenzisi",
|
||||
"Undefined": "Akuchaziwe",
|
||||
"System": "Isistimu",
|
||||
"Sync": "Vumelanisa",
|
||||
"Songs": "Amaculo",
|
||||
"Shows": "Izinhlelo",
|
||||
"Plugin": "Isijobelelo",
|
||||
"Playlists": "Izinhla Zokudlalayo",
|
||||
"Photos": "Izithombe",
|
||||
"Music": "Umculo",
|
||||
"Movies": "Amamuvi",
|
||||
"Latest": "lwakamuva",
|
||||
"Inherit": "Ngefa",
|
||||
"Forced": "Kuphoqiwe",
|
||||
"Application": "Ukusetshenziswa",
|
||||
"Genres": "Izinhlobo",
|
||||
"Folders": "Izikhwama",
|
||||
"Favorites": "Izintandokazi",
|
||||
"Default": "Okumisiwe",
|
||||
"Collections": "Amaqoqo",
|
||||
"Channels": "Amashaneli",
|
||||
"Books": "Izincwadi",
|
||||
"Artists": "Abadlali",
|
||||
"Albums": "Ama-albhamu"
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
|
||||
return _dictionaries.GetOrAdd(
|
||||
culture,
|
||||
(key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(),
|
||||
static (key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(),
|
||||
this);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Chapters;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -120,7 +121,7 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
|
||||
var path = GetChapterImagePath(video, chapter.StartPositionTicks);
|
||||
|
||||
if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
if (!currentImages.Contains(path, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (extractImages)
|
||||
{
|
||||
@ -219,7 +220,7 @@ namespace Emby.Server.Implementations.MediaEncoder
|
||||
{
|
||||
var deadImages = images
|
||||
.Except(chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)), StringComparer.OrdinalIgnoreCase)
|
||||
.Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparer.OrdinalIgnoreCase))
|
||||
.Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
foreach (var image in deadImages)
|
||||
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Model.Querying;
|
||||
@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
}
|
||||
|
||||
query.Recursive = true;
|
||||
query.IncludeItemTypes = new[] { "Playlist" };
|
||||
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
|
||||
query.Parent = null;
|
||||
return LibraryManager.GetItemsResult(query);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user