diff options
84 files changed, 593 insertions, 385 deletions
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml index 89f9c59d7..7c0733598 100644 --- a/.github/workflows/ci-codeql-analysis.yml +++ b/.github/workflows/ci-codeql-analysis.yml @@ -27,11 +27,11 @@ jobs: dotnet-version: '8.0.x' - name: Initialize CodeQL - uses: github/codeql-action/init@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11 + uses: github/codeql-action/init@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11 + uses: github/codeql-action/autobuild@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11 + uses: github/codeql-action/analyze@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml index 5ff9820cb..c9f17429a 100644 --- a/.github/workflows/ci-openapi.yml +++ b/.github/workflows/ci-openapi.yml @@ -78,12 +78,12 @@ jobs: - openapi-base steps: - name: Download openapi-head - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: openapi-head path: openapi-head - name: Download openapi-base - uses: actions/download-artifact@7a1cd3216ca9260cd8022db641d960b1db4d1be4 # v4.0.0 + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 with: name: openapi-base path: openapi-base diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 60c0ef9cb..0dacbc5c6 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -19,9 +19,9 @@ jobs: runs-on: "${{ matrix.os }}" steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4 + - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 with: dotnet-version: ${{ env.SDK_VERSION }} @@ -34,7 +34,7 @@ jobs: --verbosity minimal - name: Merge code coverage results - uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5 + uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5.2.0 with: reports: "**/coverage.cobertura.xml" targetdir: "merged/" @@ -42,9 +42,3 @@ jobs: # TODO - which action / tool to use to publish code coverage results? # - name: Publish code coverage results - - - name: Publish OpenAPI Artifact - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4 - with: - name: "OpenAPI Spec" - path: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net*/openapi.json" diff --git a/Directory.Packages.props b/Directory.Packages.props index ff76252f8..90280d98b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,7 @@ <PackageVersion Include="Diacritics" Version="3.3.18" /> <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" /> <PackageVersion Include="DotNet.Glob" Version="3.1.3" /> - <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.0.0" /> + <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.1.1" /> <PackageVersion Include="FsCheck.Xunit" Version="2.16.6" /> <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0" /> <PackageVersion Include="IDisposableAnalyzers" Version="4.0.4" /> @@ -70,7 +70,7 @@ <PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.5" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.5" /> <PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" /> - <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.507" /> + <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" /> <PackageVersion Include="Svg.Skia" Version="1.0.0.2" /> <PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" /> <PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" /> @@ -83,8 +83,8 @@ <PackageVersion Include="TMDbLib" Version="2.1.0" /> <PackageVersion Include="UTF.Unknown" Version="2.5.1" /> <PackageVersion Include="Xunit.Priority" Version="1.1.6" /> - <PackageVersion Include="xunit.runner.visualstudio" Version="2.5.3" /> + <PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6" /> <PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" /> - <PackageVersion Include="xunit" Version="2.6.1" /> + <PackageVersion Include="xunit" Version="2.6.4" /> </ItemGroup> </Project> diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 6e8f77977..34c722e41 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -32,26 +32,26 @@ namespace Emby.Server.Implementations.Images switch (viewType) { - case CollectionType.Movies: + case CollectionType.movies: includeItemTypes = new[] { BaseItemKind.Movie }; break; - case CollectionType.TvShows: + case CollectionType.tvshows: includeItemTypes = new[] { BaseItemKind.Series }; break; - case CollectionType.Music: + case CollectionType.music: includeItemTypes = new[] { BaseItemKind.MusicAlbum }; break; - case CollectionType.MusicVideos: + case CollectionType.musicvideos: includeItemTypes = new[] { BaseItemKind.MusicVideo }; break; - case CollectionType.Books: + case CollectionType.books: includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook }; break; - case CollectionType.BoxSets: + case CollectionType.boxsets: includeItemTypes = new[] { BaseItemKind.BoxSet }; break; - case CollectionType.HomeVideos: - case CollectionType.Photos: + case CollectionType.homevideos: + case CollectionType.photos: includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo }; break; default: @@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Images break; } - var recursive = viewType != CollectionType.Playlists; + var recursive = viewType != CollectionType.playlists; return view.GetItemList(new InternalItemsQuery { diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 5de53df73..6b2ae23b3 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Images var view = (UserView)item; var isUsingCollectionStrip = IsUsingCollectionStrip(view); - var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.BoxSets && view.ViewType != CollectionType.Playlists; + var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.boxsets && view.ViewType != CollectionType.playlists; var result = view.GetItemList(new InternalItemsQuery { @@ -114,9 +114,9 @@ namespace Emby.Server.Implementations.Images { CollectionType[] collectionStripViewTypes = { - CollectionType.Movies, - CollectionType.TvShows, - CollectionType.Playlists + CollectionType.movies, + CollectionType.tvshows, + CollectionType.playlists }; return view?.ViewType is not null && collectionStripViewTypes.Contains(view.ViewType.Value); diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 868071a99..b1649afad 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -12,7 +12,7 @@ using MediaBrowser.Model.Dto; namespace Emby.Server.Implementations.Library { - public class ExclusiveLiveStream : ILiveStream + public sealed class ExclusiveLiveStream : ILiveStream { private readonly Func<Task> _closeFn; @@ -51,5 +51,10 @@ namespace Emby.Server.Implementations.Library { return Task.CompletedTask; } + + /// <inheritdoc /> + public void Dispose() + { + } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f40177fa7..a79ffd9cb 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1514,7 +1514,7 @@ namespace Emby.Server.Implementations.Library { if (item is UserView view) { - if (view.ViewType == CollectionType.LiveTv) + if (view.ViewType == CollectionType.livetv) { return new[] { view.Id }; } @@ -1543,7 +1543,7 @@ namespace Emby.Server.Implementations.Library } // Handle grouping - if (user is not null && view.ViewType != CollectionType.Unknown && UserView.IsEligibleForGrouping(view.ViewType) + if (user is not null && view.ViewType != CollectionType.unknown && UserView.IsEligibleForGrouping(view.ViewType) && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) { return GetUserRootFolder() diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index ac423ed09..dbf05c1db 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio List<FileSystemMetadata> files, CollectionType? collectionType) { - if (collectionType == CollectionType.Books) + if (collectionType == CollectionType.books) { return ResolveMultipleAudio(parent, files, true); } @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); - var isBooksCollectionType = collectionType == CollectionType.Books; + var isBooksCollectionType = collectionType == CollectionType.books; if (args.IsDirectory) { @@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio MediaBrowser.Controller.Entities.Audio.Audio item = null; - var isMusicCollectionType = collectionType == CollectionType.Music; + var isMusicCollectionType = collectionType == CollectionType.music; // Use regular audio type for mixed libraries, owned items and music if (isMixedCollectionType || diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 06e292f4c..0bfb7fbe6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio protected override MusicAlbum Resolve(ItemResolveArgs args) { var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = collectionType == CollectionType.Music; + var isMusicMediaFolder = collectionType == CollectionType.music; // If there's a collection type and it's not music, don't allow it. if (!isMusicMediaFolder) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 7d6f97b12..1bdae7f62 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); - var isMusicMediaFolder = collectionType == CollectionType.Music; + var isMusicMediaFolder = collectionType == CollectionType.music; // If there's a collection type and it's not music, it can't be a music artist if (!isMusicMediaFolder) diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index b76bfe427..464a548ab 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books var collectionType = args.GetCollectionType(); // Only process items that are in a collection folder containing books - if (collectionType != CollectionType.Books) + if (collectionType != CollectionType.books) { return null; } diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 50fd8b877..1a210e3cc 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -31,11 +31,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies private static readonly CollectionType[] _validCollectionTypes = new[] { - CollectionType.Movies, - CollectionType.HomeVideos, - CollectionType.MusicVideos, - CollectionType.TvShows, - CollectionType.Photos + CollectionType.movies, + CollectionType.homevideos, + CollectionType.musicvideos, + CollectionType.tvshows, + CollectionType.photos }; /// <summary> @@ -100,12 +100,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies Video movie = null; var files = args.GetActualFileSystemChildren().ToList(); - if (collectionType == CollectionType.MusicVideos) + if (collectionType == CollectionType.musicvideos) { movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); } - if (collectionType == CollectionType.HomeVideos) + if (collectionType == CollectionType.homevideos) { movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); } @@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); } - if (collectionType == CollectionType.Movies) + if (collectionType == CollectionType.movies) { movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); } @@ -147,17 +147,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies Video item = null; - if (collectionType == CollectionType.MusicVideos) + if (collectionType == CollectionType.musicvideos) { item = ResolveVideo<MusicVideo>(args, false); } // To find a movie file, the collection type must be movies or boxsets - else if (collectionType == CollectionType.Movies) + else if (collectionType == CollectionType.movies) { item = ResolveVideo<Movie>(args, true); } - else if (collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos) + else if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos) { item = ResolveVideo<Video>(args, false); } @@ -195,12 +195,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - if (collectionType is CollectionType.MusicVideos) + if (collectionType is CollectionType.musicvideos) { return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false); } - if (collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos) + if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos) { return ResolveVideos<Video>(parent, files, false, collectionType, false); } @@ -221,12 +221,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return ResolveVideos<Movie>(parent, files, false, collectionType, true); } - if (collectionType == CollectionType.Movies) + if (collectionType == CollectionType.movies) { return ResolveVideos<Movie>(parent, files, true, collectionType, true); } - if (collectionType == CollectionType.TvShows) + if (collectionType == CollectionType.tvshows) { return ResolveVideos<Episode>(parent, files, false, collectionType, true); } @@ -403,7 +403,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var multiDiscFolders = new List<FileSystemMetadata>(); var libraryOptions = args.LibraryOptions; - var supportPhotos = collectionType == CollectionType.HomeVideos && libraryOptions.EnablePhotos; + var supportPhotos = collectionType == CollectionType.homevideos && libraryOptions.EnablePhotos; var photos = new List<FileSystemMetadata>(); // Search for a folder rip @@ -459,7 +459,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ?? new MultiItemResolverResult(); - var isPhotosCollection = collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos; + var isPhotosCollection = collectionType == CollectionType.homevideos || collectionType == CollectionType.photos; if (!isPhotosCollection && result.Items.Count == 1) { var videoPath = result.Items[0].Path; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 29d540700..c0b00caaf 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -46,8 +46,8 @@ namespace Emby.Server.Implementations.Library.Resolvers // Must be an image file within a photo collection var collectionType = args.GetCollectionType(); - if (collectionType == CollectionType.Photos - || (collectionType == CollectionType.HomeVideos && args.LibraryOptions.EnablePhotos)) + if (collectionType == CollectionType.photos + || (collectionType == CollectionType.homevideos && args.LibraryOptions.EnablePhotos)) { if (HasPhotos(args)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index d166ac37f..0934555b2 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -61,8 +61,8 @@ namespace Emby.Server.Implementations.Library.Resolvers // Must be an image file within a photo collection var collectionType = args.CollectionType; - if (collectionType == CollectionType.Photos - || (collectionType == CollectionType.HomeVideos && args.LibraryOptions.EnablePhotos)) + if (collectionType == CollectionType.photos + || (collectionType == CollectionType.homevideos && args.LibraryOptions.EnablePhotos)) { if (IsImageFile(args.Path, _imageProcessor)) { diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index d4b3722c9..a50435ae6 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers private CollectionType?[] _musicPlaylistCollectionTypes = { null, - CollectionType.Music + CollectionType.music }; /// <inheritdoc/> diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 8274881be..5fd23c9f5 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // Also handle flat tv folders if (season is not null - || args.GetCollectionType() == CollectionType.TvShows + || args.GetCollectionType() == CollectionType.tvshows || args.HasParent<Series>()) { var episode = ResolveVideo<Episode>(args, false); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 2ae1138a5..1484c34bc 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -60,11 +60,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path); var collectionType = args.GetCollectionType(); - if (collectionType == CollectionType.TvShows) + if (collectionType == CollectionType.tvshows) { // TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType var configuredContentType = args.GetConfiguredContentType(); - if (configuredContentType != CollectionType.TvShows) + if (configuredContentType != CollectionType.tvshows) { return new Series { diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 113370fc3..1d662ed8d 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library var folderViewType = collectionFolder?.CollectionType; // Playlist library requires special handling because the folder only references user playlists - if (folderViewType == CollectionType.Playlists) + if (folderViewType == CollectionType.playlists) { var items = folder.GetItemList(new InternalItemsQuery(user) { @@ -99,14 +99,14 @@ namespace Emby.Server.Implementations.Library } } - foreach (var viewType in new[] { CollectionType.Movies, CollectionType.TvShows }) + foreach (var viewType in new[] { CollectionType.movies, CollectionType.tvshows }) { var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null) .ToList(); if (parents.Count > 0) { - var localizationKey = viewType == CollectionType.TvShows + var localizationKey = viewType == CollectionType.tvshows ? "TvShows" : "Movies"; @@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.Library if (_config.Configuration.EnableFolderView) { var name = _localizationManager.GetLocalizedString("Folders"); - list.Add(_libraryManager.GetNamedView(name, CollectionType.Folders, string.Empty)); + list.Add(_libraryManager.GetNamedView(name, CollectionType.folders, string.Empty)); } if (query.IncludeExternalContent) @@ -279,7 +279,7 @@ namespace Emby.Server.Implementations.Library var isPlayed = request.IsPlayed; - if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.Music)) + if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.music)) { isPlayed = null; } @@ -305,11 +305,11 @@ namespace Emby.Server.Implementations.Library var hasCollectionType = parents.OfType<UserView>().ToArray(); if (hasCollectionType.Length > 0) { - if (hasCollectionType.All(i => i.CollectionType == CollectionType.Movies)) + if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies)) { includeItemTypes = new[] { BaseItemKind.Movie }; } - else if (hasCollectionType.All(i => i.CollectionType == CollectionType.TvShows)) + else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows)) { includeItemTypes = new[] { BaseItemKind.Episode }; } @@ -324,18 +324,18 @@ namespace Emby.Server.Implementations.Library { switch (parent.CollectionType) { - case CollectionType.Books: + case CollectionType.books: mediaTypes.Add(MediaType.Book); mediaTypes.Add(MediaType.Audio); break; - case CollectionType.Music: + case CollectionType.music: mediaTypes.Add(MediaType.Audio); break; - case CollectionType.Photos: + case CollectionType.photos: mediaTypes.Add(MediaType.Photo); mediaTypes.Add(MediaType.Video); break; - case CollectionType.HomeVideos: + case CollectionType.homevideos: mediaTypes.Add(MediaType.Photo); mediaTypes.Add(MediaType.Video); break; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 49833de73..ddf7b882a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - public class DirectRecorder : IRecorder + public sealed class DirectRecorder : IRecorder { private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; @@ -46,7 +46,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); - await using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + var output = new FileStream( + targetFile, + FileMode.CreateNew, + FileAccess.Write, + FileShare.Read, + IODefaults.FileStreamBufferSize, + FileOptions.Asynchronous); + + await using (output.ConfigureAwait(false)) { onStarted(); @@ -80,24 +88,31 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile))); - await using var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.CopyToBufferSize, FileOptions.Asynchronous); + var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.CopyToBufferSize, FileOptions.Asynchronous); + await using (output.ConfigureAwait(false)) + { + onStarted(); - onStarted(); + _logger.LogInformation("Copying recording stream to file {0}", targetFile); - _logger.LogInformation("Copying recording stream to file {0}", targetFile); + // The media source if infinite so we need to handle stopping ourselves + using var durationToken = new CancellationTokenSource(duration); + using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); + cancellationToken = linkedCancellationToken.Token; - // The media source if infinite so we need to handle stopping ourselves - using var durationToken = new CancellationTokenSource(duration); - using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token); - cancellationToken = linkedCancellationToken.Token; + await _streamHelper.CopyUntilCancelled( + await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), + output, + IODefaults.CopyToBufferSize, + cancellationToken).ConfigureAwait(false); - await _streamHelper.CopyUntilCancelled( - await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), - output, - IODefaults.CopyToBufferSize, - cancellationToken).ConfigureAwait(false); + _logger.LogInformation("Recording completed to file {0}", targetFile); + } + } - _logger.LogInformation("Recording completed to file {0}", targetFile); + /// <inheritdoc /> + public void Dispose() + { } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 74b62ca3f..abe3ff349 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -37,12 +37,11 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable + public sealed class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable { public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; @@ -74,7 +73,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1); - private bool _disposed = false; + private bool _disposed; public EmbyTV( IServerApplicationHost appHost, @@ -1270,7 +1269,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV directStreamProvider = liveStreamResponse.Item2; } - var recorder = GetRecorder(mediaStreamInfo); + using var recorder = GetRecorder(mediaStreamInfo); recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = EnsureFileUnique(recordPath, timer.Id); @@ -2525,21 +2524,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV /// <inheritdoc /> public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { if (_disposed) { return; } - if (disposing) - { - _recordingDeleteSemaphore.Dispose(); - } + _recordingDeleteSemaphore.Dispose(); foreach (var pair in _activeRecordings.ToList()) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 5369c9b3d..9a9fd0273 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -25,7 +25,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - public class EncodedRecorder : IRecorder, IDisposable + public class EncodedRecorder : IRecorder { private readonly ILogger _logger; private readonly IMediaEncoder _mediaEncoder; @@ -34,10 +34,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly IServerConfigurationManager _serverConfigurationManager; private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private bool _hasExited; - private Stream _logFileStream; + private FileStream _logFileStream; private string _targetPath; private Process _process; - private bool _disposed = false; + private bool _disposed; public EncodedRecorder( ILogger logger, @@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async Task StartStreamingLog(Stream source, Stream target) + private async Task StartStreamingLog(Stream source, FileStream target) { try { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs index 7705132da..de14d6d08 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs @@ -8,7 +8,7 @@ using MediaBrowser.Model.Dto; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - public interface IRecorder + public interface IRecorder : IDisposable { /// <summary> /// Records the specified media source. diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 6b0520ad0..5be3a7488 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings IsMovie = IsMovie(details), Etag = programInfo.Md5, IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase), - IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1 + IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).Contains("premiere", StringComparison.OrdinalIgnoreCase) }; var showId = programId; @@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings return null; } - if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1) + if (uri.Contains("http", StringComparison.OrdinalIgnoreCase)) { return uri; } diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 066afb956..e60e9dcc1 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -84,38 +84,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings _logger.LogInformation("Downloading xmltv listings from {Path}", info.Path); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false); + var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using (stream.ConfigureAwait(false)) + { + return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false); + } } else { - await using var stream = AsyncFile.OpenRead(info.Path); - return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false); + var stream = AsyncFile.OpenRead(info.Path); + await using (stream.ConfigureAwait(false)) + { + return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false); + } } } private async Task<string> UnzipIfNeededAndCopy(string originalUrl, Stream stream, string file, CancellationToken cancellationToken) { - await using var fileStream = new FileStream(file, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - - if (Path.GetExtension(originalUrl.AsSpan().LeftPart('?')).Equals(".gz", StringComparison.OrdinalIgnoreCase)) + var fileStream = new FileStream( + file, + FileMode.CreateNew, + FileAccess.Write, + FileShare.None, + IODefaults.FileStreamBufferSize, + FileOptions.Asynchronous); + + await using (fileStream.ConfigureAwait(false)) { - try + if (Path.GetExtension(originalUrl.AsSpan().LeftPart('?')).Equals(".gz", StringComparison.OrdinalIgnoreCase)) { - using var reader = new GZipStream(stream, CompressionMode.Decompress); - await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); + try + { + using var reader = new GZipStream(stream, CompressionMode.Decompress); + await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error extracting from gz file {File}", originalUrl); + } } - catch (Exception ex) + else { - _logger.LogError(ex, "Error extracting from gz file {File}", originalUrl); + await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } - } - else - { - await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); - } - return file; + return file; + } } public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index dd427c736..0544e2a4b 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1101,7 +1101,7 @@ namespace Emby.Server.Implementations.LiveTv progress.Report(100); } - private async Task<Tuple<List<Guid>, List<Guid>>> RefreshChannelsInternal(ILiveTvService service, IProgress<double> progress, CancellationToken cancellationToken) + private async Task<Tuple<List<Guid>, List<Guid>>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress<double> progress, CancellationToken cancellationToken) { progress.Report(10); @@ -2168,7 +2168,7 @@ namespace Emby.Server.Implementations.LiveTv public Folder GetInternalLiveTvFolder(CancellationToken cancellationToken) { var name = _localization.GetLocalizedString("HeaderLiveTV"); - return _libraryManager.GetNamedView(name, CollectionType.LiveTv, name); + return _libraryManager.GetNamedView(name, CollectionType.livetv, name); } public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index ff25ee585..da597056a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return list; } - protected virtual List<TunerHostInfo> GetTunerHosts() + protected virtual IList<TunerHostInfo> GetTunerHosts() { return GetConfiguration().TunerHosts .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)) @@ -96,8 +96,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts try { Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile)); - await using var writeStream = AsyncFile.OpenWrite(channelCacheFile); - await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false); + var writeStream = AsyncFile.OpenWrite(channelCacheFile); + await using (writeStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false); + } } catch (IOException) { @@ -112,10 +115,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { try { - await using var readStream = AsyncFile.OpenRead(channelCacheFile); - var channels = await JsonSerializer.DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken) - .ConfigureAwait(false); - list.AddRange(channels); + var readStream = AsyncFile.OpenRead(channelCacheFile); + await using (readStream.ConfigureAwait(false)) + { + var channels = await JsonSerializer + .DeserializeAsync<List<ChannelInfo>>(readStream, cancellationToken: cancellationToken) + .ConfigureAwait(false); + list.AddRange(channels); + } } catch (IOException) { @@ -159,9 +166,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return new List<MediaSourceInfo>(); } - protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); + protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); - public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) + public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrEmpty(channelId); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs index 42068cd34..39b357142 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs @@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { var model = ModelNumber ?? string.Empty; - if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1) + if (model.Contains("hdtc", StringComparison.OrdinalIgnoreCase)) { return true; } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 8cd0c4ffb..79e15a82e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -527,7 +527,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return list; } - protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) + protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) { var tunerCount = tunerHost.TunerCount; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 767b94136..c18594a29 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -112,6 +112,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return stream; } + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + LiveStreamCancellationTokenSource?.Dispose(); + } + } + protected async Task DeleteTempFiles(string path, int retryCount = 0) { if (retryCount == 0) @@ -134,7 +149,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - private void TrySeek(Stream stream, long offset) + private void TrySeek(FileStream stream, long offset) { if (!stream.CanSeek) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index db5e81df5..11bf03b18 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(list); } - protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) + protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) { var tunerCount = tunerHost.TunerCount; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 341782d9d..0b5575b99 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -66,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts .ConfigureAwait(false); response.EnsureSuccessStatusCode(); - return await response.Content.ReadAsStreamAsync(cancellationToken); + return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); } private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 51f46f4da..efb84a515 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -83,14 +83,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath); using (response) { - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - await StreamHelper.CopyToAsync( - stream, - fileStream, - IODefaults.CopyToBufferSize, - () => Resolve(openTaskCompletionSource), - cancellationToken).ConfigureAwait(false); + var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using (stream.ConfigureAwait(false)) + { + var fileStream = new FileStream( + TempFilePath, + FileMode.Create, + FileAccess.Write, + FileShare.Read, + IODefaults.FileStreamBufferSize, + FileOptions.Asynchronous); + + await using (fileStream.ConfigureAwait(false)) + { + await StreamHelper.CopyToAsync( + stream, + fileStream, + IODefaults.CopyToBufferSize, + () => Resolve(openTaskCompletionSource), + cancellationToken).ConfigureAwait(false); + } + } } } catch (OperationCanceledException ex) diff --git a/Emby.Server.Implementations/Localization/Core/ab.json b/Emby.Server.Implementations/Localization/Core/ab.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/ab.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 8e4bba25b..8364ce236 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -124,5 +124,7 @@ "TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.", "TaskKeyframeExtractor": "استخراج کننده فریم کلیدی", "External": "خارجی", - "HearingImpaired": "مشکل شنوایی" + "HearingImpaired": "مشکل شنوایی", + "TaskRefreshTrickplayImages": "تولید تصاویر Trickplay", + "TaskRefreshTrickplayImagesDescription": "تولید پیشنمایش های trickplay برای ویدیو های فعال شده در کتابخانه." } diff --git a/Emby.Server.Implementations/Localization/Core/hy.json b/Emby.Server.Implementations/Localization/Core/hy.json new file mode 100644 index 000000000..c348836fe --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/hy.json @@ -0,0 +1,35 @@ +{ + "TasksLibraryCategory": "Գրադարան", + "TasksApplicationCategory": "Հավելված", + "TaskCleanActivityLog": "Մաքրել ակտիվության մատյանը", + "Application": "Հավելված", + "AuthenticationSucceededWithUserName": "{0} հաջողությամբ վավերականացվել են", + "Books": "Գրքեր", + "CameraImageUploadedFrom": "Նոր լուսանկար է վերբեռնվել {0}-ի կողմից", + "Channels": "Ալիքներ", + "DeviceOfflineWithName": "{0}ը անջատվեց", + "External": "Արտաքին", + "FailedLoginAttemptWithUserName": "Ձախողված մուտքի փործ {0}-ի կողմից", + "Folders": "Պանակներ", + "HeaderContinueWatching": "Շարունակել դիտումը", + "Inherit": "Ժառանգել", + "ItemAddedWithName": "{0}ը ավացված է գրադարանի մեջ", + "ItemRemovedWithName": "{0}ը հեռացված է գրադարանից", + "LabelIpAddressValue": "IP հասցե` {0}", + "Movies": "Ֆիլմեր", + "Music": "Երաժշտություն", + "NameSeasonNumber": "Սեզոն {0}", + "Photos": "Լուսանկարներ", + "PluginInstalledWithName": "{0}ն տեղադրված է", + "Songs": "Երգեր", + "System": "Համակարգ", + "TvShows": "Հեռուստասերիալներ", + "User": "Օգտատեր", + "VersionNumber": "Տարբերակ {0}", + "TasksMaintenanceCategory": "Սպասարկում", + "TasksChannelsCategory": "Ինտերնետային ալիքներ", + "TaskRefreshPeople": "Թարմացնել մարդկանց", + "TaskRefreshChannels": "Թարմացնել ալիքները", + "TaskDownloadMissingSubtitles": "Ներբեռնել պակասող ենթագրերը", + "Albums": "Ալբոմներ" +} diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index a4877f4b5..6a04115fa 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -89,7 +89,7 @@ "UserPolicyUpdatedWithName": "{0} için kullanıcı politikası güncellendi", "UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor", "UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi", - "ValueHasBeenAddedToLibrary": "Medya kütüphanenize {0} eklendi", + "ValueHasBeenAddedToLibrary": "{0} medya kütüphanenize eklendi", "ValueSpecialEpisodeName": "Özel - {0}", "VersionNumber": "Sürüm {0}", "TaskCleanCache": "Önbellek Dizinini Temizle", diff --git a/Emby.Server.Implementations/Localization/countries.json b/Emby.Server.Implementations/Localization/countries.json index 22ffc5e09..0a11b3e45 100644 --- a/Emby.Server.Implementations/Localization/countries.json +++ b/Emby.Server.Implementations/Localization/countries.json @@ -696,7 +696,7 @@ "TwoLetterISORegionName": "SI" }, { - "DisplayName": "Soomaaliya", + "DisplayName": "Somalia", "Name": "SO", "ThreeLetterISORegionName": "SOM", "TwoLetterISORegionName": "SO" diff --git a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs index 5c616d534..f65d609c7 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistsFolder.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Playlists public override bool SupportsInheritedParentImages => false; [JsonIgnore] - public override CollectionType? CollectionType => Jellyfin.Data.Enums.CollectionType.Playlists; + public override CollectionType? CollectionType => Jellyfin.Data.Enums.CollectionType.playlists; protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) { diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index 062e1062d..6cb1993e4 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -131,8 +131,8 @@ public class GenresController : BaseJellyfinApiController QueryResult<(BaseItem, ItemCounts)> result; if (parentItem is ICollectionFolder parentCollectionFolder - && (parentCollectionFolder.CollectionType == CollectionType.Music - || parentCollectionFolder.CollectionType == CollectionType.MusicVideos)) + && (parentCollectionFolder.CollectionType == CollectionType.music + || parentCollectionFolder.CollectionType == CollectionType.musicvideos)) { result = _libraryManager.GetMusicGenres(query); } diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs index 4e5ed60d5..9800248c6 100644 --- a/Jellyfin.Api/Controllers/ItemUpdateController.cs +++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs @@ -171,7 +171,7 @@ public class ItemUpdateController : BaseJellyfinApiController info.ContentTypeOptions = GetContentTypeOptions(true).ToArray(); info.ContentType = configuredContentType; - if (inheritedContentType is null || inheritedContentType == CollectionType.TvShows) + if (inheritedContentType is null || inheritedContentType == CollectionType.tvshows) { info.ContentTypeOptions = info.ContentTypeOptions .Where(i => string.IsNullOrWhiteSpace(i.Value) diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index ae80d15e6..a1fc8e11b 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -279,7 +279,7 @@ public class ItemsController : BaseJellyfinApiController collectionType = hasCollectionType.CollectionType; } - if (collectionType == CollectionType.Playlists) + if (collectionType == CollectionType.playlists) { recursive = true; includeItemTypes = new[] { BaseItemKind.Playlist }; diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index af9a93719..de057bbab 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -927,15 +927,15 @@ public class LibraryController : BaseJellyfinApiController { return contentType switch { - CollectionType.BoxSets => new[] { "BoxSet" }, - CollectionType.Playlists => new[] { "Playlist" }, - CollectionType.Movies => new[] { "Movie" }, - CollectionType.TvShows => new[] { "Series", "Season", "Episode" }, - CollectionType.Books => new[] { "Book" }, - CollectionType.Music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" }, - CollectionType.HomeVideos => new[] { "Video", "Photo" }, - CollectionType.Photos => new[] { "Video", "Photo" }, - CollectionType.MusicVideos => new[] { "MusicVideo" }, + CollectionType.boxsets => new[] { "BoxSet" }, + CollectionType.playlists => new[] { "Playlist" }, + CollectionType.movies => new[] { "Movie" }, + CollectionType.tvshows => new[] { "Series", "Season", "Episode" }, + CollectionType.books => new[] { "Book" }, + CollectionType.music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" }, + CollectionType.homevideos => new[] { "Video", "Photo" }, + CollectionType.photos => new[] { "Video", "Photo" }, + CollectionType.musicvideos => new[] { "MusicVideo" }, _ => new[] { "Series", "Season", "Episode", "Movie" } }; } diff --git a/Jellyfin.Data/Enums/CollectionType.cs b/Jellyfin.Data/Enums/CollectionType.cs index e2044a0bc..e3d3b07af 100644 --- a/Jellyfin.Data/Enums/CollectionType.cs +++ b/Jellyfin.Data/Enums/CollectionType.cs @@ -1,3 +1,4 @@ +#pragma warning disable SA1300 // The name of a C# element does not begin with an upper-case letter. - disabled due to legacy requirement. using Jellyfin.Data.Attributes; namespace Jellyfin.Data.Enums; @@ -10,155 +11,155 @@ public enum CollectionType /// <summary> /// Unknown collection. /// </summary> - Unknown = 0, + unknown = 0, /// <summary> /// Movies collection. /// </summary> - Movies = 1, + movies = 1, /// <summary> /// Tv shows collection. /// </summary> - TvShows = 2, + tvshows = 2, /// <summary> /// Music collection. /// </summary> - Music = 3, + music = 3, /// <summary> /// Music videos collection. /// </summary> - MusicVideos = 4, + musicvideos = 4, /// <summary> /// Trailers collection. /// </summary> - Trailers = 5, + trailers = 5, /// <summary> /// Home videos collection. /// </summary> - HomeVideos = 6, + homevideos = 6, /// <summary> /// Box sets collection. /// </summary> - BoxSets = 7, + boxsets = 7, /// <summary> /// Books collection. /// </summary> - Books = 8, + books = 8, /// <summary> /// Photos collection. /// </summary> - Photos = 9, + photos = 9, /// <summary> /// Live tv collection. /// </summary> - LiveTv = 10, + livetv = 10, /// <summary> /// Playlists collection. /// </summary> - Playlists = 11, + playlists = 11, /// <summary> /// Folders collection. /// </summary> - Folders = 12, + folders = 12, /// <summary> /// Tv show series collection. /// </summary> [OpenApiIgnoreEnum] - TvShowSeries = 101, + tvshowseries = 101, /// <summary> /// Tv genres collection. /// </summary> [OpenApiIgnoreEnum] - TvGenres = 102, + tvgenres = 102, /// <summary> /// Tv genre collection. /// </summary> [OpenApiIgnoreEnum] - TvGenre = 103, + tvgenre = 103, /// <summary> /// Tv latest collection. /// </summary> [OpenApiIgnoreEnum] - TvLatest = 104, + tvlatest = 104, /// <summary> /// Tv next up collection. /// </summary> [OpenApiIgnoreEnum] - TvNextUp = 105, + tvnextup = 105, /// <summary> /// Tv resume collection. /// </summary> [OpenApiIgnoreEnum] - TvResume = 106, + tvresume = 106, /// <summary> /// Tv favorite series collection. /// </summary> [OpenApiIgnoreEnum] - TvFavoriteSeries = 107, + tvfavoriteseries = 107, /// <summary> /// Tv favorite episodes collection. /// </summary> [OpenApiIgnoreEnum] - TvFavoriteEpisodes = 108, + tvfavoriteepisodes = 108, /// <summary> /// Latest movies collection. /// </summary> [OpenApiIgnoreEnum] - MovieLatest = 109, + movielatest = 109, /// <summary> /// Movies to resume collection. /// </summary> [OpenApiIgnoreEnum] - MovieResume = 110, + movieresume = 110, /// <summary> /// Movie movie collection. /// </summary> [OpenApiIgnoreEnum] - MovieMovies = 111, + moviemovies = 111, /// <summary> /// Movie collections collection. /// </summary> [OpenApiIgnoreEnum] - MovieCollections = 112, + moviecollection = 112, /// <summary> /// Movie favorites collection. /// </summary> [OpenApiIgnoreEnum] - MovieFavorites = 113, + moviefavorites = 113, /// <summary> /// Movie genres collection. /// </summary> [OpenApiIgnoreEnum] - MovieGenres = 114, + moviegenres = 114, /// <summary> /// Movie genre collection. /// </summary> [OpenApiIgnoreEnum] - MovieGenre = 115 + moviegenre = 115 } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index a4b4c1959..d8eee1246 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -110,21 +110,21 @@ namespace Jellyfin.Server.Implementations.Devices /// <inheritdoc /> public async Task<DeviceInfo?> GetDevice(string id) { - Device? device; var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); await using (dbContext.ConfigureAwait(false)) { - device = await dbContext.Devices + var device = await dbContext.Devices .Where(d => d.DeviceId == id) .OrderByDescending(d => d.DateLastActivity) .Include(d => d.User) + .SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o }) .FirstOrDefaultAsync() .ConfigureAwait(false); - } - var deviceInfo = device is null ? null : ToDeviceInfo(device); + var deviceInfo = device is null ? null : ToDeviceInfo(device.Device, device.Options); - return deviceInfo; + return deviceInfo; + } } /// <inheritdoc /> @@ -172,15 +172,15 @@ namespace Jellyfin.Server.Implementations.Devices var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false); await using (dbContext.ConfigureAwait(false)) { - IAsyncEnumerable<Device> sessions = dbContext.Devices + var sessions = dbContext.Devices .Include(d => d.User) .OrderByDescending(d => d.DateLastActivity) .ThenBy(d => d.DeviceId) + .SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o }) .AsAsyncEnumerable(); - if (supportsSync.HasValue) { - sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == supportsSync.Value); + sessions = sessions.Where(i => GetCapabilities(i.Device.DeviceId).SupportsSync == supportsSync.Value); } if (userId.HasValue) @@ -191,10 +191,10 @@ namespace Jellyfin.Server.Implementations.Devices throw new ResourceNotFoundException(); } - sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); + sessions = sessions.Where(i => CanAccessDevice(user, i.Device.DeviceId)); } - var array = await sessions.Select(device => ToDeviceInfo(device)).ToArrayAsync().ConfigureAwait(false); + var array = await sessions.Select(device => ToDeviceInfo(device.Device, device.Options)).ToArrayAsync().ConfigureAwait(false); return new QueryResult<DeviceInfo>(array); } @@ -226,7 +226,7 @@ namespace Jellyfin.Server.Implementations.Devices || !GetCapabilities(deviceId).SupportsPersistentIdentifier; } - private DeviceInfo ToDeviceInfo(Device authInfo) + private DeviceInfo ToDeviceInfo(Device authInfo, DeviceOptions? options = null) { var caps = GetCapabilities(authInfo.DeviceId); @@ -239,7 +239,8 @@ namespace Jellyfin.Server.Implementations.Devices LastUserName = authInfo.User.Username, Name = authInfo.DeviceName, DateLastActivity = authInfo.DateLastActivity, - IconUrl = caps.IconUrl + IconUrl = caps.IconUrl, + CustomName = options?.CustomName, }; } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 98485f9a8..7c04fcbfc 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -724,7 +724,7 @@ namespace MediaBrowser.Controller.Entities if (this is IHasCollectionType view) { - if (view.CollectionType == CollectionType.LiveTv) + if (view.CollectionType == CollectionType.livetv) { return true; } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 1f94cf767..eb026deb4 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -19,19 +19,19 @@ namespace MediaBrowser.Controller.Entities { private static readonly CollectionType?[] _viewTypesEligibleForGrouping = { - Jellyfin.Data.Enums.CollectionType.Movies, - Jellyfin.Data.Enums.CollectionType.TvShows, + Jellyfin.Data.Enums.CollectionType.movies, + Jellyfin.Data.Enums.CollectionType.tvshows, null }; private static readonly CollectionType?[] _originalFolderViewTypes = { - Jellyfin.Data.Enums.CollectionType.Books, - Jellyfin.Data.Enums.CollectionType.MusicVideos, - Jellyfin.Data.Enums.CollectionType.HomeVideos, - Jellyfin.Data.Enums.CollectionType.Photos, - Jellyfin.Data.Enums.CollectionType.Music, - Jellyfin.Data.Enums.CollectionType.BoxSets + Jellyfin.Data.Enums.CollectionType.books, + Jellyfin.Data.Enums.CollectionType.musicvideos, + Jellyfin.Data.Enums.CollectionType.homevideos, + Jellyfin.Data.Enums.CollectionType.photos, + Jellyfin.Data.Enums.CollectionType.music, + Jellyfin.Data.Enums.CollectionType.boxsets }; public static ITVSeriesManager TVSeriesManager { get; set; } @@ -161,7 +161,7 @@ namespace MediaBrowser.Controller.Entities return true; } - return collectionFolder.CollectionType == Jellyfin.Data.Enums.CollectionType.Playlists; + return collectionFolder.CollectionType == Jellyfin.Data.Enums.CollectionType.playlists; } public static bool IsEligibleForGrouping(Folder folder) diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 42431c832..a3525c862 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -58,58 +58,58 @@ namespace MediaBrowser.Controller.Entities switch (viewType) { - case CollectionType.Folders: + case CollectionType.folders: return GetResult(_libraryManager.GetUserRootFolder().GetChildren(user, true), query); - case CollectionType.TvShows: + case CollectionType.tvshows: return GetTvView(queryParent, user, query); - case CollectionType.Movies: + case CollectionType.movies: return GetMovieFolders(queryParent, user, query); - case CollectionType.TvShowSeries: + case CollectionType.tvshowseries: return GetTvSeries(queryParent, user, query); - case CollectionType.TvGenres: + case CollectionType.tvgenres: return GetTvGenres(queryParent, user, query); - case CollectionType.TvGenre: + case CollectionType.tvgenre: return GetTvGenreItems(queryParent, displayParent, user, query); - case CollectionType.TvResume: + case CollectionType.tvresume: return GetTvResume(queryParent, user, query); - case CollectionType.TvNextUp: + case CollectionType.tvnextup: return GetTvNextUp(queryParent, query); - case CollectionType.TvLatest: + case CollectionType.tvlatest: return GetTvLatest(queryParent, user, query); - case CollectionType.MovieFavorites: + case CollectionType.moviefavorites: return GetFavoriteMovies(queryParent, user, query); - case CollectionType.MovieLatest: + case CollectionType.movielatest: return GetMovieLatest(queryParent, user, query); - case CollectionType.MovieGenres: + case CollectionType.moviegenres: return GetMovieGenres(queryParent, user, query); - case CollectionType.MovieGenre: + case CollectionType.moviegenre: return GetMovieGenreItems(queryParent, displayParent, user, query); - case CollectionType.MovieResume: + case CollectionType.movieresume: return GetMovieResume(queryParent, user, query); - case CollectionType.MovieMovies: + case CollectionType.moviemovies: return GetMovieMovies(queryParent, user, query); - case CollectionType.MovieCollections: + case CollectionType.moviecollection: return GetMovieCollections(user, query); - case CollectionType.TvFavoriteEpisodes: + case CollectionType.tvfavoriteepisodes: return GetFavoriteEpisodes(queryParent, user, query); - case CollectionType.TvFavoriteSeries: + case CollectionType.tvfavoriteseries: return GetFavoriteSeries(queryParent, user, query); default: @@ -146,12 +146,12 @@ namespace MediaBrowser.Controller.Entities var list = new List<BaseItem> { - GetUserView(CollectionType.MovieResume, "HeaderContinueWatching", "0", parent), - GetUserView(CollectionType.MovieLatest, "Latest", "1", parent), - GetUserView(CollectionType.MovieMovies, "Movies", "2", parent), - GetUserView(CollectionType.MovieCollections, "Collections", "3", parent), - GetUserView(CollectionType.MovieFavorites, "Favorites", "4", parent), - GetUserView(CollectionType.MovieGenres, "Genres", "5", parent) + GetUserView(CollectionType.movieresume, "HeaderContinueWatching", "0", parent), + GetUserView(CollectionType.movielatest, "Latest", "1", parent), + GetUserView(CollectionType.moviemovies, "Movies", "2", parent), + GetUserView(CollectionType.moviecollection, "Collections", "3", parent), + GetUserView(CollectionType.moviefavorites, "Favorites", "4", parent), + GetUserView(CollectionType.moviegenres, "Genres", "5", parent) }; return GetResult(list, query); @@ -264,7 +264,7 @@ namespace MediaBrowser.Controller.Entities } }) .Where(i => i is not null) - .Select(i => GetUserViewWithName(CollectionType.MovieGenre, i.SortName, parent)); + .Select(i => GetUserViewWithName(CollectionType.moviegenre, i.SortName, parent)); return GetResult(genres, query); } @@ -303,13 +303,13 @@ namespace MediaBrowser.Controller.Entities var list = new List<BaseItem> { - GetUserView(CollectionType.TvResume, "HeaderContinueWatching", "0", parent), - GetUserView(CollectionType.TvNextUp, "HeaderNextUp", "1", parent), - GetUserView(CollectionType.TvLatest, "Latest", "2", parent), - GetUserView(CollectionType.TvShowSeries, "Shows", "3", parent), - GetUserView(CollectionType.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent), - GetUserView(CollectionType.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent), - GetUserView(CollectionType.TvGenres, "Genres", "6", parent) + GetUserView(CollectionType.tvresume, "HeaderContinueWatching", "0", parent), + GetUserView(CollectionType.tvnextup, "HeaderNextUp", "1", parent), + GetUserView(CollectionType.tvlatest, "Latest", "2", parent), + GetUserView(CollectionType.tvshowseries, "Shows", "3", parent), + GetUserView(CollectionType.tvfavoriteseries, "HeaderFavoriteShows", "4", parent), + GetUserView(CollectionType.tvfavoriteepisodes, "HeaderFavoriteEpisodes", "5", parent), + GetUserView(CollectionType.tvgenres, "Genres", "6", parent) }; return GetResult(list, query); @@ -330,7 +330,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetTvNextUp(Folder parent, InternalItemsQuery query) { - var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows }); + var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.tvshows }); var result = _tvSeriesManager.GetNextUp( new NextUpQuery @@ -392,7 +392,7 @@ namespace MediaBrowser.Controller.Entities } }) .Where(i => i is not null) - .Select(i => GetUserViewWithName(CollectionType.TvGenre, i.SortName, parent)); + .Select(i => GetUserViewWithName(CollectionType.tvgenre, i.SortName, parent)); return GetResult(genres, query); } diff --git a/MediaBrowser.Controller/Library/ILiveStream.cs b/MediaBrowser.Controller/Library/ILiveStream.cs index 4c44a17fd..bf64aca0f 100644 --- a/MediaBrowser.Controller/Library/ILiveStream.cs +++ b/MediaBrowser.Controller/Library/ILiveStream.cs @@ -2,6 +2,7 @@ #pragma warning disable CA1711, CS1591 +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -9,7 +10,7 @@ using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Library { - public interface ILiveStream + public interface ILiveStream : IDisposable { int ConsumerCount { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 24820abb9..b98309158 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="currentLiveStreams">The current live streams.</param> /// <param name="cancellationToken">The cancellation token to cancel operation.</param> /// <returns>Live stream wrapped in a task.</returns> - Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); + Task<ILiveStream> GetChannelStream(string channelId, string streamId, IList<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); /// <summary> /// Gets the channel stream media sources. diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index 7a1c7a738..4962992a0 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -15,6 +15,8 @@ namespace MediaBrowser.Model.Devices public string Name { get; set; } + public string CustomName { get; set; } + /// <summary> /// Gets or sets the access token. /// </summary> diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64 index 0374624d8..a0ab93e4e 100755 --- a/deployment/build.centos.amd64 +++ b/deployment/build.centos.amd64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove BuildRequires for dotnet, since it's installed manually @@ -39,10 +39,10 @@ make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm # Move the artifacts out -mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/ +mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi rm -f fedora/jellyfin*.tar.gz @@ -51,7 +51,7 @@ if [[ ${IS_DOCKER} == YES ]]; then pushd fedora cp -a /tmp/spec.orig jellyfin.spec - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" popd fi diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64 index 7e968192b..1a59d02e9 100755 --- a/deployment/build.debian.amd64 +++ b/deployment/build.debian.amd64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove build-dep for dotnet-sdk-8.0, since it's installed manually @@ -32,12 +32,12 @@ fi # Build DEB dpkg-buildpackage -us -uc --pre-clean --post-clean -mkdir -p ${ARTIFACT_DIR}/ -mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64 index 7b7b603d6..e1e30fab4 100755 --- a/deployment/build.debian.arm64 +++ b/deployment/build.debian.arm64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove build-dep for dotnet-sdk-8.0, since it's installed manually @@ -33,12 +33,12 @@ fi export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean -mkdir -p ${ARTIFACT_DIR}/ -mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf index 3d894ba20..e3e8ae004 100755 --- a/deployment/build.debian.armhf +++ b/deployment/build.debian.armhf @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove build-dep for dotnet-sdk-8.0, since it's installed manually @@ -33,12 +33,12 @@ fi export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean -mkdir -p ${ARTIFACT_DIR}/ -mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64 index 1b629289f..da345ec08 100755 --- a/deployment/build.fedora.amd64 +++ b/deployment/build.fedora.amd64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove BuildRequires for dotnet, since it's installed manually @@ -39,10 +39,10 @@ make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm # Move the artifacts out -mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/ +mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi rm -f fedora/jellyfin*.tar.gz @@ -51,7 +51,7 @@ if [[ ${IS_DOCKER} == YES ]]; then pushd fedora cp -a /tmp/spec.orig jellyfin.spec - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" popd fi diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64 index 05059e4ed..c6baa61f6 100755 --- a/deployment/build.linux.amd64 +++ b/deployment/build.linux.amd64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -21,11 +21,11 @@ tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_$ rm -rf dist/jellyfin-server_${version} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.linux.amd64-musl b/deployment/build.linux.amd64-musl index 0ee4b05fb..6523f8319 100755 --- a/deployment/build.linux.amd64-musl +++ b/deployment/build.linux.amd64-musl @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -21,11 +21,11 @@ tar -czf jellyfin-server_${version}_linux-amd64-musl.tar.gz -C dist jellyfin-ser rm -rf dist/jellyfin-server_${version} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.linux.arm64 b/deployment/build.linux.arm64 index 6e36db0eb..6d6a8f803 100755 --- a/deployment/build.linux.arm64 +++ b/deployment/build.linux.arm64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -21,11 +21,11 @@ tar -czf jellyfin-server_${version}_linux-arm64.tar.gz -C dist jellyfin-server_$ rm -rf dist/jellyfin-server_${version} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.linux.armhf b/deployment/build.linux.armhf index f83eeebf1..5167dfcb8 100755 --- a/deployment/build.linux.armhf +++ b/deployment/build.linux.armhf @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -21,11 +21,11 @@ tar -czf jellyfin-server_${version}_linux-armhf.tar.gz -C dist jellyfin-server_$ rm -rf dist/jellyfin-server_${version} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.linux.musl-linux-arm64 b/deployment/build.linux.musl-linux-arm64 index 38826ae7f..57980314d 100755 --- a/deployment/build.linux.musl-linux-arm64 +++ b/deployment/build.linux.musl-linux-arm64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -21,11 +21,11 @@ tar -czf jellyfin-server_${version}_linux-arm64-musl.tar.gz -C dist jellyfin-ser rm -rf dist/jellyfin-server_${version} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.macos.amd64 b/deployment/build.macos.amd64 index eac353877..c7711e82c 100755 --- a/deployment/build.macos.amd64 +++ b/deployment/build.macos.amd64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -21,11 +21,11 @@ tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_$ rm -rf dist/jellyfin-server_${version} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.macos.arm64 b/deployment/build.macos.arm64 index 42da07e2f..b07eaad4e 100755 --- a/deployment/build.macos.arm64 +++ b/deployment/build.macos.arm64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -21,11 +21,11 @@ tar -czf jellyfin-server_${version}_macos-arm64.tar.gz -C dist jellyfin-server_$ rm -rf dist/jellyfin-server_${version} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.portable b/deployment/build.portable index 27e5e987f..ec151d295 100755 --- a/deployment/build.portable +++ b/deployment/build.portable @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -21,11 +21,11 @@ tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${ve rm -rf dist/jellyfin-server_${version} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64 index 5f25cb610..17968a6e9 100755 --- a/deployment/build.ubuntu.amd64 +++ b/deployment/build.ubuntu.amd64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove build-dep for dotnet-sdk-8.0, since it's installed manually @@ -32,12 +32,12 @@ fi # Build DEB dpkg-buildpackage -us -uc --pre-clean --post-clean -mkdir -p ${ARTIFACT_DIR}/ -mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64 index 334ced997..ee7da9bb9 100755 --- a/deployment/build.ubuntu.arm64 +++ b/deployment/build.ubuntu.arm64 @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove build-dep for dotnet-sdk-8.0, since it's installed manually @@ -33,12 +33,12 @@ fi export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean -mkdir -p ${ARTIFACT_DIR}/ -mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf index 77e33c307..85c993282 100755 --- a/deployment/build.ubuntu.armhf +++ b/deployment/build.ubuntu.armhf @@ -6,7 +6,7 @@ set -o errexit set -o xtrace # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" if [[ ${IS_DOCKER} == YES ]]; then # Remove build-dep for dotnet-sdk-8.0, since it's installed manually @@ -33,12 +33,12 @@ fi export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean -mkdir -p ${ARTIFACT_DIR}/ -mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then cp -a /tmp/control.orig debian/control - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64 index 0786358bd..20f976365 100755 --- a/deployment/build.windows.amd64 +++ b/deployment/build.windows.amd64 @@ -11,7 +11,7 @@ NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip" FFMPEG_URL="https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg-portable_win64.zip"; # Move to source directory -pushd ${SOURCE_DIR} +pushd "${SOURCE_DIR}" # Get version if [[ ${IS_UNSTABLE} == 'yes' ]]; then @@ -42,11 +42,11 @@ popd rm -rf ${output_dir} # Move the artifacts out -mkdir -p ${ARTIFACT_DIR}/ -mv dist/jellyfin[-_]*.zip ${ARTIFACT_DIR}/ +mkdir -p "${ARTIFACT_DIR}/" +mv dist/jellyfin[-_]*.zip "${ARTIFACT_DIR}/" if [[ ${IS_DOCKER} == YES ]]; then - chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} + chown -Rc $(stat -c %u:%g "${ARTIFACT_DIR}") "${ARTIFACT_DIR}" fi popd diff --git a/tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs index 0254a1ec6..5034ad3c7 100644 --- a/tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs +++ b/tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs @@ -27,7 +27,7 @@ public static class ImageControllerTests [InlineData(null)] [InlineData("")] [InlineData("text/html")] - public static void TryGetImageExtensionFromContentType_InValid_False(string contentType) + public static void TryGetImageExtensionFromContentType_InValid_False(string? contentType) { Assert.False(ImageController.TryGetImageExtensionFromContentType(contentType, out var ex)); Assert.Null(ex); diff --git a/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs index d46beedd9..95f9a5fcf 100644 --- a/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs +++ b/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs @@ -8,20 +8,18 @@ namespace Jellyfin.Extensions.Tests { public static TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>> CopyTo_Valid_Correct_TestData() { - var data = new TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>>(); - - data.Add( - new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 }); - - data.Add( - new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } ); + var data = new TheoryData<IReadOnlyList<int>, IList<int>, int, IList<int>> + { + { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } }, + { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } } + }; return data; } [Theory] [MemberData(nameof(CopyTo_Valid_Correct_TestData))] - public static void CopyTo_Valid_Correct<T>(IReadOnlyList<T> source, IList<T> destination, int index, IList<T> expected) + public static void CopyTo_Valid_Correct(IReadOnlyList<int> source, IList<int> destination, int index, IList<int> expected) { source.CopyTo(destination, index); Assert.Equal(expected, destination); @@ -29,29 +27,21 @@ namespace Jellyfin.Extensions.Tests public static TheoryData<IReadOnlyList<int>, IList<int>, int> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData() { - var data = new TheoryData<IReadOnlyList<int>, IList<int>, int>(); - - data.Add( - new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 ); - - data.Add( - new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 ); - - data.Add( - new[] { 0, 1, 2 }, Array.Empty<int>(), 0 ); - - data.Add( - new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 ); - - data.Add( - new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 ); + var data = new TheoryData<IReadOnlyList<int>, IList<int>, int> + { + { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 }, + { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 }, + { new[] { 0, 1, 2 }, Array.Empty<int>(), 0 }, + { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 }, + { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 } + }; return data; } [Theory] [MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))] - public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException<T>(IReadOnlyList<T> source, IList<T> destination, int index) + public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException(IReadOnlyList<int> source, IList<int> destination, int index) { Assert.Throws<ArgumentOutOfRangeException>(() => source.CopyTo(destination, index)); } diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs index 210ce4a47..2f84fa544 100644 --- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs +++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Model.Tests [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")] - [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450 [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // Firefox @@ -38,7 +38,7 @@ namespace Jellyfin.Model.Tests [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")] - [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450 [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // Safari @@ -89,7 +89,7 @@ namespace Jellyfin.Model.Tests [InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] [InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode", "http")] - [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 + [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // TranscodeMedia @@ -177,7 +177,7 @@ namespace Jellyfin.Model.Tests [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] - [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 + [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450 [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // Firefox @@ -187,7 +187,7 @@ namespace Jellyfin.Model.Tests [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")] - [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 + [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported, "Remux")] // #6450 [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 // Safari @@ -274,13 +274,16 @@ namespace Jellyfin.Model.Tests [Theory] // Chrome [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450 + [InlineData("Chrome", "mp4-h264-ac3-aac-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")] // Firefox [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450 + [InlineData("Firefox", "mp4-h264-ac3-aac-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")] // Yatse [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450 + [InlineData("Yatse", "mp4-h264-ac3-aac-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450 // RokuSSPlus [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 @@ -291,11 +294,13 @@ namespace Jellyfin.Model.Tests [InlineData("AndroidTVExoPlayer", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] [InlineData("AndroidTVExoPlayer", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // Tizen 3 Stereo - [InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] - [InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] + [InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] + [InlineData("Tizen3-stereo", "mp4-h264-ac3-aac-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] + [InlineData("Tizen3-stereo", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // Tizen 4 4K 5.1 - [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] - [InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] + [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] + [InlineData("Tizen4-4K-5.1", "mp4-h264-ac3-aac-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] + [InlineData("Tizen4-4K-5.1", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] public async Task BuildVideoItemWithDirectPlayExplicitStreams(string deviceName, string mediaSource, PlayMethod? playMethod, TranscodeReason why = default, string transcodeMode = "DirectStream", string transcodeProtocol = "") { var options = await GetMediaOptions(deviceName, mediaSource); @@ -419,14 +424,7 @@ namespace Jellyfin.Model.Tests if (targetAudioStream?.IsExternal == false) { // Check expected audio codecs (1) - if (streamInfo.TranscodeReasons.HasFlag(TranscodeReason.ContainerNotSupported)) - { - Assert.Contains(targetAudioStream.Codec, streamInfo.AudioCodecs); - } - else - { - Assert.DoesNotContain(targetAudioStream.Codec, streamInfo.AudioCodecs); - } + Assert.DoesNotContain(targetAudioStream.Codec, streamInfo.AudioCodecs); } } else if (transcodeMode.Equals("Remux", StringComparison.Ordinal)) diff --git a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs index d39a22e30..f4c0d9fe8 100644 --- a/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs +++ b/tests/Jellyfin.Model.Tests/Entities/MediaStreamTests.cs @@ -192,7 +192,7 @@ namespace Jellyfin.Model.Tests.Entities [InlineData(4090, 3070, false, "4K")] [InlineData(7680, 4320, false, "8K")] [InlineData(8190, 6140, false, "8K")] - public void GetResolutionText_Valid(int? width, int? height, bool interlaced, string expected) + public void GetResolutionText_Valid(int? width, int? height, bool interlaced, string? expected) { var mediaStream = new MediaStream() { diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json index 53637b793..2e3e6e6de 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen3-stereo.json @@ -414,6 +414,19 @@ ], "CodecProfiles": [ { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "$type": "CodecProfile" + }, + { "Type": "Video", "Conditions": [ { diff --git a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json index d3ef22c25..156230471 100644 --- a/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json +++ b/tests/Jellyfin.Model.Tests/Test Data/DeviceProfile-Tizen4-4K-5.1.json @@ -414,6 +414,19 @@ ], "CodecProfiles": [ { + "Type": "VideoAudio", + "Conditions": [ + { + "Condition": "Equals", + "Property": "IsSecondaryAudio", + "Value": "false", + "IsRequired": false, + "$type": "ProfileCondition" + } + ], + "$type": "CodecProfile" + }, + { "Type": "Video", "Conditions": [ { diff --git a/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-aac-srt-2600k.json b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-aac-srt-2600k.json new file mode 100644 index 000000000..9d819c4ad --- /dev/null +++ b/tests/Jellyfin.Model.Tests/Test Data/MediaSourceInfo-mp4-h264-ac3-aac-aac-srt-2600k.json @@ -0,0 +1,102 @@ +{ + "Id": "a766d122b58e45d9492d17af77748bf5", + "Path": "/Media/MyVideo-720p.mp4", + "Container": "mov,mp4,m4a,3gp,3g2,mj2", + "Size": 835317696, + "Name": "MyVideo-720p", + "ETag": "579a34c6d5dfb21d81539a51220b6a23", + "RunTimeTicks": 25801230336, + "SupportsTranscoding": true, + "SupportsDirectStream": true, + "SupportsDirectPlay": true, + "SupportsProbing": true, + "MediaStreams": [ + { + "Codec": "h264", + "CodecTag": "avc1", + "Language": "eng", + "TimeBase": "1/11988", + "VideoRange": "SDR", + "DisplayTitle": "720p H264 SDR", + "NalLengthSize": "0", + "BitRate": 2032876, + "BitDepth": 8, + "RefFrames": 1, + "IsDefault": true, + "Height": 720, + "Width": 1280, + "AverageFrameRate": 23.976, + "RealFrameRate": 23.976, + "Profile": "High", + "Type": 1, + "AspectRatio": "16:9", + "PixelFormat": "yuv420p", + "Level": 41 + }, + { + "Codec": "ac3", + "CodecTag": "ac-3", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - Dolby Digital - 5.1 - Default", + "ChannelLayout": "5.1", + "BitRate": 384000, + "Channels": 6, + "SampleRate": 48000, + "IsDefault": true, + "Index": 1, + "Score": 202 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "eng", + "TimeBase": "1/48000", + "DisplayTitle": "En - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": true, + "Profile": "LC", + "Index": 2, + "Score": 203 + }, + { + "Codec": "aac", + "CodecTag": "mp4a", + "Language": "rus", + "TimeBase": "1/48000", + "DisplayTitle": "Ru - AAC - Stereo - Default", + "ChannelLayout": "stereo", + "BitRate": 164741, + "Channels": 2, + "SampleRate": 48000, + "IsDefault": false, + "Profile": "LC", + "Index": 3, + "Score": 203 + }, + { + "Codec": "srt", + "Language": "eng", + "TimeBase": "1/1000000", + "localizedUndefined": "Undefined", + "localizedDefault": "Default", + "localizedForced": "Forced", + "DisplayTitle": "En - Default", + "BitRate": 92, + "IsDefault": true, + "Type": 2, + "Index": 4, + "Score": 6421, + "IsExternal": true, + "IsTextSubtitleStream": true, + "SupportsExternalStream": true, + "Path": "/Media/MyVideo-WEBDL-2160p.default.eng.srt" + } + ], + "Bitrate": 2590008, + "DefaultAudioStreamIndex": 1, + "DefaultSubtitleStreamIndex": 4 +} diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs index 62d60e5a4..5029a8793 100644 --- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs @@ -52,9 +52,8 @@ namespace Jellyfin.Naming.Tests.Video [InlineData("My Movie 2013-12-09", "My Movie 2013-12-09", null)] [InlineData("My Movie 20131209", "My Movie 20131209", null)] [InlineData("My Movie 2013-12-09 2013", "My Movie 2013-12-09", 2013)] - [InlineData(null, null, null)] [InlineData("", "", null)] - public void CleanDateTimeTest(string input, string expectedName, int? expectedYear) + public void CleanDateTimeTest(string input, string? expectedName, int? expectedYear) { input = Path.GetFileName(input); diff --git a/tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs b/tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs index 30726f1d3..f337fe20b 100644 --- a/tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs +++ b/tests/Jellyfin.Networking.Tests/Configuration/NetworkConfigurationTests.cs @@ -6,7 +6,6 @@ namespace Jellyfin.Networking.Tests.Configuration; public static class NetworkConfigurationTests { [Theory] - [InlineData("", null)] [InlineData("", "")] [InlineData("/Test", "/Test")] [InlineData("/Test", "Test")] diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs index 2bc686a33..85963e5de 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -55,7 +55,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo [InlineData("clearlogo.png", null, 1, ImageType.Logo, ImageFormat.Png)] // extract extension from name [InlineData("backdrop", "image/bmp", 2, ImageType.Backdrop, ImageFormat.Bmp)] // extract extension from mimetype [InlineData("poster", null, 3, ImageType.Primary, ImageFormat.Jpg)] // default extension to jpg - public async void GetImage_Attachment_ReturnsCorrectSelection(string filename, string mimetype, int targetIndex, ImageType type, ImageFormat? expectedFormat) + public async void GetImage_Attachment_ReturnsCorrectSelection(string filename, string? mimetype, int targetIndex, ImageType type, ImageFormat? expectedFormat) { var attachments = new List<MediaAttachment>(); string pathPrefix = "path"; @@ -103,7 +103,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo [InlineData(null, "mjpeg", 1, ImageType.Primary, ImageFormat.Jpg)] [InlineData(null, "png", 1, ImageType.Primary, ImageFormat.Png)] [InlineData(null, "webp", 1, ImageType.Primary, ImageFormat.Webp)] - public async void GetImage_Embedded_ReturnsCorrectSelection(string label, string? codec, int targetIndex, ImageType type, ImageFormat? expectedFormat) + public async void GetImage_Embedded_ReturnsCorrectSelection(string? label, string? codec, int targetIndex, ImageType type, ImageFormat? expectedFormat) { var streams = new List<MediaStream>(); for (int i = 1; i <= targetIndex; i++) diff --git a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs index efd2d9553..0bfa330cb 100644 --- a/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs +++ b/tests/Jellyfin.Providers.Tests/Tmdb/TmdbUtilsTests.cs @@ -25,14 +25,11 @@ namespace Jellyfin.Providers.Tests.Tmdb } [Theory] - [InlineData(null, null, null)] - [InlineData(null, "en-US", null)] - [InlineData("en", null, "en")] [InlineData("en", "en-US", "en-US")] [InlineData("fr-CA", "fr-BE", "fr-CA")] [InlineData("fr-CA", "fr", "fr-CA")] [InlineData("de", "en-US", "de")] - public static void AdjustImageLanguage_Valid_Success(string imageLanguage, string requestLanguage, string expected) + public static void AdjustImageLanguage_Valid_Success(string imageLanguage, string requestLanguage, string? expected) { Assert.Equal(expected, TmdbUtils.AdjustImageLanguage(imageLanguage, requestLanguage)); } diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs index 16202aea9..5aa7c04f6 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/AudioResolverTests.cs @@ -63,7 +63,7 @@ public class AudioResolverTests null, Mock.Of<ILibraryManager>()) { - CollectionType = CollectionType.Books, + CollectionType = CollectionType.books, FileInfo = new FileSystemMetadata { FullName = parent, diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs index 92bac722b..cc2e47c33 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs @@ -29,7 +29,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library null) { Parent = parent, - CollectionType = CollectionType.TvShows, + CollectionType = CollectionType.tvshows, FileInfo = new FileSystemMetadata { FullName = "All My Children/Season 01/Extras/All My Children S01E01 - Behind The Scenes.mkv" @@ -52,7 +52,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library null) { Parent = series, - CollectionType = CollectionType.TvShows, + CollectionType = CollectionType.tvshows, FileInfo = new FileSystemMetadata { FullName = "Extras/Extras S01E01.mkv" diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs index 1c35eb3f5..d1be07aa2 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs @@ -83,7 +83,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library [InlineData(@"\home/jeff\myfile.mkv", '\\', @"\home\jeff\myfile.mkv")] [InlineData(@"\home/jeff\myfile.mkv", '/', "/home/jeff/myfile.mkv")] [InlineData("", '/', "")] - public void NormalizePath_SpecifyingSeparator_Normalizes(string path, char separator, string expectedPath) + public void NormalizePath_SpecifyingSeparator_Normalizes(string? path, char separator, string? expectedPath) { Assert.Equal(expectedPath, path.NormalizePath(separator)); } diff --git a/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs index c32d89ea5..30f72f595 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/QuickConnect/QuickConnectManagerTests.cs @@ -85,10 +85,10 @@ namespace Jellyfin.Server.Implementations.Tests.QuickConnect } [Fact] - public void AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException() + public async Task AuthorizeRequest_QuickConnectUnavailable_ThrowsAuthenticationException() { _config.QuickConnectAvailable = false; - Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty)); + await Assert.ThrowsAsync<AuthenticationException>(() => _quickConnectManager.AuthorizeRequest(Guid.Empty, string.Empty)); } [Fact] diff --git a/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs index ebd3a3891..e463d838e 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/SessionManager/SessionManagerTests.cs @@ -21,7 +21,7 @@ public class SessionManagerTests [Theory] [InlineData("", typeof(ArgumentException))] [InlineData(null, typeof(ArgumentNullException))] - public async Task GetAuthorizationToken_Should_ThrowException(string deviceId, Type exceptionType) + public async Task GetAuthorizationToken_Should_ThrowException(string? deviceId, Type exceptionType) { await using var sessionManager = new Emby.Server.Implementations.Session.SessionManager( NullLogger<Emby.Server.Implementations.Session.SessionManager>.Instance, |
