Compare commits
191 Commits
| 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'
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
steps:
|
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'
|
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
|
||||||
displayName: 'Build Dockerfile'
|
displayName: 'Build Dockerfile'
|
||||||
|
|
||||||
@@ -80,6 +88,14 @@ jobs:
|
|||||||
vmImage: 'ubuntu-latest'
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
steps:
|
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
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: 'Download OpenAPI Spec'
|
displayName: 'Download OpenAPI Spec'
|
||||||
inputs:
|
inputs:
|
||||||
@@ -127,6 +143,10 @@ jobs:
|
|||||||
displayName: Set release version (stable)
|
displayName: Set release version (stable)
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
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
|
- task: Docker@2
|
||||||
displayName: 'Push Unstable Image'
|
displayName: 'Push Unstable Image'
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
../fedora/Makefile
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
<PropertyGroup>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,8 @@ using MediaBrowser.Controller.Drawing;
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Playlists;
|
|
||||||
using MediaBrowser.Controller.TV;
|
using MediaBrowser.Controller.TV;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
@@ -30,12 +27,7 @@ using MediaBrowser.Model.Entities;
|
|||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Book = MediaBrowser.Controller.Entities.Book;
|
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
|
||||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
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
|
namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
@@ -539,7 +531,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
User = user,
|
User = user,
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
IsMissing = false,
|
IsMissing = false,
|
||||||
ExcludeItemTypes = new[] { nameof(Book) },
|
ExcludeItemTypes = new[] { BaseItemKind.Book },
|
||||||
IsFolder = isFolder,
|
IsFolder = isFolder,
|
||||||
MediaTypes = mediaTypes,
|
MediaTypes = mediaTypes,
|
||||||
DtoOptions = GetDtoOptions()
|
DtoOptions = GetDtoOptions()
|
||||||
@@ -619,7 +611,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false,
|
||||||
ExcludeItemTypes = new[] { nameof(Book) },
|
ExcludeItemTypes = new[] { BaseItemKind.Book },
|
||||||
IsPlaceHolder = false,
|
IsPlaceHolder = false,
|
||||||
DtoOptions = GetDtoOptions(),
|
DtoOptions = GetDtoOptions(),
|
||||||
OrderBy = GetOrderBy(sort, folder.IsPreSorted)
|
OrderBy = GetOrderBy(sort, folder.IsPreSorted)
|
||||||
@@ -644,7 +636,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
{
|
{
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||||
OrderBy = GetOrderBy(sort, false)
|
OrderBy = GetOrderBy(sort, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -675,39 +667,39 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
switch (stubType)
|
switch (stubType)
|
||||||
{
|
{
|
||||||
case StubType.Latest:
|
case StubType.Latest:
|
||||||
return GetLatest(item, query, nameof(Audio));
|
return GetLatest(item, query, BaseItemKind.Audio);
|
||||||
case StubType.Playlists:
|
case StubType.Playlists:
|
||||||
return GetMusicPlaylists(query);
|
return GetMusicPlaylists(query);
|
||||||
case StubType.Albums:
|
case StubType.Albums:
|
||||||
return GetChildrenOfItem(item, query, nameof(MusicAlbum));
|
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum);
|
||||||
case StubType.Artists:
|
case StubType.Artists:
|
||||||
return GetMusicArtists(item, query);
|
return GetMusicArtists(item, query);
|
||||||
case StubType.AlbumArtists:
|
case StubType.AlbumArtists:
|
||||||
return GetMusicAlbumArtists(item, query);
|
return GetMusicAlbumArtists(item, query);
|
||||||
case StubType.FavoriteAlbums:
|
case StubType.FavoriteAlbums:
|
||||||
return GetChildrenOfItem(item, query, nameof(MusicAlbum), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.MusicAlbum, true);
|
||||||
case StubType.FavoriteArtists:
|
case StubType.FavoriteArtists:
|
||||||
return GetFavoriteArtists(item, query);
|
return GetFavoriteArtists(item, query);
|
||||||
case StubType.FavoriteSongs:
|
case StubType.FavoriteSongs:
|
||||||
return GetChildrenOfItem(item, query, nameof(Audio), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.Audio, true);
|
||||||
case StubType.Songs:
|
case StubType.Songs:
|
||||||
return GetChildrenOfItem(item, query, nameof(Audio));
|
return GetChildrenOfItem(item, query, BaseItemKind.Audio);
|
||||||
case StubType.Genres:
|
case StubType.Genres:
|
||||||
return GetMusicGenres(item, query);
|
return GetMusicGenres(item, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverItems = new ServerItem[]
|
var serverItems = new ServerItem[]
|
||||||
{
|
{
|
||||||
new (item, StubType.Latest),
|
new(item, StubType.Latest),
|
||||||
new (item, StubType.Playlists),
|
new(item, StubType.Playlists),
|
||||||
new (item, StubType.Albums),
|
new(item, StubType.Albums),
|
||||||
new (item, StubType.AlbumArtists),
|
new(item, StubType.AlbumArtists),
|
||||||
new (item, StubType.Artists),
|
new(item, StubType.Artists),
|
||||||
new (item, StubType.Songs),
|
new(item, StubType.Songs),
|
||||||
new (item, StubType.Genres),
|
new(item, StubType.Genres),
|
||||||
new (item, StubType.FavoriteArtists),
|
new(item, StubType.FavoriteArtists),
|
||||||
new (item, StubType.FavoriteAlbums),
|
new(item, StubType.FavoriteAlbums),
|
||||||
new (item, StubType.FavoriteSongs)
|
new(item, StubType.FavoriteSongs)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (limit < serverItems.Length)
|
if (limit < serverItems.Length)
|
||||||
@@ -746,25 +738,25 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
case StubType.ContinueWatching:
|
case StubType.ContinueWatching:
|
||||||
return GetMovieContinueWatching(item, query);
|
return GetMovieContinueWatching(item, query);
|
||||||
case StubType.Latest:
|
case StubType.Latest:
|
||||||
return GetLatest(item, query, nameof(Movie));
|
return GetLatest(item, query, BaseItemKind.Movie);
|
||||||
case StubType.Movies:
|
case StubType.Movies:
|
||||||
return GetChildrenOfItem(item, query, nameof(Movie));
|
return GetChildrenOfItem(item, query, BaseItemKind.Movie);
|
||||||
case StubType.Collections:
|
case StubType.Collections:
|
||||||
return GetMovieCollections(query);
|
return GetMovieCollections(query);
|
||||||
case StubType.Favorites:
|
case StubType.Favorites:
|
||||||
return GetChildrenOfItem(item, query, nameof(Movie), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.Movie, true);
|
||||||
case StubType.Genres:
|
case StubType.Genres:
|
||||||
return GetGenres(item, query);
|
return GetGenres(item, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var array = new ServerItem[]
|
var array = new ServerItem[]
|
||||||
{
|
{
|
||||||
new (item, StubType.ContinueWatching),
|
new(item, StubType.ContinueWatching),
|
||||||
new (item, StubType.Latest),
|
new(item, StubType.Latest),
|
||||||
new (item, StubType.Movies),
|
new(item, StubType.Movies),
|
||||||
new (item, StubType.Collections),
|
new(item, StubType.Collections),
|
||||||
new (item, StubType.Favorites),
|
new(item, StubType.Favorites),
|
||||||
new (item, StubType.Genres)
|
new(item, StubType.Genres)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (limit < array.Length)
|
if (limit < array.Length)
|
||||||
@@ -831,26 +823,26 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
case StubType.NextUp:
|
case StubType.NextUp:
|
||||||
return GetNextUp(item, query);
|
return GetNextUp(item, query);
|
||||||
case StubType.Latest:
|
case StubType.Latest:
|
||||||
return GetLatest(item, query, nameof(Episode));
|
return GetLatest(item, query, BaseItemKind.Episode);
|
||||||
case StubType.Series:
|
case StubType.Series:
|
||||||
return GetChildrenOfItem(item, query, nameof(Series));
|
return GetChildrenOfItem(item, query, BaseItemKind.Series);
|
||||||
case StubType.FavoriteSeries:
|
case StubType.FavoriteSeries:
|
||||||
return GetChildrenOfItem(item, query, nameof(Series), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.Series, true);
|
||||||
case StubType.FavoriteEpisodes:
|
case StubType.FavoriteEpisodes:
|
||||||
return GetChildrenOfItem(item, query, nameof(Episode), true);
|
return GetChildrenOfItem(item, query, BaseItemKind.Episode, true);
|
||||||
case StubType.Genres:
|
case StubType.Genres:
|
||||||
return GetGenres(item, query);
|
return GetGenres(item, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverItems = new ServerItem[]
|
var serverItems = new ServerItem[]
|
||||||
{
|
{
|
||||||
new (item, StubType.ContinueWatching),
|
new(item, StubType.ContinueWatching),
|
||||||
new (item, StubType.NextUp),
|
new(item, StubType.NextUp),
|
||||||
new (item, StubType.Latest),
|
new(item, StubType.Latest),
|
||||||
new (item, StubType.Series),
|
new(item, StubType.Series),
|
||||||
new (item, StubType.FavoriteSeries),
|
new(item, StubType.FavoriteSeries),
|
||||||
new (item, StubType.FavoriteEpisodes),
|
new(item, StubType.FavoriteEpisodes),
|
||||||
new (item, StubType.Genres)
|
new(item, StubType.Genres)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (limit < serverItems.Length)
|
if (limit < serverItems.Length)
|
||||||
@@ -898,7 +890,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMovieCollections(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
query.IncludeItemTypes = new[] { nameof(BoxSet) };
|
query.IncludeItemTypes = new[] { BaseItemKind.BoxSet };
|
||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
|
|
||||||
@@ -913,7 +905,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
/// <param name="itemType">The item type.</param>
|
/// <param name="itemType">The item type.</param>
|
||||||
/// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param>
|
/// <param name="isFavorite">A value indicating whether to only fetch favorite items.</param>
|
||||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <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.Recursive = true;
|
||||||
query.Parent = parent;
|
query.Parent = parent;
|
||||||
@@ -1013,7 +1005,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMusicPlaylists(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.Parent = null;
|
query.Parent = null;
|
||||||
query.IncludeItemTypes = new[] { nameof(Playlist) };
|
query.IncludeItemTypes = new[] { BaseItemKind.Playlist };
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
|
|
||||||
var result = _libraryManager.GetItemsResult(query);
|
var result = _libraryManager.GetItemsResult(query);
|
||||||
@@ -1052,7 +1044,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
/// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
|
/// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
|
||||||
/// <param name="itemType">The item type.</param>
|
/// <param name="itemType">The item type.</param>
|
||||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <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)>();
|
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||||
|
|
||||||
@@ -1086,7 +1078,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
{
|
{
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
ArtistIds = new[] { item.Id },
|
ArtistIds = new[] { item.Id },
|
||||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
DtoOptions = GetDtoOptions(),
|
DtoOptions = GetDtoOptions(),
|
||||||
@@ -1115,8 +1107,8 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
GenreIds = new[] { item.Id },
|
GenreIds = new[] { item.Id },
|
||||||
IncludeItemTypes = new[]
|
IncludeItemTypes = new[]
|
||||||
{
|
{
|
||||||
nameof(Movie),
|
BaseItemKind.Movie,
|
||||||
nameof(Series)
|
BaseItemKind.Series
|
||||||
},
|
},
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
@@ -1144,7 +1136,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
{
|
{
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
GenreIds = new[] { item.Id },
|
GenreIds = new[] { item.Id },
|
||||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
DtoOptions = GetDtoOptions(),
|
DtoOptions = GetDtoOptions(),
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ namespace Emby.Dlna.Didl
|
|||||||
public Filter(string filter)
|
public Filter(string filter)
|
||||||
{
|
{
|
||||||
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
||||||
|
_fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||||
_fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Contains(string field)
|
public bool Contains(string field)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -84,8 +83,7 @@ namespace Emby.Dlna
|
|||||||
{
|
{
|
||||||
lock (_profiles)
|
lock (_profiles)
|
||||||
{
|
{
|
||||||
var list = _profiles.Values.ToList();
|
return _profiles.Values
|
||||||
return list
|
|
||||||
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
|
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||||
.ThenBy(i => i.Item1.Info.Name)
|
.ThenBy(i => i.Item1.Info.Name)
|
||||||
.Select(i => i.Item2)
|
.Select(i => i.Item2)
|
||||||
@@ -227,11 +225,8 @@ namespace Emby.Dlna
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var xmlFies = _fileSystem.GetFilePaths(path)
|
return _fileSystem.GetFilePaths(path)
|
||||||
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
|
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return xmlFies
|
|
||||||
.Select(i => ParseProfileFile(i, type))
|
.Select(i => ParseProfileFile(i, type))
|
||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.ToList()!; // We just filtered out all the nulls
|
.ToList()!; // We just filtered out all the nulls
|
||||||
@@ -253,11 +248,8 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeviceProfile profile;
|
|
||||||
|
|
||||||
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
|
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
|
||||||
|
var profile = ReserializeProfile(tempProfile);
|
||||||
profile = ReserializeProfile(tempProfile);
|
|
||||||
|
|
||||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
@@ -296,8 +288,7 @@ namespace Emby.Dlna
|
|||||||
{
|
{
|
||||||
lock (_profiles)
|
lock (_profiles)
|
||||||
{
|
{
|
||||||
var list = _profiles.Values.ToList();
|
return _profiles.Values
|
||||||
return list
|
|
||||||
.Select(i => i.Item1)
|
.Select(i => i.Item1)
|
||||||
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
|
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||||
.ThenBy(i => i.Info.Name);
|
.ThenBy(i => i.Info.Name);
|
||||||
|
|||||||
@@ -22,10 +22,14 @@
|
|||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<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" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
+26
-33
@@ -1179,6 +1179,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return new Device(deviceProperties, httpClientFactory, logger);
|
return new Device(deviceProperties, httpClientFactory, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
private static DeviceIcon CreateIcon(XElement element)
|
private static DeviceIcon CreateIcon(XElement element)
|
||||||
{
|
{
|
||||||
if (element == null)
|
if (element == null)
|
||||||
@@ -1186,69 +1187,61 @@ namespace Emby.Dlna.PlayTo
|
|||||||
throw new ArgumentNullException(nameof(element));
|
throw new ArgumentNullException(nameof(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype"));
|
|
||||||
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
|
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
|
||||||
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
|
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);
|
_ = int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var widthValue);
|
||||||
var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
_ = int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var heightValue);
|
||||||
|
|
||||||
return new DeviceIcon
|
return new DeviceIcon
|
||||||
{
|
{
|
||||||
Depth = depth,
|
Depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")) ?? string.Empty,
|
||||||
Height = heightValue,
|
Height = heightValue,
|
||||||
MimeType = mimeType,
|
MimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")) ?? string.Empty,
|
||||||
Url = url,
|
Url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")) ?? string.Empty,
|
||||||
Width = widthValue
|
Width = widthValue
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DeviceService Create(XElement element)
|
private static DeviceService Create(XElement element)
|
||||||
{
|
=> new DeviceService()
|
||||||
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
|
|
||||||
{
|
{
|
||||||
ControlUrl = controlURL,
|
ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty,
|
||||||
EventSubUrl = eventSubURL,
|
EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty,
|
||||||
ScpdUrl = scpdUrl,
|
ScpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")) ?? string.Empty,
|
||||||
ServiceId = id,
|
ServiceId = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")) ?? string.Empty,
|
||||||
ServiceType = type
|
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;
|
TransportState = state;
|
||||||
|
|
||||||
var previousMediaInfo = CurrentMediaInfo;
|
var previousMediaInfo = CurrentMediaInfo;
|
||||||
CurrentMediaInfo = mediaInfo;
|
CurrentMediaInfo = mediaInfo;
|
||||||
|
|
||||||
if (previousMediaInfo == null && mediaInfo != null)
|
if (mediaInfo == null)
|
||||||
|
{
|
||||||
|
if (previousMediaInfo != null)
|
||||||
|
{
|
||||||
|
OnPlaybackStop(previousMediaInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (previousMediaInfo == null)
|
||||||
{
|
{
|
||||||
if (state != TransportState.Stopped)
|
if (state != TransportState.Stopped)
|
||||||
{
|
{
|
||||||
OnPlaybackStart(mediaInfo);
|
OnPlaybackStart(mediaInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mediaInfo != null && previousMediaInfo != null && !mediaInfo.Equals(previousMediaInfo))
|
else if (mediaInfo.Equals(previousMediaInfo))
|
||||||
{
|
|
||||||
OnMediaChanged(previousMediaInfo, mediaInfo);
|
|
||||||
}
|
|
||||||
else if (mediaInfo == null && previousMediaInfo != null)
|
|
||||||
{
|
|
||||||
OnPlaybackStop(previousMediaInfo);
|
|
||||||
}
|
|
||||||
else if (mediaInfo != null && mediaInfo.Equals(previousMediaInfo))
|
|
||||||
{
|
{
|
||||||
OnPlaybackProgress(mediaInfo);
|
OnPlaybackProgress(mediaInfo);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnMediaChanged(previousMediaInfo, mediaInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStart(UBaseObject mediaInfo)
|
private void OnPlaybackStart(UBaseObject mediaInfo)
|
||||||
|
|||||||
@@ -210,9 +210,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
|
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var duration = mediaSource == null ?
|
var duration = mediaSource == null
|
||||||
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
|
? _device.Duration?.Ticks
|
||||||
mediaSource.RunTimeTicks;
|
: mediaSource.RunTimeTicks;
|
||||||
|
|
||||||
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
|
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)) ??
|
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
|
||||||
(state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
|
(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);
|
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)
|
public void AddXmlRootAttribute(string name, string value)
|
||||||
{
|
{
|
||||||
var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>();
|
var list = XmlRootAttributes.ToList();
|
||||||
var list = atts.ToList();
|
|
||||||
|
|
||||||
list.Add(new XmlAttribute
|
list.Add(new XmlAttribute
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ namespace Emby.Dlna.Server
|
|||||||
builder.Append("<icon>");
|
builder.Append("<icon>");
|
||||||
|
|
||||||
builder.Append("<mimetype>")
|
builder.Append("<mimetype>")
|
||||||
.Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
|
.Append(SecurityElement.Escape(icon.MimeType))
|
||||||
.Append("</mimetype>");
|
.Append("</mimetype>");
|
||||||
builder.Append("<width>")
|
builder.Append("<width>")
|
||||||
.Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture)))
|
.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(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture)))
|
||||||
.Append("</height>");
|
.Append("</height>");
|
||||||
builder.Append("<depth>")
|
builder.Append("<depth>")
|
||||||
.Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
|
.Append(SecurityElement.Escape(icon.Depth))
|
||||||
.Append("</depth>");
|
.Append("</depth>");
|
||||||
builder.Append("<url>")
|
builder.Append("<url>")
|
||||||
.Append(BuildUrl(icon.Url))
|
.Append(BuildUrl(icon.Url))
|
||||||
@@ -219,10 +219,10 @@ namespace Emby.Dlna.Server
|
|||||||
builder.Append("<service>");
|
builder.Append("<service>");
|
||||||
|
|
||||||
builder.Append("<serviceType>")
|
builder.Append("<serviceType>")
|
||||||
.Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
|
.Append(SecurityElement.Escape(service.ServiceType))
|
||||||
.Append("</serviceType>");
|
.Append("</serviceType>");
|
||||||
builder.Append("<serviceId>")
|
builder.Append("<serviceId>")
|
||||||
.Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
|
.Append(SecurityElement.Escape(service.ServiceId))
|
||||||
.Append("</serviceId>");
|
.Append("</serviceId>");
|
||||||
builder.Append("<SCPDURL>")
|
builder.Append("<SCPDURL>")
|
||||||
.Append(BuildUrl(service.ScpdUrl))
|
.Append(BuildUrl(service.ScpdUrl))
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace Emby.Dlna.Service
|
|||||||
builder.Append("<action>");
|
builder.Append("<action>");
|
||||||
|
|
||||||
builder.Append("<name>")
|
builder.Append("<name>")
|
||||||
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
.Append(SecurityElement.Escape(item.Name))
|
||||||
.Append("</name>");
|
.Append("</name>");
|
||||||
|
|
||||||
builder.Append("<argumentList>");
|
builder.Append("<argumentList>");
|
||||||
@@ -48,13 +48,13 @@ namespace Emby.Dlna.Service
|
|||||||
builder.Append("<argument>");
|
builder.Append("<argument>");
|
||||||
|
|
||||||
builder.Append("<name>")
|
builder.Append("<name>")
|
||||||
.Append(SecurityElement.Escape(argument.Name ?? string.Empty))
|
.Append(SecurityElement.Escape(argument.Name))
|
||||||
.Append("</name>");
|
.Append("</name>");
|
||||||
builder.Append("<direction>")
|
builder.Append("<direction>")
|
||||||
.Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
|
.Append(SecurityElement.Escape(argument.Direction))
|
||||||
.Append("</direction>");
|
.Append("</direction>");
|
||||||
builder.Append("<relatedStateVariable>")
|
builder.Append("<relatedStateVariable>")
|
||||||
.Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
|
.Append(SecurityElement.Escape(argument.RelatedStateVariable))
|
||||||
.Append("</relatedStateVariable>");
|
.Append("</relatedStateVariable>");
|
||||||
|
|
||||||
builder.Append("</argument>");
|
builder.Append("</argument>");
|
||||||
@@ -81,10 +81,10 @@ namespace Emby.Dlna.Service
|
|||||||
.Append("\">");
|
.Append("\">");
|
||||||
|
|
||||||
builder.Append("<name>")
|
builder.Append("<name>")
|
||||||
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
.Append(SecurityElement.Escape(item.Name))
|
||||||
.Append("</name>");
|
.Append("</name>");
|
||||||
builder.Append("<dataType>")
|
builder.Append("<dataType>")
|
||||||
.Append(SecurityElement.Escape(item.DataType ?? string.Empty))
|
.Append(SecurityElement.Escape(item.DataType))
|
||||||
.Append("</dataType>");
|
.Append("</dataType>");
|
||||||
|
|
||||||
if (item.AllowedValues.Count > 0)
|
if (item.AllowedValues.Count > 0)
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||||
@@ -24,7 +28,7 @@
|
|||||||
<!-- Code analysers-->
|
<!-- Code analysers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<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" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
@@ -101,8 +102,7 @@ namespace Emby.Drawing
|
|||||||
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
|
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
|
||||||
{
|
{
|
||||||
var file = await ProcessImage(options).ConfigureAwait(false);
|
var file = await ProcessImage(options).ConfigureAwait(false);
|
||||||
|
using (var fileStream = AsyncFile.OpenRead(file.Path))
|
||||||
using (var fileStream = AsyncFile.OpenRead(file.Item1))
|
|
||||||
{
|
{
|
||||||
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ namespace Emby.Drawing
|
|||||||
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
=> _transparentImageTypes.Contains(Path.GetExtension(path));
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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;
|
ItemImageInfo originalImage = options.Image;
|
||||||
BaseItem item = options.Item;
|
BaseItem item = options.Item;
|
||||||
@@ -130,20 +130,22 @@ namespace Emby.Drawing
|
|||||||
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
|
originalImageSize = new ImageDimensions(originalImage.Width, originalImage.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mimeType = MimeTypes.GetMimeType(originalImagePath);
|
||||||
if (!_imageEncoder.SupportsImageEncoding)
|
if (!_imageEncoder.SupportsImageEncoding)
|
||||||
{
|
{
|
||||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
return (originalImagePath, mimeType, dateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
|
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 requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
|
||||||
|
|
||||||
bool autoOrient = false;
|
bool autoOrient = false;
|
||||||
@@ -243,7 +245,7 @@ namespace Emby.Drawing
|
|||||||
return ImageFormat.Jpg;
|
return ImageFormat.Jpg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? GetMimeType(ImageFormat format, string path)
|
private string GetMimeType(ImageFormat format, string path)
|
||||||
=> format switch
|
=> format switch
|
||||||
{
|
{
|
||||||
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
|
||||||
@@ -437,7 +439,7 @@ namespace Emby.Drawing
|
|||||||
.ToString("N", CultureInfo.InvariantCulture);
|
.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)
|
var inputFormat = Path.GetExtension(originalImagePath)
|
||||||
.TrimStart('.')
|
.TrimStart('.')
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
public class AudioBookListResolver
|
public class AudioBookListResolver
|
||||||
{
|
{
|
||||||
private readonly NamingOptions _options;
|
private readonly NamingOptions _options;
|
||||||
|
private readonly AudioBookResolver _audioBookResolver;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
|
/// Initializes a new instance of the <see cref="AudioBookListResolver"/> class.
|
||||||
@@ -22,6 +23,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
public AudioBookListResolver(NamingOptions options)
|
public AudioBookListResolver(NamingOptions options)
|
||||||
{
|
{
|
||||||
_options = options;
|
_options = options;
|
||||||
|
_audioBookResolver = new AudioBookResolver(_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -31,21 +33,18 @@ namespace Emby.Naming.AudioBook
|
|||||||
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
|
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
|
||||||
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
|
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||||
{
|
{
|
||||||
var audioBookResolver = new AudioBookResolver(_options);
|
|
||||||
|
|
||||||
// File with empty fullname will be sorted out here.
|
// File with empty fullname will be sorted out here.
|
||||||
var audiobookFileInfos = files
|
var audiobookFileInfos = files
|
||||||
.Select(i => audioBookResolver.Resolve(i.FullName))
|
.Select(i => _audioBookResolver.Resolve(i.FullName))
|
||||||
.OfType<AudioBookFileInfo>()
|
.OfType<AudioBookFileInfo>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var stackResult = new StackResolver(_options)
|
var stackResult = StackResolver.ResolveAudioBooks(audiobookFileInfos);
|
||||||
.ResolveAudioBooks(audiobookFileInfos);
|
|
||||||
|
|
||||||
foreach (var stack in stackResult)
|
foreach (var stack in stackResult)
|
||||||
{
|
{
|
||||||
var stackFiles = stack.Files
|
var stackFiles = stack.Files
|
||||||
.Select(i => audioBookResolver.Resolve(i))
|
.Select(i => _audioBookResolver.Resolve(i))
|
||||||
.OfType<AudioBookFileInfo>()
|
.OfType<AudioBookFileInfo>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.AudioBook
|
namespace Emby.Naming.AudioBook
|
||||||
{
|
{
|
||||||
@@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma warning disable CA1819
|
#pragma warning disable CA1819
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
@@ -124,11 +125,11 @@ namespace Emby.Naming.Common
|
|||||||
token: "DSR")
|
token: "DSR")
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoFileStackingExpressions = new[]
|
VideoFileStackingRules = new[]
|
||||||
{
|
{
|
||||||
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true),
|
||||||
"(?<title>.*?)(?<volume>[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$",
|
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false),
|
||||||
"(?<title>.*?)(?<volume>[ ._-]*[a-d])(?<ignore>.*?)(?<extension>\\.[^.]+)$"
|
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false)
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
@@ -403,6 +404,12 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
VideoExtraRules = new[]
|
VideoExtraRules = new[]
|
||||||
{
|
{
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.Trailer,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"trailers",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
new ExtraRule(
|
new ExtraRule(
|
||||||
ExtraType.Trailer,
|
ExtraType.Trailer,
|
||||||
ExtraRuleType.Filename,
|
ExtraRuleType.Filename,
|
||||||
@@ -463,12 +470,24 @@ namespace Emby.Naming.Common
|
|||||||
" sample",
|
" sample",
|
||||||
MediaType.Video),
|
MediaType.Video),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.ThemeVideo,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"backdrops",
|
||||||
|
MediaType.Video),
|
||||||
|
|
||||||
new ExtraRule(
|
new ExtraRule(
|
||||||
ExtraType.ThemeSong,
|
ExtraType.ThemeSong,
|
||||||
ExtraRuleType.Filename,
|
ExtraRuleType.Filename,
|
||||||
"theme",
|
"theme",
|
||||||
MediaType.Audio),
|
MediaType.Audio),
|
||||||
|
|
||||||
|
new ExtraRule(
|
||||||
|
ExtraType.ThemeSong,
|
||||||
|
ExtraRuleType.DirectoryName,
|
||||||
|
"theme-music",
|
||||||
|
MediaType.Audio),
|
||||||
|
|
||||||
new ExtraRule(
|
new ExtraRule(
|
||||||
ExtraType.Scene,
|
ExtraType.Scene,
|
||||||
ExtraRuleType.Suffix,
|
ExtraRuleType.Suffix,
|
||||||
@@ -563,7 +582,7 @@ namespace Emby.Naming.Common
|
|||||||
ExtraType.Unknown,
|
ExtraType.Unknown,
|
||||||
ExtraRuleType.DirectoryName,
|
ExtraRuleType.DirectoryName,
|
||||||
"extras",
|
"extras",
|
||||||
MediaType.Video),
|
MediaType.Video)
|
||||||
};
|
};
|
||||||
|
|
||||||
Format3DRules = new[]
|
Format3DRules = new[]
|
||||||
@@ -675,9 +694,29 @@ namespace Emby.Naming.Common
|
|||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.ToArray();
|
.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();
|
Compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the folder name to extra types mapping.
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, ExtraType> AllExtrasTypesFolderNames { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of audio file extensions.
|
/// Gets or sets list of audio file extensions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -759,9 +798,9 @@ namespace Emby.Naming.Common
|
|||||||
public Format3DRule[] Format3DRules { get; set; }
|
public Format3DRule[] Format3DRules { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of raw video file-stacking expressions strings.
|
/// Gets the file stacking rules.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] VideoFileStackingExpressions { get; set; }
|
public FileStackRule[] VideoFileStackingRules { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of raw clean DateTimes regular expressions strings.
|
/// Gets or sets list of raw clean DateTimes regular expressions strings.
|
||||||
@@ -783,11 +822,6 @@ namespace Emby.Naming.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ExtraRule[] VideoExtraRules { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Gets list of clean datetime regular expressions.
|
/// Gets list of clean datetime regular expressions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -813,7 +847,6 @@ namespace Emby.Naming.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Compile()
|
public void Compile()
|
||||||
{
|
{
|
||||||
VideoFileStackingRegexes = VideoFileStackingExpressions.Select(Compile).ToArray();
|
|
||||||
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
|
CleanDateTimeRegexes = CleanDateTimes.Select(Compile).ToArray();
|
||||||
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
|
CleanStringRegexes = CleanStrings.Select(Compile).ToArray();
|
||||||
EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();
|
EpisodeWithoutSeasonRegexes = EpisodeWithoutSeasonExpressions.Select(Compile).ToArray();
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
@@ -44,7 +48,7 @@
|
|||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<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" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Subtitles
|
namespace Emby.Naming.Subtitles
|
||||||
{
|
{
|
||||||
@@ -34,7 +35,7 @@ namespace Emby.Naming.Subtitles
|
|||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -42,11 +43,11 @@ namespace Emby.Naming.Subtitles
|
|||||||
var flags = GetFlags(path);
|
var flags = GetFlags(path);
|
||||||
var info = new SubtitleInfo(
|
var info = new SubtitleInfo(
|
||||||
path,
|
path,
|
||||||
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)),
|
_options.SubtitleDefaultFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)),
|
||||||
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase)));
|
_options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase)
|
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparison.OrdinalIgnoreCase)
|
||||||
&& !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase))
|
&& !_options.SubtitleForcedFlags.Contains(i, StringComparison.OrdinalIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Should have a name, language and file extension
|
// Should have a name, language and file extension
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.TV
|
namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
@@ -48,7 +48,7 @@ namespace Emby.Naming.TV
|
|||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// It's not supported. Check stub extensions
|
// It's not supported. Check stub extensions
|
||||||
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
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="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>
|
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
|
||||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||||
private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
|
private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
|
||||||
string path,
|
string path,
|
||||||
bool supportSpecialAliases,
|
bool supportSpecialAliases,
|
||||||
bool supportNumericSeasonFolders)
|
bool supportNumericSeasonFolders)
|
||||||
@@ -99,7 +99,7 @@ namespace Emby.Naming.TV
|
|||||||
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
|
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
|
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
|
||||||
if (result.seasonNumber.HasValue)
|
if (result.SeasonNumber.HasValue)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ namespace Emby.Naming.TV
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
/// <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 numericStart = -1;
|
||||||
var length = 0;
|
var length = 0;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Globalization;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
|
||||||
namespace Emby.Naming.TV
|
namespace Emby.Naming.TV
|
||||||
@@ -51,7 +50,7 @@ namespace Emby.Naming.TV
|
|||||||
if (expression.IsNamed)
|
if (expression.IsNamed)
|
||||||
{
|
{
|
||||||
result.SeriesName = match.Groups["seriesname"].Value;
|
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.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|||||||
@@ -9,45 +9,27 @@ namespace Emby.Naming.Video
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolve if file is extra for video.
|
/// Resolve if file is extra for video.
|
||||||
/// </summary>
|
/// </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 static readonly char[] _digits = { '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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to resolve if file is extra.
|
/// Attempts to resolve if file is extra.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Path to file.</param>
|
/// <param name="path">Path to file.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
/// <returns>Returns <see cref="ExtraResult"/> object.</returns>
|
||||||
public ExtraResult GetExtraInfo(string path)
|
public static ExtraResult GetExtraInfo(string path, NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
var result = new ExtraResult();
|
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];
|
var rule = namingOptions.VideoExtraRules[i];
|
||||||
if (rule.MediaType == MediaType.Audio)
|
if ((rule.MediaType == MediaType.Audio && !AudioFileParser.IsAudioFile(path, namingOptions))
|
||||||
|
|| (rule.MediaType == MediaType.Video && !VideoResolver.IsVideoFile(path, namingOptions)))
|
||||||
{
|
{
|
||||||
if (!AudioFileParser.IsAudioFile(path, _options))
|
continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (rule.MediaType == MediaType.Video)
|
|
||||||
{
|
|
||||||
if (!VideoResolver.IsVideoFile(path, _options))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathSpan = path.AsSpan();
|
var pathSpan = path.AsSpan();
|
||||||
@@ -76,9 +58,9 @@ namespace Emby.Naming.Video
|
|||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
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.ExtraType = rule.ExtraType;
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
@@ -12,25 +12,30 @@ namespace Emby.Naming.Video
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="FileStack"/> class.
|
/// Initializes a new instance of the <see cref="FileStack"/> class.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets or sets name of file stack.
|
/// Gets the name of file stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets list of paths in stack.
|
/// Gets the list of paths in stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> Files { get; set; }
|
public IReadOnlyList<string> Files { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether stack is directory stack.
|
/// Gets a value indicating whether stack is directory stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsDirectoryStack { get; set; }
|
public bool IsDirectoryStack { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper function to determine if path is in the stack.
|
/// 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>
|
/// <returns>True if file is in the stack.</returns>
|
||||||
public bool ContainsFile(string file, bool isDirectory)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
public static class Format3DParser
|
||||||
{
|
{
|
||||||
// Static default result to save on allocation costs.
|
// 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>
|
/// <summary>
|
||||||
/// Parse 3D format related flags.
|
/// Parse 3D format related flags.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Emby.Naming.AudioBook;
|
using Emby.Naming.AudioBook;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
@@ -12,37 +11,28 @@ namespace Emby.Naming.Video
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolve <see cref="FileStack"/> from list of paths.
|
/// Resolve <see cref="FileStack"/> from list of paths.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Resolves only directories from paths.
|
/// Resolves only directories from paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of paths.</param>
|
/// <param name="files">List of paths.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
/// <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>
|
/// <summary>
|
||||||
/// Resolves only files from paths.
|
/// Resolves only files from paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of paths.</param>
|
/// <param name="files">List of paths.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <returns>Enumerable <see cref="FileStack"/> of files.</returns>
|
/// <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>
|
/// <summary>
|
||||||
@@ -50,7 +40,7 @@ namespace Emby.Naming.Video
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of paths.</param>
|
/// <param name="files">List of paths.</param>
|
||||||
/// <returns>Enumerable <see cref="FileStack"/> of directories.</returns>
|
/// <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));
|
var groupedDirectoryFiles = files.GroupBy(file => Path.GetDirectoryName(file.Path));
|
||||||
|
|
||||||
@@ -60,19 +50,13 @@ namespace Emby.Naming.Video
|
|||||||
{
|
{
|
||||||
foreach (var file in directory)
|
foreach (var file in directory)
|
||||||
{
|
{
|
||||||
var stack = new FileStack { Name = Path.GetFileNameWithoutExtension(file.Path), IsDirectoryStack = false };
|
var stack = new FileStack(Path.GetFileNameWithoutExtension(file.Path), false, new[] { file.Path });
|
||||||
stack.Files.Add(file.Path);
|
|
||||||
yield return stack;
|
yield return stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
|
var stack = new FileStack(Path.GetFileName(directory.Key), false, directory.Select(f => f.Path).ToArray());
|
||||||
foreach (var file in directory)
|
|
||||||
{
|
|
||||||
stack.Files.Add(file.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return stack;
|
yield return stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,158 +66,91 @@ namespace Emby.Naming.Video
|
|||||||
/// Resolves videos from paths.
|
/// Resolves videos from paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">List of paths.</param>
|
/// <param name="files">List of paths.</param>
|
||||||
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
/// <returns>Enumerable <see cref="FileStack"/> of videos.</returns>
|
/// <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
|
var potentialFiles = files
|
||||||
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, _options) || VideoResolver.IsStubFile(i.FullName, _options))
|
.Where(i => i.IsDirectory || VideoResolver.IsVideoFile(i.FullName, namingOptions) || VideoResolver.IsStubFile(i.FullName, namingOptions))
|
||||||
.OrderBy(i => i.FullName)
|
.OrderBy(i => i.FullName);
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var expressions = _options.VideoFileStackingRegexes;
|
var potentialStacks = new Dictionary<string, StackMetadata>();
|
||||||
|
foreach (var file in potentialFiles)
|
||||||
for (var i = 0; i < list.Count; i++)
|
|
||||||
{
|
{
|
||||||
var offset = 0;
|
var name = file.Name;
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
var file1 = list[i];
|
|
||||||
|
|
||||||
var expressionIndex = 0;
|
|
||||||
while (expressionIndex < expressions.Length)
|
|
||||||
{
|
{
|
||||||
var exp = expressions[expressionIndex];
|
name = Path.GetFileName(file.FullName);
|
||||||
var stack = new FileStack();
|
}
|
||||||
|
|
||||||
// (Title)(Volume)(Ignore)(Extension)
|
for (var i = 0; i < namingOptions.VideoFileStackingRules.Length; i++)
|
||||||
var match1 = FindMatch(file1, exp, offset);
|
{
|
||||||
|
var rule = namingOptions.VideoFileStackingRules[i];
|
||||||
if (match1.Success)
|
if (!rule.Match(name, out var stackParsingResult))
|
||||||
{
|
{
|
||||||
var title1 = match1.Groups["title"].Value;
|
continue;
|
||||||
var volume1 = match1.Groups["volume"].Value;
|
}
|
||||||
var ignore1 = match1.Groups["ignore"].Value;
|
|
||||||
var extension1 = match1.Groups["extension"].Value;
|
|
||||||
|
|
||||||
var j = i + 1;
|
var stackName = stackParsingResult.Value.StackName;
|
||||||
while (j < list.Count)
|
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];
|
continue;
|
||||||
|
|
||||||
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++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
stackResult.Parts.Add(partNumber, file);
|
||||||
{
|
break;
|
||||||
yield return stack;
|
|
||||||
i += stack.Files.Count - 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetRegexInput(FileSystemMetadata file)
|
foreach (var (fileName, stack) in potentialStacks)
|
||||||
{
|
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
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;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
@@ -28,7 +28,7 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
@@ -17,7 +18,6 @@ namespace Emby.Naming.Video
|
|||||||
Name = name;
|
Name = name;
|
||||||
|
|
||||||
Files = Array.Empty<VideoFileInfo>();
|
Files = Array.Empty<VideoFileInfo>();
|
||||||
Extras = Array.Empty<VideoFileInfo>();
|
|
||||||
AlternateVersions = Array.Empty<VideoFileInfo>();
|
AlternateVersions = Array.Empty<VideoFileInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,16 +39,15 @@ namespace Emby.Naming.Video
|
|||||||
/// <value>The files.</value>
|
/// <value>The files.</value>
|
||||||
public IReadOnlyList<VideoFileInfo> Files { get; set; }
|
public IReadOnlyList<VideoFileInfo> Files { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the extras.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The extras.</value>
|
|
||||||
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the alternate versions.
|
/// Gets or sets the alternate versions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The alternate versions.</value>
|
/// <value>The alternate versions.</value>
|
||||||
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
|
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.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
@@ -17,29 +16,41 @@ namespace Emby.Naming.Video
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves alternative versions and extras from list of video files.
|
/// Resolves alternative versions and extras from list of video files.
|
||||||
/// </summary>
|
/// </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="namingOptions">The naming options.</param>
|
||||||
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</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>
|
/// <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
|
// Filter out all extras, otherwise they could cause stacks to not be resolved
|
||||||
// See the unit test TestStackedWithTrailer
|
// See the unit test TestStackedWithTrailer
|
||||||
var nonExtras = videoInfos
|
var nonExtras = videoInfos
|
||||||
.Where(i => i.ExtraType == null)
|
.Where(i => i.ExtraType == null)
|
||||||
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
|
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
|
||||||
|
|
||||||
var stackResult = new StackResolver(namingOptions)
|
var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList();
|
||||||
.Resolve(nonExtras).ToList();
|
|
||||||
|
|
||||||
var remainingFiles = videoInfos
|
var remainingFiles = new List<VideoFileInfo>();
|
||||||
.Where(i => !stackResult.Any(s => i.Path != null && s.ContainsFile(i.Path, i.IsDirectory)))
|
var standaloneMedia = new List<VideoFileInfo>();
|
||||||
.ToList();
|
|
||||||
|
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>();
|
var list = new List<VideoInfo>();
|
||||||
|
|
||||||
@@ -47,38 +58,20 @@ namespace Emby.Naming.Video
|
|||||||
{
|
{
|
||||||
var info = new VideoInfo(stack.Name)
|
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>()
|
.OfType<VideoFileInfo>()
|
||||||
.ToList()
|
.ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
info.Year = info.Files[0].Year;
|
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);
|
list.Add(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
var standaloneMedia = remainingFiles
|
|
||||||
.Where(i => i.ExtraType == null)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var media in standaloneMedia)
|
foreach (var media in standaloneMedia)
|
||||||
{
|
{
|
||||||
var info = new VideoInfo(media.Name) { Files = new[] { media } };
|
var info = new VideoInfo(media.Name) { Files = new[] { media } };
|
||||||
|
|
||||||
info.Year = info.Files[0].Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
|
||||||
remainingFiles.Remove(media);
|
|
||||||
var extras = ExtractExtras(remainingFiles, media.FileNameWithoutExtension, namingOptions.VideoFlagDelimiters);
|
|
||||||
|
|
||||||
info.Extras = extras;
|
|
||||||
|
|
||||||
list.Add(info);
|
list.Add(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,58 +80,12 @@ namespace Emby.Naming.Video
|
|||||||
list = GetVideosGroupedByVersion(list, namingOptions);
|
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
|
// Whatever files are left, just add them
|
||||||
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
|
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
|
||||||
{
|
{
|
||||||
Files = new[] { i },
|
Files = new[] { i },
|
||||||
Year = i.Year
|
Year = i.Year,
|
||||||
|
ExtraType = i.ExtraType
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
@@ -162,6 +109,11 @@ namespace Emby.Naming.Video
|
|||||||
for (var i = 0; i < videos.Count; i++)
|
for (var i = 0; i < videos.Count; i++)
|
||||||
{
|
{
|
||||||
var video = videos[i];
|
var video = videos[i];
|
||||||
|
if (video.ExtraType != null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
|
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
|
||||||
{
|
{
|
||||||
return videos;
|
return videos;
|
||||||
@@ -178,17 +130,14 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
var alternateVersionsLen = videos.Count - 1;
|
var alternateVersionsLen = videos.Count - 1;
|
||||||
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
|
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
|
||||||
var extras = new List<VideoFileInfo>(list[0].Extras);
|
|
||||||
for (int i = 0; i < alternateVersionsLen; i++)
|
for (int i = 0; i < alternateVersionsLen; i++)
|
||||||
{
|
{
|
||||||
var video = videos[i + 1];
|
var video = videos[i + 1];
|
||||||
alternateVersions[i] = video.Files[0];
|
alternateVersions[i] = video.Files[0];
|
||||||
extras.AddRange(video.Extras);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list[0].AlternateVersions = alternateVersions;
|
list[0].AlternateVersions = alternateVersions;
|
||||||
list[0].Name = folderName.ToString();
|
list[0].Name = folderName.ToString();
|
||||||
list[0].Extras = extras;
|
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@@ -230,7 +179,7 @@ namespace Emby.Naming.Video
|
|||||||
var tmpTestFilename = testFilename.ToString();
|
var tmpTestFilename = testFilename.ToString();
|
||||||
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
|
if (CleanStringParser.TryClean(tmpTestFilename, namingOptions.CleanStringRegexes, out var cleanName))
|
||||||
{
|
{
|
||||||
tmpTestFilename = cleanName.Trim().ToString();
|
tmpTestFilename = cleanName.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The CleanStringParser should have removed common keywords etc.
|
// The CleanStringParser should have removed common keywords etc.
|
||||||
@@ -238,67 +187,5 @@ namespace Emby.Naming.Video
|
|||||||
|| testFilename[0] == '-'
|
|| testFilename[0] == '-'
|
||||||
|| Regex.IsMatch(tmpTestFilename, @"^\[([^]]*)\]", RegexOptions.Compiled);
|
|| 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>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="namingOptions">The naming options.</param>
|
/// <param name="namingOptions">The naming options.</param>
|
||||||
|
/// <param name="parseName">Whether to parse the name or use the filename.</param>
|
||||||
/// <returns>VideoFileInfo.</returns>
|
/// <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>
|
/// <summary>
|
||||||
@@ -74,7 +75,7 @@ namespace Emby.Naming.Video
|
|||||||
|
|
||||||
var format3DResult = Format3DParser.Parse(path, namingOptions);
|
var format3DResult = Format3DParser.Parse(path, namingOptions);
|
||||||
|
|
||||||
var extraResult = new ExtraResolver(namingOptions).GetExtraInfo(path);
|
var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions);
|
||||||
|
|
||||||
var name = Path.GetFileNameWithoutExtension(path);
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
|
|
||||||
|
|||||||
@@ -24,63 +24,63 @@ namespace Emby.Notifications
|
|||||||
{
|
{
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.ApplicationUpdateInstalled.ToString()
|
Type = nameof(NotificationType.ApplicationUpdateInstalled)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.InstallationFailed.ToString()
|
Type = nameof(NotificationType.InstallationFailed)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.PluginInstalled.ToString()
|
Type = nameof(NotificationType.PluginInstalled)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.PluginError.ToString()
|
Type = nameof(NotificationType.PluginError)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.PluginUninstalled.ToString()
|
Type = nameof(NotificationType.PluginUninstalled)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.PluginUpdateInstalled.ToString()
|
Type = nameof(NotificationType.PluginUpdateInstalled)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.ServerRestartRequired.ToString()
|
Type = nameof(NotificationType.ServerRestartRequired)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.TaskFailed.ToString()
|
Type = nameof(NotificationType.TaskFailed)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.NewLibraryContent.ToString()
|
Type = nameof(NotificationType.NewLibraryContent)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.AudioPlayback.ToString()
|
Type = nameof(NotificationType.AudioPlayback)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.VideoPlayback.ToString()
|
Type = nameof(NotificationType.VideoPlayback)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.AudioPlaybackStopped.ToString()
|
Type = nameof(NotificationType.AudioPlaybackStopped)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.VideoPlaybackStopped.ToString()
|
Type = nameof(NotificationType.VideoPlaybackStopped)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.UserLockedOut.ToString()
|
Type = nameof(NotificationType.UserLockedOut)
|
||||||
},
|
},
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.ApplicationUpdateAvailable.ToString()
|
Type = nameof(NotificationType.ApplicationUpdateAvailable)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ namespace Emby.Notifications
|
|||||||
|
|
||||||
private void Update(NotificationTypeInfo note)
|
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;
|
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<!-- Code analyzers-->
|
<!-- Code analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<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" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@@ -104,7 +105,7 @@ namespace Emby.Notifications
|
|||||||
|
|
||||||
var type = entry.Type;
|
var type = entry.Type;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparer.OrdinalIgnoreCase))
|
if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<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="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@@ -60,7 +61,7 @@ namespace Emby.Photos
|
|||||||
item.SetImagePath(ImageType.Primary, item.Path);
|
item.SetImagePath(ImageType.Primary, item.Path);
|
||||||
|
|
||||||
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
|
// 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
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
{
|
{
|
||||||
return _configurations.GetOrAdd(
|
return _configurations.GetOrAdd(
|
||||||
key,
|
key,
|
||||||
(k, configurationManager) =>
|
static (k, configurationManager) =>
|
||||||
{
|
{
|
||||||
var file = configurationManager.GetConfigurationFile(k);
|
var file = configurationManager.GetConfigurationFile(k);
|
||||||
|
|
||||||
@@ -371,7 +371,7 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
NewConfiguration = configuration
|
NewConfiguration = configuration
|
||||||
});
|
});
|
||||||
|
|
||||||
_configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
|
_configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
|
||||||
|
|
||||||
var path = GetConfigurationFile(key);
|
var path = GetConfigurationFile(key);
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.AppBase
|
namespace Emby.Server.Implementations.AppBase
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The disposable parts.
|
/// The disposable parts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new ();
|
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new();
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystemManager;
|
private readonly IFileSystem _fileSystemManager;
|
||||||
private readonly IConfiguration _startupConfig;
|
private readonly IConfiguration _startupConfig;
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using SharpCompress.Archives.SevenZip;
|
|
||||||
using SharpCompress.Archives.Tar;
|
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using SharpCompress.Readers;
|
using SharpCompress.Readers;
|
||||||
using SharpCompress.Readers.GZip;
|
using SharpCompress.Readers.GZip;
|
||||||
using SharpCompress.Readers.Zip;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Archiving
|
namespace Emby.Server.Implementations.Archiving
|
||||||
{
|
{
|
||||||
@@ -14,55 +11,6 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ZipClient : IZipClient
|
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 />
|
/// <inheritdoc />
|
||||||
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
@@ -94,69 +42,5 @@ namespace Emby.Server.Implementations.Archiving
|
|||||||
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
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 System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using Jellyfin.Extensions.Json;
|
using Jellyfin.Extensions.Json;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
@@ -129,16 +130,14 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
if (internalChannel == null)
|
if (internalChannel == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException();
|
throw new ArgumentException(nameof(item.ChannelId));
|
||||||
}
|
}
|
||||||
|
|
||||||
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
||||||
|
|
||||||
var supportsDelete = channel as ISupportsDelete;
|
if (channel is not ISupportsDelete supportsDelete)
|
||||||
|
|
||||||
if (supportsDelete == null)
|
|
||||||
{
|
{
|
||||||
throw new ArgumentException();
|
throw new ArgumentException(nameof(channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
|
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
|
||||||
@@ -179,7 +178,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
|
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
|
||||||
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
&& hasAttributes.Attributes.Contains("Recordings", StringComparison.OrdinalIgnoreCase)) == val;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -541,7 +540,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
return _libraryManager.GetItemIds(
|
return _libraryManager.GetItemIds(
|
||||||
new InternalItemsQuery
|
new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Channel) },
|
IncludeItemTypes = new[] { BaseItemKind.Channel },
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
||||||
}).Select(i => GetChannelFeatures(i)).ToArray();
|
}).Select(i => GetChannelFeatures(i)).ToArray();
|
||||||
}
|
}
|
||||||
@@ -1135,7 +1134,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
if (!info.IsLiveStream)
|
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();
|
item.Tags = item.Tags.Except(new[] { "livestream" }, StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
||||||
@@ -1144,7 +1143,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!item.Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase))
|
if (!item.Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
|
item.Tags = item.Tags.Concat(new[] { "livestream" }).ToArray();
|
||||||
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
_logger.LogDebug("Forcing update due to Tags {0}", item.Name);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@@ -51,7 +52,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
|
|
||||||
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
|
var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Channel) },
|
IncludeItemTypes = new[] { BaseItemKind.Channel },
|
||||||
ExcludeItemIds = installedChannelIds.ToArray()
|
ExcludeItemIds = installedChannelIds.ToArray()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
if (parentFolder == null)
|
if (parentFolder == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException();
|
throw new ArgumentException(nameof(parentFolder));
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = Path.Combine(parentFolder.Path, folderName);
|
var path = Path.Combine(parentFolder.Path, folderName);
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SQLitePCL.pretty;
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
@@ -160,21 +160,22 @@ namespace Emby.Server.Implementations.Data
|
|||||||
protected bool TableExists(ManagedConnection connection, string name)
|
protected bool TableExists(ManagedConnection connection, string name)
|
||||||
{
|
{
|
||||||
return connection.RunInTransaction(
|
return connection.RunInTransaction(
|
||||||
db =>
|
db =>
|
||||||
{
|
|
||||||
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
|
|
||||||
{
|
{
|
||||||
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;
|
return false;
|
||||||
}, ReadTransactionMode);
|
},
|
||||||
|
ReadTransactionMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<string> GetColumnNames(IDatabaseConnection connection, string table)
|
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)
|
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;
|
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;
|
var users = userDatasTableExists ? null : userManager.Users;
|
||||||
|
|
||||||
connection.RunInTransaction(
|
connection.RunInTransaction(
|
||||||
db =>
|
db =>
|
||||||
{
|
|
||||||
db.ExecuteAll(string.Join(';', new[]
|
|
||||||
{
|
{
|
||||||
"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(string.Join(';', new[]
|
||||||
|
|
||||||
"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);
|
"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())
|
using (var connection = GetConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(
|
connection.RunInTransaction(
|
||||||
db =>
|
db =>
|
||||||
{
|
{
|
||||||
SaveUserData(db, internalUserId, key, userData);
|
SaveUserData(db, internalUserId, key, userData);
|
||||||
}, TransactionMode);
|
},
|
||||||
|
TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,13 +254,14 @@ namespace Emby.Server.Implementations.Data
|
|||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection())
|
||||||
{
|
{
|
||||||
connection.RunInTransaction(
|
connection.RunInTransaction(
|
||||||
db =>
|
db =>
|
||||||
{
|
|
||||||
foreach (var userItemData in userDataList)
|
|
||||||
{
|
{
|
||||||
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
|
foreach (var userItemData in userDataList)
|
||||||
}
|
{
|
||||||
}, TransactionMode);
|
SaveUserData(db, internalUserId, userItemData.Key, userItemData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TransactionMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Devices
|
|||||||
{
|
{
|
||||||
var value = File.ReadAllText(CachePath, Encoding.UTF8);
|
var value = File.ReadAllText(CachePath, Encoding.UTF8);
|
||||||
|
|
||||||
if (Guid.TryParse(value, out var guid))
|
if (Guid.TryParse(value, out _))
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Drawing;
|
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)
|
new DtoOptions(false)
|
||||||
{
|
{
|
||||||
EnableImages = false
|
EnableImages = false
|
||||||
}),
|
}));
|
||||||
user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
@@ -294,7 +293,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
path = path.TrimStart('.');
|
path = path.TrimStart('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase))
|
if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
fileExtensionContainer = path;
|
fileExtensionContainer = path;
|
||||||
}
|
}
|
||||||
@@ -311,13 +310,13 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
|
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
|
||||||
{
|
{
|
||||||
SetItemByNameInfo(item, dto, taggedItems, user);
|
SetItemByNameInfo(item, dto, taggedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dto;
|
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)
|
if (item is MusicArtist)
|
||||||
{
|
{
|
||||||
@@ -470,7 +469,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
{
|
{
|
||||||
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(MusicAlbum) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
|
||||||
Name = item.Album,
|
Name = item.Album,
|
||||||
Limit = 1
|
Limit = 1
|
||||||
});
|
});
|
||||||
@@ -1404,44 +1403,27 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDimensions size;
|
|
||||||
|
|
||||||
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
|
||||||
|
|
||||||
if (defaultAspectRatio > 0)
|
|
||||||
{
|
|
||||||
return defaultAspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!imageInfo.IsLocalFile)
|
if (!imageInfo.IsLocalFile)
|
||||||
{
|
{
|
||||||
return null;
|
return item.GetDefaultPrimaryImageAspectRatio();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
var size = _imageProcessor.GetImageDimensions(item, imageInfo);
|
||||||
|
var width = size.Width;
|
||||||
if (size.Width <= 0 || size.Height <= 0)
|
var height = size.Height;
|
||||||
|
if (width > 0 && height > 0)
|
||||||
{
|
{
|
||||||
return null;
|
return (double)width / height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
|
_logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var width = size.Width;
|
return item.GetDefaultPrimaryImageAspectRatio();
|
||||||
var height = size.Height;
|
|
||||||
|
|
||||||
if (width <= 0 || height <= 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (double)width / height;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" 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.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="Mono.Nat" Version="3.0.2" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.30.1" />
|
<PackageReference Include="sharpcompress" Version="0.30.1" />
|
||||||
@@ -49,10 +49,14 @@
|
|||||||
<NoWarn>AD0001</NoWarn>
|
<NoWarn>AD0001</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<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" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using Jellyfin.Networking.Configuration;
|
|||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Mono.Nat;
|
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>();
|
var dict = new Dictionary<string, string>();
|
||||||
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
|
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)));
|
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)
|
private static bool EnableRefreshMessage(BaseItem item)
|
||||||
@@ -423,7 +423,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
|
|
||||||
foreach (var folder in allUserRootChildren)
|
foreach (var folder in allUserRootChildren)
|
||||||
{
|
{
|
||||||
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
|
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
@@ -465,6 +464,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
var changes = _changedItems.ToList();
|
var changes = _changedItems.ToList();
|
||||||
_changedItems.Clear();
|
_changedItems.Clear();
|
||||||
|
|
||||||
var task = SendNotifications(changes, CancellationToken.None);
|
SendNotifications(changes, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (_updateTimer != null)
|
if (_updateTimer != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Authentication;
|
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
var session = await GetSession(requestContext).ConfigureAwait(false);
|
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)
|
public Task<User?> GetUser(object requestContext)
|
||||||
|
|||||||
@@ -42,17 +42,14 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="socket">The socket.</param>
|
/// <param name="socket">The socket.</param>
|
||||||
/// <param name="remoteEndPoint">The remote end point.</param>
|
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||||
/// <param name="query">The query.</param>
|
|
||||||
public WebSocketConnection(
|
public WebSocketConnection(
|
||||||
ILogger<WebSocketConnection> logger,
|
ILogger<WebSocketConnection> logger,
|
||||||
WebSocket socket,
|
WebSocket socket,
|
||||||
IPAddress? remoteEndPoint,
|
IPAddress? remoteEndPoint)
|
||||||
IQueryCollection query)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
RemoteEndPoint = remoteEndPoint;
|
RemoteEndPoint = remoteEndPoint;
|
||||||
QueryString = query;
|
|
||||||
|
|
||||||
_jsonOptions = JsonDefaults.Options;
|
_jsonOptions = JsonDefaults.Options;
|
||||||
LastActivityDate = DateTime.Now;
|
LastActivityDate = DateTime.Now;
|
||||||
@@ -81,12 +78,6 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public DateTime LastKeepAliveDate { get; set; }
|
public DateTime LastKeepAliveDate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the query string.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The query string.</value>
|
|
||||||
public IQueryCollection QueryString { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the state.
|
/// Gets the state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -180,7 +171,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebSocketMessage<object>? stub;
|
WebSocketMessage<object>? stub;
|
||||||
long bytesConsumed = 0;
|
long bytesConsumed;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
|
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
|
||||||
@@ -236,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
{
|
{
|
||||||
MessageId = Guid.NewGuid(),
|
MessageId = Guid.NewGuid(),
|
||||||
MessageType = SessionMessageType.KeepAlive
|
MessageType = SessionMessageType.KeepAlive
|
||||||
}, CancellationToken.None);
|
},
|
||||||
|
CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -50,8 +51,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
using var connection = new WebSocketConnection(
|
using var connection = new WebSocketConnection(
|
||||||
_loggerFactory.CreateLogger<WebSocketConnection>(),
|
_loggerFactory.CreateLogger<WebSocketConnection>(),
|
||||||
webSocket,
|
webSocket,
|
||||||
context.Connection.RemoteIpAddress,
|
context.GetNormalizedRemoteIp())
|
||||||
context.Request.Query)
|
|
||||||
{
|
{
|
||||||
OnReceive = ProcessWebSocketMessageReceived
|
OnReceive = ProcessWebSocketMessageReceived
|
||||||
};
|
};
|
||||||
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
var tasks = new Task[_webSocketListeners.Length];
|
var tasks = new Task[_webSocketListeners.Length];
|
||||||
for (var i = 0; i < _webSocketListeners.Length; ++i)
|
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);
|
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -217,8 +217,13 @@ namespace Emby.Server.Implementations.IO
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposed = true;
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DisposeTimer();
|
DisposeTimer();
|
||||||
|
_disposed = true;
|
||||||
GC.SuppressFinalize(this);
|
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
|
// 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);
|
await Task.Delay(45000).ConfigureAwait(false);
|
||||||
|
|
||||||
_tempIgnoredPaths.TryRemove(path, out var val);
|
_tempIgnoredPaths.TryRemove(path, out _);
|
||||||
|
|
||||||
if (refreshPath)
|
if (refreshPath)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -544,16 +544,6 @@ namespace Emby.Server.Implementations.IO
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual bool AreEqual(string path1, string path2)
|
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(
|
return string.Equals(
|
||||||
NormalizePath(path1),
|
NormalizePath(path1),
|
||||||
NormalizePath(path2),
|
NormalizePath(path2),
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
int read;
|
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();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
|
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (onStarted != null)
|
if (onStarted != null)
|
||||||
{
|
{
|
||||||
@@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO
|
|||||||
if (emptyReadLimit <= 0)
|
if (emptyReadLimit <= 0)
|
||||||
{
|
{
|
||||||
int read;
|
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();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
|
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
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)
|
if (bytesRead == 0)
|
||||||
{
|
{
|
||||||
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
eofCount = 0;
|
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;
|
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);
|
var bytesToWrite = Math.Min(bytesRead, copyLength);
|
||||||
|
|
||||||
if (bytesToWrite > 0)
|
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;
|
copyLength -= bytesToWrite;
|
||||||
@@ -137,9 +137,9 @@ namespace Emby.Server.Implementations.IO
|
|||||||
int bytesRead;
|
int bytesRead;
|
||||||
int totalBytesRead = 0;
|
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;
|
totalBytesRead += bytesRead;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,35 +28,35 @@ namespace Emby.Server.Implementations.Images
|
|||||||
var view = (CollectionFolder)item;
|
var view = (CollectionFolder)item;
|
||||||
var viewType = view.CollectionType;
|
var viewType = view.CollectionType;
|
||||||
|
|
||||||
string[] includeItemTypes;
|
BaseItemKind[] includeItemTypes;
|
||||||
|
|
||||||
if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
|
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))
|
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))
|
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))
|
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))
|
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))
|
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
|
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);
|
var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);
|
||||||
@@ -68,9 +68,9 @@ namespace Emby.Server.Implementations.Images
|
|||||||
DtoOptions = new DtoOptions(false),
|
DtoOptions = new DtoOptions(false),
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
Limit = 8,
|
Limit = 8,
|
||||||
OrderBy = new ValueTuple<string, SortOrder>[]
|
OrderBy = new[]
|
||||||
{
|
{
|
||||||
new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
|
(ItemSortBy.Random, SortOrder.Ascending)
|
||||||
},
|
},
|
||||||
IncludeItemTypes = includeItemTypes
|
IncludeItemTypes = includeItemTypes
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
@@ -34,14 +36,14 @@ namespace Emby.Server.Implementations.Images
|
|||||||
var view = (UserView)item;
|
var view = (UserView)item;
|
||||||
|
|
||||||
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
|
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
|
var result = view.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
|
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
|
||||||
CollapseBoxSetItems = false,
|
CollapseBoxSetItems = false,
|
||||||
Recursive = recursive,
|
Recursive = recursive,
|
||||||
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" },
|
ExcludeItemTypes = new[] { BaseItemKind.UserView, BaseItemKind.CollectionFolder, BaseItemKind.Person },
|
||||||
DtoOptions = new DtoOptions(false)
|
DtoOptions = new DtoOptions(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ using MediaBrowser.Common.Configuration;
|
|||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@@ -43,7 +41,7 @@ namespace Emby.Server.Implementations.Images
|
|||||||
return _libraryManager.GetItemList(new InternalItemsQuery
|
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
Genres = new[] { item.Name },
|
Genres = new[] { item.Name },
|
||||||
IncludeItemTypes = new[] { nameof(Series), nameof(Movie) },
|
IncludeItemTypes = new[] { BaseItemKind.Series, BaseItemKind.Movie },
|
||||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||||
Limit = 4,
|
Limit = 4,
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ namespace Emby.Server.Implementations.Images
|
|||||||
Genres = new[] { item.Name },
|
Genres = new[] { item.Name },
|
||||||
IncludeItemTypes = new[]
|
IncludeItemTypes = new[]
|
||||||
{
|
{
|
||||||
nameof(MusicAlbum),
|
BaseItemKind.MusicAlbum,
|
||||||
nameof(MusicVideo),
|
BaseItemKind.MusicVideo,
|
||||||
nameof(Audio)
|
BaseItemKind.Audio
|
||||||
},
|
},
|
||||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) },
|
||||||
Limit = 4,
|
Limit = 4,
|
||||||
|
|||||||
@@ -54,20 +54,10 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
// Ignore trailer folders but allow it at the collection level
|
// Ignore extras folders but allow it at the collection level
|
||||||
if (string.Equals(filename, BaseItem.TrailersFolderName, StringComparison.OrdinalIgnoreCase)
|
if (_namingOptions.AllExtrasTypesFolderNames.ContainsKey(filename)
|
||||||
&& !(parent is AggregateFolder)
|
&& parent is not AggregateFolder
|
||||||
&& !(parent is UserRootFolder))
|
&& parent is not UserRootFolder)
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,12 @@ using System.Net;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Naming.Audio;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.TV;
|
using Emby.Naming.TV;
|
||||||
using Emby.Naming.Video;
|
|
||||||
using Emby.Server.Implementations.Library.Resolvers;
|
using Emby.Server.Implementations.Library.Resolvers;
|
||||||
using Emby.Server.Implementations.Library.Validators;
|
using Emby.Server.Implementations.Library.Validators;
|
||||||
using Emby.Server.Implementations.Playlists;
|
using Emby.Server.Implementations.Playlists;
|
||||||
using Emby.Server.Implementations.ScheduledTasks;
|
using Emby.Server.Implementations.ScheduledTasks.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
@@ -80,6 +78,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
private readonly IItemRepository _itemRepository;
|
private readonly IItemRepository _itemRepository;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
private readonly NamingOptions _namingOptions;
|
private readonly NamingOptions _namingOptions;
|
||||||
|
private readonly ExtraResolver _extraResolver;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder sync lock.
|
/// The _root folder sync lock.
|
||||||
@@ -148,6 +147,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_memoryCache = memoryCache;
|
_memoryCache = memoryCache;
|
||||||
_namingOptions = namingOptions;
|
_namingOptions = namingOptions;
|
||||||
|
|
||||||
|
_extraResolver = new ExtraResolver(namingOptions);
|
||||||
|
|
||||||
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||||
|
|
||||||
RecordConfigurationValues(configurationManager.Configuration);
|
RecordConfigurationValues(configurationManager.Configuration);
|
||||||
@@ -533,8 +534,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return key.GetMD5();
|
return key.GetMD5();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
|
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, IDirectoryService directoryService = null)
|
||||||
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
|
=> ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent);
|
||||||
|
|
||||||
private BaseItem ResolvePath(
|
private BaseItem ResolvePath(
|
||||||
FileSystemMetadata fileInfo,
|
FileSystemMetadata fileInfo,
|
||||||
@@ -654,7 +655,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return !args.ContainsFileSystemEntryByName(".ignore");
|
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);
|
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);
|
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>();
|
var items = new List<BaseItem>();
|
||||||
items.AddRange(result.Items);
|
items.AddRange(result.Items);
|
||||||
@@ -965,7 +966,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var existing = GetItemList(new InternalItemsQuery
|
var existing = GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(MusicArtist) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
|
||||||
Name = name,
|
Name = name,
|
||||||
DtoOptions = options
|
DtoOptions = options
|
||||||
}).Cast<MusicArtist>()
|
}).Cast<MusicArtist>()
|
||||||
@@ -1375,7 +1376,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetItemIdsList(query);
|
return _itemRepository.GetItemIdsList(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1386,7 +1387,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetStudios(query);
|
return _itemRepository.GetStudios(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1397,7 +1398,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetGenres(query);
|
return _itemRepository.GetGenres(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1408,7 +1409,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetMusicGenres(query);
|
return _itemRepository.GetMusicGenres(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1419,7 +1420,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetAllArtists(query);
|
return _itemRepository.GetAllArtists(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
|
public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query.User != null)
|
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)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
@@ -1759,7 +1760,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return orderedItems ?? items;
|
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;
|
var isFirst = true;
|
||||||
|
|
||||||
@@ -2680,94 +2681,70 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
return new ItemLookupInfo
|
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
|
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 ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions);
|
||||||
|
if (ownerVideoInfo == null)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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)
|
var current = fileSystemChildren[i];
|
||||||
};
|
if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
|
||||||
|
|
||||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
|
|
||||||
.OfType<Trailer>()
|
|
||||||
.Select(video =>
|
|
||||||
{
|
{
|
||||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);
|
||||||
if (GetItemById(video.Id) is Trailer dbItem)
|
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;
|
else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType))
|
||||||
video.OwnerId = owner.Id;
|
{
|
||||||
video.ExtraType = ExtraType.Trailer;
|
var extra = GetExtra(current, extraType.Value);
|
||||||
video.TrailerTypes = new[] { TrailerType.LocalTrailer };
|
if (extra != null)
|
||||||
|
{
|
||||||
return video;
|
yield return extra;
|
||||||
|
}
|
||||||
// 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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null)
|
BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType)
|
||||||
.OfType<Video>()
|
{
|
||||||
.Select(video =>
|
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
|
return null;
|
||||||
var dbItem = GetItemById(video.Id) as Video;
|
}
|
||||||
|
|
||||||
if (dbItem != null)
|
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||||
{
|
var itemById = GetItemById(extra.Id);
|
||||||
video = dbItem;
|
if (itemById != null)
|
||||||
}
|
{
|
||||||
|
extra = itemById;
|
||||||
|
}
|
||||||
|
|
||||||
video.ParentId = Guid.Empty;
|
extra.ExtraType = extraType;
|
||||||
video.OwnerId = owner.Id;
|
extra.ParentId = Guid.Empty;
|
||||||
|
extra.OwnerId = owner.Id;
|
||||||
SetExtraTypeFromFilename(video);
|
return extra;
|
||||||
|
}
|
||||||
return video;
|
|
||||||
|
|
||||||
// Sort them so that the list can be easily compared for changes
|
|
||||||
}).OrderBy(i => i.Path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
|
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem)
|
||||||
@@ -2817,15 +2794,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return path;
|
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)
|
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
|
||||||
{
|
{
|
||||||
return _itemRepository.GetPeople(query);
|
return _itemRepository.GetPeople(query);
|
||||||
@@ -2932,11 +2900,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
|
|
||||||
|
var existingNameCount = 1; // first numbered name will be 2
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
var virtualFolderPath = Path.Combine(rootFolderPath, name);
|
||||||
while (Directory.Exists(virtualFolderPath))
|
while (Directory.Exists(virtualFolderPath))
|
||||||
{
|
{
|
||||||
name += "1";
|
existingNameCount++;
|
||||||
virtualFolderPath = Path.Combine(rootFolderPath, name);
|
virtualFolderPath = Path.Combine(rootFolderPath, name + " " + existingNameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaPathInfos = options.PathInfos;
|
var mediaPathInfos = options.PathInfos;
|
||||||
|
|||||||
@@ -66,11 +66,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
|
var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
|
||||||
delayMs = Math.Max(3000, delayMs);
|
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;
|
mediaSource.AnalyzeDurationMs = 3000;
|
||||||
|
|||||||
@@ -464,12 +464,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tuple = GetProvider(request.OpenToken);
|
var (provider, keyId) = GetProvider(request.OpenToken);
|
||||||
var provider = tuple.Item1;
|
|
||||||
|
|
||||||
var currentLiveStreams = _openStreams.Values.ToList();
|
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;
|
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))
|
if (string.IsNullOrEmpty(key))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
@@ -64,18 +65,18 @@ namespace Emby.Server.Implementations.Library
|
|||||||
stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
|
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 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)
|
else if (mode == SubtitlePlaybackMode.Smart)
|
||||||
{
|
{
|
||||||
// if the audio language is not understood by the user, load their preferred subs, if there are any
|
// 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)) ??
|
stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ??
|
||||||
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparer.OrdinalIgnoreCase));
|
streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mode == SubtitlePlaybackMode.Always)
|
else if (mode == SubtitlePlaybackMode.Always)
|
||||||
@@ -136,9 +137,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
else if (mode == SubtitlePlaybackMode.Smart)
|
else if (mode == SubtitlePlaybackMode.Smart)
|
||||||
{
|
{
|
||||||
// Prefer smart logic over embedded metadata
|
// 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();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var genres = item
|
var genres = item
|
||||||
.GetRecursiveChildren(user, new InternalItemsQuery(user)
|
.GetRecursiveChildren(user, new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Audio) },
|
IncludeItemTypes = new[] { BaseItemKind.Audio },
|
||||||
DtoOptions = dtoOptions
|
DtoOptions = dtoOptions
|
||||||
})
|
})
|
||||||
.Cast<Audio>()
|
.Cast<Audio>()
|
||||||
@@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Audio) },
|
IncludeItemTypes = new[] { BaseItemKind.Audio },
|
||||||
|
|
||||||
GenreIds = genreIds.ToArray(),
|
GenreIds = genreIds.ToArray(),
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
|
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);
|
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <param name="attribute">The attrib.</param>
|
/// <param name="attribute">The attrib.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
/// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
|
/// <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)
|
if (str.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -28,17 +28,31 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentException("String can't be empty.", nameof(attribute));
|
throw new ArgumentException("String can't be empty.", nameof(attribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
string srch = "[" + attribute + "=";
|
var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
|
||||||
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
|
|
||||||
if (start != -1)
|
// 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;
|
var attributeEnd = attributeIndex + attribute.Length;
|
||||||
int end = str.IndexOf(']', start);
|
if (attributeIndex > 0
|
||||||
return str.Substring(start, end - start);
|
&& 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
|
// 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);
|
var match = ProviderIdParsers.TryFindImdbId(str, out var imdbId);
|
||||||
return match ? imdbId.ToString() : null;
|
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.
|
/// Determine if the supplied list contains what we should consider music.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool ContainsMusic(
|
private bool ContainsMusic(
|
||||||
IEnumerable<FileSystemMetadata> list,
|
ICollection<FileSystemMetadata> list,
|
||||||
bool allowSubfolders,
|
bool allowSubfolders,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
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)
|
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
|
||||||
where TVideoType : Video, new()
|
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 the path is a file check for a matching extensions
|
||||||
if (args.IsDirectory)
|
if (args.IsDirectory)
|
||||||
{
|
{
|
||||||
TVideoType video = null;
|
|
||||||
VideoFileInfo videoInfo = null;
|
|
||||||
|
|
||||||
// Loop through each child file/folder and see if we find a video
|
// Loop through each child file/folder and see if we find a video
|
||||||
foreach (var child in args.FileSystemChildren)
|
foreach (var child in args.FileSystemChildren)
|
||||||
{
|
{
|
||||||
var filename = child.Name;
|
var filename = child.Name;
|
||||||
|
|
||||||
if (child.IsDirectory)
|
if (child.IsDirectory)
|
||||||
{
|
{
|
||||||
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
|
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
|
||||||
{
|
{
|
||||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
videoType = VideoType.Dvd;
|
||||||
|
|
||||||
if (videoInfo == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
video = new TVideoType
|
|
||||||
{
|
|
||||||
Path = args.Path,
|
|
||||||
VideoType = VideoType.Dvd,
|
|
||||||
ProductionYear = videoInfo.Year
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else if (IsBluRayDirectory(filename))
|
||||||
if (IsBluRayDirectory(filename))
|
|
||||||
{
|
{
|
||||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
videoType = VideoType.BluRay;
|
||||||
|
|
||||||
if (videoInfo == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
video = new TVideoType
|
|
||||||
{
|
|
||||||
Path = args.Path,
|
|
||||||
VideoType = VideoType.BluRay,
|
|
||||||
ProductionYear = videoInfo.Year
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (IsDvdFile(filename))
|
else if (IsDvdFile(filename))
|
||||||
{
|
{
|
||||||
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
|
videoType = VideoType.Dvd;
|
||||||
|
|
||||||
if (videoInfo == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
video = new TVideoType
|
|
||||||
{
|
|
||||||
Path = args.Path,
|
|
||||||
VideoType = VideoType.Dvd,
|
|
||||||
ProductionYear = videoInfo.Year
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
|
videoInfo = VideoResolver.Resolve(args.Path, false, NamingOptions, parseName);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
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
|
// use disc-utils, both DVDs and BDs use UDF filesystem
|
||||||
using (var videoFileStream = File.Open(video.Path, FileMode.Open, FileAccess.Read))
|
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"))
|
if (udfReader.DirectoryExists("VIDEO_TS"))
|
||||||
{
|
{
|
||||||
video.IsoType = IsoType.Dvd;
|
video.IsoType = IsoType.Dvd;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@@ -32,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
|
|||||||
|
|
||||||
var extension = Path.GetExtension(args.Path);
|
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
|
// It's a book
|
||||||
return new 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
|
#nullable disable
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library.Resolvers
|
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>
|
public class GenericVideoResolver<T> : BaseVideoResolver<T>
|
||||||
where T : Video, new()
|
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)
|
public GenericVideoResolver(NamingOptions namingOptions)
|
||||||
: base(namingOptions)
|
: base(namingOptions)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
private static void SetProviderIdFromPath(BaseItem item)
|
private static void SetProviderIdFromPath(BaseItem item)
|
||||||
{
|
{
|
||||||
// we need to only look at the name of this actual item (not parents)
|
// 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");
|
var id = justName.GetAttributeValue("tmdbid");
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
|
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
|
||||||
{
|
{
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
private readonly StackResolver _stackResolver;
|
|
||||||
|
|
||||||
private string[] _validCollectionTypes = new[]
|
private string[] _validCollectionTypes = new[]
|
||||||
{
|
{
|
||||||
@@ -46,7 +45,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
: base(namingOptions)
|
: base(namingOptions)
|
||||||
{
|
{
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_stackResolver = new StackResolver(NamingOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -62,7 +60,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
string collectionType,
|
string collectionType,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
|
var result = ResolveMultipleInternal(parent, files, collectionType);
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
@@ -92,24 +90,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Video movie = null;
|
||||||
var files = args.GetActualFileSystemChildren().ToList();
|
var files = args.GetActualFileSystemChildren().ToList();
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
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))
|
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))
|
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)
|
if (args.Parent == null)
|
||||||
{
|
{
|
||||||
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,23 +116,22 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
||||||
return FindMovie<Movie>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
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)
|
if (args.Parent == null)
|
||||||
{
|
{
|
||||||
return base.Resolve(args);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsInvalid(args.Parent, collectionType))
|
if (IsInvalid(args.Parent, collectionType))
|
||||||
@@ -169,6 +166,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
item = ResolveVideo<Video>(args, false);
|
item = ResolveVideo<Video>(args, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore extras
|
||||||
|
if (item?.ExtraType != null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
item.IsInMixedFolder = true;
|
item.IsInMixedFolder = true;
|
||||||
@@ -180,8 +183,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
private MultiItemResolverResult ResolveMultipleInternal(
|
private MultiItemResolverResult ResolveMultipleInternal(
|
||||||
Folder parent,
|
Folder parent,
|
||||||
List<FileSystemMetadata> files,
|
List<FileSystemMetadata> files,
|
||||||
string collectionType,
|
string collectionType)
|
||||||
IDirectoryService directoryService)
|
|
||||||
{
|
{
|
||||||
if (IsInvalid(parent, collectionType))
|
if (IsInvalid(parent, collectionType))
|
||||||
{
|
{
|
||||||
@@ -190,13 +192,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
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) ||
|
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(collectionType, CollectionType.Photos, 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))
|
if (string.IsNullOrEmpty(collectionType))
|
||||||
@@ -204,7 +206,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
// Owned items should just use the plain video type
|
// Owned items should just use the plain video type
|
||||||
if (parent == null)
|
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())
|
if (parent is Series || parent.GetParents().OfType<Series>().Any())
|
||||||
@@ -212,12 +214,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
return null;
|
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))
|
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;
|
return null;
|
||||||
@@ -226,21 +228,20 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
private MultiItemResolverResult ResolveVideos<T>(
|
private MultiItemResolverResult ResolveVideos<T>(
|
||||||
Folder parent,
|
Folder parent,
|
||||||
IEnumerable<FileSystemMetadata> fileSystemEntries,
|
IEnumerable<FileSystemMetadata> fileSystemEntries,
|
||||||
IDirectoryService directoryService,
|
bool supportMultiEditions,
|
||||||
bool suppportMultiEditions,
|
|
||||||
string collectionType,
|
string collectionType,
|
||||||
bool parseName)
|
bool parseName)
|
||||||
where T : Video, new()
|
where T : Video, new()
|
||||||
{
|
{
|
||||||
var files = new List<FileSystemMetadata>();
|
var files = new List<FileSystemMetadata>();
|
||||||
var videos = new List<BaseItem>();
|
|
||||||
var leftOver = new List<FileSystemMetadata>();
|
var leftOver = new List<FileSystemMetadata>();
|
||||||
|
var hasCollectionType = !string.IsNullOrEmpty(collectionType);
|
||||||
|
|
||||||
// Loop through each child file/folder and see if we find a video
|
// Loop through each child file/folder and see if we find a video
|
||||||
foreach (var child in fileSystemEntries)
|
foreach (var child in fileSystemEntries)
|
||||||
{
|
{
|
||||||
// This is a hack but currently no better way to resolve a sometimes ambiguous situation
|
// 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)
|
if (string.Equals(child.Name, "tvshow.nfo", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(child.Name, "season.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
|
var result = new MultiItemResolverResult
|
||||||
{
|
{
|
||||||
ExtraFiles = leftOver,
|
ExtraFiles = leftOver
|
||||||
Items = videos
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent);
|
var isInMixedFolder = resolverResult.Count > 1 || parent?.IsTopParent == true;
|
||||||
|
|
||||||
foreach (var video in resolverResult)
|
foreach (var video in resolverResult)
|
||||||
{
|
{
|
||||||
var firstVideo = video.Files[0];
|
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
|
var videoItem = new T
|
||||||
{
|
{
|
||||||
Path = video.Files[0].Path,
|
Path = path,
|
||||||
IsInMixedFolder = isInMixedFolder,
|
IsInMixedFolder = isInMixedFolder,
|
||||||
ProductionYear = video.Year,
|
ProductionYear = video.Year,
|
||||||
Name = parseName ?
|
Name = parseName ? video.Name : firstVideo.Name,
|
||||||
video.Name :
|
AdditionalParts = additionalParts,
|
||||||
Path.GetFileNameWithoutExtension(video.Files[0].Path),
|
|
||||||
AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToArray(),
|
|
||||||
LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
|
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)
|
private static bool IsIgnored(string filename)
|
||||||
{
|
{
|
||||||
// Ignore samples
|
// 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;
|
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)
|
for (var j = 0; j < current.AlternateVersions.Count; j++)
|
||||||
{
|
{
|
||||||
return result.Files.Any(i => ContainsFile(i, file)) ||
|
if (ContainsFile(current.AlternateVersions[j], file))
|
||||||
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
|
{
|
||||||
result.Extras.Any(i => ContainsFile(i, file));
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
|
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)
|
if (item is Movie || item is MusicVideo)
|
||||||
{
|
{
|
||||||
// We need to only look at the name of this actual item (not parents)
|
// 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
|
// check for tmdb id
|
||||||
var tmdbid = justName.GetAttributeValue("tmdbid");
|
var tmdbid = justName.GetAttributeValue("tmdbid");
|
||||||
@@ -358,7 +382,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
if (!string.IsNullOrEmpty(item.Path))
|
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)
|
// 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))
|
if (!string.IsNullOrWhiteSpace(imdbid))
|
||||||
{
|
{
|
||||||
@@ -431,7 +455,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
// TODO: Allow GetMultiDiscMovie in here
|
// TODO: Allow GetMultiDiscMovie in here
|
||||||
const bool SupportsMultiVersion = true;
|
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();
|
new MultiItemResolverResult();
|
||||||
|
|
||||||
if (result.Items.Count == 1)
|
if (result.Items.Count == 1)
|
||||||
@@ -510,7 +534,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = _stackResolver.ResolveDirectories(folderPaths).ToList();
|
var result = StackResolver.ResolveDirectories(folderPaths, NamingOptions).ToList();
|
||||||
|
|
||||||
if (result.Count != 1)
|
if (result.Count != 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@@ -33,7 +34,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
"default"
|
"default"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
|
public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
}
|
}
|
||||||
|
|
||||||
string extension = Path.GetExtension(path).TrimStart('.');
|
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;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
@@ -57,10 +58,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
|
|
||||||
// Check if this is a music playlist file
|
// Check if this is a music playlist file
|
||||||
// It should have the correct collection type and a supported file extension
|
// 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);
|
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
|
return new Playlist
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Entities;
|
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
|
// 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
|
// Also handle flat tv folders
|
||||||
if ((season != null ||
|
if (season != null ||
|
||||||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
||||||
args.HasParent<Series>())
|
args.HasParent<Series>())
|
||||||
&& (parent is Series || !BaseItem.AllExtrasTypesFolderNames.ContainsKey(parent.Name)))
|
|
||||||
{
|
{
|
||||||
var episode = ResolveVideo<Episode>(args, false);
|
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)
|
var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
|
||||||
{
|
|
||||||
episode.SeriesId = series.Id;
|
|
||||||
episode.SeriesName = series.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (season != null)
|
if (series != null)
|
||||||
{
|
{
|
||||||
episode.SeasonId = season.Id;
|
episode.SeriesId = series.Id;
|
||||||
episode.SeasonName = season.Name;
|
episode.SeriesName = series.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume season 1 if there's no season folder and a season number could not be determined
|
if (season != null)
|
||||||
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
|
{
|
||||||
{
|
episode.SeasonId = season.Id;
|
||||||
episode.ParentIndexNumber = 1;
|
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;
|
return episode;
|
||||||
|
|||||||
@@ -5,12 +5,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.TV;
|
using Emby.Naming.TV;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
@@ -185,13 +182,42 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
private static void SetProviderIdFromPath(Series item, string path)
|
private static void SetProviderIdFromPath(Series item, string path)
|
||||||
{
|
{
|
||||||
var justName = Path.GetFileName(path);
|
var justName = Path.GetFileName(path.AsSpan());
|
||||||
|
|
||||||
var id = justName.GetAttributeValue("tvdbid");
|
var tvdbId = justName.GetAttributeValue("tvdbid");
|
||||||
|
if (!string.IsNullOrEmpty(tvdbId))
|
||||||
if (!string.IsNullOrEmpty(id))
|
|
||||||
{
|
{
|
||||||
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 Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Search;
|
using MediaBrowser.Model.Search;
|
||||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
|
||||||
using Person = MediaBrowser.Controller.Entities.Person;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
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);
|
list.Add(value);
|
||||||
}
|
}
|
||||||
@@ -86,63 +83,63 @@ namespace Emby.Server.Implementations.Library
|
|||||||
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
||||||
|
|
||||||
var excludeItemTypes = query.ExcludeItemTypes.ToList();
|
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(BaseItemKind.Year);
|
||||||
excludeItemTypes.Add(nameof(Folder));
|
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)
|
if (!query.IncludeMedia)
|
||||||
{
|
{
|
||||||
AddIfMissing(includeItemTypes, nameof(Genre));
|
AddIfMissing(includeItemTypes, BaseItemKind.Genre);
|
||||||
AddIfMissing(includeItemTypes, nameof(MusicGenre));
|
AddIfMissing(includeItemTypes, BaseItemKind.MusicGenre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddIfMissing(excludeItemTypes, nameof(Genre));
|
AddIfMissing(excludeItemTypes, BaseItemKind.Genre);
|
||||||
AddIfMissing(excludeItemTypes, nameof(MusicGenre));
|
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)
|
if (!query.IncludeMedia)
|
||||||
{
|
{
|
||||||
AddIfMissing(includeItemTypes, nameof(Person));
|
AddIfMissing(includeItemTypes, BaseItemKind.Person);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (!query.IncludeMedia)
|
||||||
{
|
{
|
||||||
AddIfMissing(includeItemTypes, nameof(Studio));
|
AddIfMissing(includeItemTypes, BaseItemKind.Studio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (!query.IncludeMedia)
|
||||||
{
|
{
|
||||||
AddIfMissing(includeItemTypes, nameof(MusicArtist));
|
AddIfMissing(includeItemTypes, BaseItemKind.MusicArtist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddIfMissing(excludeItemTypes, nameof(MusicArtist));
|
AddIfMissing(excludeItemTypes, BaseItemKind.MusicArtist);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddIfMissing(excludeItemTypes, nameof(CollectionFolder));
|
AddIfMissing(excludeItemTypes, BaseItemKind.CollectionFolder);
|
||||||
AddIfMissing(excludeItemTypes, nameof(Folder));
|
AddIfMissing(excludeItemTypes, BaseItemKind.Folder);
|
||||||
var mediaTypes = query.MediaTypes.ToList();
|
var mediaTypes = query.MediaTypes.ToList();
|
||||||
|
|
||||||
if (includeItemTypes.Count > 0)
|
if (includeItemTypes.Count > 0)
|
||||||
@@ -183,7 +180,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
List<BaseItem> mediaItems;
|
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))
|
if (!searchQuery.ParentId.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
@@ -192,8 +189,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
searchQuery.ParentId = Guid.Empty;
|
searchQuery.ParentId = Guid.Empty;
|
||||||
searchQuery.IncludeItemsByName = true;
|
searchQuery.IncludeItemsByName = true;
|
||||||
searchQuery.IncludeItemTypes = Array.Empty<string>();
|
searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
|
||||||
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList();
|
mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cacheKey = GetCacheKey(userId, item.Id);
|
var cacheKey = GetCacheKey(userId, item.Id);
|
||||||
_userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
|
_userData.AddOrUpdate(cacheKey, userData, (_, _) => userData);
|
||||||
|
|
||||||
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
|
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
|
||||||
{
|
{
|
||||||
@@ -125,7 +125,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var cacheKey = GetCacheKey(userId, itemId);
|
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)
|
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Model.Channels;
|
using MediaBrowser.Model.Channels;
|
||||||
@@ -20,8 +20,6 @@ using MediaBrowser.Model.Entities;
|
|||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Library;
|
using MediaBrowser.Model.Library;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
|
||||||
using Person = MediaBrowser.Controller.Entities.Person;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library
|
namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
@@ -80,7 +78,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
continue;
|
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));
|
list.Add(GetUserView(folder, folderViewType, string.Empty));
|
||||||
}
|
}
|
||||||
@@ -175,12 +173,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
string viewType,
|
string viewType,
|
||||||
string localizationKey,
|
string localizationKey,
|
||||||
string sortName,
|
string sortName,
|
||||||
Jellyfin.Data.Entities.User user,
|
User user,
|
||||||
string[] presetViews)
|
string[] presetViews)
|
||||||
{
|
{
|
||||||
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
|
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];
|
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)))
|
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)))
|
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
|
var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0
|
||||||
? new[]
|
? new[]
|
||||||
{
|
{
|
||||||
nameof(Person),
|
BaseItemKind.Person,
|
||||||
nameof(Studio),
|
BaseItemKind.Studio,
|
||||||
nameof(Year),
|
BaseItemKind.Year,
|
||||||
nameof(MusicGenre),
|
BaseItemKind.MusicGenre,
|
||||||
nameof(Genre)
|
BaseItemKind.Genre
|
||||||
}
|
}
|
||||||
: Array.Empty<string>();
|
: Array.Empty<BaseItemKind>();
|
||||||
|
|
||||||
var query = new InternalItemsQuery(user)
|
var query = new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
@@ -361,7 +359,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
(ItemSortBy.SortName, SortOrder.Descending),
|
(ItemSortBy.SortName, SortOrder.Descending),
|
||||||
(ItemSortBy.ProductionYear, SortOrder.Descending)
|
(ItemSortBy.ProductionYear, SortOrder.Descending)
|
||||||
},
|
},
|
||||||
IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
|
IsFolder = includeItemTypes.Length == 0 ? false : null,
|
||||||
ExcludeItemTypes = excludeItemTypes,
|
ExcludeItemTypes = excludeItemTypes,
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false,
|
||||||
Limit = limit * 5,
|
Limit = limit * 5,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Globalization;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
|
|
||||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(MusicArtist) },
|
IncludeItemTypes = new[] { BaseItemKind.MusicArtist },
|
||||||
IsDeadArtist = true,
|
IsDeadArtist = true,
|
||||||
IsLocked = false
|
IsLocked = false
|
||||||
}).Cast<MusicArtist>().ToList();
|
}).Cast<MusicArtist>().ToList();
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Collections.Generic;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Collections;
|
using MediaBrowser.Controller.Collections;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
using Jellyfin.Data.Enums;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library.Validators
|
namespace Emby.Server.Implementations.Library.Validators
|
||||||
{
|
{
|
||||||
@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
var movies = _libraryManager.GetItemList(new InternalItemsQuery
|
var movies = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
MediaTypes = new string[] { MediaType.Video },
|
MediaTypes = new string[] { MediaType.Video },
|
||||||
IncludeItemTypes = new[] { nameof(Movie) },
|
IncludeItemTypes = new[] { BaseItemKind.Movie },
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false,
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
|
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
|
||||||
Parent = library,
|
Parent = library,
|
||||||
@@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
|
|
||||||
var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
|
var boxSets = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(BoxSet) },
|
IncludeItemTypes = new[] { BaseItemKind.BoxSet },
|
||||||
CollapseBoxSetItems = false,
|
CollapseBoxSetItems = false,
|
||||||
Recursive = true
|
Recursive = true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
@@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
|
|
||||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Person) },
|
IncludeItemTypes = new[] { BaseItemKind.Person },
|
||||||
IsDeadPerson = true,
|
IsDeadPerson = true,
|
||||||
IsLocked = false
|
IsLocked = false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
@@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
|||||||
|
|
||||||
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Studio) },
|
IncludeItemTypes = new[] { BaseItemKind.Studio },
|
||||||
IsDeadStudio = true,
|
IsDeadStudio = true,
|
||||||
IsLocked = false
|
IsLocked = false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ using System.Xml;
|
|||||||
using Emby.Server.Implementations.Library;
|
using Emby.Server.Implementations.Library;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.Progress;
|
||||||
@@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
foreach (var virtualFolder in virtualFolders)
|
foreach (var virtualFolder in virtualFolders)
|
||||||
{
|
{
|
||||||
if (!virtualFolder.Locations.Contains(path, StringComparer.OrdinalIgnoreCase))
|
if (!virtualFolder.Locations.Contains(path, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -397,7 +398,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
|
|
||||||
result = new EpgChannelData(channels);
|
result = new EpgChannelData(channels);
|
||||||
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
|
_epgChannels.AddOrUpdate(info.Id, result, (_, _) => result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -891,7 +892,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
throw new ArgumentNullException(nameof(tunerHostId));
|
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)
|
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 remoteMetadata = await FetchInternetMetadata(timer, CancellationToken.None).ConfigureAwait(false);
|
||||||
var recordPath = GetRecordingPath(timer, remoteMetadata, out string seriesPath);
|
var recordPath = GetRecordingPath(timer, remoteMetadata, out string seriesPath);
|
||||||
var recordingStatus = RecordingStatus.New;
|
|
||||||
|
|
||||||
string liveStreamId = null;
|
|
||||||
|
|
||||||
var channelItem = _liveTvManager.GetLiveTvChannel(timer, this);
|
var channelItem = _liveTvManager.GetLiveTvChannel(timer, this);
|
||||||
|
|
||||||
|
string liveStreamId = null;
|
||||||
|
RecordingStatus recordingStatus;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var allMediaSources = await _mediaSourceManager.GetPlaybackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
|
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);
|
TriggerRefresh(recordPath);
|
||||||
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
|
_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)
|
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
|
var program = string.IsNullOrWhiteSpace(timer.ProgramId) ? null : _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ExternalId = timer.ProgramId,
|
ExternalId = timer.ProgramId,
|
||||||
DtoOptions = new DtoOptions(true)
|
DtoOptions = new DtoOptions(true)
|
||||||
@@ -1936,7 +1936,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
writer.WriteElementString("title", timer.EpisodeTitle);
|
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)
|
if (premiereDate.HasValue)
|
||||||
{
|
{
|
||||||
@@ -2125,19 +2125,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer)
|
private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer)
|
||||||
{
|
{
|
||||||
return GetProgramInfoFromCache(timer.ProgramId, timer.ChannelId);
|
return GetProgramInfoFromCache(timer.ProgramId);
|
||||||
}
|
|
||||||
|
|
||||||
private LiveTvProgram GetProgramInfoFromCache(string programId, string channelId)
|
|
||||||
{
|
|
||||||
return GetProgramInfoFromCache(programId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc)
|
private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc)
|
||||||
{
|
{
|
||||||
var query = new InternalItemsQuery
|
var query = new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
DtoOptions = new DtoOptions(true)
|
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
|
// 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
|
// 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);
|
UpdateExistingTimerWithNewMetadata(existingTimer, timer);
|
||||||
|
|
||||||
@@ -2297,17 +2292,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
enabledTimersForSeries.Add(existingTimer);
|
enabledTimersForSeries.Add(existingTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateTimerSettings)
|
existingTimer.KeepUntil = seriesTimer.KeepUntil;
|
||||||
{
|
existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
|
||||||
existingTimer.KeepUntil = seriesTimer.KeepUntil;
|
existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
|
||||||
existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
|
existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
|
||||||
existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
|
existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
|
||||||
existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
|
existingTimer.Priority = seriesTimer.Priority;
|
||||||
existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
|
|
||||||
existingTimer.Priority = seriesTimer.Priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
existingTimer.SeriesTimerId = seriesTimer.Id;
|
existingTimer.SeriesTimerId = seriesTimer.Id;
|
||||||
|
|
||||||
_timerProvider.Update(existingTimer);
|
_timerProvider.Update(existingTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2332,7 +2324,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
var deletes = _timerProvider.GetAll()
|
var deletes = _timerProvider.GetAll()
|
||||||
.Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
|
.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))
|
.Where(i => deleteStatuses.Contains(i.Status))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -2352,7 +2344,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
var query = new InternalItemsQuery
|
var query = new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
ExternalSeriesId = seriesTimer.SeriesId,
|
ExternalSeriesId = seriesTimer.SeriesId,
|
||||||
DtoOptions = new DtoOptions(true)
|
DtoOptions = new DtoOptions(true)
|
||||||
{
|
{
|
||||||
@@ -2387,7 +2379,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
channel = _libraryManager.GetItemList(
|
channel = _libraryManager.GetItemList(
|
||||||
new InternalItemsQuery
|
new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(LiveTvChannel) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||||
ItemIds = new[] { parent.ChannelId },
|
ItemIds = new[] { parent.ChannelId },
|
||||||
DtoOptions = new DtoOptions()
|
DtoOptions = new DtoOptions()
|
||||||
}).FirstOrDefault() as LiveTvChannel;
|
}).FirstOrDefault() as LiveTvChannel;
|
||||||
@@ -2446,7 +2438,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
channel = _libraryManager.GetItemList(
|
channel = _libraryManager.GetItemList(
|
||||||
new InternalItemsQuery
|
new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(LiveTvChannel) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||||
ItemIds = new[] { programInfo.ChannelId },
|
ItemIds = new[] { programInfo.ChannelId },
|
||||||
DtoOptions = new DtoOptions()
|
DtoOptions = new DtoOptions()
|
||||||
}).FirstOrDefault() as LiveTvChannel;
|
}).FirstOrDefault() as LiveTvChannel;
|
||||||
@@ -2511,7 +2503,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
var seriesIds = _libraryManager.GetItemIds(
|
var seriesIds = _libraryManager.GetItemIds(
|
||||||
new InternalItemsQuery
|
new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Series) },
|
IncludeItemTypes = new[] { BaseItemKind.Series },
|
||||||
Name = program.Name
|
Name = program.Name
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
@@ -2524,7 +2516,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
{
|
{
|
||||||
var result = _libraryManager.GetItemIds(new InternalItemsQuery
|
var result = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(Episode) },
|
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
||||||
ParentIndexNumber = program.SeasonNumber.Value,
|
ParentIndexNumber = program.SeasonNumber.Value,
|
||||||
IndexNumber = program.EpisodeNumber.Value,
|
IndexNumber = program.EpisodeNumber.Value,
|
||||||
AncestorIds = seriesIds,
|
AncestorIds = seriesIds,
|
||||||
@@ -2621,7 +2613,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
if (newDevicesOnly)
|
if (newDevicesOnly)
|
||||||
{
|
{
|
||||||
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase))
|
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,12 +62,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
using var durationToken = new CancellationTokenSource(duration);
|
using var durationToken = new CancellationTokenSource(duration);
|
||||||
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
|
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);
|
_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;
|
_targetPath = targetFile;
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||||
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
RedirectStandardInput = true,
|
RedirectStandardInput = true,
|
||||||
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile),
|
||||||
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false
|
ErrorDialog = false
|
||||||
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
StartInfo = processStartInfo,
|
StartInfo = processStartInfo,
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
};
|
};
|
||||||
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process);
|
_process.Exited += (_, _) => OnFfMpegProcessExited(_process);
|
||||||
|
|
||||||
_process.Start();
|
_process.Start();
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
_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;
|
string videoArgs;
|
||||||
if (EncodeVideo(mediaSource))
|
if (EncodeVideo(mediaSource))
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -160,8 +159,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
var programEntry = programDict[schedule.ProgramId];
|
var programEntry = programDict[schedule.ProgramId];
|
||||||
|
|
||||||
var allImages = images[imageIndex].Data;
|
var allImages = images[imageIndex].Data;
|
||||||
var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", 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));
|
var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
|
|
||||||
const double DesiredAspect = 2.0 / 3;
|
const double DesiredAspect = 2.0 / 3;
|
||||||
|
|
||||||
@@ -242,19 +241,19 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
|
|
||||||
if (programInfo.AudioProperties.Count != 0)
|
if (programInfo.AudioProperties.Count != 0)
|
||||||
{
|
{
|
||||||
if (programInfo.AudioProperties.Contains("atmos", StringComparer.OrdinalIgnoreCase))
|
if (programInfo.AudioProperties.Contains("atmos", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
audioType = ProgramAudio.Atmos;
|
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;
|
audioType = ProgramAudio.DolbyDigital;
|
||||||
}
|
}
|
||||||
else if (programInfo.AudioProperties.Contains("dd", StringComparer.OrdinalIgnoreCase))
|
else if (programInfo.AudioProperties.Contains("dd", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
audioType = ProgramAudio.DolbyDigital;
|
audioType = ProgramAudio.DolbyDigital;
|
||||||
}
|
}
|
||||||
else if (programInfo.AudioProperties.Contains("stereo", StringComparer.OrdinalIgnoreCase))
|
else if (programInfo.AudioProperties.Contains("stereo", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
audioType = ProgramAudio.Stereo;
|
audioType = ProgramAudio.Stereo;
|
||||||
}
|
}
|
||||||
@@ -316,8 +315,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
|
|
||||||
if (programInfo.VideoProperties != null)
|
if (programInfo.VideoProperties != null)
|
||||||
{
|
{
|
||||||
info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
|
info.IsHD = programInfo.VideoProperties.Contains("hdtv", StringComparison.OrdinalIgnoreCase);
|
||||||
info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
|
info.Is3D = programInfo.VideoProperties.Contains("3d", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.ContentRating != null && details.ContentRating.Count > 0)
|
if (details.ContentRating != null && details.ContentRating.Count > 0)
|
||||||
@@ -326,7 +325,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
.Replace("--", "-", StringComparison.Ordinal);
|
.Replace("--", "-", StringComparison.Ordinal);
|
||||||
|
|
||||||
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
|
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;
|
info.OfficialRating = null;
|
||||||
}
|
}
|
||||||
@@ -388,9 +387,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
if (details.Genres != null)
|
if (details.Genres != null)
|
||||||
{
|
{
|
||||||
info.Genres = details.Genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
|
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;
|
info.IsKids = true;
|
||||||
}
|
}
|
||||||
@@ -644,7 +643,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
|
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));
|
var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password));
|
||||||
|
#pragma warning restore CA5350
|
||||||
// TODO: remove ToLower when Convert.ToHexString supports lowercase
|
// TODO: remove ToLower when Convert.ToHexString supports lowercase
|
||||||
// Schedules Direct requires the hex to be lowercase
|
// Schedules Direct requires the hex to be lowercase
|
||||||
string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant();
|
string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant();
|
||||||
@@ -821,10 +822,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
|
|
||||||
return list;
|
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,
|
IsSeries = program.Episode != null,
|
||||||
IsRepeat = program.IsPreviouslyShown && !program.IsNew,
|
IsRepeat = program.IsPreviouslyShown && !program.IsNew,
|
||||||
IsPremiere = program.Premiere != null,
|
IsPremiere = program.Premiere != null,
|
||||||
IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
|
IsKids = program.Categories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||||
IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
|
IsMovie = program.Categories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||||
IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
|
IsNews = program.Categories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
|
||||||
IsSports = program.Categories.Any(c => info.SportsCategories.Contains(c, StringComparer.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,
|
ImageUrl = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source) ? program.Icon.Source : null,
|
||||||
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
|
HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source),
|
||||||
OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null,
|
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.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
@@ -161,7 +161,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
|
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(Series) },
|
IncludeItemTypes = new[] { BaseItemKind.Series },
|
||||||
Name = seriesName,
|
Name = seriesName,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||||
@@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
ExternalSeriesId = programSeriesId,
|
ExternalSeriesId = programSeriesId,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
@@ -255,7 +255,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
|
var librarySeries = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(Series) },
|
IncludeItemTypes = new[] { BaseItemKind.Series },
|
||||||
Name = seriesName,
|
Name = seriesName,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||||
@@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
var program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(Series) },
|
IncludeItemTypes = new[] { BaseItemKind.Series },
|
||||||
Name = seriesName,
|
Name = seriesName,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
@@ -309,7 +309,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
program = _libraryManager.GetItemList(new InternalItemsQuery
|
program = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
ExternalSeriesId = programSeriesId,
|
ExternalSeriesId = programSeriesId,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ using MediaBrowser.Model.LiveTv;
|
|||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
|
||||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv
|
namespace Emby.Server.Implementations.LiveTv
|
||||||
{
|
{
|
||||||
@@ -191,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
IsKids = query.IsKids,
|
IsKids = query.IsKids,
|
||||||
IsSports = query.IsSports,
|
IsSports = query.IsSports,
|
||||||
IsSeries = query.IsSeries,
|
IsSeries = query.IsSeries,
|
||||||
IncludeItemTypes = new[] { nameof(LiveTvChannel) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvChannel },
|
||||||
TopParentIds = new[] { topFolder.Id },
|
TopParentIds = new[] { topFolder.Id },
|
||||||
IsFavorite = query.IsFavorite,
|
IsFavorite = query.IsFavorite,
|
||||||
IsLiked = query.IsLiked,
|
IsLiked = query.IsLiked,
|
||||||
@@ -209,7 +207,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
|
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));
|
orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
|
||||||
}
|
}
|
||||||
@@ -522,7 +520,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return item;
|
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);
|
var id = _tvDtoService.GetInternalProgramId(info.Id);
|
||||||
|
|
||||||
@@ -781,9 +779,9 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
|
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);
|
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -810,7 +808,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var internalQuery = new InternalItemsQuery(user)
|
var internalQuery = new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
MinEndDate = query.MinEndDate,
|
MinEndDate = query.MinEndDate,
|
||||||
MinStartDate = query.MinStartDate,
|
MinStartDate = query.MinStartDate,
|
||||||
MaxEndDate = query.MaxEndDate,
|
MaxEndDate = query.MaxEndDate,
|
||||||
@@ -874,7 +872,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var internalQuery = new InternalItemsQuery(user)
|
var internalQuery = new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
IsAiring = query.IsAiring,
|
IsAiring = query.IsAiring,
|
||||||
HasAired = query.HasAired,
|
HasAired = query.HasAired,
|
||||||
IsNews = query.IsNews,
|
IsNews = query.IsNews,
|
||||||
@@ -978,16 +976,16 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return score;
|
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<TimerInfo> timerList = null;
|
||||||
IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
|
IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
|
||||||
|
|
||||||
foreach (var programTuple in programs)
|
foreach (var programTuple in programs)
|
||||||
{
|
{
|
||||||
var program = programTuple.Item1;
|
var program = programTuple.ItemDto;
|
||||||
var externalProgramId = programTuple.Item2;
|
var externalProgramId = programTuple.ExternalId;
|
||||||
string externalSeriesId = programTuple.Item3;
|
string externalSeriesId = programTuple.ExternalSeriesId;
|
||||||
|
|
||||||
timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
|
timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
|
||||||
|
|
||||||
@@ -1085,8 +1083,8 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
if (cleanDatabase)
|
if (cleanDatabase)
|
||||||
{
|
{
|
||||||
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { nameof(LiveTvChannel) }, progress, cancellationToken);
|
CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { BaseItemKind.LiveTvChannel }, progress, cancellationToken);
|
||||||
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { nameof(LiveTvProgram) }, progress, cancellationToken);
|
CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { BaseItemKind.LiveTvProgram }, progress, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
|
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
|
||||||
@@ -1177,7 +1175,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
|
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
ChannelIds = new Guid[] { currentChannel.Id },
|
ChannelIds = new Guid[] { currentChannel.Id },
|
||||||
DtoOptions = new DtoOptions(true)
|
DtoOptions = new DtoOptions(true)
|
||||||
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
|
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
|
||||||
@@ -1188,13 +1186,13 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
foreach (var program in channelPrograms)
|
foreach (var program in channelPrograms)
|
||||||
{
|
{
|
||||||
var programTuple = GetProgram(program, existingPrograms, currentChannel);
|
var programTuple = GetProgram(program, existingPrograms, currentChannel);
|
||||||
var programItem = programTuple.item;
|
var programItem = programTuple.Item;
|
||||||
|
|
||||||
if (programTuple.isNew)
|
if (programTuple.IsNew)
|
||||||
{
|
{
|
||||||
newPrograms.Add(programItem);
|
newPrograms.Add(programItem);
|
||||||
}
|
}
|
||||||
else if (programTuple.isUpdated)
|
else if (programTuple.IsUpdated)
|
||||||
{
|
{
|
||||||
updatedPrograms.Add(programItem);
|
updatedPrograms.Add(programItem);
|
||||||
}
|
}
|
||||||
@@ -1261,7 +1259,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return new Tuple<List<Guid>, List<Guid>>(channels, programs);
|
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
|
var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
@@ -1328,25 +1326,25 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
.Select(i => i.Id)
|
.Select(i => i.Id)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var excludeItemTypes = new List<string>();
|
var excludeItemTypes = new List<BaseItemKind>();
|
||||||
|
|
||||||
if (folderIds.Count == 0)
|
if (folderIds.Count == 0)
|
||||||
{
|
{
|
||||||
return new QueryResult<BaseItem>();
|
return new QueryResult<BaseItem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var includeItemTypes = new List<string>();
|
var includeItemTypes = new List<BaseItemKind>();
|
||||||
var genres = new List<string>();
|
var genres = new List<string>();
|
||||||
|
|
||||||
if (query.IsMovie.HasValue)
|
if (query.IsMovie.HasValue)
|
||||||
{
|
{
|
||||||
if (query.IsMovie.Value)
|
if (query.IsMovie.Value)
|
||||||
{
|
{
|
||||||
includeItemTypes.Add(nameof(Movie));
|
includeItemTypes.Add(BaseItemKind.Movie);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
excludeItemTypes.Add(nameof(Movie));
|
excludeItemTypes.Add(BaseItemKind.Movie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1354,11 +1352,11 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
if (query.IsSeries.Value)
|
if (query.IsSeries.Value)
|
||||||
{
|
{
|
||||||
includeItemTypes.Add(nameof(Episode));
|
includeItemTypes.Add(BaseItemKind.Episode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
excludeItemTypes.Add(nameof(Episode));
|
excludeItemTypes.Add(BaseItemKind.Episode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1425,9 +1423,9 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return result;
|
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 hasChannelImage = fields.Contains(ItemFields.ChannelImage);
|
||||||
var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
|
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);
|
return AddRecordingInfo(programTuples, CancellationToken.None);
|
||||||
@@ -1870,15 +1868,15 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
return _libraryManager.GetItemById(internalChannelId);
|
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 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)
|
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { nameof(LiveTvProgram) },
|
IncludeItemTypes = new[] { BaseItemKind.LiveTvProgram },
|
||||||
ChannelIds = channelIds,
|
ChannelIds = channelIds,
|
||||||
MaxStartDate = now,
|
MaxStartDate = now,
|
||||||
MinEndDate = now,
|
MinEndDate = now,
|
||||||
@@ -1895,11 +1893,8 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
var addCurrentProgram = options.AddCurrentProgram;
|
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.Number = channel.Number;
|
||||||
dto.ChannelNumber = channel.Number;
|
dto.ChannelNumber = channel.Number;
|
||||||
dto.ChannelType = channel.ChannelType;
|
dto.ChannelType = channel.ChannelType;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
|||||||
_profile = profile;
|
_profile = profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(string, string)> GetCommands()
|
public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_channel))
|
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