mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-15 23:58:57 +00:00
Compare commits
191 Commits
v10.8.0-al
...
v10.8.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75a46a1072 | ||
|
|
6aa37ebb8f | ||
|
|
072526d798 | ||
|
|
4da240d731 | ||
|
|
b5b4e2ea89 | ||
|
|
7d63377194 | ||
|
|
71ebe220f0 | ||
|
|
3eb4bbbb86 | ||
|
|
c6a1dcf420 | ||
|
|
fddcaf17e1 | ||
|
|
b9cd9487bd | ||
|
|
fcb8ff7c0f | ||
|
|
e51777fe4b | ||
|
|
6d299eed49 | ||
|
|
8952819494 | ||
|
|
f802763e8f | ||
|
|
37061e5b6b | ||
|
|
1c5c0ebb0f | ||
|
|
0c4b5a5ce8 | ||
|
|
65bf393efb | ||
|
|
38d4300309 | ||
|
|
11d0c6827f | ||
|
|
0765fd568f | ||
|
|
28c2ac9cc0 | ||
|
|
58b9e5af79 | ||
|
|
1725b2ee69 | ||
|
|
25abe479eb | ||
|
|
1b9cb8cca6 | ||
|
|
c3071f8996 | ||
|
|
dc9e1ece29 | ||
|
|
226a43619f | ||
|
|
53447976f0 | ||
|
|
b2a2bdb088 | ||
|
|
bb713d1931 | ||
|
|
73a5fc6e26 | ||
|
|
019b631d7b | ||
|
|
6f4a0683ed | ||
|
|
5a1b347b83 | ||
|
|
828bf1f0ac | ||
|
|
4ec7488bab | ||
|
|
7ad1527fd0 | ||
|
|
80fce87f7a | ||
|
|
2ab3bf97ef | ||
|
|
57db188c2e | ||
|
|
2749509f00 | ||
|
|
7bfc6b5679 | ||
|
|
251b9a5235 | ||
|
|
ea8f40e84a | ||
|
|
4441513ca4 | ||
|
|
ebbde383e8 | ||
|
|
78bb581f0c | ||
|
|
a615f87680 | ||
|
|
d594f81f65 | ||
|
|
6804624c15 | ||
|
|
cf29aae690 | ||
|
|
c0ab54f0bd | ||
|
|
60e75f7b70 | ||
|
|
17e6bd5f88 | ||
|
|
42c661e30f | ||
|
|
9f5873b8c4 | ||
|
|
cbfa355e31 | ||
|
|
728a5988b3 | ||
|
|
2e7d173188 | ||
|
|
ec2645c0c0 | ||
|
|
7db753d247 | ||
|
|
d0832c60d4 | ||
|
|
b2d85a02c2 | ||
|
|
4b9c84c52e | ||
|
|
daa76d3d34 | ||
|
|
599fc9e671 | ||
|
|
17f43c8e01 | ||
|
|
634ce40c2f | ||
|
|
b5459f49d3 | ||
|
|
932c2c6665 | ||
|
|
a04ab6b876 | ||
|
|
a8a8ce4e7b | ||
|
|
8c7dd0a691 | ||
|
|
55b429edb7 | ||
|
|
076a13abeb | ||
|
|
259bc231d6 | ||
|
|
7774cb2067 | ||
|
|
00211ca056 | ||
|
|
ca4769ab68 | ||
|
|
84ac692312 | ||
|
|
a7a7173cd5 | ||
|
|
c86b064f80 | ||
|
|
05c8834a3a | ||
|
|
9158511017 | ||
|
|
afaff1310f | ||
|
|
b880dc8a4a | ||
|
|
91292b8ea5 | ||
|
|
83a94aa612 | ||
|
|
cb41dda5b3 | ||
|
|
a633a3052f | ||
|
|
e8ae501430 | ||
|
|
db46eaa744 | ||
|
|
71f6326e1a | ||
|
|
91f3ce3109 | ||
|
|
3f3295a5d6 | ||
|
|
76c2775d8c | ||
|
|
cd760943a9 | ||
|
|
ea9fc9f9cc | ||
|
|
d9bc65469f | ||
|
|
077f13ae4c | ||
|
|
a4565da4a9 | ||
|
|
c35b704a83 | ||
|
|
55146334e8 | ||
|
|
232a148d3f | ||
|
|
968c534864 | ||
|
|
f8fcbc88fc | ||
|
|
92448ffabd | ||
|
|
923720c988 | ||
|
|
16bf4a013b | ||
|
|
b3c48e8e78 | ||
|
|
42a7318fd0 | ||
|
|
8ddba55214 | ||
|
|
0dd304f86c | ||
|
|
249a1ca955 | ||
|
|
dea5a3f3bc | ||
|
|
b5d4fdb56e | ||
|
|
e34fd15196 | ||
|
|
7405450da2 | ||
|
|
9a0618552b | ||
|
|
4a58582ad5 | ||
|
|
4c9bd905c6 | ||
|
|
5e8aaa68cf | ||
|
|
543b0127b3 | ||
|
|
87439665d7 | ||
|
|
c3c4dc6839 | ||
|
|
e575d815cc | ||
|
|
ec9cff29df | ||
|
|
0f4da9f635 | ||
|
|
0edf77994a | ||
|
|
38f0e611c8 | ||
|
|
4e03801931 | ||
|
|
250332104b | ||
|
|
0872ede57b | ||
|
|
0120d80b78 | ||
|
|
9e665ff520 | ||
|
|
5c3d0b5510 | ||
|
|
3a4e7fb075 | ||
|
|
c5569c701c | ||
|
|
32629cd7da | ||
|
|
2c6d6dbbf8 | ||
|
|
bdf9bdb9e6 | ||
|
|
0e8c97ed60 | ||
|
|
843ba4506e | ||
|
|
3699fa43f0 | ||
|
|
296a61cbc4 | ||
|
|
a04f8f5efb | ||
|
|
58f788e8ab | ||
|
|
7ee96a59d3 | ||
|
|
510f92f4c5 | ||
|
|
a90614d194 | ||
|
|
5e8f27d473 | ||
|
|
1f02ab83cb | ||
|
|
2a82f8dc40 | ||
|
|
584cf47ef5 | ||
|
|
3223ef0e49 | ||
|
|
3797879d05 | ||
|
|
6d6fb1bba3 | ||
|
|
681bfbd535 | ||
|
|
220443eca1 | ||
|
|
d707a201c9 | ||
|
|
7d86ef6f22 | ||
|
|
eeb8192602 | ||
|
|
593b2fd359 | ||
|
|
3513f5a84b | ||
|
|
6030946d78 | ||
|
|
fde84a1e00 | ||
|
|
e446e9fde9 | ||
|
|
148fcb1bb8 | ||
|
|
757970bfc1 | ||
|
|
1df56335ee | ||
|
|
065d3fa837 | ||
|
|
4890454935 | ||
|
|
3734c95fd4 | ||
|
|
5d19c26d59 | ||
|
|
0415d1ccef | ||
|
|
d10de5b7f9 | ||
|
|
9abe9e7e54 | ||
|
|
3a89e88033 | ||
|
|
d5b63092ed | ||
|
|
6648b7d7da | ||
|
|
97c2c523a8 | ||
|
|
e682c230bd | ||
|
|
a3a4689af2 | ||
|
|
107412f2f2 | ||
|
|
6b2b484987 | ||
|
|
80877aa945 | ||
|
|
c52a2f2f7b |
@@ -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
|
||||
@@ -6,7 +6,7 @@
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PropertyGroup>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -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,39 +667,39 @@ 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);
|
||||
}
|
||||
|
||||
var serverItems = new ServerItem[]
|
||||
{
|
||||
new (item, StubType.Latest),
|
||||
new (item, StubType.Playlists),
|
||||
new (item, StubType.Albums),
|
||||
new (item, StubType.AlbumArtists),
|
||||
new (item, StubType.Artists),
|
||||
new (item, StubType.Songs),
|
||||
new (item, StubType.Genres),
|
||||
new (item, StubType.FavoriteArtists),
|
||||
new (item, StubType.FavoriteAlbums),
|
||||
new (item, StubType.FavoriteSongs)
|
||||
new(item, StubType.Latest),
|
||||
new(item, StubType.Playlists),
|
||||
new(item, StubType.Albums),
|
||||
new(item, StubType.AlbumArtists),
|
||||
new(item, StubType.Artists),
|
||||
new(item, StubType.Songs),
|
||||
new(item, StubType.Genres),
|
||||
new(item, StubType.FavoriteArtists),
|
||||
new(item, StubType.FavoriteAlbums),
|
||||
new(item, StubType.FavoriteSongs)
|
||||
};
|
||||
|
||||
if (limit < serverItems.Length)
|
||||
@@ -746,25 +738,25 @@ 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);
|
||||
}
|
||||
|
||||
var array = new ServerItem[]
|
||||
{
|
||||
new (item, StubType.ContinueWatching),
|
||||
new (item, StubType.Latest),
|
||||
new (item, StubType.Movies),
|
||||
new (item, StubType.Collections),
|
||||
new (item, StubType.Favorites),
|
||||
new (item, StubType.Genres)
|
||||
new(item, StubType.ContinueWatching),
|
||||
new(item, StubType.Latest),
|
||||
new(item, StubType.Movies),
|
||||
new(item, StubType.Collections),
|
||||
new(item, StubType.Favorites),
|
||||
new(item, StubType.Genres)
|
||||
};
|
||||
|
||||
if (limit < array.Length)
|
||||
@@ -831,26 +823,26 @@ 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);
|
||||
}
|
||||
|
||||
var serverItems = new ServerItem[]
|
||||
{
|
||||
new (item, StubType.ContinueWatching),
|
||||
new (item, StubType.NextUp),
|
||||
new (item, StubType.Latest),
|
||||
new (item, StubType.Series),
|
||||
new (item, StubType.FavoriteSeries),
|
||||
new (item, StubType.FavoriteEpisodes),
|
||||
new (item, StubType.Genres)
|
||||
new(item, StubType.ContinueWatching),
|
||||
new(item, StubType.NextUp),
|
||||
new(item, StubType.Latest),
|
||||
new(item, StubType.Series),
|
||||
new(item, StubType.FavoriteSeries),
|
||||
new(item, StubType.FavoriteEpisodes),
|
||||
new(item, StubType.Genres)
|
||||
};
|
||||
|
||||
if (limit < serverItems.Length)
|
||||
@@ -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(),
|
||||
|
||||
@@ -17,8 +17,7 @@ namespace Emby.Dlna.Didl
|
||||
public Filter(string filter)
|
||||
{
|
||||
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
_fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
_fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public bool Contains(string field)
|
||||
|
||||
@@ -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;
|
||||
@@ -84,8 +83,7 @@ namespace Emby.Dlna
|
||||
{
|
||||
lock (_profiles)
|
||||
{
|
||||
var list = _profiles.Values.ToList();
|
||||
return list
|
||||
return _profiles.Values
|
||||
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||
.ThenBy(i => i.Item1.Info.Name)
|
||||
.Select(i => i.Item2)
|
||||
@@ -227,11 +225,8 @@ namespace Emby.Dlna
|
||||
{
|
||||
try
|
||||
{
|
||||
var xmlFies = _fileSystem.GetFilePaths(path)
|
||||
return _fileSystem.GetFilePaths(path)
|
||||
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
return xmlFies
|
||||
.Select(i => ParseProfileFile(i, type))
|
||||
.Where(i => i != null)
|
||||
.ToList()!; // We just filtered out all the nulls
|
||||
@@ -253,11 +248,8 @@ namespace Emby.Dlna
|
||||
|
||||
try
|
||||
{
|
||||
DeviceProfile profile;
|
||||
|
||||
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
|
||||
|
||||
profile = ReserializeProfile(tempProfile);
|
||||
var profile = ReserializeProfile(tempProfile);
|
||||
|
||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
@@ -296,8 +288,7 @@ namespace Emby.Dlna
|
||||
{
|
||||
lock (_profiles)
|
||||
{
|
||||
var list = _profiles.Values.ToList();
|
||||
return list
|
||||
return _profiles.Values
|
||||
.Select(i => i.Item1)
|
||||
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||
.ThenBy(i => i.Info.Name);
|
||||
|
||||
@@ -22,10 +22,14 @@
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1179,6 +1179,7 @@ namespace Emby.Dlna.PlayTo
|
||||
return new Device(deviceProperties, httpClientFactory, logger);
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
private static DeviceIcon CreateIcon(XElement element)
|
||||
{
|
||||
if (element == null)
|
||||
@@ -1186,69 +1187,61 @@ namespace Emby.Dlna.PlayTo
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype"));
|
||||
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
|
||||
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
|
||||
var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth"));
|
||||
var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url"));
|
||||
|
||||
var widthValue = int.Parse(width, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
||||
var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
||||
_ = int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var widthValue);
|
||||
_ = int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var heightValue);
|
||||
|
||||
return new DeviceIcon
|
||||
{
|
||||
Depth = depth,
|
||||
Depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")) ?? string.Empty,
|
||||
Height = heightValue,
|
||||
MimeType = mimeType,
|
||||
Url = url,
|
||||
MimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")) ?? string.Empty,
|
||||
Url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")) ?? string.Empty,
|
||||
Width = widthValue
|
||||
};
|
||||
}
|
||||
|
||||
private static DeviceService Create(XElement element)
|
||||
{
|
||||
var type = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType"));
|
||||
var id = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId"));
|
||||
var scpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL"));
|
||||
var controlURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL"));
|
||||
var eventSubURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL"));
|
||||
|
||||
return new DeviceService
|
||||
=> new DeviceService()
|
||||
{
|
||||
ControlUrl = controlURL,
|
||||
EventSubUrl = eventSubURL,
|
||||
ScpdUrl = scpdUrl,
|
||||
ServiceId = id,
|
||||
ServiceType = type
|
||||
ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty,
|
||||
EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty,
|
||||
ScpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")) ?? string.Empty,
|
||||
ServiceId = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")) ?? string.Empty,
|
||||
ServiceType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")) ?? string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateMediaInfo(UBaseObject mediaInfo, TransportState state)
|
||||
private void UpdateMediaInfo(UBaseObject? mediaInfo, TransportState state)
|
||||
{
|
||||
TransportState = state;
|
||||
|
||||
var previousMediaInfo = CurrentMediaInfo;
|
||||
CurrentMediaInfo = mediaInfo;
|
||||
|
||||
if (previousMediaInfo == null && mediaInfo != null)
|
||||
if (mediaInfo == null)
|
||||
{
|
||||
if (previousMediaInfo != null)
|
||||
{
|
||||
OnPlaybackStop(previousMediaInfo);
|
||||
}
|
||||
}
|
||||
else if (previousMediaInfo == null)
|
||||
{
|
||||
if (state != TransportState.Stopped)
|
||||
{
|
||||
OnPlaybackStart(mediaInfo);
|
||||
}
|
||||
}
|
||||
else if (mediaInfo != null && previousMediaInfo != null && !mediaInfo.Equals(previousMediaInfo))
|
||||
{
|
||||
OnMediaChanged(previousMediaInfo, mediaInfo);
|
||||
}
|
||||
else if (mediaInfo == null && previousMediaInfo != null)
|
||||
{
|
||||
OnPlaybackStop(previousMediaInfo);
|
||||
}
|
||||
else if (mediaInfo != null && mediaInfo.Equals(previousMediaInfo))
|
||||
else if (mediaInfo.Equals(previousMediaInfo))
|
||||
{
|
||||
OnPlaybackProgress(mediaInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnMediaChanged(previousMediaInfo, mediaInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlaybackStart(UBaseObject mediaInfo)
|
||||
|
||||
@@ -210,9 +210,9 @@ namespace Emby.Dlna.PlayTo
|
||||
|
||||
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var duration = mediaSource == null ?
|
||||
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
|
||||
mediaSource.RunTimeTicks;
|
||||
var duration = mediaSource == null
|
||||
? _device.Duration?.Ticks
|
||||
: mediaSource.RunTimeTicks;
|
||||
|
||||
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ namespace Emby.Dlna.PlayTo
|
||||
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
|
||||
(state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
|
||||
return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType, sendValue);
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);
|
||||
|
||||
@@ -167,8 +167,7 @@ namespace Emby.Dlna.Profiles
|
||||
|
||||
public void AddXmlRootAttribute(string name, string value)
|
||||
{
|
||||
var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>();
|
||||
var list = atts.ToList();
|
||||
var list = XmlRootAttributes.ToList();
|
||||
|
||||
list.Add(new XmlAttribute
|
||||
{
|
||||
|
||||
@@ -189,7 +189,7 @@ namespace Emby.Dlna.Server
|
||||
builder.Append("<icon>");
|
||||
|
||||
builder.Append("<mimetype>")
|
||||
.Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(icon.MimeType))
|
||||
.Append("</mimetype>");
|
||||
builder.Append("<width>")
|
||||
.Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture)))
|
||||
@@ -198,7 +198,7 @@ namespace Emby.Dlna.Server
|
||||
.Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture)))
|
||||
.Append("</height>");
|
||||
builder.Append("<depth>")
|
||||
.Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(icon.Depth))
|
||||
.Append("</depth>");
|
||||
builder.Append("<url>")
|
||||
.Append(BuildUrl(icon.Url))
|
||||
@@ -219,10 +219,10 @@ namespace Emby.Dlna.Server
|
||||
builder.Append("<service>");
|
||||
|
||||
builder.Append("<serviceType>")
|
||||
.Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(service.ServiceType))
|
||||
.Append("</serviceType>");
|
||||
builder.Append("<serviceId>")
|
||||
.Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(service.ServiceId))
|
||||
.Append("</serviceId>");
|
||||
builder.Append("<SCPDURL>")
|
||||
.Append(BuildUrl(service.ScpdUrl))
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Emby.Dlna.Service
|
||||
builder.Append("<action>");
|
||||
|
||||
builder.Append("<name>")
|
||||
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(item.Name))
|
||||
.Append("</name>");
|
||||
|
||||
builder.Append("<argumentList>");
|
||||
@@ -48,13 +48,13 @@ namespace Emby.Dlna.Service
|
||||
builder.Append("<argument>");
|
||||
|
||||
builder.Append("<name>")
|
||||
.Append(SecurityElement.Escape(argument.Name ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(argument.Name))
|
||||
.Append("</name>");
|
||||
builder.Append("<direction>")
|
||||
.Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(argument.Direction))
|
||||
.Append("</direction>");
|
||||
builder.Append("<relatedStateVariable>")
|
||||
.Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(argument.RelatedStateVariable))
|
||||
.Append("</relatedStateVariable>");
|
||||
|
||||
builder.Append("</argument>");
|
||||
@@ -81,10 +81,10 @@ namespace Emby.Dlna.Service
|
||||
.Append("\">");
|
||||
|
||||
builder.Append("<name>")
|
||||
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(item.Name))
|
||||
.Append("</name>");
|
||||
builder.Append("<dataType>")
|
||||
.Append(SecurityElement.Escape(item.DataType ?? string.Empty))
|
||||
.Append(SecurityElement.Escape(item.DataType))
|
||||
.Append("</dataType>");
|
||||
|
||||
if (item.AllowedValues.Count > 0)
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
@@ -24,7 +28,7 @@
|
||||
<!-- Code analysers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
@@ -101,8 +102,7 @@ namespace Emby.Drawing
|
||||
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
|
||||
{
|
||||
var file = await ProcessImage(options).ConfigureAwait(false);
|
||||
|
||||
using (var fileStream = AsyncFile.OpenRead(file.Item1))
|
||||
using (var fileStream = AsyncFile.OpenRead(file.Path))
|
||||
{
|
||||
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ namespace Emby.Drawing
|
||||
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
|
||||
public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options)
|
||||
{
|
||||
ItemImageInfo originalImage = options.Image;
|
||||
BaseItem item = options.Item;
|
||||
@@ -130,20 +130,22 @@ namespace Emby.Drawing
|
||||
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
|
||||
}
|
||||
|
||||
var mimeType = MimeTypes.GetMimeType(originalImagePath);
|
||||
if (!_imageEncoder.SupportsImageEncoding)
|
||||
{
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
return (originalImagePath, mimeType, dateModified);
|
||||
}
|
||||
|
||||
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
||||
originalImagePath = supportedImageInfo.path;
|
||||
originalImagePath = supportedImageInfo.Path;
|
||||
|
||||
if (!File.Exists(originalImagePath))
|
||||
// Original file doesn't exist, or original file is gif.
|
||||
if (!File.Exists(originalImagePath) || string.Equals(mimeType, MediaTypeNames.Image.Gif, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||
return (originalImagePath, mimeType, dateModified);
|
||||
}
|
||||
|
||||
dateModified = supportedImageInfo.dateModified;
|
||||
dateModified = supportedImageInfo.DateModified;
|
||||
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
||||
|
||||
bool autoOrient = false;
|
||||
@@ -243,7 +245,7 @@ namespace Emby.Drawing
|
||||
return ImageFormat.Jpg;
|
||||
}
|
||||
|
||||
private string? GetMimeType(ImageFormat format, string path)
|
||||
private string GetMimeType(ImageFormat format, string path)
|
||||
=> format switch
|
||||
{
|
||||
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
||||
@@ -437,7 +439,7 @@ namespace Emby.Drawing
|
||||
.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||
private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||
{
|
||||
var inputFormat = Path.GetExtension(originalImagePath)
|
||||
.TrimStart('.')
|
||||
|
||||
@@ -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,18 @@ 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,
|
||||
@@ -463,12 +470,24 @@ namespace Emby.Naming.Common
|
||||
" sample",
|
||||
MediaType.Video),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.ThemeVideo,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"backdrops",
|
||||
MediaType.Video),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.ThemeSong,
|
||||
ExtraRuleType.Filename,
|
||||
"theme",
|
||||
MediaType.Audio),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.ThemeSong,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"theme-music",
|
||||
MediaType.Audio),
|
||||
|
||||
new ExtraRule(
|
||||
ExtraType.Scene,
|
||||
ExtraRuleType.Suffix,
|
||||
@@ -563,7 +582,7 @@ namespace Emby.Naming.Common
|
||||
ExtraType.Unknown,
|
||||
ExtraRuleType.DirectoryName,
|
||||
"extras",
|
||||
MediaType.Video),
|
||||
MediaType.Video)
|
||||
};
|
||||
|
||||
Format3DRules = new[]
|
||||
@@ -675,9 +694,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 +798,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 +822,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 +847,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();
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||
@@ -44,7 +48,7 @@
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Emby.Naming.TV
|
||||
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
|
||||
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
|
||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
|
||||
private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
|
||||
string path,
|
||||
bool supportSpecialAliases,
|
||||
bool supportNumericSeasonFolders)
|
||||
@@ -99,7 +99,7 @@ namespace Emby.Naming.TV
|
||||
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
|
||||
if (result.seasonNumber.HasValue)
|
||||
if (result.SeasonNumber.HasValue)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
@@ -142,7 +142,7 @@ namespace Emby.Naming.TV
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
|
||||
private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
|
||||
{
|
||||
var numericStart = -1;
|
||||
var length = 0;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Globalization;
|
||||
using Emby.Naming.Common;
|
||||
|
||||
namespace Emby.Naming.TV
|
||||
@@ -51,7 +50,7 @@ namespace Emby.Naming.TV
|
||||
if (expression.IsNamed)
|
||||
{
|
||||
result.SeriesName = match.Groups["seriesname"].Value;
|
||||
result.Success = !string.IsNullOrEmpty(result.SeriesName) && !string.IsNullOrEmpty(match.Groups["seasonnumber"]?.Value);
|
||||
result.Success = !string.IsNullOrEmpty(result.SeriesName) && !match.Groups["seasonnumber"].ValueSpan.IsEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -9,45 +9,27 @@ namespace Emby.Naming.Video
|
||||
/// <summary>
|
||||
/// Resolve if file is extra for video.
|
||||
/// </summary>
|
||||
public class ExtraResolver
|
||||
public static class ExtraRuleResolver
|
||||
{
|
||||
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 +58,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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace Emby.Naming.Video
|
||||
public static class Format3DParser
|
||||
{
|
||||
// Static default result to save on allocation costs.
|
||||
private static readonly Format3DResult _defaultResult = new (false, null);
|
||||
private static readonly Format3DResult _defaultResult = new(false, null);
|
||||
|
||||
/// <summary>
|
||||
/// Parse 3D format related flags.
|
||||
|
||||
@@ -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,41 @@ 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;
|
||||
}
|
||||
|
||||
if (current.ExtraType == null)
|
||||
{
|
||||
standaloneMedia.Add(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
remainingFiles.Add(current);
|
||||
}
|
||||
}
|
||||
|
||||
var list = new List<VideoInfo>();
|
||||
|
||||
@@ -47,38 +58,20 @@ 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 } };
|
||||
|
||||
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 +80,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 +109,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 +130,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 +179,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 +187,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 = ExtraRuleResolver.GetExtraInfo(path, namingOptions);
|
||||
|
||||
var name = Path.GetFileNameWithoutExtension(path);
|
||||
|
||||
|
||||
@@ -24,63 +24,63 @@ namespace Emby.Notifications
|
||||
{
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.ApplicationUpdateInstalled.ToString()
|
||||
Type = nameof(NotificationType.ApplicationUpdateInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.InstallationFailed.ToString()
|
||||
Type = nameof(NotificationType.InstallationFailed)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.PluginInstalled.ToString()
|
||||
Type = nameof(NotificationType.PluginInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.PluginError.ToString()
|
||||
Type = nameof(NotificationType.PluginError)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.PluginUninstalled.ToString()
|
||||
Type = nameof(NotificationType.PluginUninstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.PluginUpdateInstalled.ToString()
|
||||
Type = nameof(NotificationType.PluginUpdateInstalled)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.ServerRestartRequired.ToString()
|
||||
Type = nameof(NotificationType.ServerRestartRequired)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.TaskFailed.ToString()
|
||||
Type = nameof(NotificationType.TaskFailed)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.NewLibraryContent.ToString()
|
||||
Type = nameof(NotificationType.NewLibraryContent)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.AudioPlayback.ToString()
|
||||
Type = nameof(NotificationType.AudioPlayback)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.VideoPlayback.ToString()
|
||||
Type = nameof(NotificationType.VideoPlayback)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.AudioPlaybackStopped.ToString()
|
||||
Type = nameof(NotificationType.AudioPlaybackStopped)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.VideoPlaybackStopped.ToString()
|
||||
Type = nameof(NotificationType.VideoPlaybackStopped)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.UserLockedOut.ToString()
|
||||
Type = nameof(NotificationType.UserLockedOut)
|
||||
},
|
||||
new NotificationTypeInfo
|
||||
{
|
||||
Type = NotificationType.ApplicationUpdateAvailable.ToString()
|
||||
Type = nameof(NotificationType.ApplicationUpdateAvailable)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Emby.Notifications
|
||||
|
||||
private void Update(NotificationTypeInfo note)
|
||||
{
|
||||
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type) ?? note.Type;
|
||||
note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type);
|
||||
|
||||
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<!-- Code analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -371,7 +371,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
NewConfiguration = configuration
|
||||
});
|
||||
|
||||
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
||||
_configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
|
||||
|
||||
var path = GetConfigurationFile(key);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace Emby.Server.Implementations.AppBase
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace Emby.Server.Implementations
|
||||
/// <summary>
|
||||
/// The disposable parts.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new ();
|
||||
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new();
|
||||
|
||||
private readonly IFileSystem _fileSystemManager;
|
||||
private readonly IConfiguration _startupConfig;
|
||||
|
||||
@@ -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;
|
||||
@@ -129,16 +130,14 @@ namespace Emby.Server.Implementations.Channels
|
||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||
if (internalChannel == null)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
throw new ArgumentException(nameof(item.ChannelId));
|
||||
}
|
||||
|
||||
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
||||
|
||||
var supportsDelete = channel as ISupportsDelete;
|
||||
|
||||
if (supportsDelete == null)
|
||||
if (channel is not ISupportsDelete supportsDelete)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
throw new ArgumentException(nameof(channel));
|
||||
}
|
||||
|
||||
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
|
||||
@@ -179,7 +178,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 +540,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 +1134,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 +1143,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()
|
||||
});
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
|
||||
if (parentFolder == null)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
throw new ArgumentException(nameof(parentFolder));
|
||||
}
|
||||
|
||||
var path = Path.Combine(parentFolder.Path, folderName);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -160,21 +160,22 @@ namespace Emby.Server.Implementations.Data
|
||||
protected bool TableExists(ManagedConnection connection, string name)
|
||||
{
|
||||
return connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
|
||||
db =>
|
||||
{
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
|
||||
{
|
||||
if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
return true;
|
||||
if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}, ReadTransactionMode);
|
||||
return false;
|
||||
},
|
||||
ReadTransactionMode);
|
||||
}
|
||||
|
||||
protected List<string> GetColumnNames(IDatabaseConnection connection, string table)
|
||||
@@ -194,7 +195,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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,41 +50,42 @@ namespace Emby.Server.Implementations.Data
|
||||
var users = userDatasTableExists ? null : userManager.Users;
|
||||
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
db.ExecuteAll(string.Join(';', new[]
|
||||
db =>
|
||||
{
|
||||
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
|
||||
|
||||
"drop index if exists idx_userdata",
|
||||
"drop index if exists idx_userdata1",
|
||||
"drop index if exists idx_userdata2",
|
||||
"drop index if exists userdataindex1",
|
||||
"drop index if exists userdataindex",
|
||||
"drop index if exists userdataindex3",
|
||||
"drop index if exists userdataindex4",
|
||||
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
|
||||
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
|
||||
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
|
||||
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
|
||||
}));
|
||||
|
||||
if (userDataTableExists)
|
||||
{
|
||||
var existingColumnNames = GetColumnNames(db, "userdata");
|
||||
|
||||
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
|
||||
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
|
||||
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
|
||||
|
||||
if (!userDatasTableExists)
|
||||
db.ExecuteAll(string.Join(';', new[]
|
||||
{
|
||||
ImportUserIds(db, users);
|
||||
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
|
||||
|
||||
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
|
||||
"drop index if exists idx_userdata",
|
||||
"drop index if exists idx_userdata1",
|
||||
"drop index if exists idx_userdata2",
|
||||
"drop index if exists userdataindex1",
|
||||
"drop index if exists userdataindex",
|
||||
"drop index if exists userdataindex3",
|
||||
"drop index if exists userdataindex4",
|
||||
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
|
||||
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
|
||||
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
|
||||
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
|
||||
}));
|
||||
|
||||
if (userDataTableExists)
|
||||
{
|
||||
var existingColumnNames = GetColumnNames(db, "userdata");
|
||||
|
||||
AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
|
||||
AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
|
||||
AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
|
||||
|
||||
if (!userDatasTableExists)
|
||||
{
|
||||
ImportUserIds(db, users);
|
||||
|
||||
db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, TransactionMode);
|
||||
},
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,10 +184,11 @@ namespace Emby.Server.Implementations.Data
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
SaveUserData(db, internalUserId, key, userData);
|
||||
}, TransactionMode);
|
||||
db =>
|
||||
{
|
||||
SaveUserData(db, internalUserId, key, userData);
|
||||
},
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,13 +254,14 @@ namespace Emby.Server.Implementations.Data
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
foreach (var userItemData in userDataList)
|
||||
db =>
|
||||
{
|
||||
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
|
||||
}
|
||||
}, TransactionMode);
|
||||
foreach (var userItemData in userDataList)
|
||||
{
|
||||
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
|
||||
}
|
||||
},
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
var value = File.ReadAllText(CachePath, Encoding.UTF8);
|
||||
|
||||
if (Guid.TryParse(value, out var guid))
|
||||
if (Guid.TryParse(value, out _))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
});
|
||||
|
||||
SetItemByNameInfo(item, dto, libraryItems, user);
|
||||
SetItemByNameInfo(item, dto, libraryItems);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +153,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
}),
|
||||
user);
|
||||
}));
|
||||
}
|
||||
|
||||
return dto;
|
||||
@@ -294,7 +293,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;
|
||||
}
|
||||
@@ -311,13 +310,13 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
|
||||
{
|
||||
SetItemByNameInfo(item, dto, taggedItems, user);
|
||||
SetItemByNameInfo(item, dto, taggedItems);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems, User user = null)
|
||||
private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems)
|
||||
{
|
||||
if (item is MusicArtist)
|
||||
{
|
||||
@@ -470,7 +469,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 +1403,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" />
|
||||
@@ -49,10 +49,14 @@
|
||||
<NoWarn>AD0001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
}
|
||||
}
|
||||
|
||||
_lastProgressMessageTimes.AddOrUpdate(item.Id, key => DateTime.UtcNow, (key, existing) => DateTime.UtcNow);
|
||||
_lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
|
||||
|
||||
var dict = new Dictionary<string, string>();
|
||||
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
@@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{
|
||||
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
|
||||
|
||||
_lastProgressMessageTimes.TryRemove(e.Argument.Id, out DateTime removed);
|
||||
_lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
|
||||
}
|
||||
|
||||
private static bool EnableRefreshMessage(BaseItem item)
|
||||
@@ -423,7 +423,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
continue;
|
||||
}
|
||||
|
||||
var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
|
||||
foreach (var folder in allUserRootChildren)
|
||||
{
|
||||
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||
@@ -465,6 +464,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
var changes = _changedItems.ToList();
|
||||
_changedItems.Clear();
|
||||
|
||||
var task = SendNotifications(changes, CancellationToken.None);
|
||||
SendNotifications(changes, CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||
{
|
||||
var session = await GetSession(requestContext).ConfigureAwait(false);
|
||||
|
||||
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
|
||||
return session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
|
||||
}
|
||||
|
||||
public Task<User?> GetUser(object requestContext)
|
||||
|
||||
@@ -42,17 +42,14 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="socket">The socket.</param>
|
||||
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||
/// <param name="query">The query.</param>
|
||||
public WebSocketConnection(
|
||||
ILogger<WebSocketConnection> logger,
|
||||
WebSocket socket,
|
||||
IPAddress? remoteEndPoint,
|
||||
IQueryCollection query)
|
||||
IPAddress? remoteEndPoint)
|
||||
{
|
||||
_logger = logger;
|
||||
_socket = socket;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
QueryString = query;
|
||||
|
||||
_jsonOptions = JsonDefaults.Options;
|
||||
LastActivityDate = DateTime.Now;
|
||||
@@ -81,12 +78,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <inheritdoc />
|
||||
public DateTime LastKeepAliveDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the query string.
|
||||
/// </summary>
|
||||
/// <value>The query string.</value>
|
||||
public IQueryCollection QueryString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state.
|
||||
/// </summary>
|
||||
@@ -180,7 +171,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
|
||||
WebSocketMessage<object>? stub;
|
||||
long bytesConsumed = 0;
|
||||
long bytesConsumed;
|
||||
try
|
||||
{
|
||||
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
|
||||
@@ -236,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
{
|
||||
MessageId = Guid.NewGuid(),
|
||||
MessageType = SessionMessageType.KeepAlive
|
||||
}, CancellationToken.None);
|
||||
},
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -50,8 +51,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
using var connection = new WebSocketConnection(
|
||||
_loggerFactory.CreateLogger<WebSocketConnection>(),
|
||||
webSocket,
|
||||
context.Connection.RemoteIpAddress,
|
||||
context.Request.Query)
|
||||
context.GetNormalizedRemoteIp())
|
||||
{
|
||||
OnReceive = ProcessWebSocketMessageReceived
|
||||
};
|
||||
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
var tasks = new Task[_webSocketListeners.Length];
|
||||
for (var i = 0; i < _webSocketListeners.Length; ++i)
|
||||
{
|
||||
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection);
|
||||
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context);
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
@@ -217,8 +217,13 @@ namespace Emby.Server.Implementations.IO
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DisposeTimer();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.IO
|
||||
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
|
||||
await Task.Delay(45000).ConfigureAwait(false);
|
||||
|
||||
_tempIgnoredPaths.TryRemove(path, out var val);
|
||||
_tempIgnoredPaths.TryRemove(path, out _);
|
||||
|
||||
if (refreshPath)
|
||||
{
|
||||
|
||||
@@ -544,16 +544,6 @@ namespace Emby.Server.Implementations.IO
|
||||
/// <inheritdoc />
|
||||
public virtual bool AreEqual(string path1, string path2)
|
||||
{
|
||||
if (path1 == null && path2 == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (path1 == null || path2 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(
|
||||
NormalizePath(path1),
|
||||
NormalizePath(path2),
|
||||
|
||||
@@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO
|
||||
try
|
||||
{
|
||||
int read;
|
||||
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
|
||||
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (onStarted != null)
|
||||
{
|
||||
@@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO
|
||||
if (emptyReadLimit <= 0)
|
||||
{
|
||||
int read;
|
||||
while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
|
||||
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
var bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
eofCount = 0;
|
||||
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||
|
||||
if (bytesToWrite > 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||
await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
copyLength -= bytesToWrite;
|
||||
@@ -137,9 +137,9 @@ namespace Emby.Server.Implementations.IO
|
||||
int bytesRead;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -68,9 +68,9 @@ namespace Emby.Server.Implementations.Images
|
||||
DtoOptions = new DtoOptions(false),
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
Limit = 8,
|
||||
OrderBy = new ValueTuple<string, SortOrder>[]
|
||||
OrderBy = new[]
|
||||
{
|
||||
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
|
||||
(ItemSortBy.Random, SortOrder.Ascending)
|
||||
},
|
||||
IncludeItemTypes = includeItemTypes
|
||||
});
|
||||
|
||||
@@ -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,14 +11,12 @@ 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;
|
||||
using Emby.Server.Implementations.ScheduledTasks.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
@@ -80,6 +78,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly IItemRepository _itemRepository;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly NamingOptions _namingOptions;
|
||||
private readonly ExtraResolver _extraResolver;
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder sync lock.
|
||||
@@ -148,6 +147,8 @@ namespace Emby.Server.Implementations.Library
|
||||
_memoryCache = memoryCache;
|
||||
_namingOptions = namingOptions;
|
||||
|
||||
_extraResolver = new ExtraResolver(namingOptions);
|
||||
|
||||
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||
|
||||
RecordConfigurationValues(configurationManager.Configuration);
|
||||
@@ -533,8 +534,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 +655,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 +678,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 +966,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>()
|
||||
@@ -1375,7 +1376,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetItemIdsList(query);
|
||||
}
|
||||
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
@@ -1386,7 +1387,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetStudios(query);
|
||||
}
|
||||
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
@@ -1397,7 +1398,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetGenres(query);
|
||||
}
|
||||
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
@@ -1408,7 +1409,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetMusicGenres(query);
|
||||
}
|
||||
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
@@ -1419,7 +1420,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetAllArtists(query);
|
||||
}
|
||||
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
@@ -1460,7 +1461,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
||||
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
|
||||
{
|
||||
if (query.User != null)
|
||||
{
|
||||
@@ -1759,7 +1760,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return orderedItems ?? items;
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
|
||||
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy)
|
||||
{
|
||||
var isFirst = true;
|
||||
|
||||
@@ -2680,94 +2681,70 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
return new ItemLookupInfo
|
||||
{
|
||||
Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name,
|
||||
Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName : result.Name,
|
||||
Year = result.Year
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
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, null, false, false);
|
||||
foreach (var file in filesInSubFolder)
|
||||
{
|
||||
video = dbItem;
|
||||
if (!_extraResolver.TryGetExtraTypeForOwner(file.FullName, ownerVideoInfo, out var extraType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var extra = GetExtra(file, extraType.Value);
|
||||
if (extra != null)
|
||||
{
|
||||
yield return extra;
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType))
|
||||
{
|
||||
var extra = GetExtra(current, extraType.Value);
|
||||
if (extra != null)
|
||||
{
|
||||
yield return extra;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
|
||||
.OfType<Video>()
|
||||
.Select(video =>
|
||||
BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType)
|
||||
{
|
||||
var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType));
|
||||
if (extra is not Video && extra is not Audio)
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var dbItem = GetItemById(video.Id) as Video;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
video = dbItem;
|
||||
}
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var itemById = GetItemById(extra.Id);
|
||||
if (itemById != null)
|
||||
{
|
||||
extra = itemById;
|
||||
}
|
||||
|
||||
video.ParentId = Guid.Empty;
|
||||
video.OwnerId = owner.Id;
|
||||
|
||||
SetExtraTypeFromFilename(video);
|
||||
|
||||
return video;
|
||||
|
||||
// Sort them so that the list can be easily compared for changes
|
||||
}).OrderBy(i => i.Path);
|
||||
extra.ExtraType = extraType;
|
||||
extra.ParentId = Guid.Empty;
|
||||
extra.OwnerId = owner.Id;
|
||||
return extra;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
|
||||
@@ -2817,15 +2794,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 +2900,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;
|
||||
|
||||
@@ -66,11 +66,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
|
||||
delayMs = Math.Max(3000, delayMs);
|
||||
if (delayMs > 0)
|
||||
{
|
||||
_logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
|
||||
await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
_logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
|
||||
await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
mediaSource.AnalyzeDurationMs = 3000;
|
||||
|
||||
@@ -464,12 +464,11 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
try
|
||||
{
|
||||
var tuple = GetProvider(request.OpenToken);
|
||||
var provider = tuple.Item1;
|
||||
var (provider, keyId) = GetProvider(request.OpenToken);
|
||||
|
||||
var currentLiveStreams = _openStreams.Values.ToList();
|
||||
|
||||
liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false);
|
||||
liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
mediaSource = liveStream.MediaSource;
|
||||
|
||||
@@ -829,7 +828,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
private (IMediaSourceProvider, string) GetProvider(string key)
|
||||
private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
if (item is MusicGenre genre)
|
||||
if (item is MusicGenre)
|
||||
{
|
||||
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
|
||||
}
|
||||
|
||||
@@ -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] == '=' || 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;
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||
/// Determine if the supplied list contains what we should consider music.
|
||||
/// </summary>
|
||||
private bool ContainsMusic(
|
||||
IEnumerable<FileSystemMetadata> list,
|
||||
ICollection<FileSystemMetadata> list,
|
||||
bool allowSubfolders,
|
||||
IDirectoryService directoryService)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Emby.Naming.Common;
|
||||
using Emby.Naming.Video;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using static Emby.Naming.Video.ExtraRuleResolver;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a Path into a Video or Video subclass.
|
||||
/// </summary>
|
||||
internal class ExtraResolver
|
||||
{
|
||||
private readonly NamingOptions _namingOptions;
|
||||
private readonly IItemResolver[] _trailerResolvers;
|
||||
private readonly IItemResolver[] _videoResolvers;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an new instance of the <see cref="ExtraResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
|
||||
public ExtraResolver(NamingOptions namingOptions)
|
||||
{
|
||||
_namingOptions = namingOptions;
|
||||
_trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(namingOptions) };
|
||||
_videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(namingOptions) };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resolvers for the extra type.
|
||||
/// </summary>
|
||||
/// <param name="extraType">The extra type.</param>
|
||||
/// <returns>The resolvers for the extra type.</returns>
|
||||
public IItemResolver[]? GetResolversForExtraType(ExtraType extraType) => extraType switch
|
||||
{
|
||||
ExtraType.Trailer => _trailerResolvers,
|
||||
// For audio we'll have to rely on the AudioResolver, which is a "built-in"
|
||||
ExtraType.ThemeSong => null,
|
||||
_ => _videoResolvers
|
||||
};
|
||||
|
||||
public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType)
|
||||
{
|
||||
var extraResult = GetExtraInfo(path, _namingOptions);
|
||||
if (extraResult.ExtraType == null)
|
||||
{
|
||||
extraType = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var cleanDateTimeResult = CleanDateTimeParser.Clean(Path.GetFileNameWithoutExtension(path), _namingOptions.CleanDateTimeRegexes);
|
||||
var name = cleanDateTimeResult.Name;
|
||||
var year = cleanDateTimeResult.Year;
|
||||
|
||||
var parentDir = ownerVideoFileInfo.IsDirectory ? ownerVideoFileInfo.Path : Path.GetDirectoryName(ownerVideoFileInfo.Path.AsSpan());
|
||||
|
||||
var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(ownerVideoFileInfo.FileNameWithoutExtension, _namingOptions.VideoFlagDelimiters);
|
||||
var trimmedVideoInfoName = TrimFilenameDelimiters(ownerVideoFileInfo.Name, _namingOptions.VideoFlagDelimiters);
|
||||
var trimmedExtraFileName = TrimFilenameDelimiters(name, _namingOptions.VideoFlagDelimiters);
|
||||
|
||||
// first check filenames
|
||||
bool isValid = StartsWith(trimmedExtraFileName, trimmedFileNameWithoutExtension)
|
||||
|| (StartsWith(trimmedExtraFileName, trimmedVideoInfoName) && year == ownerVideoFileInfo.Year);
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
// When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
|
||||
var currentParentDir = extraResult.Rule?.RuleType == ExtraRuleType.DirectoryName
|
||||
? Path.GetDirectoryName(Path.GetDirectoryName(path.AsSpan()))
|
||||
: Path.GetDirectoryName(path.AsSpan());
|
||||
|
||||
isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
extraType = extraResult.ExtraType;
|
||||
return isValid;
|
||||
}
|
||||
|
||||
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,15 +1,21 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
#nullable disable
|
||||
|
||||
using Emby.Naming.Common;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Resolvers
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a Path into an instance of the <see cref="Video"/> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item to resolve.</typeparam>
|
||||
public class GenericVideoResolver<T> : BaseVideoResolver<T>
|
||||
where T : Video, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
public GenericVideoResolver(NamingOptions namingOptions)
|
||||
: base(namingOptions)
|
||||
{
|
||||
|
||||
@@ -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,24 +90,24 @@ 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))
|
||||
{
|
||||
// Owned items will be caught by the plain video resolver
|
||||
// Owned items will be caught by the video extra resolver
|
||||
if (args.Parent == null)
|
||||
{
|
||||
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -118,23 +116,22 @@ 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
|
||||
// Owned items will be caught by the video extra resolver
|
||||
if (args.Parent == null)
|
||||
{
|
||||
return base.Resolve(args);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (IsInvalid(args.Parent, collectionType))
|
||||
@@ -169,6 +166,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 +183,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 +192,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 +206,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 +214,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 +228,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 +260,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 +310,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 +366,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 +382,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 +455,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 +534,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;
|
||||
@@ -33,7 +34,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||
"default"
|
||||
};
|
||||
|
||||
|
||||
public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
|
||||
{
|
||||
_imageProcessor = imageProcessor;
|
||||
@@ -109,7 +109,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,8 +189,8 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
searchQuery.ParentId = Guid.Empty;
|
||||
searchQuery.IncludeItemsByName = true;
|
||||
searchQuery.IncludeItemTypes = Array.Empty<string>();
|
||||
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList();
|
||||
searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
|
||||
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
var cacheKey = GetCacheKey(userId, item.Id);
|
||||
_userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
|
||||
_userData.AddOrUpdate(cacheKey, userData, (_, _) => userData);
|
||||
|
||||
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
|
||||
{
|
||||
@@ -125,7 +125,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var cacheKey = GetCacheKey(userId, itemId);
|
||||
|
||||
return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys));
|
||||
return _userData.GetOrAdd(cacheKey, _ => GetUserDataInternal(userId, keys));
|
||||
}
|
||||
|
||||
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -175,12 +173,12 @@ namespace Emby.Server.Implementations.Library
|
||||
string viewType,
|
||||
string localizationKey,
|
||||
string sortName,
|
||||
Jellyfin.Data.Entities.User user,
|
||||
User user,
|
||||
string[] presetViews)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -361,7 +359,7 @@ namespace Emby.Server.Implementations.Library
|
||||
(ItemSortBy.SortName, SortOrder.Descending),
|
||||
(ItemSortBy.ProductionYear, SortOrder.Descending)
|
||||
},
|
||||
IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
|
||||
IsFolder = includeItemTypes.Length == 0 ? false : null,
|
||||
ExcludeItemTypes = excludeItemTypes,
|
||||
IsVirtualItem = false,
|
||||
Limit = limit * 5,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Library.Validators
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
@@ -397,7 +398,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
|
||||
result = new EpgChannelData(channels);
|
||||
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
|
||||
_epgChannels.AddOrUpdate(info.Id, result, (_, _) => result);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -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)
|
||||
@@ -1247,12 +1248,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
var remoteMetadata = await FetchInternetMetadata(timer, CancellationToken.None).ConfigureAwait(false);
|
||||
var recordPath = GetRecordingPath(timer, remoteMetadata, out string seriesPath);
|
||||
var recordingStatus = RecordingStatus.New;
|
||||
|
||||
string liveStreamId = null;
|
||||
|
||||
var channelItem = _liveTvManager.GetLiveTvChannel(timer, this);
|
||||
|
||||
string liveStreamId = null;
|
||||
RecordingStatus recordingStatus;
|
||||
try
|
||||
{
|
||||
var allMediaSources = await _mediaSourceManager.GetPlaybackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
|
||||
@@ -1338,7 +1338,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
TriggerRefresh(recordPath);
|
||||
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
|
||||
|
||||
_activeRecordings.TryRemove(timer.Id, out var removed);
|
||||
_activeRecordings.TryRemove(timer.Id, out _);
|
||||
|
||||
if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate && timer.RetryCount < 10)
|
||||
{
|
||||
@@ -1778,7 +1778,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)
|
||||
@@ -1936,7 +1936,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
writer.WriteElementString("title", timer.EpisodeTitle);
|
||||
}
|
||||
|
||||
var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : (DateTime?)null);
|
||||
var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : null);
|
||||
|
||||
if (premiereDate.HasValue)
|
||||
{
|
||||
@@ -2125,19 +2125,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer)
|
||||
{
|
||||
return GetProgramInfoFromCache(timer.ProgramId, timer.ChannelId);
|
||||
}
|
||||
|
||||
private LiveTvProgram GetProgramInfoFromCache(string programId, string channelId)
|
||||
{
|
||||
return GetProgramInfoFromCache(programId);
|
||||
return GetProgramInfoFromCache(timer.ProgramId);
|
||||
}
|
||||
|
||||
private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc)
|
||||
{
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
Limit = 1,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
{
|
||||
@@ -2276,7 +2271,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
// Only update if not currently active - test both new timer and existing in case Id's are different
|
||||
// Id's could be different if the timer was created manually prior to series timer creation
|
||||
if (!_activeRecordings.TryGetValue(timer.Id, out var activeRecordingInfo) && !_activeRecordings.TryGetValue(existingTimer.Id, out activeRecordingInfo))
|
||||
if (!_activeRecordings.TryGetValue(timer.Id, out _) && !_activeRecordings.TryGetValue(existingTimer.Id, out _))
|
||||
{
|
||||
UpdateExistingTimerWithNewMetadata(existingTimer, timer);
|
||||
|
||||
@@ -2297,17 +2292,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
enabledTimersForSeries.Add(existingTimer);
|
||||
}
|
||||
|
||||
if (updateTimerSettings)
|
||||
{
|
||||
existingTimer.KeepUntil = seriesTimer.KeepUntil;
|
||||
existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
|
||||
existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
|
||||
existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
|
||||
existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
|
||||
existingTimer.Priority = seriesTimer.Priority;
|
||||
}
|
||||
|
||||
existingTimer.KeepUntil = seriesTimer.KeepUntil;
|
||||
existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
|
||||
existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
|
||||
existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
|
||||
existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
|
||||
existingTimer.Priority = seriesTimer.Priority;
|
||||
existingTimer.SeriesTimerId = seriesTimer.Id;
|
||||
|
||||
_timerProvider.Update(existingTimer);
|
||||
}
|
||||
}
|
||||
@@ -2332,7 +2324,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 +2344,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 +2379,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 +2438,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 +2503,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 +2516,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 +2613,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -62,12 +62,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
using var durationToken = new CancellationTokenSource(duration);
|
||||
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
||||
|
||||
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
_logger.LogInformation("Recording completed to file {0}", targetFile);
|
||||
}
|
||||
|
||||
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
_targetPath = targetFile;
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
RedirectStandardInput = true,
|
||||
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
||||
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile),
|
||||
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
StartInfo = processStartInfo,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process);
|
||||
_process.Exited += (_, _) => OnFfMpegProcessExited(_process);
|
||||
|
||||
_process.Start();
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
||||
}
|
||||
|
||||
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile, TimeSpan duration)
|
||||
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)
|
||||
{
|
||||
string videoArgs;
|
||||
if (EncodeVideo(mediaSource))
|
||||
|
||||
@@ -10,7 +10,6 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@@ -160,8 +159,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
var programEntry = programDict[schedule.ProgramId];
|
||||
|
||||
var allImages = images[imageIndex].Data;
|
||||
var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase));
|
||||
var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase));
|
||||
var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
const double DesiredAspect = 2.0 / 3;
|
||||
|
||||
@@ -242,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;
|
||||
}
|
||||
@@ -316,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)
|
||||
@@ -326,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;
|
||||
}
|
||||
@@ -388,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;
|
||||
}
|
||||
@@ -644,7 +643,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
|
||||
#pragma warning disable CA5350 // SchedulesDirect is always SHA1.
|
||||
var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password));
|
||||
#pragma warning restore CA5350
|
||||
// TODO: remove ToLower when Convert.ToHexString supports lowercase
|
||||
// Schedules Direct requires the hex to be lowercase
|
||||
string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant();
|
||||
@@ -821,10 +822,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string NormalizeName(string value)
|
||||
{
|
||||
return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -209,7 +207,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
|
||||
}
|
||||
|
||||
if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
|
||||
if (!internalQuery.OrderBy.Any(i => string.Equals(i.OrderBy, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
|
||||
}
|
||||
@@ -522,7 +520,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return item;
|
||||
}
|
||||
|
||||
private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
|
||||
private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
|
||||
{
|
||||
var id = _tvDtoService.GetInternalProgramId(info.Id);
|
||||
|
||||
@@ -781,9 +779,9 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
|
||||
|
||||
var list = new List<Tuple<BaseItemDto, string, string>>
|
||||
var list = new List<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)>
|
||||
{
|
||||
new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId)
|
||||
(dto, program.ExternalId, program.ExternalSeriesId)
|
||||
};
|
||||
|
||||
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
|
||||
@@ -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,
|
||||
@@ -978,16 +976,16 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return score;
|
||||
}
|
||||
|
||||
private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken)
|
||||
private async Task AddRecordingInfo(IEnumerable<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)> programs, CancellationToken cancellationToken)
|
||||
{
|
||||
IReadOnlyList<TimerInfo> timerList = null;
|
||||
IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
|
||||
|
||||
foreach (var programTuple in programs)
|
||||
{
|
||||
var program = programTuple.Item1;
|
||||
var externalProgramId = programTuple.Item2;
|
||||
string externalSeriesId = programTuple.Item3;
|
||||
var program = programTuple.ItemDto;
|
||||
var externalProgramId = programTuple.ExternalId;
|
||||
string externalSeriesId = programTuple.ExternalSeriesId;
|
||||
|
||||
timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
|
||||
|
||||
@@ -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);
|
||||
@@ -1188,13 +1186,13 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
foreach (var program in channelPrograms)
|
||||
{
|
||||
var programTuple = GetProgram(program, existingPrograms, currentChannel);
|
||||
var programItem = programTuple.item;
|
||||
var programItem = programTuple.Item;
|
||||
|
||||
if (programTuple.isNew)
|
||||
if (programTuple.IsNew)
|
||||
{
|
||||
newPrograms.Add(programItem);
|
||||
}
|
||||
else if (programTuple.isUpdated)
|
||||
else if (programTuple.IsUpdated)
|
||||
{
|
||||
updatedPrograms.Add(programItem);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1425,9 +1423,9 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return result;
|
||||
}
|
||||
|
||||
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
|
||||
public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
|
||||
{
|
||||
var programTuples = new List<Tuple<BaseItemDto, string, string>>();
|
||||
var programTuples = new List<(BaseItemDto Dto, string ExternalId, string ExternalSeriesId)>();
|
||||
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
|
||||
var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
|
||||
|
||||
@@ -1463,7 +1461,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
}
|
||||
}
|
||||
|
||||
programTuples.Add(new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId));
|
||||
programTuples.Add((dto, program.ExternalId, program.ExternalSeriesId));
|
||||
}
|
||||
|
||||
return AddRecordingInfo(programTuples, CancellationToken.None);
|
||||
@@ -1870,15 +1868,15 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return _libraryManager.GetItemById(internalChannelId);
|
||||
}
|
||||
|
||||
public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user)
|
||||
public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray();
|
||||
var channelIds = items.Select(i => i.Channel.Id).Distinct().ToArray();
|
||||
|
||||
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
||||
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||
ChannelIds = channelIds,
|
||||
MaxStartDate = now,
|
||||
MinEndDate = now,
|
||||
@@ -1895,11 +1893,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var addCurrentProgram = options.AddCurrentProgram;
|
||||
|
||||
foreach (var tuple in items)
|
||||
foreach (var (dto, channel) in items)
|
||||
{
|
||||
var dto = tuple.Item1;
|
||||
var channel = tuple.Item2;
|
||||
|
||||
dto.Number = channel.Number;
|
||||
dto.ChannelNumber = channel.Number;
|
||||
dto.ChannelType = channel.ChannelType;
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
public IEnumerable<(string, string)> GetCommands()
|
||||
public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_channel))
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user