From 1883ecd81716acb108424924711aed842dd6338a Mon Sep 17 00:00:00 2001 From: MichaIng Date: Fri, 24 Jul 2020 22:43:32 +0200 Subject: [PATCH 001/151] Fix left /usr/bin/jellyfin symlink on removal and typo After removal of the symlink target file "/usr/lib/jellyfin/bin/jellyfin", file existence check on the symlink "[[ -f /usr/bin/jellyfin ]]" returns false. As a result the symlink is left in place on package purge. The correct check would be "[[ -L /usr/bin/jellyfin ]]", but since it could be a file in cases, e.g. manual fix on file systems with no symlink support or for any other reason, it is easiest to use "rm -f" to assure that it is removed in both cases and not return false even if it does not exist at all. Additionally this fixes a typo on upstart script check. Signed-off-by: MichaIng --- debian/postrm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/postrm b/debian/postrm index 1d00a984ec..3d56a5f1e8 100644 --- a/debian/postrm +++ b/debian/postrm @@ -25,7 +25,7 @@ case "$1" in purge) echo PURGE | debconf-communicate $NAME > /dev/null 2>&1 || true - if [[ -x "/etc/init.d/jellyfin" ]] || [[ -e "/etc/init/jellyfin.connf" ]]; then + if [[ -x "/etc/init.d/jellyfin" ]] || [[ -e "/etc/init/jellyfin.conf" ]]; then update-rc.d jellyfin remove >/dev/null 2>&1 || true fi @@ -54,7 +54,7 @@ case "$1" in rm -rf $PROGRAMDATA fi # Remove binary symlink - [[ -f /usr/bin/jellyfin ]] && rm /usr/bin/jellyfin + rm -f /usr/bin/jellyfin # Remove sudoers config [[ -f /etc/sudoers.d/jellyfin-sudoers ]] && rm /etc/sudoers.d/jellyfin-sudoers # Remove anything at the default locations; catches situations where the user moved the defaults From a3020f2917176baef19653a485cc0aaaeb8d00f2 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 21 Aug 2020 19:53:55 +0200 Subject: [PATCH 002/151] Use backdrop with library name as library thumbnail --- Emby.Drawing/ImageProcessor.cs | 6 +- Emby.Drawing/NullImageEncoder.cs | 2 +- .../Images/BaseDynamicImageProvider.cs | 13 +++- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 4 +- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 60 ++++++++++--------- .../Drawing/IImageEncoder.cs | 3 +- .../Drawing/IImageProcessor.cs | 3 +- 7 files changed, 55 insertions(+), 36 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index f585b90ca1..e86c22fd6e 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -36,7 +36,7 @@ namespace Emby.Drawing private readonly IImageEncoder _imageEncoder; private readonly IMediaEncoder _mediaEncoder; - private bool _disposed = false; + private bool _disposed; /// /// Initializes a new instance of the class. @@ -466,11 +466,11 @@ namespace Emby.Drawing } /// - public void CreateImageCollage(ImageCollageOptions options) + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) { _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); - _imageEncoder.CreateImageCollage(options); + _imageEncoder.CreateImageCollage(options, libraryName); _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); } diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index bbb5c17162..2a1cfd3da5 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -38,7 +38,7 @@ namespace Emby.Drawing } /// - public void CreateImageCollage(ImageCollageOptions options) + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) { throw new NotImplementedException(); } diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 57302b5067..5f7e51858a 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -133,9 +133,20 @@ namespace Emby.Server.Implementations.Images protected virtual IEnumerable GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable items) { + var useBackdrop = primaryItem is CollectionFolder; return items .Select(i => { + // Use Backdrop instead of Primary image for Library images. + if (useBackdrop) + { + var backdrop = i.GetImageInfo(ImageType.Backdrop, 0); + if (backdrop != null && backdrop.IsLocalFile) + { + return backdrop.Path; + } + } + var image = i.GetImageInfo(ImageType.Primary, 0); if (image != null && image.IsLocalFile) { @@ -190,7 +201,7 @@ namespace Emby.Server.Implementations.Images return null; } - ImageProcessor.CreateImageCollage(options); + ImageProcessor.CreateImageCollage(options, primaryItem.Name); return outputPath; } diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index a1caa751b1..6a9dbdae42 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -553,13 +553,13 @@ namespace Jellyfin.Drawing.Skia } /// - public void CreateImageCollage(ImageCollageOptions options) + public void CreateImageCollage(ImageCollageOptions options, string? libraryName) { double ratio = (double)options.Width / options.Height; if (ratio >= 1.4) { - new StripCollageBuilder(this).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + new StripCollageBuilder(this).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height, libraryName); } else if (ratio >= .9) { diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 10bb59648f..302df6fcb9 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -82,48 +82,54 @@ namespace Jellyfin.Drawing.Skia /// The path at which to place the resulting image. /// The desired width of the collage. /// The desired height of the collage. - public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) + /// The name of the library to draw on the collage. + public void BuildThumbCollage(string[] paths, string outputPath, int width, int height, string? libraryName) { - using var bitmap = BuildThumbCollageBitmap(paths, width, height); + using var bitmap = BuildThumbCollageBitmap(paths, width, height, libraryName); using var outputStream = new SKFileWStream(outputPath); using var pixmap = new SKPixmap(new SKImageInfo(width, height), bitmap.GetPixels()); pixmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); } - private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height) + private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height, string? libraryName) { var bitmap = new SKBitmap(width, height); using var canvas = new SKCanvas(bitmap); canvas.Clear(SKColors.Black); - // number of images used in the thumbnail - var iCount = 3; - - // determine sizes for each image that will composited into the final image - var iSlice = Convert.ToInt32(width / iCount); - int iHeight = Convert.ToInt32(height * 1.00); - int imageIndex = 0; - for (int i = 0; i < iCount; i++) + using var backdrop = GetNextValidImage(paths, 0, out _); + if (backdrop == null) { - using var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex); - imageIndex = newIndex; - if (currentBitmap == null) - { - continue; - } - - // resize to the same aspect as the original - int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height); - using var resizedImage = SkiaEncoder.ResizeImage(currentBitmap, new SKImageInfo(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType, currentBitmap.ColorSpace)); - - // crop image - int ix = Math.Abs((iWidth - iSlice) / 2); - using var subset = resizedImage.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)); - // draw image onto canvas - canvas.DrawImage(subset ?? resizedImage, iSlice * i, 0); + return bitmap; } + // resize to the same aspect as the original + var backdropHeight = Math.Abs(width * backdrop.Height / backdrop.Width); + using var residedBackdrop = SkiaEncoder.ResizeImage(backdrop, new SKImageInfo(width, backdropHeight, backdrop.ColorType, backdrop.AlphaType, backdrop.ColorSpace)); + // draw the backdrop + canvas.DrawImage(residedBackdrop, 0, 0); + + // draw shadow rectangle + var paintColor = new SKPaint + { + Color = SKColors.Black.WithAlpha(0x78), + Style = SKPaintStyle.Fill + }; + canvas.DrawRect(0, 0, width, height, paintColor); + + // draw library name + var textPaint = new SKPaint + { + Color = SKColors.White, + Style = SKPaintStyle.Fill, + TextSize = 56, + TextAlign = SKTextAlign.Center, + Typeface = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright), + IsAntialias = true + }; + canvas.DrawText(libraryName, width / 2f, height / 2f, textPaint); + return bitmap; } diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index e09ccd204a..0f97f05688 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -61,6 +61,7 @@ namespace MediaBrowser.Controller.Drawing /// Create an image collage. /// /// The options to use when creating the collage. - void CreateImageCollage(ImageCollageOptions options); + /// Optional. + void CreateImageCollage(ImageCollageOptions options, string? libraryName); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 69d7991652..80687c9d04 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -85,7 +85,8 @@ namespace MediaBrowser.Controller.Drawing /// Creates the image collage. /// /// The options. - void CreateImageCollage(ImageCollageOptions options); + /// The library name to draw onto the collage. + void CreateImageCollage(ImageCollageOptions options, string? libraryName); bool SupportsTransparency(string path); } From d740e7aee6582d576c5bc2688df3c356203f5974 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 21 Aug 2020 20:36:56 +0200 Subject: [PATCH 003/151] Increase font size, center text --- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 302df6fcb9..09036b9f91 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -123,12 +123,12 @@ namespace Jellyfin.Drawing.Skia { Color = SKColors.White, Style = SKPaintStyle.Fill, - TextSize = 56, + TextSize = 112, TextAlign = SKTextAlign.Center, Typeface = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright), IsAntialias = true }; - canvas.DrawText(libraryName, width / 2f, height / 2f, textPaint); + canvas.DrawText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint); return bitmap; } From 9165dc3b3a94714da307079413f62326d5e585ef Mon Sep 17 00:00:00 2001 From: David Date: Sat, 22 Aug 2020 14:31:28 +0200 Subject: [PATCH 004/151] Scale down text if too long --- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 09036b9f91..0e94f87f6a 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -128,6 +128,14 @@ namespace Jellyfin.Drawing.Skia Typeface = SKTypeface.FromFamilyName("sans-serif", SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright), IsAntialias = true }; + + // scale down text to 90% of the width if text is larger than 95% of the width + var textWidth = textPaint.MeasureText(libraryName); + if (textWidth > width * 0.95) + { + textPaint.TextSize = 0.9f * width * textPaint.TextSize / textWidth; + } + canvas.DrawText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint); return bitmap; From 2a84d5a6933842e99b3b1f734e9a2b7241705a7f Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Mon, 31 Aug 2020 16:35:37 +0200 Subject: [PATCH 005/151] Enable nullable for interface --- MediaBrowser.Controller/Drawing/IImageEncoder.cs | 1 + MediaBrowser.Controller/Drawing/IImageProcessor.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 4e640d4215..770c6dc2d2 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#nullable enable using System; using System.Collections.Generic; diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index cab73ed0c8..935a790312 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#nullable enable using System; using System.Collections.Generic; @@ -75,7 +76,7 @@ namespace MediaBrowser.Controller.Drawing /// /// The options. /// Task. - Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options); + Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options); /// /// Gets the supported image output formats. From 320e3b98ab50e83932156b1a8ce7fc25685f7252 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 5 Sep 2020 23:32:21 -0600 Subject: [PATCH 006/151] Add ci task to publish api client --- .ci/azure-pipelines-api-client.yml | 72 +++++++++++++++++++ .gitignore | 1 + apiclient/.openapi-generator-ignore | 2 + .../templates/typescript/package.mustache | 30 ++++++++ apiclient/templates/typescript/stable.sh | 10 +++ apiclient/templates/typescript/unstable.sh | 10 +++ 6 files changed, 125 insertions(+) create mode 100644 .ci/azure-pipelines-api-client.yml create mode 100644 apiclient/.openapi-generator-ignore create mode 100644 apiclient/templates/typescript/package.mustache create mode 100644 apiclient/templates/typescript/stable.sh create mode 100644 apiclient/templates/typescript/unstable.sh diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml new file mode 100644 index 0000000000..babee15b5a --- /dev/null +++ b/.ci/azure-pipelines-api-client.yml @@ -0,0 +1,72 @@ +parameters: + - name: LinuxImage + type: string + default: "ubuntu-latest" + - name: GeneratorVersion + type: string + default: "4.3.1" + +jobs: +- job: GenerateApiClients + displayName: 'Generate Api Clients' + + pool: + vmImage: "${{ parameters.LinuxImage }}" + + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download OpenAPI Spec Artifact' + inputs: + source: "specific" + artifact: "OpenAPI Spec" + path: "$(System.ArtifactsDirectory)/openapi" + project: "$(System.TeamProjectId)" + pipeline: "29" # The main server CI build + runVersion: "latestFromBranch" + runBranch: "refs/heads/$(System.PullRequest.TargetBranch)" + + - task: CmdLine@2 + displayName: 'Download OpenApi Generator' + inputs: + scripts: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar" + +# Generate npm api client +# Unstable + - task: npmAuthenticate@0 + displayName: 'Authenticate to unstable npm feed' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + + - task: CmdLine@2 + displayName: 'Build unstable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + inputs: + script: 'bash ./apiclient/templates/typescript/unstable.sh axios' + + - task: Npm@1 + displayName: 'Publish unstable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + inputs: + command: publish + publishRegistry: useFeed + publishFeed: jellyfin/unstable + workingDir: ./apiclient/generated/typescript/axios + +# Stable + - task: npmAuthenticate@0 + displayName: 'Authenticate to stable npm feed' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + + - task: CmdLine@2 + displayName: 'Build stable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + inputs: + script: 'bash ./apiclient/templates/typescript/stable.sh axios' + + - task: Npm@1 + displayName: 'Publish stable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + inputs: + command: publish + publishRegistry: useExternalRegistry + publishEndpoint: + workingDir: ./apiclient/generated/typescript/axios diff --git a/.gitignore b/.gitignore index 0df7606ce9..7cd3d0068a 100644 --- a/.gitignore +++ b/.gitignore @@ -276,3 +276,4 @@ BenchmarkDotNet.Artifacts web/ web-src.* MediaBrowser.WebDashboard/jellyfin-web +apiclient/generated diff --git a/apiclient/.openapi-generator-ignore b/apiclient/.openapi-generator-ignore new file mode 100644 index 0000000000..f3802cf541 --- /dev/null +++ b/apiclient/.openapi-generator-ignore @@ -0,0 +1,2 @@ +# Prevent generator from creating these files: +git_push.sh diff --git a/apiclient/templates/typescript/package.mustache b/apiclient/templates/typescript/package.mustache new file mode 100644 index 0000000000..5127917a14 --- /dev/null +++ b/apiclient/templates/typescript/package.mustache @@ -0,0 +1,30 @@ +{ + "name": "jellyfin-apiclient-{{npmName}}", + "version": "10.7.0{{snapshotVersion}}", + "description": "Jellyfin api client using {{npmName}}", + "author": "Jellyfin Contributors", + "keywords": [ + "{{npmName}}", + "typescript", + "jellyfin" + ], + "license": "GPL-3.0-only", + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "scripts": { + "build": "tsc --outDir dist/", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "axios": "^0.19.2" + }, + "devDependencies": { + "@types/node": "^12.11.5", + "typescript": "^3.6.4" + }{{#npmRepository}},{{/npmRepository}} +{{#npmRepository}} + "publishConfig": { + "registry": "{{npmRepository}}" + } +{{/npmRepository}} +} diff --git a/apiclient/templates/typescript/stable.sh b/apiclient/templates/typescript/stable.sh new file mode 100644 index 0000000000..46af6433f4 --- /dev/null +++ b/apiclient/templates/typescript/stable.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +CLIENT=$1 +openapi-generator generate \ + --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ + --generator-name typescript-${CLIENT} \ + --output ./apiclient/generated/typescript/${CLIENT} \ + --template-dir ./apiclient/templates/typescript \ + --ignore-file-override ./apiclient/.openapi-generator-ignore \ + --additional-properties=useSingleRequestParameter="true",npmName="${CLIENT}" diff --git a/apiclient/templates/typescript/unstable.sh b/apiclient/templates/typescript/unstable.sh new file mode 100644 index 0000000000..255e20c4fc --- /dev/null +++ b/apiclient/templates/typescript/unstable.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +CLIENT=$1 +openapi-generator generate \ + --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ + --generator-name typescript-${CLIENT} \ + --output ./apiclient/generated/typescript/${CLIENT} \ + --template-dir ./apiclient/templates/typescript \ + --ignore-file-override ./apiclient/.openapi-generator-ignore \ + --additional-properties=useSingleRequestParameter="true",npmName="${CLIENT}",snapshotVersion="-SNAPSHOT.$(Build.BuildNumber)",npmRepository="https://dev.azure.com/jellyfin-project/jellyfin/_packaging" From 6ffffa95a7cfa092bc4dd1b75e25f1010d0d8855 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 6 Sep 2020 08:26:36 -0600 Subject: [PATCH 007/151] Use jar directly --- .ci/azure-pipelines.yml | 3 +++ apiclient/templates/typescript/stable.sh | 2 +- apiclient/templates/typescript/unstable.sh | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index b417aae678..f3e515447d 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -55,3 +55,6 @@ jobs: - ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: - template: azure-pipelines-package.yml + +- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: + - template: azure-pipelines-api-client.yml diff --git a/apiclient/templates/typescript/stable.sh b/apiclient/templates/typescript/stable.sh index 46af6433f4..f23a85cc96 100644 --- a/apiclient/templates/typescript/stable.sh +++ b/apiclient/templates/typescript/stable.sh @@ -1,7 +1,7 @@ #!/bin/bash CLIENT=$1 -openapi-generator generate \ +java -jar openapi-generator-cli.jar generate \ --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ --generator-name typescript-${CLIENT} \ --output ./apiclient/generated/typescript/${CLIENT} \ diff --git a/apiclient/templates/typescript/unstable.sh b/apiclient/templates/typescript/unstable.sh index 255e20c4fc..3571c8ad5c 100644 --- a/apiclient/templates/typescript/unstable.sh +++ b/apiclient/templates/typescript/unstable.sh @@ -1,7 +1,7 @@ #!/bin/bash CLIENT=$1 -openapi-generator generate \ +java -jar openapi-generator-cli.jar generate \ --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ --generator-name typescript-${CLIENT} \ --output ./apiclient/generated/typescript/${CLIENT} \ From c41ed13b3dd875498b5784f11250ae7f421d94e2 Mon Sep 17 00:00:00 2001 From: Jim Cartlidge Date: Sun, 13 Sep 2020 14:36:10 +0100 Subject: [PATCH 008/151] Commenting. --- .../MediaReceiverRegistrar/ControlHandler.cs | 18 ++- .../MediaReceiverRegistrarService.cs | 13 +- .../MediaReceiverRegistrarXmlBuilder.cs | 122 ++++++++++-------- .../ServiceActionListBuilder.cs | 48 ++++++- Emby.Dlna/Service/BaseControlHandler.cs | 20 +-- 5 files changed, 140 insertions(+), 81 deletions(-) diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index 8bf0cd961b..464f71a6f1 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Xml; @@ -10,8 +8,16 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar { + /// + /// Defines the . + /// public class ControlHandler : BaseControlHandler { + /// + /// Initializes a new instance of the class. + /// + /// The for use with the instance. + /// The for use with the instance. public ControlHandler(IServerConfigurationManager config, ILogger logger) : base(config, logger) { @@ -35,9 +41,17 @@ namespace Emby.Dlna.MediaReceiverRegistrar throw new ResourceNotFoundException("Unexpected control request name: " + methodName); } + /// + /// Records that the handle is authorized in the xml stream. + /// + /// The . private static void HandleIsAuthorized(XmlWriter xmlWriter) => xmlWriter.WriteElementString("Result", "1"); + /// + /// Records that the handle is validated in the xml stream. + /// + /// The . private static void HandleIsValidated(XmlWriter xmlWriter) => xmlWriter.WriteElementString("Result", "1"); } diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs index e6d845e1e4..a5aae515c4 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarService.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Net.Http; using System.Threading.Tasks; using Emby.Dlna.Service; @@ -8,10 +6,19 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar { + /// + /// Defines the . + /// public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar { private readonly IServerConfigurationManager _config; + /// + /// Initializes a new instance of the class. + /// + /// The for use with the instance. + /// The for use with the instance. + /// The for use with the instance. public MediaReceiverRegistrarService( ILogger logger, IHttpClientFactory httpClientFactory, @@ -24,7 +31,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar /// public string GetServiceXml() { - return new MediaReceiverRegistrarXmlBuilder().GetXml(); + return MediaReceiverRegistrarXmlBuilder.GetXml(); } /// diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs index 26994925d1..37840cd096 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs @@ -1,79 +1,89 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Emby.Dlna.Common; using Emby.Dlna.Service; +using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { - public class MediaReceiverRegistrarXmlBuilder + /// + /// Defines the . + /// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drmnd/5d37515e-7a63-4709-8258-8fd4e0ed4482. + /// + public static class MediaReceiverRegistrarXmlBuilder { - public string GetXml() + /// + /// Retrieves an XML description of the X_MS_MediaReceiverRegistrar. + /// + /// An XML representation of this service. + public static string GetXml() { - return new ServiceXmlBuilder().GetXml( - new ServiceActionListBuilder().GetActions(), - GetStateVariables()); + return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables()); } + /// + /// The a list of all the state variables for this invocation. + /// + /// The . private static IEnumerable GetStateVariables() { - var list = new List(); - - list.Add(new StateVariable + var list = new List { - Name = "AuthorizationGrantedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "AuthorizationGrantedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_DeviceID", - DataType = "string", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_DeviceID", + DataType = "string", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "AuthorizationDeniedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "AuthorizationDeniedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "ValidationSucceededUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "ValidationSucceededUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_RegistrationRespMsg", - DataType = "bin.base64", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_RegistrationRespMsg", + DataType = "bin.base64", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_RegistrationReqMsg", - DataType = "bin.base64", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_RegistrationReqMsg", + DataType = "bin.base64", + SendsEvents = false + }, - list.Add(new StateVariable - { - Name = "ValidationRevokedUpdateID", - DataType = "ui4", - SendsEvents = true - }); + new StateVariable + { + Name = "ValidationRevokedUpdateID", + DataType = "ui4", + SendsEvents = true + }, - list.Add(new StateVariable - { - Name = "A_ARG_TYPE_Result", - DataType = "int", - SendsEvents = false - }); + new StateVariable + { + Name = "A_ARG_TYPE_Result", + DataType = "int", + SendsEvents = false + } + }; return list; } diff --git a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs index 13545c6894..1dc9c79c14 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs @@ -1,13 +1,19 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using Emby.Dlna.Common; +using MediaBrowser.Model.Dlna; namespace Emby.Dlna.MediaReceiverRegistrar { - public class ServiceActionListBuilder + /// + /// Defines the . + /// + public static class ServiceActionListBuilder { - public IEnumerable GetActions() + /// + /// Returns a list of services that this instance provides. + /// + /// An . + public static IEnumerable GetActions() { return new[] { @@ -21,6 +27,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar }; } + /// + /// Returns the action details for "IsValidated". + /// + /// The . private static ServiceAction GetIsValidated() { var action = new ServiceAction @@ -43,6 +53,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// + /// Returns the action details for "IsAuthorized". + /// + /// The . private static ServiceAction GetIsAuthorized() { var action = new ServiceAction @@ -65,6 +79,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// + /// Returns the action details for "RegisterDevice". + /// + /// The . private static ServiceAction GetRegisterDevice() { var action = new ServiceAction @@ -87,6 +105,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } + /// + /// Returns the action details for "GetValidationSucceededUpdateID". + /// + /// The . private static ServiceAction GetGetValidationSucceededUpdateID() { var action = new ServiceAction @@ -103,7 +125,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetAuthorizationDeniedUpdateID() + /// + /// Returns the action details for "GetGetAuthorizationDeniedUpdateID". + /// + /// The . + private static ServiceAction GetGetAuthorizationDeniedUpdateID() { var action = new ServiceAction { @@ -119,7 +145,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetValidationRevokedUpdateID() + /// + /// Returns the action details for "GetValidationRevokedUpdateID". + /// + /// The . + private static ServiceAction GetGetValidationRevokedUpdateID() { var action = new ServiceAction { @@ -135,7 +165,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar return action; } - private ServiceAction GetGetAuthorizationGrantedUpdateID() + /// + /// Returns the action details for "GetAuthorizationGrantedUpdateID". + /// + /// The . + private static ServiceAction GetGetAuthorizationGrantedUpdateID() { var action = new ServiceAction { diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index d160e33393..36903b5ea8 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -60,10 +60,8 @@ namespace Emby.Dlna.Service Async = true }; - using (var reader = XmlReader.Create(streamReader, readerSettings)) - { - requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false); - } + using var reader = XmlReader.Create(streamReader, readerSettings); + requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false); } Logger.LogDebug("Received control request {0}", requestInfo.LocalName); @@ -124,10 +122,8 @@ namespace Emby.Dlna.Service { if (!reader.IsEmptyElement) { - using (var subReader = reader.ReadSubtree()) - { - return await ParseBodyTagAsync(subReader).ConfigureAwait(false); - } + using var subReader = reader.ReadSubtree(); + return await ParseBodyTagAsync(subReader).ConfigureAwait(false); } else { @@ -170,11 +166,9 @@ namespace Emby.Dlna.Service if (!reader.IsEmptyElement) { - using (var subReader = reader.ReadSubtree()) - { - await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); - return result; - } + using var subReader = reader.ReadSubtree(); + await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); + return result; } else { From db07510017dc589da38698fd5e5f9afc55092334 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 17 Sep 2020 19:16:23 +0800 Subject: [PATCH 009/151] add tonemap for AMD AMF --- .../MediaEncoding/EncodingHelper.cs | 228 ++++++++++-------- 1 file changed, 127 insertions(+), 101 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2c30ca4588..790b26f699 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -452,11 +452,13 @@ namespace MediaBrowser.Controller.MediaEncoding var arg = new StringBuilder(); var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + var isSwDecoder = string.IsNullOrEmpty(videoDecoder); + var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isNvencHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); @@ -517,11 +519,12 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + && (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder) + || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder)) { var isColorDepth10 = IsColorDepth10(state); - if (isNvencHevcDecoder && isColorDepth10 + if (isColorDepth10 && _mediaEncoder.SupportsHwaccel("opencl") && encodingOptions.EnableTonemapping && !string.IsNullOrEmpty(state.VideoStream.VideoRange) @@ -1023,19 +1026,19 @@ namespace MediaBrowser.Controller.MediaEncoding && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; } - if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)) { - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; var videoStream = state.VideoStream; var isColorDepth10 = IsColorDepth10(state); - if (videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1 - && isColorDepth10 + if (isColorDepth10 && _mediaEncoder.SupportsHwaccel("opencl") && encodingOptions.EnableTonemapping && !string.IsNullOrEmpty(videoStream.VideoRange) @@ -1652,47 +1655,7 @@ namespace MediaBrowser.Controller.MediaEncoding var outputSizeParam = ReadOnlySpan.Empty; var request = state.BaseRequest; - outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); - - // All possible beginning of video filters - // Don't break the order - string[] beginOfOutputSizeParam = new[] - { - // for tonemap_opencl - "hwupload,tonemap_opencl", - - // hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux) - "hwupload=extra_hw_frames", - - // vpp_qsv - "vpp", - - // hwdownload,format=p010le (hardware decode + software encode for vaapi) - "hwdownload", - - // format=nv12|vaapi,hwupload,scale_vaapi - "format", - - // bwdif,scale=expr - "bwdif", - - // yadif,scale=expr - "yadif", - - // scale=expr - "scale" - }; - - var index = -1; - foreach (var param in beginOfOutputSizeParam) - { - index = outputSizeParam.IndexOf(param, StringComparison.OrdinalIgnoreCase); - if (index != -1) - { - outputSizeParam = outputSizeParam.Slice(index); - break; - } - } + outputSizeParam = GetOutputSizeParamInternal(state, options, outputVideoCodec); var videoSizeParam = string.Empty; var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; @@ -2083,10 +2046,19 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam); } + public string GetOutputSizeParam( + EncodingJobInfo state, + EncodingOptions options, + string outputVideoCodec) + { + string filters = GetOutputSizeParamInternal(state, options, outputVideoCodec); + return string.IsNullOrEmpty(filters) ? string.Empty : " -vf \"" + filters + "\""; + } + /// /// If we're going to put a fixed size on the command line, this will calculate it. /// - public string GetOutputSizeParam( + public string GetOutputSizeParamInternal( EncodingJobInfo state, EncodingOptions options, string outputVideoCodec) @@ -2102,6 +2074,8 @@ namespace MediaBrowser.Controller.MediaEncoding var inputHeight = videoStream?.Height; var threeDFormat = state.MediaSource.Video3DFormat; + var isSwDecoder = string.IsNullOrEmpty(videoDecoder); + var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; @@ -2117,47 +2091,78 @@ namespace MediaBrowser.Controller.MediaEncoding // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30; - // Currently only with the use of NVENC decoder can we get a decent performance. - // Currently only the HEVC/H265 format is supported. - // NVIDIA Pascal and Turing or higher are recommended. - if (isNvdecHevcDecoder && isColorDepth10 - && _mediaEncoder.SupportsHwaccel("opencl") - && options.EnableTonemapping - && !string.IsNullOrEmpty(videoStream.VideoRange) - && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + var isScalingInAdvance = false; + var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); + var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + + if ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder) + || (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder)) { - var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; - - if (options.TonemappingParam != 0) + // Currently only with the use of NVENC decoder can we get a decent performance. + // Currently only the HEVC/H265 format is supported with NVDEC decoder. + // NVIDIA Pascal and Turing or higher are recommended. + // AMD Polaris and Vega or higher are recommended. + if (isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && options.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) { - parameters += ":param={4}"; - } + var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; - if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) - { - parameters += ":range={5}"; - } + if (options.TonemappingParam != 0) + { + parameters += ":param={4}"; + } - // Upload the HDR10 or HLG data to the OpenCL device, - // use tonemap_opencl filter for tone mapping, - // and then download the SDR data to memory. - filters.Add("hwupload"); - filters.Add( - string.Format( - CultureInfo.InvariantCulture, - parameters, - options.TonemappingAlgorithm, - options.TonemappingDesat, - options.TonemappingThreshold, - options.TonemappingPeak, - options.TonemappingParam, - options.TonemappingRange)); - filters.Add("hwdownload"); + if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) + { + parameters += ":range={5}"; + } - if (hasGraphicalSubs || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true) - || string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) - { - filters.Add("format=nv12"); + if (isSwDecoder || isD3d11vaDecoder) + { + isScalingInAdvance = true; + // Add scaling filter before tonemapping filter for performance. + filters.AddRange( + GetScalingFilters( + state, + inputWidth, + inputHeight, + threeDFormat, + videoDecoder, + outputVideoCodec, + request.Width, + request.Height, + request.MaxWidth, + request.MaxHeight)); + // Convert to hardware pixel format p010 when using SW decoder. + filters.Add("format=p010"); + } + + // Upload the HDR10 or HLG data to the OpenCL device, + // use tonemap_opencl filter for tone mapping, + // and then download the SDR data to memory. + filters.Add("hwupload"); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + parameters, + options.TonemappingAlgorithm, + options.TonemappingDesat, + options.TonemappingThreshold, + options.TonemappingPeak, + options.TonemappingParam, + options.TonemappingRange)); + filters.Add("hwdownload"); + + if (isLibX264Encoder + || hasGraphicalSubs + || (isNvdecHevcDecoder && isDeinterlaceHevc) + || (!isNvdecHevcDecoder && isDeinterlaceH264 || isDeinterlaceHevc)) + { + filters.Add("format=nv12"); + } } } @@ -2202,7 +2207,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add hardware deinterlace filter before scaling filter - if (state.DeInterlace("h264", true) || state.DeInterlace("avc", true)) + if (isDeinterlaceH264) { if (isVaapiH264Encoder) { @@ -2215,10 +2220,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add software deinterlace filter before scaling filter - if ((state.DeInterlace("h264", true) - || state.DeInterlace("avc", true) - || state.DeInterlace("h265", true) - || state.DeInterlace("hevc", true)) + if ((isDeinterlaceH264 || isDeinterlaceHevc) && !isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder) @@ -2242,7 +2244,21 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr - filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); + if (!isScalingInAdvance) + { + filters.AddRange( + GetScalingFilters( + state, + inputWidth, + inputHeight, + threeDFormat, + videoDecoder, + outputVideoCodec, + request.Width, + request.Height, + request.MaxWidth, + request.MaxHeight)); + } // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642) if (isVaapiH264Encoder) @@ -2275,7 +2291,7 @@ namespace MediaBrowser.Controller.MediaEncoding { output += string.Format( CultureInfo.InvariantCulture, - " -vf \"{0}\"", + "{0}", string.Join(",", filters)); } @@ -3069,21 +3085,31 @@ namespace MediaBrowser.Controller.MediaEncoding var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1); var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va"); - if ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) + if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { - if (isLinux) + // Currently there is no AMF decoder on Linux, only have h264 encoder. + if (isDxvaSupported && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) { - return "-hwaccel vaapi"; - } + if (isWindows && isWindows8orLater) + { + return "-hwaccel d3d11va"; + } - if (isWindows && isWindows8orLater) - { - return "-hwaccel d3d11va"; + if (isWindows && !isWindows8orLater) + { + return "-hwaccel dxva2"; + } } + } - if (isWindows && !isWindows8orLater) + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + if (IsVaapiSupported(state) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) { - return "-hwaccel dxva2"; + if (isLinux) + { + return "-hwaccel vaapi"; + } } } From 4db5700e18b98cb34784930707dc4ee2833865b6 Mon Sep 17 00:00:00 2001 From: Ryan Petris Date: Wed, 23 Sep 2020 14:12:26 -0700 Subject: [PATCH 010/151] Don't take a lock if there's no intention to manipulate the list of open streams. Instead, use a ConcurrentDictionary so that, in those situations, thread-safe access to the dictionary is ensured. --- .../Library/MediaSourceManager.cs | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 67cf8bf5ba..376a155705 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Library private readonly ILocalizationManager _localizationManager; private readonly IApplicationPaths _appPaths; - private readonly Dictionary _openStreams = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary _openStreams = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); private IMediaSourceProvider[] _providers; @@ -582,29 +583,20 @@ namespace Emby.Server.Implementations.Library mediaSource.InferTotalBitrate(); } - public async Task GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) + public Task GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) { - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + var info = _openStreams.Values.FirstOrDefault(i => { - var info = _openStreams.Values.FirstOrDefault(i => + var liveStream = i as ILiveStream; + if (liveStream != null) { - var liveStream = i as ILiveStream; - if (liveStream != null) - { - return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase); - } + return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase); + } - return false; - }); + return false; + }); - return info as IDirectStreamProvider; - } - finally - { - _liveStreamSemaphore.Release(); - } + return Task.FromResult(info as IDirectStreamProvider); } public async Task OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) @@ -793,29 +785,20 @@ namespace Emby.Server.Implementations.Library return new Tuple(info.MediaSource, info as IDirectStreamProvider); } - private async Task GetLiveStreamInfo(string id, CancellationToken cancellationToken) + private Task GetLiveStreamInfo(string id, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException(nameof(id)); } - await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - - try + if (_openStreams.TryGetValue(id, out ILiveStream info)) { - if (_openStreams.TryGetValue(id, out ILiveStream info)) - { - return info; - } - else - { - throw new ResourceNotFoundException(); - } + return Task.FromResult(info); } - finally + else { - _liveStreamSemaphore.Release(); + return Task.FromException(new ResourceNotFoundException()); } } @@ -844,7 +827,7 @@ namespace Emby.Server.Implementations.Library if (liveStream.ConsumerCount <= 0) { - _openStreams.Remove(id); + _openStreams.TryRemove(id, out _); _logger.LogInformation("Closing live stream {0}", id); From 293237b714835fadd0a7c387815d146a2e4b810b Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 18:40:10 +0100 Subject: [PATCH 011/151] Update BaseControlHandler.cs --- Emby.Dlna/Service/BaseControlHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index d160e33393..ea17773b1a 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -150,7 +150,7 @@ namespace Emby.Dlna.Service } } - return new ControlRequestInfo(); + throw new EndOfStreamException("Stream ended but no body tag found."); } private async Task ParseBodyTagAsync(XmlReader reader) From 12b5f1127e0bef04173a9ad0f80b13e2ec86552f Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 18:46:20 +0100 Subject: [PATCH 012/151] Update DescriptionXmlBuilder.cs --- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 1f429d0de3..98945c2443 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -235,13 +235,13 @@ namespace Emby.Dlna.Server .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty)) .Append(""); builder.Append("") - .Append(BuildUrl(service.ScpdUrl, true)) + .Append(BuildUrl(service.ScpdUrl)) .Append(""); builder.Append("") - .Append(BuildUrl(service.ControlUrl, true)) + .Append(BuildUrl(service.ControlUrl)) .Append(""); builder.Append("") - .Append(BuildUrl(service.EventSubUrl, true)) + .Append(BuildUrl(service.EventSubUrl)) .Append(""); builder.Append(""); From f3a90bab477ed7008c2569dcd3d89f21a92558fb Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 18:46:52 +0100 Subject: [PATCH 013/151] Update DescriptionXmlBuilder.cs --- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 98945c2443..573f7adc0c 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -249,14 +249,8 @@ namespace Emby.Dlna.Server builder.Append(""); } - - /// - /// Builds a valid url for inclusion in the xml. - /// - /// Url to include. - /// Optional. When set to true, the absolute url is always used. - /// The url to use for the element. - private string BuildUrl(string url, bool absoluteUrl = false) + + private string BuildUrl(string ure) { if (string.IsNullOrEmpty(url)) { @@ -267,7 +261,7 @@ namespace Emby.Dlna.Server url = "/dlna/" + _serverUdn + "/" + url; - if (EnableAbsoluteUrls || absoluteUrl) + if (EnableAbsoluteUrls) { url = _serverAddress.TrimEnd('/') + url; } From a52ab69e13d2b924522a21fa4da98f38ec298bd1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 18:47:24 +0100 Subject: [PATCH 014/151] Update DescriptionXmlBuilder.cs --- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 573f7adc0c..bca9e81cd0 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -249,8 +249,8 @@ namespace Emby.Dlna.Server builder.Append(""); } - - private string BuildUrl(string ure) + + private string BuildUrl(string url) { if (string.IsNullOrEmpty(url)) { From 63571578ae04ec3b16f8cc5c5b2f9252de78aeda Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 19:44:16 +0100 Subject: [PATCH 015/151] Update BaseControlHandler.cs --- Emby.Dlna/Service/BaseControlHandler.cs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index ea17773b1a..39004d3944 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -155,7 +155,7 @@ namespace Emby.Dlna.Service private async Task ParseBodyTagAsync(XmlReader reader) { - var result = new ControlRequestInfo(); + string namespaceURI = null, localName = null; await reader.MoveToContentAsync().ConfigureAwait(false); await reader.ReadAsync().ConfigureAwait(false); @@ -165,11 +165,12 @@ namespace Emby.Dlna.Service { if (reader.NodeType == XmlNodeType.Element) { - result.LocalName = reader.LocalName; - result.NamespaceURI = reader.NamespaceURI; + localName = reader.LocalName; + namespaceURI = reader.NamespaceURI; if (!reader.IsEmptyElement) { + var result = new ControlRequestInfo(localName, namespaceURI); using (var subReader = reader.ReadSubtree()) { await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); @@ -187,7 +188,12 @@ namespace Emby.Dlna.Service } } - return result; + if (localName != null && namespaceURI != null) + { + return new ControlRequestInfo(localName, namespaceURI); + } + + throw new EndOfStreamException("Stream ended but no control found."); } private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary headers) @@ -234,11 +240,18 @@ namespace Emby.Dlna.Service private class ControlRequestInfo { + public ControlRequestInfo(string localName, string namespaceUri) + { + LocalName = localName; + NamespaceURI = namespaceUri; + Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + public string LocalName { get; set; } public string NamespaceURI { get; set; } - public Dictionary Headers { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + public Dictionary Headers { get; } } } } From 75677791275792e8dcfae1e9361b93af75443a75 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Fri, 25 Sep 2020 19:51:37 +0100 Subject: [PATCH 016/151] Update BaseControlHandler.cs --- Emby.Dlna/Service/BaseControlHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 39004d3944..776882aa55 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -188,7 +188,7 @@ namespace Emby.Dlna.Service } } - if (localName != null && namespaceURI != null) + if (localName != null && namespaceURI != null) { return new ControlRequestInfo(localName, namespaceURI); } From 05fa95f149582b3de1881c42aaa0ac00408a9947 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sat, 26 Sep 2020 19:33:59 -0700 Subject: [PATCH 017/151] Increase scan speed for music libraries --- .../Resolvers/Audio/MusicAlbumResolver.cs | 70 ++++++++++--------- .../Resolvers/Audio/MusicArtistResolver.cs | 14 +++- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 79b6dded3b..ff485a18e7 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Emby.Naming.Audio; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -113,50 +115,50 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio IFileSystem fileSystem, ILibraryManager libraryManager) { + // check for audio files before digging down into directories + var firstAudioFile = list + .Where(fileSystemInfo => !fileSystemInfo.IsDirectory) + .FirstOrDefault(fileSystemInfo => libraryManager.IsAudioFile(fileSystemInfo.FullName)); + if (firstAudioFile != null) + { + // at least one audio file exists + return true; + } + + if (!allowSubfolders) + { + // not music since no audio file exists and we're not looking into subfolders + return false; + } + var discSubfolderCount = 0; var notMultiDisc = false; var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var parser = new AlbumParser(namingOptions); - foreach (var fileSystemInfo in list) + + var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory); + + var result = Parallel.ForEach(directories, (fileSystemInfo, state) => { - if (fileSystemInfo.IsDirectory) + var path = fileSystemInfo.FullName; + var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); + + if (hasMusic) { - if (allowSubfolders) + if (parser.IsMultiPart(path)) { - if (notMultiDisc) - { - continue; - } - - var path = fileSystemInfo.FullName; - var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); - - if (hasMusic) - { - if (parser.IsMultiPart(path)) - { - logger.LogDebug("Found multi-disc folder: " + path); - discSubfolderCount++; - } - else - { - // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album - notMultiDisc = true; - } - } + logger.LogDebug("Found multi-disc folder: " + path); + discSubfolderCount++; + } + else + { + // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album + notMultiDisc = true; + state.Stop(); } } - else - { - var fullName = fileSystemInfo.FullName; - - if (libraryManager.IsAudioFile(fullName)) - { - return true; - } - } - } + }); if (notMultiDisc) { diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 5f5cd0e928..e9e688fa67 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -94,7 +95,18 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); // If we contain an album assume we are an artist folder - return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null; + var directories = args.FileSystemChildren.Where(i => i.IsDirectory); + + var result = Parallel.ForEach(directories, (fileSystemInfo, state) => + { + if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, directoryService)) + { + // stop once we see a music album + state.Stop(); + } + }); + + return !result.IsCompleted ? new MusicArtist() : null; } } } From 72534f9d667f2457cbe96ea870d140528e366ba2 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Fri, 25 Sep 2020 09:25:59 +0200 Subject: [PATCH 018/151] Use SessionMessageType for WebSocket messages --- Emby.Dlna/PlayTo/PlayToController.cs | 8 +-- .../EntryPoints/LibraryChangedNotifier.cs | 7 +-- .../EntryPoints/RecordingNotifier.cs | 11 ++-- .../EntryPoints/UserDataChangeNotifier.cs | 2 +- .../HttpServer/WebSocketConnection.cs | 5 +- .../Session/SessionManager.cs | 28 +++++------ .../Session/SessionWebSocketListener.cs | 3 +- .../Session/WebSocketController.cs | 3 +- .../ActivityLogWebSocketListener.cs | 14 ++++-- .../ScheduledTasksWebSocketListener.cs | 14 ++++-- .../SessionInfoWebSocketListener.cs | 9 +++- .../Consumers/System/TaskCompletedNotifier.cs | 3 +- .../PluginInstallationCancelledNotifier.cs | 3 +- .../PluginInstallationFailedNotifier.cs | 3 +- .../Updates/PluginInstalledNotifier.cs | 3 +- .../Updates/PluginInstallingNotifier.cs | 3 +- .../Updates/PluginUninstalledNotifier.cs | 3 +- .../Consumers/Users/UserDeletedNotifier.cs | 3 +- .../Consumers/Users/UserUpdatedNotifier.cs | 3 +- .../Net/BasePeriodicWebSocketListener.cs | 27 +++++++--- .../Session/ISessionController.cs | 3 +- .../Session/ISessionManager.cs | 8 +-- MediaBrowser.Model/Net/WebSocketMessage.cs | 3 +- .../Session/SessionMessageType.cs | 50 +++++++++++++++++++ 24 files changed, 156 insertions(+), 63 deletions(-) create mode 100644 MediaBrowser.Model/Session/SessionMessageType.cs diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 460ac2d8d1..d09a11930d 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -811,7 +811,7 @@ namespace Emby.Dlna.PlayTo } /// - public Task SendMessage(string name, Guid messageId, T data, CancellationToken cancellationToken) + public Task SendMessage(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) { if (_disposed) { @@ -823,17 +823,17 @@ namespace Emby.Dlna.PlayTo return Task.CompletedTask; } - if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.Play) { return SendPlayCommand(data as PlayRequest, cancellationToken); } - if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.PlayState) { return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); } - if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.GeneralCommand) { return SendGeneralCommand(data as GeneralCommand, cancellationToken); } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index c9d21d9638..ff64e217a0 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -105,7 +106,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - _sessionManager.SendMessageToAdminSessions("RefreshProgress", dict, CancellationToken.None); + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None); } catch { @@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - _sessionManager.SendMessageToAdminSessions("RefreshProgress", collectionFolderDict, CancellationToken.None); + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None); } catch { @@ -345,7 +346,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - await _sessionManager.SendMessageToUserSessions(new List { userId }, "LibraryChanged", info, cancellationToken).ConfigureAwait(false); + await _sessionManager.SendMessageToUserSessions(new List { userId }, SessionMessageType.LibraryChanged, info, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 44d2580d68..824bb85f44 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -46,25 +47,25 @@ namespace Emby.Server.Implementations.EntryPoints private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs e) { - await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.SeriesTimerCreated, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs e) { - await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.TimerCreated, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs e) { - await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.SeriesTimerCancelled, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs e) { - await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.TimerCancelled, e.Argument).ConfigureAwait(false); } - private async Task SendMessage(string name, TimerEventInfo info) + private async Task SendMessage(SessionMessageType name, TimerEventInfo info) { var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList(); diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 3618b88c5d..ff09cc81ed 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.EntryPoints private Task SendNotifications(Guid userId, List changedItems, CancellationToken cancellationToken) { - return _sessionManager.SendMessageToUserSessions(new List { userId }, "UserDataChanged", () => GetUserDataChangeInfo(userId, changedItems), cancellationToken); + return _sessionManager.SendMessageToUserSessions(new List { userId }, SessionMessageType.UserDataChanged, () => GetUserDataChangeInfo(userId, changedItems), cancellationToken); } private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List changedItems) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 7eae4e7646..fed2addf80 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Json; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.HttpServer Connection = this }; - if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) + if (info.MessageType == SessionMessageType.KeepAlive) { await SendKeepAliveResponse().ConfigureAwait(false); } @@ -244,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer new WebSocketMessage { MessageId = Guid.NewGuid(), - MessageType = "KeepAlive" + MessageType = SessionMessageType.KeepAlive }, CancellationToken.None); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index e42d478533..df1b1e4525 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1064,10 +1064,10 @@ namespace Emby.Server.Implementations.Session AssertCanControl(session, controllingSession); } - return SendMessageToSession(session, "GeneralCommand", command, cancellationToken); + return SendMessageToSession(session, SessionMessageType.GeneralCommand, command, cancellationToken); } - private static async Task SendMessageToSession(SessionInfo session, string name, T data, CancellationToken cancellationToken) + private static async Task SendMessageToSession(SessionInfo session, SessionMessageType name, T data, CancellationToken cancellationToken) { var controllers = session.SessionControllers; var messageId = Guid.NewGuid(); @@ -1078,7 +1078,7 @@ namespace Emby.Server.Implementations.Session } } - private static Task SendMessageToSessions(IEnumerable sessions, string name, T data, CancellationToken cancellationToken) + private static Task SendMessageToSessions(IEnumerable sessions, SessionMessageType name, T data, CancellationToken cancellationToken) { IEnumerable GetTasks() { @@ -1178,7 +1178,7 @@ namespace Emby.Server.Implementations.Session } } - await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.Play, command, cancellationToken).ConfigureAwait(false); } /// @@ -1186,7 +1186,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false); } /// @@ -1194,7 +1194,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncPlayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false); } private IEnumerable TranslateItemForPlayback(Guid id, User user) @@ -1297,7 +1297,7 @@ namespace Emby.Server.Implementations.Session } } - return SendMessageToSession(session, "Playstate", command, cancellationToken); + return SendMessageToSession(session, SessionMessageType.PlayState, command, cancellationToken); } private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession) @@ -1322,7 +1322,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.RestartRequired, string.Empty, cancellationToken); } /// @@ -1334,7 +1334,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); } /// @@ -1348,7 +1348,7 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Beginning SendServerRestartNotification"); - return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.ServerRestarting, string.Empty, cancellationToken); } /// @@ -1866,7 +1866,7 @@ namespace Emby.Server.Implementations.Session } /// - public Task SendMessageToAdminSessions(string name, T data, CancellationToken cancellationToken) + public Task SendMessageToAdminSessions(SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1879,7 +1879,7 @@ namespace Emby.Server.Implementations.Session } /// - public Task SendMessageToUserSessions(List userIds, string name, Func dataFn, CancellationToken cancellationToken) + public Task SendMessageToUserSessions(List userIds, SessionMessageType name, Func dataFn, CancellationToken cancellationToken) { CheckDisposed(); @@ -1894,7 +1894,7 @@ namespace Emby.Server.Implementations.Session } /// - public Task SendMessageToUserSessions(List userIds, string name, T data, CancellationToken cancellationToken) + public Task SendMessageToUserSessions(List userIds, SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1903,7 +1903,7 @@ namespace Emby.Server.Implementations.Session } /// - public Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, CancellationToken cancellationToken) + public Task SendMessageToUserDeviceSessions(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 15c2af220d..a5f8479537 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -8,6 +8,7 @@ using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -316,7 +317,7 @@ namespace Emby.Server.Implementations.Session return webSocket.SendAsync( new WebSocketMessage { - MessageType = "ForceKeepAlive", + MessageType = SessionMessageType.ForceKeepAlive, Data = WebSocketLostTimeout }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 94604ca1e0..b986ffa1cd 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -65,7 +66,7 @@ namespace Emby.Server.Implementations.Session /// public Task SendMessage( - string name, + SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index 849b3b7095..77d55828d1 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.WebSocketListeners @@ -29,11 +30,14 @@ namespace Jellyfin.Api.WebSocketListeners _activityManager.EntryCreated += OnEntryCreated; } - /// - /// Gets the name. - /// - /// The name. - protected override string Name => "ActivityLogEntry"; + /// + protected override SessionMessageType Type => SessionMessageType.ActivityLogEntry; + + /// + protected override SessionMessageType StartType => SessionMessageType.ActivityLogEntryStart; + + /// + protected override SessionMessageType StopType => SessionMessageType.ActivityLogEntryStop; /// /// Gets the data to send. diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs index 8a966c1376..80314b9236 100644 --- a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Session; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -33,11 +34,14 @@ namespace Jellyfin.Api.WebSocketListeners _taskManager.TaskCompleted += OnTaskCompleted; } - /// - /// Gets the name. - /// - /// The name. - protected override string Name => "ScheduledTasksInfo"; + /// + protected override SessionMessageType Type => SessionMessageType.ScheduledTasksInfo; + + /// + protected override SessionMessageType StartType => SessionMessageType.ScheduledTasksInfoStart; + + /// + protected override SessionMessageType StopType => SessionMessageType.ScheduledTasksInfoStop; /// /// Gets the data to send. diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs index 1fb5dc412c..1cf43a0053 100644 --- a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.WebSocketListeners @@ -34,7 +35,13 @@ namespace Jellyfin.Api.WebSocketListeners } /// - protected override string Name => "Sessions"; + protected override SessionMessageType Type => SessionMessageType.Sessions; + + /// + protected override SessionMessageType StartType => SessionMessageType.SessionsStart; + + /// + protected override SessionMessageType StopType => SessionMessageType.SessionsStop; /// /// Gets the data to send. diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs index 80ed56cd8f..0993c6df7b 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using MediaBrowser.Model.Tasks; namespace Jellyfin.Server.Implementations.Events.Consumers.System @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System /// public async Task OnEvent(TaskCompletionEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("ScheduledTaskEnded", eventArgs.Result, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.ScheduledTaskEnded, eventArgs.Result, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs index 1c600683a9..1d790da6b2 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationCancelled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCancelled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs index ea0c878d42..a1faf18fc4 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// public async Task OnEvent(InstallationFailedEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationFailed", eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationFailed, eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs index 3dda5a04c4..bd1a714045 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// public async Task OnEvent(PluginInstalledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationCompleted", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCompleted, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs index f691d11a7d..b513ac64ae 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// public async Task OnEvent(PluginInstallingEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstalling", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstalling, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs index 709692f6bb..1fd7b9adfc 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// public async Task OnEvent(PluginUninstalledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PluginUninstalled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageUninstalled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs index 10367a939b..303e886210 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Events.Users; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Users { @@ -30,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users { await _sessionManager.SendMessageToUserSessions( new List { eventArgs.Argument.Id }, - "UserDeleted", + SessionMessageType.UserDeleted, eventArgs.Argument.Id.ToString("N", CultureInfo.InvariantCulture), CancellationToken.None).ConfigureAwait(false); } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs index 6081dd044f..a14911b94a 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs @@ -6,6 +6,7 @@ using Jellyfin.Data.Events.Users; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Users { @@ -33,7 +34,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users { await _sessionManager.SendMessageToUserSessions( new List { e.Argument.Id }, - "UserUpdated", + SessionMessageType.UserUpdated, _userManager.GetUserDto(e.Argument), CancellationToken.None).ConfigureAwait(false); } diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 916dea58be..28227603b2 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -8,6 +8,7 @@ using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Net @@ -28,10 +29,22 @@ namespace MediaBrowser.Controller.Net new List>(); /// - /// Gets the name. + /// Gets the type used for the messages sent to the client. /// - /// The name. - protected abstract string Name { get; } + /// The type. + protected abstract SessionMessageType Type { get; } + + /// + /// Gets the message type received from the client to start sending messages. + /// + /// The type. + protected abstract SessionMessageType StartType { get; } + + /// + /// Gets the message type received from the client to stop sending messages. + /// + /// The type. + protected abstract SessionMessageType StopType { get; } /// /// Gets the data to send. @@ -66,12 +79,12 @@ namespace MediaBrowser.Controller.Net throw new ArgumentNullException(nameof(message)); } - if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase)) + if (message.MessageType == StartType) { Start(message); } - if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase)) + if (message.MessageType == StopType) { Stop(message); } @@ -159,7 +172,7 @@ namespace MediaBrowser.Controller.Net new WebSocketMessage { MessageId = Guid.NewGuid(), - MessageType = Name, + MessageType = Type, Data = data }, cancellationToken).ConfigureAwait(false); @@ -176,7 +189,7 @@ namespace MediaBrowser.Controller.Net } catch (Exception ex) { - Logger.LogError(ex, "Error sending web socket message {Name}", Name); + Logger.LogError(ex, "Error sending web socket message {Name}", Type); DisposeConnection(tuple); } } diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index 22d6e2a04e..bc4ccd44ca 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Session; namespace MediaBrowser.Controller.Session { @@ -23,6 +24,6 @@ namespace MediaBrowser.Controller.Session /// /// Sends the message. /// - Task SendMessage(string name, Guid messageId, T data, CancellationToken cancellationToken); + Task SendMessage(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 228b2331dc..04c3004ee6 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -188,16 +188,16 @@ namespace MediaBrowser.Controller.Session /// The data. /// The cancellation token. /// Task. - Task SendMessageToAdminSessions(string name, T data, CancellationToken cancellationToken); + Task SendMessageToAdminSessions(SessionMessageType name, T data, CancellationToken cancellationToken); /// /// Sends the message to user sessions. /// /// /// Task. - Task SendMessageToUserSessions(List userIds, string name, T data, CancellationToken cancellationToken); + Task SendMessageToUserSessions(List userIds, SessionMessageType name, T data, CancellationToken cancellationToken); - Task SendMessageToUserSessions(List userIds, string name, Func dataFn, CancellationToken cancellationToken); + Task SendMessageToUserSessions(List userIds, SessionMessageType name, Func dataFn, CancellationToken cancellationToken); /// /// Sends the message to user device sessions. @@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Session /// The data. /// The cancellation token. /// Task. - Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, CancellationToken cancellationToken); + Task SendMessageToUserDeviceSessions(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken); /// /// Sends the restart required message. diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs index 660eebeda6..bffbbe612d 100644 --- a/MediaBrowser.Model/Net/WebSocketMessage.cs +++ b/MediaBrowser.Model/Net/WebSocketMessage.cs @@ -2,6 +2,7 @@ #pragma warning disable CS1591 using System; +using MediaBrowser.Model.Session; namespace MediaBrowser.Model.Net { @@ -15,7 +16,7 @@ namespace MediaBrowser.Model.Net /// Gets or sets the type of the message. /// /// The type of the message. - public string MessageType { get; set; } + public SessionMessageType MessageType { get; set; } public Guid MessageId { get; set; } diff --git a/MediaBrowser.Model/Session/SessionMessageType.cs b/MediaBrowser.Model/Session/SessionMessageType.cs new file mode 100644 index 0000000000..23c41026dc --- /dev/null +++ b/MediaBrowser.Model/Session/SessionMessageType.cs @@ -0,0 +1,50 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Model.Session +{ + /// + /// The different kinds of messages that are used in the WebSocket api. + /// + public enum SessionMessageType + { + // Server -> Client + ForceKeepAlive, + GeneralCommand, + UserDataChanged, + Sessions, + Play, + SyncPlayCommand, + SyncPlayGroupUpdate, + PlayState, + RestartRequired, + ServerShuttingDown, + ServerRestarting, + LibraryChanged, + UserDeleted, + UserUpdated, + SeriesTimerCreated, + TimerCreated, + SeriesTimerCancelled, + TimerCancelled, + RefreshProgress, + ScheduledTaskEnded, + PackageInstallationCancelled, + PackageInstallationFailed, + PackageInstallationCompleted, + PackageInstalling, + PackageUninstalled, + ActivityLogEntry, + ScheduledTasksInfo, + + // Client -> Server + ActivityLogEntryStart, + ActivityLogEntryStop, + SessionsStart, + SessionsStop, + ScheduledTasksInfoStart, + ScheduledTasksInfoStop, + + // Shared + KeepAlive, + } +} From ac790cd77b81a8235f6d1faf9512c85c96fcd088 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 27 Sep 2020 09:45:11 -0600 Subject: [PATCH 019/151] Properly handle null structs in json --- .../Converters/JsonNullableStructConverter.cs | 39 ++++++++++--------- .../JsonNullableStructConverterFactory.cs | 27 +++++++++++++ MediaBrowser.Common/Json/JsonDefaults.cs | 7 +--- 3 files changed, 48 insertions(+), 25 deletions(-) create mode 100644 MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs index cffc41ba34..0501f7b2a1 100644 --- a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs @@ -8,37 +8,38 @@ namespace MediaBrowser.Common.Json.Converters /// Converts a nullable struct or value to/from JSON. /// Required - some clients send an empty string. /// - /// The struct type. - public class JsonNullableStructConverter : JsonConverter - where T : struct + /// The struct type. + public class JsonNullableStructConverter : JsonConverter + where TStruct : struct { - private readonly JsonConverter _baseJsonConverter; - - /// - /// Initializes a new instance of the class. - /// - /// The base json converter. - public JsonNullableStructConverter(JsonConverter baseJsonConverter) - { - _baseJsonConverter = baseJsonConverter; - } - /// - public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TStruct? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - // Handle empty string. + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + // Token is empty string. if (reader.TokenType == JsonTokenType.String && ((reader.HasValueSequence && reader.ValueSequence.IsEmpty) || reader.ValueSpan.IsEmpty)) { return null; } - return _baseJsonConverter.Read(ref reader, typeToConvert, options); + return JsonSerializer.Deserialize(ref reader, options); } /// - public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TStruct? value, JsonSerializerOptions options) { - _baseJsonConverter.Write(writer, value, options); + if (value.HasValue) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + else + { + writer.WriteNullValue(); + } } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs new file mode 100644 index 0000000000..d5b54e3ca8 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs @@ -0,0 +1,27 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Json nullable struct converter factory. + /// + public class JsonNullableStructConverterFactory : JsonConverterFactory + { + /// + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsGenericType + && typeToConvert.GetGenericTypeDefinition() == typeof(Nullable<>) + && typeToConvert.GenericTypeArguments[0].IsValueType; + } + + /// + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var structType = typeToConvert.GenericTypeArguments[0]; + return (JsonConverter)Activator.CreateInstance(typeof(JsonNullableStructConverter<>).MakeGenericType(structType)); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 67f7e8f14a..6605ae9624 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -39,14 +39,9 @@ namespace MediaBrowser.Common.Json NumberHandling = JsonNumberHandling.AllowReadingFromString }; - // Get built-in converters for fallback converting. - var baseNullableInt32Converter = (JsonConverter)options.GetConverter(typeof(int?)); - var baseNullableInt64Converter = (JsonConverter)options.GetConverter(typeof(long?)); - options.Converters.Add(new JsonGuidConverter()); options.Converters.Add(new JsonStringEnumConverter()); - options.Converters.Add(new JsonNullableStructConverter(baseNullableInt32Converter)); - options.Converters.Add(new JsonNullableStructConverter(baseNullableInt64Converter)); + options.Converters.Add(new JsonNullableStructConverterFactory()); return options; } From 75041e7f39f76a39fcfae15d6cd2aee510b4d599 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 12:56:12 -0700 Subject: [PATCH 020/151] interlocked increment --- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index ff485a18e7..d690a383d3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Emby.Naming.Audio; using MediaBrowser.Controller.Entities.Audio; @@ -132,7 +133,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio } var discSubfolderCount = 0; - var notMultiDisc = false; var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var parser = new AlbumParser(namingOptions); @@ -149,18 +149,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio if (parser.IsMultiPart(path)) { logger.LogDebug("Found multi-disc folder: " + path); - discSubfolderCount++; + Interlocked.Increment(ref discSubfolderCount); } else { // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album - notMultiDisc = true; state.Stop(); } } }); - if (notMultiDisc) + if (!result.IsCompleted) { return false; } From 3cfbe6e3401546a56abc826736578d9bfa054d99 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 13:28:19 -0700 Subject: [PATCH 021/151] better audio file check --- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index d690a383d3..18ceb5e761 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -117,10 +117,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio ILibraryManager libraryManager) { // check for audio files before digging down into directories - var firstAudioFile = list - .Where(fileSystemInfo => !fileSystemInfo.IsDirectory) - .FirstOrDefault(fileSystemInfo => libraryManager.IsAudioFile(fileSystemInfo.FullName)); - if (firstAudioFile != null) + var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && libraryManager.IsAudioFile(fileSystemInfo.FullName)); + if (foundAudioFile) { // at least one audio file exists return true; From 303eccaffeb42eb39b04f3ed372dc9fe84de5455 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 16:22:52 -0700 Subject: [PATCH 022/151] Fix InvalidOperationException in TvdbSeriesProvider --- .../Plugins/TheTvdb/TvdbSeriesProvider.cs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index ca9b1d738f..b637fc8b29 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb await _tvdbClientManager .GetSeriesByIdAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken) .ConfigureAwait(false); - MapSeriesToResult(result, seriesResult.Data, metadataLanguage); + await MapSeriesToResult(result, seriesResult.Data, metadataLanguage).ConfigureAwait(false); } catch (TvDbServerException e) { @@ -297,7 +297,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb return name.Trim(); } - private void MapSeriesToResult(MetadataResult result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage) + private async Task MapSeriesToResult(MetadataResult result, TvDbSharper.Dto.Series tvdbSeries, string metadataLanguage) { Series series = result.Item; series.SetProviderId(MetadataProvider.Tvdb, tvdbSeries.Id.ToString()); @@ -340,20 +340,25 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb { try { - var episodeSummary = _tvdbClientManager - .GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).Result.Data; - var maxSeasonNumber = episodeSummary.AiredSeasons.Select(s => Convert.ToInt32(s)).Max(); - var episodeQuery = new EpisodeQuery + var episodeSummary = await _tvdbClientManager.GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).ConfigureAwait(false); + + if (episodeSummary.Data.AiredSeasons.Length > 0) { - AiredSeason = maxSeasonNumber - }; - var episodesPage = - _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).Result.Data; - result.Item.EndDate = episodesPage.Select(e => + var maxSeasonNumber = episodeSummary.Data.AiredSeasons.Select(s => Convert.ToInt32(s)).Max(); + var episodeQuery = new EpisodeQuery { - DateTime.TryParse(e.FirstAired, out var firstAired); - return firstAired; - }).Max(); + AiredSeason = maxSeasonNumber + }; + var episodesPage = await _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).ConfigureAwait(false); + + var episodeDates = episodesPage.Data + .Select(e => DateTime.TryParse(e.FirstAired, out var firstAired) ? firstAired : (DateTime?)null) + .Where(dt => dt.HasValue); + if (episodeDates.Any()) + { + result.Item.EndDate = episodeDates.Max().Value; + } + } } catch (TvDbServerException e) { From 881a7fa90805ac96d1e43c426c50dbe575297a9b Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 16:57:43 -0700 Subject: [PATCH 023/151] update based on suggestions --- .../Plugins/TheTvdb/TvdbSeriesProvider.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index b637fc8b29..cdb159c0f0 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net.Http; using System.Text; @@ -342,9 +343,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb { var episodeSummary = await _tvdbClientManager.GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).ConfigureAwait(false); - if (episodeSummary.Data.AiredSeasons.Length > 0) + if (episodeSummary.Data.AiredSeasons.Length != 0) { - var maxSeasonNumber = episodeSummary.Data.AiredSeasons.Select(s => Convert.ToInt32(s)).Max(); + var maxSeasonNumber = episodeSummary.Data.AiredSeasons.Max(s => Convert.ToInt32(s, CultureInfo.InvariantCulture)); var episodeQuery = new EpisodeQuery { AiredSeason = maxSeasonNumber @@ -353,10 +354,11 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb var episodeDates = episodesPage.Data .Select(e => DateTime.TryParse(e.FirstAired, out var firstAired) ? firstAired : (DateTime?)null) - .Where(dt => dt.HasValue); - if (episodeDates.Any()) + .Where(dt => dt.HasValue) + .ToList(); + if (episodeDates.Count != 0) { - result.Item.EndDate = episodeDates.Max().Value; + result.Item.EndDate = episodeDates.Max(); } } } From 3ca9b13f99a863fee2a2b335555311cc1e9665aa Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 21:47:30 -0700 Subject: [PATCH 024/151] Check response status code before saving images --- MediaBrowser.Providers/Manager/ProviderManager.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index b6fb4267f4..6674ca6761 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -158,6 +158,14 @@ namespace MediaBrowser.Providers.Manager var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); + if (response.StatusCode != HttpStatusCode.OK) + { + throw new HttpException($"Invalid image received ({response.StatusCode}).") + { + StatusCode = response.StatusCode + }; + } + var contentType = response.Content.Headers.ContentType.MediaType; // Workaround for tvheadend channel icons From 722ec43e258eb9e6af4c0cd09f68a4a969e50cc8 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Sun, 27 Sep 2020 23:05:43 -0700 Subject: [PATCH 025/151] remove status code from exception message --- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 6674ca6761..a0c7d4ad09 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.Manager if (response.StatusCode != HttpStatusCode.OK) { - throw new HttpException($"Invalid image received ({response.StatusCode}).") + throw new HttpException("Invalid image received.") { StatusCode = response.StatusCode }; From e9911b70ddfd9a9ee1f9b89579f5dbec154129a4 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Mon, 28 Sep 2020 15:37:10 -0700 Subject: [PATCH 026/151] Use EnsureSuccessStatusCode --- MediaBrowser.Providers/Manager/ProviderManager.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index a0c7d4ad09..40fcf7d6f4 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -157,14 +157,7 @@ namespace MediaBrowser.Providers.Manager { var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); - - if (response.StatusCode != HttpStatusCode.OK) - { - throw new HttpException("Invalid image received.") - { - StatusCode = response.StatusCode - }; - } + response.EnsureSuccessStatusCode(); var contentType = response.Content.Headers.ContentType.MediaType; From fd7a36932da16fb1e85160aaa0da792303a4e72f Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Mon, 28 Sep 2020 15:31:28 -0700 Subject: [PATCH 027/151] simplify EndDate query --- .../Plugins/TheTvdb/TvdbSeriesProvider.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index cdb159c0f0..b34e52235a 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -352,14 +352,9 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb }; var episodesPage = await _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).ConfigureAwait(false); - var episodeDates = episodesPage.Data + result.Item.EndDate = episodesPage.Data .Select(e => DateTime.TryParse(e.FirstAired, out var firstAired) ? firstAired : (DateTime?)null) - .Where(dt => dt.HasValue) - .ToList(); - if (episodeDates.Count != 0) - { - result.Item.EndDate = episodeDates.Max(); - } + .Max(); } } catch (TvDbServerException e) From 25d8d85740f984f051cebc5db53466c17688c370 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Tue, 29 Sep 2020 01:19:12 -0700 Subject: [PATCH 028/151] Back to HttpException --- MediaBrowser.Providers/Manager/ProviderManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 40fcf7d6f4..a0c7d4ad09 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -157,7 +157,14 @@ namespace MediaBrowser.Providers.Manager { var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); + + if (response.StatusCode != HttpStatusCode.OK) + { + throw new HttpException("Invalid image received.") + { + StatusCode = response.StatusCode + }; + } var contentType = response.Content.Headers.ContentType.MediaType; From 3b0c6c660d057336e26aa67cb36abaaccb00771d Mon Sep 17 00:00:00 2001 From: Justin LeCheminant Date: Wed, 30 Sep 2020 20:26:59 -0600 Subject: [PATCH 029/151] Removing string we don't use anymore. --- Emby.Server.Implementations/Localization/Core/af.json | 1 - Emby.Server.Implementations/Localization/Core/ar.json | 1 - Emby.Server.Implementations/Localization/Core/bg-BG.json | 1 - Emby.Server.Implementations/Localization/Core/bn.json | 1 - Emby.Server.Implementations/Localization/Core/ca.json | 1 - Emby.Server.Implementations/Localization/Core/cs.json | 1 - Emby.Server.Implementations/Localization/Core/da.json | 1 - Emby.Server.Implementations/Localization/Core/de.json | 1 - Emby.Server.Implementations/Localization/Core/el.json | 1 - Emby.Server.Implementations/Localization/Core/en-GB.json | 1 - Emby.Server.Implementations/Localization/Core/en-US.json | 1 - Emby.Server.Implementations/Localization/Core/es-AR.json | 1 - Emby.Server.Implementations/Localization/Core/es-MX.json | 1 - Emby.Server.Implementations/Localization/Core/es.json | 1 - Emby.Server.Implementations/Localization/Core/es_419.json | 1 - Emby.Server.Implementations/Localization/Core/es_DO.json | 1 - Emby.Server.Implementations/Localization/Core/fa.json | 1 - Emby.Server.Implementations/Localization/Core/fi.json | 1 - Emby.Server.Implementations/Localization/Core/fil.json | 1 - Emby.Server.Implementations/Localization/Core/fr-CA.json | 1 - Emby.Server.Implementations/Localization/Core/fr.json | 1 - Emby.Server.Implementations/Localization/Core/gsw.json | 1 - Emby.Server.Implementations/Localization/Core/he.json | 1 - Emby.Server.Implementations/Localization/Core/hr.json | 1 - Emby.Server.Implementations/Localization/Core/hu.json | 1 - Emby.Server.Implementations/Localization/Core/id.json | 1 - Emby.Server.Implementations/Localization/Core/is.json | 1 - Emby.Server.Implementations/Localization/Core/it.json | 1 - Emby.Server.Implementations/Localization/Core/ja.json | 1 - Emby.Server.Implementations/Localization/Core/kk.json | 1 - Emby.Server.Implementations/Localization/Core/ko.json | 1 - Emby.Server.Implementations/Localization/Core/lt-LT.json | 1 - Emby.Server.Implementations/Localization/Core/lv.json | 1 - Emby.Server.Implementations/Localization/Core/mk.json | 1 - Emby.Server.Implementations/Localization/Core/mr.json | 1 - Emby.Server.Implementations/Localization/Core/ms.json | 1 - Emby.Server.Implementations/Localization/Core/nb.json | 1 - Emby.Server.Implementations/Localization/Core/ne.json | 1 - Emby.Server.Implementations/Localization/Core/nl.json | 1 - Emby.Server.Implementations/Localization/Core/nn.json | 1 - Emby.Server.Implementations/Localization/Core/pl.json | 1 - Emby.Server.Implementations/Localization/Core/pt-BR.json | 1 - Emby.Server.Implementations/Localization/Core/pt-PT.json | 1 - Emby.Server.Implementations/Localization/Core/pt.json | 1 - Emby.Server.Implementations/Localization/Core/ro.json | 1 - Emby.Server.Implementations/Localization/Core/ru.json | 1 - Emby.Server.Implementations/Localization/Core/sk.json | 1 - Emby.Server.Implementations/Localization/Core/sl-SI.json | 1 - Emby.Server.Implementations/Localization/Core/sq.json | 1 - Emby.Server.Implementations/Localization/Core/sr.json | 1 - Emby.Server.Implementations/Localization/Core/sv.json | 1 - Emby.Server.Implementations/Localization/Core/ta.json | 1 - Emby.Server.Implementations/Localization/Core/th.json | 1 - Emby.Server.Implementations/Localization/Core/tr.json | 1 - Emby.Server.Implementations/Localization/Core/uk.json | 1 - Emby.Server.Implementations/Localization/Core/ur_PK.json | 1 - Emby.Server.Implementations/Localization/Core/vi.json | 1 - Emby.Server.Implementations/Localization/Core/zh-CN.json | 1 - Emby.Server.Implementations/Localization/Core/zh-HK.json | 1 - Emby.Server.Implementations/Localization/Core/zh-TW.json | 1 - 60 files changed, 60 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json index d33e118931..977a1c2d70 100644 --- a/Emby.Server.Implementations/Localization/Core/af.json +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -85,7 +85,6 @@ "ItemAddedWithName": "{0} is in die versameling", "HomeVideos": "Tuis opnames", "HeaderRecordingGroups": "Groep Opnames", - "HeaderCameraUploads": "Kamera Oplaai", "Genres": "Genres", "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}", "ChapterNameValue": "Hoofstuk", diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index 4eac8e75de..4b898e6fe0 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -16,7 +16,6 @@ "Folders": "المجلدات", "Genres": "التضنيفات", "HeaderAlbumArtists": "فناني الألبومات", - "HeaderCameraUploads": "تحميلات الكاميرا", "HeaderContinueWatching": "استئناف", "HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteArtists": "الفنانون المفضلون", diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index 3fc7c7dc0f..1fed832768 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -16,7 +16,6 @@ "Folders": "Папки", "Genres": "Жанрове", "HeaderAlbumArtists": "Изпълнители на албуми", - "HeaderCameraUploads": "Качени от камера", "HeaderContinueWatching": "Продължаване на гледането", "HeaderFavoriteAlbums": "Любими албуми", "HeaderFavoriteArtists": "Любими изпълнители", diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index 1bd1909827..5667bf337e 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -14,7 +14,6 @@ "HeaderFavoriteArtists": "প্রিয় শিল্পীরা", "HeaderFavoriteAlbums": "প্রিয় এলবামগুলো", "HeaderContinueWatching": "দেখতে থাকুন", - "HeaderCameraUploads": "ক্যামেরার আপলোড সমূহ", "HeaderAlbumArtists": "এলবাম শিল্পী", "Genres": "জেনার", "Folders": "ফোল্ডারগুলো", diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index 2c802a39ef..b7852eccb3 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -16,7 +16,6 @@ "Folders": "Carpetes", "Genres": "Gèneres", "HeaderAlbumArtists": "Artistes del Àlbum", - "HeaderCameraUploads": "Pujades de Càmera", "HeaderContinueWatching": "Continua Veient", "HeaderFavoriteAlbums": "Àlbums Preferits", "HeaderFavoriteArtists": "Artistes Preferits", diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 464ca28ca0..b34fad066a 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -16,7 +16,6 @@ "Folders": "Složky", "Genres": "Žánry", "HeaderAlbumArtists": "Umělci alba", - "HeaderCameraUploads": "Nahrané fotografie", "HeaderContinueWatching": "Pokračovat ve sledování", "HeaderFavoriteAlbums": "Oblíbená alba", "HeaderFavoriteArtists": "Oblíbení interpreti", diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index f5397b62cd..b29ad94eff 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -16,7 +16,6 @@ "Folders": "Mapper", "Genres": "Genrer", "HeaderAlbumArtists": "Albumkunstnere", - "HeaderCameraUploads": "Kamera Uploads", "HeaderContinueWatching": "Fortsæt Afspilning", "HeaderFavoriteAlbums": "Favoritalbummer", "HeaderFavoriteArtists": "Favoritkunstnere", diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index fcbe9566e2..97ad1694a6 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -16,7 +16,6 @@ "Folders": "Verzeichnisse", "Genres": "Genres", "HeaderAlbumArtists": "Album-Interpreten", - "HeaderCameraUploads": "Kamera-Uploads", "HeaderContinueWatching": "Fortsetzen", "HeaderFavoriteAlbums": "Lieblingsalben", "HeaderFavoriteArtists": "Lieblings-Interpreten", diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index 0753ea39d4..c45cc11cb8 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -16,7 +16,6 @@ "Folders": "Φάκελοι", "Genres": "Είδη", "HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ", - "HeaderCameraUploads": "Μεταφορτώσεις Κάμερας", "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 544c38cfa9..57ff13219f 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -16,7 +16,6 @@ "Folders": "Folders", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", "HeaderContinueWatching": "Continue Watching", "HeaderFavoriteAlbums": "Favourite Albums", "HeaderFavoriteArtists": "Favourite Artists", diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 97a843160e..92c54fb0e6 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -16,7 +16,6 @@ "Folders": "Folders", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", "HeaderContinueWatching": "Continue Watching", "HeaderFavoriteAlbums": "Favorite Albums", "HeaderFavoriteArtists": "Favorite Artists", diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index ac96c788c1..390074cdd7 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -16,7 +16,6 @@ "Folders": "Carpetas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas de álbum", - "HeaderCameraUploads": "Subidas de cámara", "HeaderContinueWatching": "Seguir viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 4ba324aa1b..ab54c0ea6a 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -16,7 +16,6 @@ "Folders": "Carpetas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas del álbum", - "HeaderCameraUploads": "Subidas desde la cámara", "HeaderContinueWatching": "Continuar viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index e7bd3959bf..d4e0b299dd 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -16,7 +16,6 @@ "Folders": "Carpetas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas del álbum", - "HeaderCameraUploads": "Subidas desde la cámara", "HeaderContinueWatching": "Continuar viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json index 0959ef2ca0..dcd30694f0 100644 --- a/Emby.Server.Implementations/Localization/Core/es_419.json +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -105,7 +105,6 @@ "Inherit": "Heredar", "HomeVideos": "Videos caseros", "HeaderRecordingGroups": "Grupos de grabación", - "HeaderCameraUploads": "Subidas desde la cámara", "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", "DeviceOnlineWithName": "{0} está conectado", "DeviceOfflineWithName": "{0} se ha desconectado", diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json index 26732eb3f1..b64ffbfbba 100644 --- a/Emby.Server.Implementations/Localization/Core/es_DO.json +++ b/Emby.Server.Implementations/Localization/Core/es_DO.json @@ -12,7 +12,6 @@ "Application": "Aplicación", "AppDeviceValues": "App: {0}, Dispositivo: {1}", "HeaderContinueWatching": "Continuar Viendo", - "HeaderCameraUploads": "Subidas de Cámara", "HeaderAlbumArtists": "Artistas del Álbum", "Genres": "Géneros", "Folders": "Carpetas", diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 500c292170..1986decf03 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -16,7 +16,6 @@ "Folders": "پوشه‌ها", "Genres": "ژانرها", "HeaderAlbumArtists": "هنرمندان آلبوم", - "HeaderCameraUploads": "آپلودهای دوربین", "HeaderContinueWatching": "ادامه تماشا", "HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه", "HeaderFavoriteArtists": "هنرمندان مورد علاقه", diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index f8d6e0e09b..aae22583ab 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -24,7 +24,6 @@ "HeaderFavoriteSongs": "Lempikappaleet", "HeaderFavoriteShows": "Lempisarjat", "HeaderFavoriteEpisodes": "Lempijaksot", - "HeaderCameraUploads": "Kamerasta Lähetetyt", "HeaderFavoriteArtists": "Lempiartistit", "HeaderFavoriteAlbums": "Lempialbumit", "HeaderContinueWatching": "Jatka katsomista", diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json index 47daf20442..1a3e188327 100644 --- a/Emby.Server.Implementations/Localization/Core/fil.json +++ b/Emby.Server.Implementations/Localization/Core/fil.json @@ -73,7 +73,6 @@ "HeaderFavoriteArtists": "Paboritong Artista", "HeaderFavoriteAlbums": "Paboritong Albums", "HeaderContinueWatching": "Ituloy Manood", - "HeaderCameraUploads": "Camera Uploads", "HeaderAlbumArtists": "Artista ng Album", "Genres": "Kategorya", "Folders": "Folders", diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index cd1c8144fd..3d7592e3c8 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -16,7 +16,6 @@ "Folders": "Dossiers", "Genres": "Genres", "HeaderAlbumArtists": "Artistes de l'album", - "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes favoris", diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 7fc9968219..f4ca8575a1 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -16,7 +16,6 @@ "Folders": "Dossiers", "Genres": "Genres", "HeaderAlbumArtists": "Artistes", - "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteArtists": "Artistes préférés", diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json index 8780a884ba..ee1f8775e5 100644 --- a/Emby.Server.Implementations/Localization/Core/gsw.json +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -16,7 +16,6 @@ "Folders": "Ordner", "Genres": "Genres", "HeaderAlbumArtists": "Album-Künstler", - "HeaderCameraUploads": "Kamera-Uploads", "HeaderContinueWatching": "weiter schauen", "HeaderFavoriteAlbums": "Lieblingsalben", "HeaderFavoriteArtists": "Lieblings-Künstler", diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index dc3a981540..f906d6e117 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -16,7 +16,6 @@ "Folders": "תיקיות", "Genres": "ז'אנרים", "HeaderAlbumArtists": "אמני האלבום", - "HeaderCameraUploads": "העלאות ממצלמה", "HeaderContinueWatching": "המשך לצפות", "HeaderFavoriteAlbums": "אלבומים מועדפים", "HeaderFavoriteArtists": "אמנים מועדפים", diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index 97c77017b5..a3c936240d 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -16,7 +16,6 @@ "Folders": "Mape", "Genres": "Žanrovi", "HeaderAlbumArtists": "Izvođači na albumu", - "HeaderCameraUploads": "Uvoz sa kamere", "HeaderContinueWatching": "Nastavi gledati", "HeaderFavoriteAlbums": "Omiljeni albumi", "HeaderFavoriteArtists": "Omiljeni izvođači", diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index c5c3844e3f..343d213d41 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -16,7 +16,6 @@ "Folders": "Könyvtárak", "Genres": "Műfajok", "HeaderAlbumArtists": "Album előadók", - "HeaderCameraUploads": "Kamera feltöltések", "HeaderContinueWatching": "Megtekintés folytatása", "HeaderFavoriteAlbums": "Kedvenc albumok", "HeaderFavoriteArtists": "Kedvenc előadók", diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json index 585fc6f027..ef3ed2580a 100644 --- a/Emby.Server.Implementations/Localization/Core/id.json +++ b/Emby.Server.Implementations/Localization/Core/id.json @@ -20,7 +20,6 @@ "HeaderFavoriteArtists": "Artis Favorit", "HeaderFavoriteAlbums": "Album Favorit", "HeaderContinueWatching": "Lanjut Menonton", - "HeaderCameraUploads": "Unggahan Kamera", "HeaderAlbumArtists": "Album Artis", "Genres": "Aliran", "Folders": "Folder", diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json index 0f0f9130b0..0f769eaada 100644 --- a/Emby.Server.Implementations/Localization/Core/is.json +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -13,7 +13,6 @@ "HeaderFavoriteArtists": "Uppáhalds Listamenn", "HeaderFavoriteAlbums": "Uppáhalds Plötur", "HeaderContinueWatching": "Halda áfram að horfa", - "HeaderCameraUploads": "Myndavéla upphal", "HeaderAlbumArtists": "Höfundur plötu", "Genres": "Tegundir", "Folders": "Möppur", diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index bf1a0ef136..0a6238578d 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -16,7 +16,6 @@ "Folders": "Cartelle", "Genres": "Generi", "HeaderAlbumArtists": "Artisti degli Album", - "HeaderCameraUploads": "Caricamenti Fotocamera", "HeaderContinueWatching": "Continua a guardare", "HeaderFavoriteAlbums": "Album Preferiti", "HeaderFavoriteArtists": "Artisti Preferiti", diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json index a4d9f9ef6b..35004f0eb4 100644 --- a/Emby.Server.Implementations/Localization/Core/ja.json +++ b/Emby.Server.Implementations/Localization/Core/ja.json @@ -16,7 +16,6 @@ "Folders": "フォルダー", "Genres": "ジャンル", "HeaderAlbumArtists": "アルバムアーティスト", - "HeaderCameraUploads": "カメラアップロード", "HeaderContinueWatching": "視聴を続ける", "HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteArtists": "お気に入りのアーティスト", diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 5618ff4a8f..91c1fb15bc 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -16,7 +16,6 @@ "Folders": "Qaltalar", "Genres": "Janrlar", "HeaderAlbumArtists": "Álbom oryndaýshylary", - "HeaderCameraUploads": "Kameradan júktelgender", "HeaderContinueWatching": "Qaraýdy jalǵastyrý", "HeaderFavoriteAlbums": "Tańdaýly álbomdar", "HeaderFavoriteArtists": "Tańdaýly oryndaýshylar", diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index a33953c273..fb01e46451 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -16,7 +16,6 @@ "Folders": "폴더", "Genres": "장르", "HeaderAlbumArtists": "앨범 아티스트", - "HeaderCameraUploads": "카메라 업로드", "HeaderContinueWatching": "계속 시청하기", "HeaderFavoriteAlbums": "즐겨찾는 앨범", "HeaderFavoriteArtists": "즐겨찾는 아티스트", diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 35053766b4..d4cb592efc 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -16,7 +16,6 @@ "Folders": "Katalogai", "Genres": "Žanrai", "HeaderAlbumArtists": "Albumo atlikėjai", - "HeaderCameraUploads": "Kameros", "HeaderContinueWatching": "Žiūrėti toliau", "HeaderFavoriteAlbums": "Mėgstami Albumai", "HeaderFavoriteArtists": "Mėgstami Atlikėjai", diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json index dbcf172871..5e3acfbe96 100644 --- a/Emby.Server.Implementations/Localization/Core/lv.json +++ b/Emby.Server.Implementations/Localization/Core/lv.json @@ -72,7 +72,6 @@ "ItemAddedWithName": "{0} tika pievienots bibliotēkai", "HeaderLiveTV": "Tiešraides TV", "HeaderContinueWatching": "Turpināt Skatīšanos", - "HeaderCameraUploads": "Kameras augšupielādes", "HeaderAlbumArtists": "Albumu Izpildītāji", "Genres": "Žanri", "Folders": "Mapes", diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json index bbdf99abab..b780ef498a 100644 --- a/Emby.Server.Implementations/Localization/Core/mk.json +++ b/Emby.Server.Implementations/Localization/Core/mk.json @@ -51,7 +51,6 @@ "HeaderFavoriteArtists": "Омилени Изведувачи", "HeaderFavoriteAlbums": "Омилени Албуми", "HeaderContinueWatching": "Продолжи со гледање", - "HeaderCameraUploads": "Поставувања од камера", "HeaderAlbumArtists": "Изведувачи од Албуми", "Genres": "Жанрови", "Folders": "Папки", diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json index b6db2b0f29..fdb4171b53 100644 --- a/Emby.Server.Implementations/Localization/Core/mr.json +++ b/Emby.Server.Implementations/Localization/Core/mr.json @@ -54,7 +54,6 @@ "ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले", "HomeVideos": "घरचे व्हिडीयो", "HeaderRecordingGroups": "रेकॉर्डिंग गट", - "HeaderCameraUploads": "कॅमेरा अपलोड", "CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे", "Application": "अ‍ॅप्लिकेशन", "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}", diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index 7f8df12895..5e3d095ff1 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -16,7 +16,6 @@ "Folders": "Fail-fail", "Genres": "Genre-genre", "HeaderAlbumArtists": "Album Artis-artis", - "HeaderCameraUploads": "Muatnaik Kamera", "HeaderContinueWatching": "Terus Menonton", "HeaderFavoriteAlbums": "Album-album Kegemaran", "HeaderFavoriteArtists": "Artis-artis Kegemaran", diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 07a5991211..245c3cd636 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -16,7 +16,6 @@ "Folders": "Mapper", "Genres": "Sjangre", "HeaderAlbumArtists": "Albumartister", - "HeaderCameraUploads": "Kameraopplastinger", "HeaderContinueWatching": "Fortsett å se", "HeaderFavoriteAlbums": "Favorittalbum", "HeaderFavoriteArtists": "Favorittartister", diff --git a/Emby.Server.Implementations/Localization/Core/ne.json b/Emby.Server.Implementations/Localization/Core/ne.json index 38c0737098..8e820d40c7 100644 --- a/Emby.Server.Implementations/Localization/Core/ne.json +++ b/Emby.Server.Implementations/Localization/Core/ne.json @@ -41,7 +41,6 @@ "HeaderFavoriteArtists": "मनपर्ने कलाकारहरू", "HeaderFavoriteAlbums": "मनपर्ने एल्बमहरू", "HeaderContinueWatching": "हेर्न जारी राख्नुहोस्", - "HeaderCameraUploads": "क्यामेरा अपलोडहरू", "HeaderAlbumArtists": "एल्बमका कलाकारहरू", "Genres": "विधाहरू", "Folders": "फोल्डरहरू", diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 41c74d54de..e102b92b90 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -16,7 +16,6 @@ "Folders": "Mappen", "Genres": "Genres", "HeaderAlbumArtists": "Albumartiesten", - "HeaderCameraUploads": "Camera-uploads", "HeaderContinueWatching": "Kijken hervatten", "HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteArtists": "Favoriete artiesten", diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json index fb6e81beb3..6236515b2c 100644 --- a/Emby.Server.Implementations/Localization/Core/nn.json +++ b/Emby.Server.Implementations/Localization/Core/nn.json @@ -19,7 +19,6 @@ "HeaderFavoriteArtists": "Favoritt Artistar", "HeaderFavoriteAlbums": "Favoritt Album", "HeaderContinueWatching": "Fortsett å sjå", - "HeaderCameraUploads": "Kamera Opplastingar", "HeaderAlbumArtists": "Album Artist", "Genres": "Sjangrar", "Folders": "Mapper", diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index bdc0d0169b..003e591b37 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -16,7 +16,6 @@ "Folders": "Foldery", "Genres": "Gatunki", "HeaderAlbumArtists": "Wykonawcy albumów", - "HeaderCameraUploads": "Przekazane obrazy", "HeaderContinueWatching": "Kontynuuj odtwarzanie", "HeaderFavoriteAlbums": "Ulubione albumy", "HeaderFavoriteArtists": "Ulubieni wykonawcy", diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 275195640b..5e49ca702e 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -16,7 +16,6 @@ "Folders": "Pastas", "Genres": "Gêneros", "HeaderAlbumArtists": "Artistas do Álbum", - "HeaderCameraUploads": "Envios da Câmera", "HeaderContinueWatching": "Continuar Assistindo", "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteArtists": "Artistas favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index c1fb65743d..8aa1ed5497 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -16,7 +16,6 @@ "Folders": "Pastas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas do Álbum", - "HeaderCameraUploads": "Envios a partir da câmara", "HeaderContinueWatching": "Continuar a Ver", "HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteArtists": "Artistas Favoritos", diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index b534d0bbeb..2079940cd1 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -83,7 +83,6 @@ "Playlists": "Listas de Reprodução", "Photos": "Fotografias", "Movies": "Filmes", - "HeaderCameraUploads": "Carregamentos a partir da câmara", "FailedLoginAttemptWithUserName": "Tentativa de ligação falhada a partir de {0}", "DeviceOnlineWithName": "{0} está connectado", "DeviceOfflineWithName": "{0} desconectou-se", diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json index 699dd26daa..bc008df3b4 100644 --- a/Emby.Server.Implementations/Localization/Core/ro.json +++ b/Emby.Server.Implementations/Localization/Core/ro.json @@ -74,7 +74,6 @@ "HeaderFavoriteArtists": "Artiști Favoriți", "HeaderFavoriteAlbums": "Albume Favorite", "HeaderContinueWatching": "Vizionează în continuare", - "HeaderCameraUploads": "Incărcări Cameră Foto", "HeaderAlbumArtists": "Album Artiști", "Genres": "Genuri", "Folders": "Dosare", diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 648aa384b0..95b93afb82 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -16,7 +16,6 @@ "Folders": "Папки", "Genres": "Жанры", "HeaderAlbumArtists": "Исполнители альбома", - "HeaderCameraUploads": "Камеры", "HeaderContinueWatching": "Продолжение просмотра", "HeaderFavoriteAlbums": "Избранные альбомы", "HeaderFavoriteArtists": "Избранные исполнители", diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index 0ee652637c..8e50269444 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -16,7 +16,6 @@ "Folders": "Priečinky", "Genres": "Žánre", "HeaderAlbumArtists": "Umelci albumu", - "HeaderCameraUploads": "Nahrané fotografie", "HeaderContinueWatching": "Pokračovať v pozeraní", "HeaderFavoriteAlbums": "Obľúbené albumy", "HeaderFavoriteArtists": "Obľúbení umelci", diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index 329c562e73..ff4b9e84fd 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -16,7 +16,6 @@ "Folders": "Mape", "Genres": "Zvrsti", "HeaderAlbumArtists": "Izvajalci albuma", - "HeaderCameraUploads": "Posnetki kamere", "HeaderContinueWatching": "Nadaljuj gledanje", "HeaderFavoriteAlbums": "Priljubljeni albumi", "HeaderFavoriteArtists": "Priljubljeni izvajalci", diff --git a/Emby.Server.Implementations/Localization/Core/sq.json b/Emby.Server.Implementations/Localization/Core/sq.json index 347ba5f970..ecca5af4cc 100644 --- a/Emby.Server.Implementations/Localization/Core/sq.json +++ b/Emby.Server.Implementations/Localization/Core/sq.json @@ -96,7 +96,6 @@ "HeaderFavoriteArtists": "Artistët e preferuar", "HeaderFavoriteAlbums": "Albumet e preferuar", "HeaderContinueWatching": "Vazhdo të shikosh", - "HeaderCameraUploads": "Ngarkimet nga Kamera", "HeaderAlbumArtists": "Artistët e albumeve", "Genres": "Zhanre", "Folders": "Dosje", diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json index 5f3cbb1c8b..2b1eccfaf3 100644 --- a/Emby.Server.Implementations/Localization/Core/sr.json +++ b/Emby.Server.Implementations/Localization/Core/sr.json @@ -74,7 +74,6 @@ "HeaderFavoriteArtists": "Омиљени извођачи", "HeaderFavoriteAlbums": "Омиљени албуми", "HeaderContinueWatching": "Настави гледање", - "HeaderCameraUploads": "Слања са камере", "HeaderAlbumArtists": "Извођачи албума", "Genres": "Жанрови", "Folders": "Фасцикле", diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index c8662b2cab..12fda8a986 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -16,7 +16,6 @@ "Folders": "Mappar", "Genres": "Genrer", "HeaderAlbumArtists": "Albumartister", - "HeaderCameraUploads": "Kamerauppladdningar", "HeaderContinueWatching": "Fortsätt kolla", "HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteArtists": "Favoritartister", diff --git a/Emby.Server.Implementations/Localization/Core/ta.json b/Emby.Server.Implementations/Localization/Core/ta.json index 810b1b9abe..ae38f45e1c 100644 --- a/Emby.Server.Implementations/Localization/Core/ta.json +++ b/Emby.Server.Implementations/Localization/Core/ta.json @@ -20,7 +20,6 @@ "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது", "Inherit": "மரபுரிமையாகப் பெறு", "HeaderRecordingGroups": "பதிவு குழுக்கள்", - "HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்", "Folders": "கோப்புறைகள்", "FailedLoginAttemptWithUserName": "{0} இலிருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது", "DeviceOnlineWithName": "{0} இணைக்கப்பட்டது", diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json index 3b77215a30..71dd2c7a30 100644 --- a/Emby.Server.Implementations/Localization/Core/th.json +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -50,7 +50,6 @@ "HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ", "HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ", "HeaderContinueWatching": "ดูต่อ", - "HeaderCameraUploads": "อัปโหลดรูปถ่าย", "HeaderAlbumArtists": "อัลบั้มศิลปิน", "Genres": "ประเภท", "Folders": "โฟลเดอร์", diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 3cf3482eba..1e5f2cf198 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -16,7 +16,6 @@ "Folders": "Klasörler", "Genres": "Türler", "HeaderAlbumArtists": "Albüm Sanatçıları", - "HeaderCameraUploads": "Kamera Yüklemeleri", "HeaderContinueWatching": "İzlemeye Devam Et", "HeaderFavoriteAlbums": "Favori Albümler", "HeaderFavoriteArtists": "Favori Sanatçılar", diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json index e673465a42..06cc5f633e 100644 --- a/Emby.Server.Implementations/Localization/Core/uk.json +++ b/Emby.Server.Implementations/Localization/Core/uk.json @@ -16,7 +16,6 @@ "HeaderFavoriteArtists": "Улюблені виконавці", "HeaderFavoriteAlbums": "Улюблені альбоми", "HeaderContinueWatching": "Продовжити перегляд", - "HeaderCameraUploads": "Завантажено з камери", "HeaderAlbumArtists": "Виконавці альбому", "Genres": "Жанри", "Folders": "Каталоги", diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json index 9a5874e295..fa7b2d4d0c 100644 --- a/Emby.Server.Implementations/Localization/Core/ur_PK.json +++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json @@ -105,7 +105,6 @@ "Inherit": "وراثت میں", "HomeVideos": "ہوم ویڈیو", "HeaderRecordingGroups": "ریکارڈنگ گروپس", - "HeaderCameraUploads": "کیمرہ اپلوڈز", "FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}", "DeviceOnlineWithName": "{0} متصل ھو چکا ھے", "DeviceOfflineWithName": "{0} منقطع ھو چکا ھے", diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json index 2392c83479..ac74deff82 100644 --- a/Emby.Server.Implementations/Localization/Core/vi.json +++ b/Emby.Server.Implementations/Localization/Core/vi.json @@ -103,7 +103,6 @@ "HeaderFavoriteEpisodes": "Tập Phim Yêu Thích", "HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích", "HeaderFavoriteAlbums": "Album Ưa Thích", - "HeaderCameraUploads": "Máy Ảnh Tải Lên", "FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}", "DeviceOnlineWithName": "{0} đã kết nối", "DeviceOfflineWithName": "{0} đã ngắt kết nối", diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 6b563a9b13..53a902de28 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -16,7 +16,6 @@ "Folders": "文件夹", "Genres": "风格", "HeaderAlbumArtists": "专辑作家", - "HeaderCameraUploads": "相机上传", "HeaderContinueWatching": "继续观影", "HeaderFavoriteAlbums": "收藏的专辑", "HeaderFavoriteArtists": "最爱的艺术家", diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 1ac62baca9..435e294ef2 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -16,7 +16,6 @@ "Folders": "檔案夾", "Genres": "風格", "HeaderAlbumArtists": "專輯藝人", - "HeaderCameraUploads": "相機上載", "HeaderContinueWatching": "繼續觀看", "HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteArtists": "最愛的藝人", diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index 7b6540c3e3..30f7266300 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -16,7 +16,6 @@ "Folders": "資料夾", "Genres": "風格", "HeaderAlbumArtists": "專輯演出者", - "HeaderCameraUploads": "相機上傳", "HeaderContinueWatching": "繼續觀賞", "HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteArtists": "最愛演出者", From 5ee6f4920497ec9f4d6c305e9d0828d21074a773 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 1 Oct 2020 08:10:47 -0600 Subject: [PATCH 030/151] Manually register models used in websocket messages. --- .../ApiServiceCollectionExtensions.cs | 1 + .../Filters/WebsocketModelFilter.cs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 Jellyfin.Server/Filters/WebsocketModelFilter.cs diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 5bcf6d5f07..e180d0cd7a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -260,6 +260,7 @@ namespace Jellyfin.Server.Extensions c.AddSwaggerTypeMappings(); c.OperationFilter(); + c.DocumentFilter(); }); } diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs new file mode 100644 index 0000000000..90fca5583c --- /dev/null +++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs @@ -0,0 +1,31 @@ +using MediaBrowser.Common.Plugins; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.SyncPlay; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters +{ + /// + /// Add models used in websocket messaging. + /// + public class WebsocketModelFilter : IDocumentFilter + { + /// + public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) + { + context.SchemaGenerator.GenerateSchema(typeof(LibraryUpdateInfo), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(IPlugin), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(PlayRequest), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(TimerEventInfo), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(SendCommand), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); + + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + } + } +} From 9585fea51e13c628f8d1223d03ffc7f05a570bbb Mon Sep 17 00:00:00 2001 From: Erwin de Haan <1627021+EraYaN@users.noreply.github.com> Date: Thu, 1 Oct 2020 17:38:36 +0200 Subject: [PATCH 031/151] Update azure-pipelines.yml --- .ci/azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index b417aae678..347918e0b0 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -34,6 +34,12 @@ jobs: Linux: 'ubuntu-latest' Windows: 'windows-latest' macOS: 'macos-latest' + +- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: + - template: azure-pipelines-test.yml + parameters: + ImageNames: + Linux: 'ubuntu-latest' - ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}: - template: azure-pipelines-abi.yml From 4a3e0062f9e4c353a79f5cba74a0499aa68565c8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 1 Oct 2020 09:39:57 -0600 Subject: [PATCH 032/151] Add missing PlaystateRequest and remove additional GroupUpdate types --- Jellyfin.Server/Filters/WebsocketModelFilter.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs index 90fca5583c..81dfdfba7e 100644 --- a/Jellyfin.Server/Filters/WebsocketModelFilter.cs +++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs @@ -19,13 +19,12 @@ namespace Jellyfin.Server.Filters context.SchemaGenerator.GenerateSchema(typeof(LibraryUpdateInfo), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(IPlugin), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(PlayRequest), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(PlaystateRequest), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(TimerEventInfo), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(SendCommand), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<>), context.SchemaRepository); } } } From c0de26f69a5856b5d4c8f821fdf7e1a5aa571969 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 1 Oct 2020 17:57:40 +0200 Subject: [PATCH 033/151] Publish OpenAPI Spec to repository server --- .ci/azure-pipelines-package.yml | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml index cc845afd43..67aac45c9d 100644 --- a/.ci/azure-pipelines-package.yml +++ b/.ci/azure-pipelines-package.yml @@ -63,7 +63,38 @@ jobs: sshEndpoint: repository sourceFolder: '$(Build.SourcesDirectory)/deployment/dist' contents: '**' - targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)' + +- job: OpenAPISpec + dependsOn: Test + condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/master'),startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) + displayName: 'Push OpenAPI Spec to repository' + + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download OpenAPI Spec' + inputs: + source: 'current' + artifact: "OpenAPI Spec" + path: "$(System.ArtifactsDirectory)/openapispec" + runVersion: "latest" + + - task: SSH@0 + displayName: 'Create target directory on repository server' + inputs: + sshEndpoint: repository + runOptions: 'inline' + inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)' + + - task: CopyFilesOverSSH@0 + displayName: 'Upload artifacts to repository server' + inputs: + sshEndpoint: repository + sourceFolder: '$(System.ArtifactsDirectory)/openapispec' + contents: 'openapi.json' + targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)' - job: BuildDocker displayName: 'Build Docker' From 2b75af987395c242b69f57b636121302eadaee30 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 1 Oct 2020 10:40:58 -0600 Subject: [PATCH 034/151] set type of GroupUpdate --- Jellyfin.Server/Filters/WebsocketModelFilter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Filters/WebsocketModelFilter.cs b/Jellyfin.Server/Filters/WebsocketModelFilter.cs index 81dfdfba7e..2488028576 100644 --- a/Jellyfin.Server/Filters/WebsocketModelFilter.cs +++ b/Jellyfin.Server/Filters/WebsocketModelFilter.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Server.Filters context.SchemaGenerator.GenerateSchema(typeof(SendCommand), context.SchemaRepository); context.SchemaGenerator.GenerateSchema(typeof(GeneralCommandType), context.SchemaRepository); - context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<>), context.SchemaRepository); + context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate), context.SchemaRepository); } } } From dd4f3a7c5184afbada50a038564c95fa780e04f8 Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Thu, 1 Oct 2020 18:43:44 +0200 Subject: [PATCH 035/151] feat: convert supportedCommands strings to enums --- Emby.Dlna/PlayTo/PlayToManager.cs | 18 +++++++++--------- Jellyfin.Api/Controllers/SessionController.cs | 6 +++--- MediaBrowser.Controller/Session/SessionInfo.cs | 4 ++-- .../Session/ClientCapabilities.cs | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 21877f121f..e93aef3043 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -217,15 +217,15 @@ namespace Emby.Dlna.PlayTo SupportedCommands = new[] { - GeneralCommandType.VolumeDown.ToString(), - GeneralCommandType.VolumeUp.ToString(), - GeneralCommandType.Mute.ToString(), - GeneralCommandType.Unmute.ToString(), - GeneralCommandType.ToggleMute.ToString(), - GeneralCommandType.SetVolume.ToString(), - GeneralCommandType.SetAudioStreamIndex.ToString(), - GeneralCommandType.SetSubtitleStreamIndex.ToString(), - GeneralCommandType.PlayMediaSource.ToString() + GeneralCommandType.VolumeDown, + GeneralCommandType.VolumeUp, + GeneralCommandType.Mute, + GeneralCommandType.Unmute, + GeneralCommandType.ToggleMute, + GeneralCommandType.SetVolume, + GeneralCommandType.SetAudioStreamIndex, + GeneralCommandType.SetSubtitleStreamIndex, + GeneralCommandType.PlayMediaSource }, SupportsMediaControl = true diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 39bf6e6dc7..2ed7019e5b 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -366,7 +366,7 @@ namespace Jellyfin.Api.Controllers /// /// The session id. /// A list of playable media types, comma delimited. Audio, Video, Book, Photo. - /// A list of supported remote control commands, comma delimited. + /// A list of supported remote control commands. /// Determines whether media can be played remotely.. /// Determines whether sync is supported. /// Determines whether the device supports a unique identifier. @@ -378,7 +378,7 @@ namespace Jellyfin.Api.Controllers public ActionResult PostCapabilities( [FromQuery] string? id, [FromQuery] string? playableMediaTypes, - [FromQuery] string? supportedCommands, + [FromQuery] GeneralCommandType[] supportedCommands, [FromQuery] bool supportsMediaControl = false, [FromQuery] bool supportsSync = false, [FromQuery] bool supportsPersistentIdentifier = true) @@ -391,7 +391,7 @@ namespace Jellyfin.Api.Controllers _sessionManager.ReportCapabilities(id, new ClientCapabilities { PlayableMediaTypes = RequestHelpers.Split(playableMediaTypes, ',', true), - SupportedCommands = RequestHelpers.Split(supportedCommands, ',', true), + SupportedCommands = supportedCommands == null ? Array.Empty() : supportedCommands, SupportsMediaControl = supportsMediaControl, SupportsSync = supportsSync, SupportsPersistentIdentifier = supportsPersistentIdentifier diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 55e44c19db..ce58a60b9a 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -230,8 +230,8 @@ namespace MediaBrowser.Controller.Session /// Gets or sets the supported commands. /// /// The supported commands. - public string[] SupportedCommands - => Capabilities == null ? Array.Empty() : Capabilities.SupportedCommands; + public GeneralCommandType[] SupportedCommands + => Capabilities == null ? Array.Empty() : Capabilities.SupportedCommands; public Tuple EnsureController(Func factory) { diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index d3878ca308..a85e6ff2a4 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Session { public string[] PlayableMediaTypes { get; set; } - public string[] SupportedCommands { get; set; } + public GeneralCommandType[] SupportedCommands { get; set; } public bool SupportsMediaControl { get; set; } @@ -31,7 +31,7 @@ namespace MediaBrowser.Model.Session public ClientCapabilities() { PlayableMediaTypes = Array.Empty(); - SupportedCommands = Array.Empty(); + SupportedCommands = Array.Empty(); SupportsPersistentIdentifier = true; } } From f314be9d8513829a5de21eeb2ef19e10943b2a0e Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Thu, 1 Oct 2020 18:44:22 +0200 Subject: [PATCH 036/151] chore(CONTRIBUTORS.md): add skyfrk --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 99060d0b09..7b4772730a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -137,6 +137,7 @@ - [KristupasSavickas](https://github.com/KristupasSavickas) - [Pusta](https://github.com/pusta) - [nielsvanvelzen](https://github.com/nielsvanvelzen) + - [skyfrk](https://github.com/skyfrk) # Emby Contributors From 0655928ab14452dde97192ead66b33c927a75d5a Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Thu, 1 Oct 2020 19:56:59 +0200 Subject: [PATCH 037/151] feat: add CommaDelimitedArrayModelBinder --- .../CommaDelimitedArrayModelBinder.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs new file mode 100644 index 0000000000..1bfd741fd9 --- /dev/null +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs @@ -0,0 +1,42 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.ModelBinders +{ + /// + /// Comma delimited array model binder. + /// Returns an empty array of specified type if there is no query parameter. + /// + public class CommaDelimitedArrayModelBinder : IModelBinder + { + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + var input = valueProviderResult.FirstValue; + var elementType = bindingContext.ModelType.GetElementType(); + + if (input != null) + { + var converter = TypeDescriptor.GetConverter(elementType); + var values = Array.ConvertAll( + input.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries), + x => { return converter.ConvertFromString(x != null ? x.Trim() : x); }); + + var typedValues = Array.CreateInstance(elementType, values.Length); + values.CopyTo(typedValues, 0); + + bindingContext.Result = ModelBindingResult.Success(typedValues); + } + else + { + var emptyResult = Array.CreateInstance(elementType, 0); + bindingContext.Result = ModelBindingResult.Success(emptyResult); + } + + return Task.CompletedTask; + } + } +} From ba12ea7f4a0bb4804bafa335d374d45bac37ea84 Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Thu, 1 Oct 2020 19:57:31 +0200 Subject: [PATCH 038/151] feat: use CommaDelimitedArrayModelBinder to retain API --- Jellyfin.Api/Controllers/SessionController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 2ed7019e5b..68cec14151 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; @@ -366,7 +367,7 @@ namespace Jellyfin.Api.Controllers /// /// The session id. /// A list of playable media types, comma delimited. Audio, Video, Book, Photo. - /// A list of supported remote control commands. + /// A list of supported remote control commands, comma delimited. /// Determines whether media can be played remotely.. /// Determines whether sync is supported. /// Determines whether the device supports a unique identifier. @@ -378,7 +379,7 @@ namespace Jellyfin.Api.Controllers public ActionResult PostCapabilities( [FromQuery] string? id, [FromQuery] string? playableMediaTypes, - [FromQuery] GeneralCommandType[] supportedCommands, + [FromQuery][ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, [FromQuery] bool supportsMediaControl = false, [FromQuery] bool supportsSync = false, [FromQuery] bool supportsPersistentIdentifier = true) From d91a4f0c6d81177b03f0543056456058604e0ce1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 1 Oct 2020 20:25:40 +0100 Subject: [PATCH 039/151] Update ApplicationHost.cs --- .../ApplicationHost.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7a46fdf2e7..b4f7319f8e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -819,38 +819,6 @@ namespace Emby.Server.Implementations { try { - if (plugin is IPluginAssembly assemblyPlugin) - { - var assembly = plugin.GetType().Assembly; - var assemblyName = assembly.GetName(); - var assemblyFilePath = assembly.Location; - - var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - - assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); - - try - { - var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); - if (idAttributes.Length > 0) - { - var attribute = (GuidAttribute)idAttributes[0]; - var assemblyId = new Guid(attribute.Value); - - assemblyPlugin.SetId(assemblyId); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName); - } - } - - if (plugin is IHasPluginConfiguration hasPluginConfiguration) - { - hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); - } - plugin.RegisterServices(ServiceCollection); } catch (Exception ex) From a69731b5e32d63042d18188f90dec800b3b1a2b9 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 1 Oct 2020 20:30:12 +0100 Subject: [PATCH 040/151] Update BasePlugin.cs Moved initialisation ApplicationHost.cs /LoadPlugin() --- MediaBrowser.Common/Plugins/BasePlugin.cs | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 4b2918d085..8d9917cd63 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -140,6 +140,37 @@ namespace MediaBrowser.Common.Plugins { ApplicationPaths = applicationPaths; XmlSerializer = xmlSerializer; + if (this is IPluginAssembly assemblyPlugin) + { + var assembly = GetType().Assembly; + var assemblyName = assembly.GetName(); + var assemblyFilePath = assembly.Location; + + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + + assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); + + try + { + var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); + if (idAttributes.Length > 0) + { + var attribute = (GuidAttribute)idAttributes[0]; + var assemblyId = new Guid(attribute.Value); + + assemblyPlugin.SetId(assemblyId); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName); + } + } + + if (this is IHasPluginConfiguration hasPluginConfiguration) + { + hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s)); + } } /// From dff2674b27da65c0ff7a82575df77be856985b96 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Thu, 1 Oct 2020 20:42:48 +0100 Subject: [PATCH 041/151] Update BasePlugin.cs --- MediaBrowser.Common/Plugins/BasePlugin.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 8d9917cd63..8545fd5dcf 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Reflection; +using System.Runtime.InteropServices; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; @@ -150,20 +151,13 @@ namespace MediaBrowser.Common.Plugins assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); - try + var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); + if (idAttributes.Length > 0) { - var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); - if (idAttributes.Length > 0) - { - var attribute = (GuidAttribute)idAttributes[0]; - var assemblyId = new Guid(attribute.Value); + var attribute = (GuidAttribute)idAttributes[0]; + var assemblyId = new Guid(attribute.Value); - assemblyPlugin.SetId(assemblyId); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName); + assemblyPlugin.SetId(assemblyId); } } From 4b4c74bdcd2ffd119f930226179360907c15fd74 Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Thu, 1 Oct 2020 22:04:53 +0200 Subject: [PATCH 042/151] feat: extend CommaDelimitedArrayModelBinder to support auto generated openAPI spec --- .../CommaDelimitedArrayModelBinder.cs | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs index 1bfd741fd9..92bbd96633 100644 --- a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs @@ -15,25 +15,42 @@ namespace Jellyfin.Api.ModelBinders public Task BindModelAsync(ModelBindingContext bindingContext) { var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); - var input = valueProviderResult.FirstValue; var elementType = bindingContext.ModelType.GetElementType(); + var converter = TypeDescriptor.GetConverter(elementType); - if (input != null) + if (valueProviderResult.Length > 1) { - var converter = TypeDescriptor.GetConverter(elementType); - var values = Array.ConvertAll( - input.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries), - x => { return converter.ConvertFromString(x != null ? x.Trim() : x); }); + var result = Array.CreateInstance(elementType, valueProviderResult.Length); - var typedValues = Array.CreateInstance(elementType, values.Length); - values.CopyTo(typedValues, 0); + for (int i = 0; i < valueProviderResult.Length; i++) + { + var value = converter.ConvertFromString(valueProviderResult.Values[i].Trim()); - bindingContext.Result = ModelBindingResult.Success(typedValues); + result.SetValue(value, i); + } + + bindingContext.Result = ModelBindingResult.Success(result); } else { - var emptyResult = Array.CreateInstance(elementType, 0); - bindingContext.Result = ModelBindingResult.Success(emptyResult); + var value = valueProviderResult.FirstValue; + + if (value != null) + { + var values = Array.ConvertAll( + value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries), + x => { return converter.ConvertFromString(x != null ? x.Trim() : x); }); + + var typedValues = Array.CreateInstance(elementType, values.Length); + values.CopyTo(typedValues, 0); + + bindingContext.Result = ModelBindingResult.Success(typedValues); + } + else + { + var emptyResult = Array.CreateInstance(elementType, 0); + bindingContext.Result = ModelBindingResult.Success(emptyResult); + } } return Task.CompletedTask; From d10090b394371e6b588a08b453a1dfb177e90ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20E=C3=9Flinger?= Date: Thu, 1 Oct 2020 22:48:42 +0200 Subject: [PATCH 043/151] fix: remove unused null check Co-authored-by: Cody Robibero --- Jellyfin.Api/Controllers/SessionController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 68cec14151..0ae49ea982 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -392,7 +392,7 @@ namespace Jellyfin.Api.Controllers _sessionManager.ReportCapabilities(id, new ClientCapabilities { PlayableMediaTypes = RequestHelpers.Split(playableMediaTypes, ',', true), - SupportedCommands = supportedCommands == null ? Array.Empty() : supportedCommands, + SupportedCommands = supportedCommands, SupportsMediaControl = supportsMediaControl, SupportsSync = supportsSync, SupportsPersistentIdentifier = supportsPersistentIdentifier From 21b39a207dd4a6864e9d7bfe1c1e9253cbfc0f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20E=C3=9Flinger?= Date: Fri, 2 Oct 2020 01:33:15 +0200 Subject: [PATCH 044/151] refactor: simplify null check Co-authored-by: Cody Robibero --- Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs index 92bbd96633..208566dc8e 100644 --- a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs @@ -39,7 +39,7 @@ namespace Jellyfin.Api.ModelBinders { var values = Array.ConvertAll( value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries), - x => { return converter.ConvertFromString(x != null ? x.Trim() : x); }); + x => converter.ConvertFromString(x?.Trim())); var typedValues = Array.CreateInstance(elementType, values.Length); values.CopyTo(typedValues, 0); From 810ec0b672c06baa34782ccfb4b0b5cd51196662 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 07:00:57 -0600 Subject: [PATCH 045/151] Fix download api spec --- .ci/azure-pipelines-api-client.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index babee15b5a..0c741495f7 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -17,13 +17,11 @@ jobs: - task: DownloadPipelineArtifact@2 displayName: 'Download OpenAPI Spec Artifact' inputs: - source: "specific" + source: 'current' artifact: "OpenAPI Spec" - path: "$(System.ArtifactsDirectory)/openapi" - project: "$(System.TeamProjectId)" - pipeline: "29" # The main server CI build - runVersion: "latestFromBranch" - runBranch: "refs/heads/$(System.PullRequest.TargetBranch)" + path: "$(System.ArtifactsDirectory)/openapispec" + runVersion: "latest" + dependsOn: Test - task: CmdLine@2 displayName: 'Download OpenApi Generator' @@ -68,5 +66,5 @@ jobs: inputs: command: publish publishRegistry: useExternalRegistry - publishEndpoint: + publishEndpoint: workingDir: ./apiclient/generated/typescript/axios From fcc9482ff94254dc853157f200f4a03c92f0366e Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 07:07:48 -0600 Subject: [PATCH 046/151] Generate document file for openapi spec in CI --- .ci/azure-pipelines-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml index eca8aa90f9..6a36698b56 100644 --- a/.ci/azure-pipelines-test.yml +++ b/.ci/azure-pipelines-test.yml @@ -56,7 +56,7 @@ jobs: inputs: command: "test" projects: ${{ parameters.TestProjects }} - arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal "-p:GenerateDocumentationFile=False"' + arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal' publishTestResults: true testRunTitle: $(Agent.JobName) workingDirectory: "$(Build.SourcesDirectory)" From 520acc11e6e1def24939527be8ccc186ded1c7b4 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 07:19:18 -0600 Subject: [PATCH 047/151] scope npm package name --- apiclient/templates/typescript/package.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiclient/templates/typescript/package.mustache b/apiclient/templates/typescript/package.mustache index 5127917a14..251a403836 100644 --- a/apiclient/templates/typescript/package.mustache +++ b/apiclient/templates/typescript/package.mustache @@ -1,5 +1,5 @@ { - "name": "jellyfin-apiclient-{{npmName}}", + "name": "@jellyfin/client-{{npmName}}", "version": "10.7.0{{snapshotVersion}}", "description": "Jellyfin api client using {{npmName}}", "author": "Jellyfin Contributors", From 7d992798fdbd1dbba7242d83f79d7fba0499d15c Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 07:40:44 -0600 Subject: [PATCH 048/151] specify client specifically instead of through template --- .ci/azure-pipelines-api-client.yml | 4 ++-- .../templates/typescript/{ => axios}/package.mustache | 6 +++--- apiclient/templates/typescript/{ => axios}/stable.sh | 9 ++++----- apiclient/templates/typescript/axios/unstable.sh | 9 +++++++++ apiclient/templates/typescript/unstable.sh | 10 ---------- 5 files changed, 18 insertions(+), 20 deletions(-) rename apiclient/templates/typescript/{ => axios}/package.mustache (83%) rename apiclient/templates/typescript/{ => axios}/stable.sh (58%) create mode 100644 apiclient/templates/typescript/axios/unstable.sh delete mode 100644 apiclient/templates/typescript/unstable.sh diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index 0c741495f7..cbedcc6079 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -38,7 +38,7 @@ jobs: displayName: 'Build unstable typescript axios client' condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') inputs: - script: 'bash ./apiclient/templates/typescript/unstable.sh axios' + script: 'bash ./apiclient/templates/typescript/axios/unstable.sh' - task: Npm@1 displayName: 'Publish unstable typescript axios client' @@ -58,7 +58,7 @@ jobs: displayName: 'Build stable typescript axios client' condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') inputs: - script: 'bash ./apiclient/templates/typescript/stable.sh axios' + script: 'bash ./apiclient/templates/typescript/axios/stable.sh' - task: Npm@1 displayName: 'Publish stable typescript axios client' diff --git a/apiclient/templates/typescript/package.mustache b/apiclient/templates/typescript/axios/package.mustache similarity index 83% rename from apiclient/templates/typescript/package.mustache rename to apiclient/templates/typescript/axios/package.mustache index 251a403836..7bfab08cbb 100644 --- a/apiclient/templates/typescript/package.mustache +++ b/apiclient/templates/typescript/axios/package.mustache @@ -1,10 +1,10 @@ { - "name": "@jellyfin/client-{{npmName}}", + "name": "@jellyfin/client-axios", "version": "10.7.0{{snapshotVersion}}", - "description": "Jellyfin api client using {{npmName}}", + "description": "Jellyfin api client using axios", "author": "Jellyfin Contributors", "keywords": [ - "{{npmName}}", + "axios", "typescript", "jellyfin" ], diff --git a/apiclient/templates/typescript/stable.sh b/apiclient/templates/typescript/axios/stable.sh similarity index 58% rename from apiclient/templates/typescript/stable.sh rename to apiclient/templates/typescript/axios/stable.sh index f23a85cc96..ecc55d2e79 100644 --- a/apiclient/templates/typescript/stable.sh +++ b/apiclient/templates/typescript/axios/stable.sh @@ -1,10 +1,9 @@ #!/bin/bash -CLIENT=$1 java -jar openapi-generator-cli.jar generate \ --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ - --generator-name typescript-${CLIENT} \ - --output ./apiclient/generated/typescript/${CLIENT} \ - --template-dir ./apiclient/templates/typescript \ + --generator-name typescript-axios \ + --output ./apiclient/generated/typescript/axios \ + --template-dir ./apiclient/templates/typescript/axios \ --ignore-file-override ./apiclient/.openapi-generator-ignore \ - --additional-properties=useSingleRequestParameter="true",npmName="${CLIENT}" + --additional-properties=useSingleRequestParameter="true",npmName="axios" diff --git a/apiclient/templates/typescript/axios/unstable.sh b/apiclient/templates/typescript/axios/unstable.sh new file mode 100644 index 0000000000..615eb5b228 --- /dev/null +++ b/apiclient/templates/typescript/axios/unstable.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +java -jar openapi-generator-cli.jar generate \ + --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ + --generator-name typescript-axios \ + --output ./apiclient/generated/typescript/axios \ + --template-dir ./apiclient/templates/typescript/axios \ + --ignore-file-override ./apiclient/.openapi-generator-ignore \ + --additional-properties=useSingleRequestParameter="true",npmName="axios",snapshotVersion="-SNAPSHOT.$(Build.BuildNumber)",npmRepository="https://dev.azure.com/jellyfin-project/jellyfin/_packaging" diff --git a/apiclient/templates/typescript/unstable.sh b/apiclient/templates/typescript/unstable.sh deleted file mode 100644 index 3571c8ad5c..0000000000 --- a/apiclient/templates/typescript/unstable.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -CLIENT=$1 -java -jar openapi-generator-cli.jar generate \ - --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ - --generator-name typescript-${CLIENT} \ - --output ./apiclient/generated/typescript/${CLIENT} \ - --template-dir ./apiclient/templates/typescript \ - --ignore-file-override ./apiclient/.openapi-generator-ignore \ - --additional-properties=useSingleRequestParameter="true",npmName="${CLIENT}",snapshotVersion="-SNAPSHOT.$(Build.BuildNumber)",npmRepository="https://dev.azure.com/jellyfin-project/jellyfin/_packaging" From 75dada6308c2be5d0c35f679cbcd49f6b7bd0cb9 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 07:47:14 -0600 Subject: [PATCH 049/151] add withSeparateModelsAndApi --- apiclient/templates/typescript/axios/stable.sh | 2 +- apiclient/templates/typescript/axios/unstable.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apiclient/templates/typescript/axios/stable.sh b/apiclient/templates/typescript/axios/stable.sh index ecc55d2e79..70c6409fb6 100644 --- a/apiclient/templates/typescript/axios/stable.sh +++ b/apiclient/templates/typescript/axios/stable.sh @@ -6,4 +6,4 @@ java -jar openapi-generator-cli.jar generate \ --output ./apiclient/generated/typescript/axios \ --template-dir ./apiclient/templates/typescript/axios \ --ignore-file-override ./apiclient/.openapi-generator-ignore \ - --additional-properties=useSingleRequestParameter="true",npmName="axios" + --additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",npmName="axios" diff --git a/apiclient/templates/typescript/axios/unstable.sh b/apiclient/templates/typescript/axios/unstable.sh index 615eb5b228..83e4ba228a 100644 --- a/apiclient/templates/typescript/axios/unstable.sh +++ b/apiclient/templates/typescript/axios/unstable.sh @@ -6,4 +6,4 @@ java -jar openapi-generator-cli.jar generate \ --output ./apiclient/generated/typescript/axios \ --template-dir ./apiclient/templates/typescript/axios \ --ignore-file-override ./apiclient/.openapi-generator-ignore \ - --additional-properties=useSingleRequestParameter="true",npmName="axios",snapshotVersion="-SNAPSHOT.$(Build.BuildNumber)",npmRepository="https://dev.azure.com/jellyfin-project/jellyfin/_packaging" + --additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",npmName="axios",snapshotVersion="-SNAPSHOT.$(Build.BuildNumber)",npmRepository="https://dev.azure.com/jellyfin-project/jellyfin/_packaging" From 3d20ff69d4c711fcde794830c27f53df5d51941b Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 07:50:38 -0600 Subject: [PATCH 050/151] Fix repo connections --- .ci/azure-pipelines-api-client.yml | 12 ++---------- apiclient/templates/typescript/axios/unstable.sh | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index cbedcc6079..dbd70c7028 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -30,10 +30,6 @@ jobs: # Generate npm api client # Unstable - - task: npmAuthenticate@0 - displayName: 'Authenticate to unstable npm feed' - condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') - - task: CmdLine@2 displayName: 'Build unstable typescript axios client' condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') @@ -46,14 +42,10 @@ jobs: inputs: command: publish publishRegistry: useFeed - publishFeed: jellyfin/unstable + publishFeed: unstable workingDir: ./apiclient/generated/typescript/axios # Stable - - task: npmAuthenticate@0 - displayName: 'Authenticate to stable npm feed' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') - - task: CmdLine@2 displayName: 'Build stable typescript axios client' condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') @@ -66,5 +58,5 @@ jobs: inputs: command: publish publishRegistry: useExternalRegistry - publishEndpoint: + publishEndpoint: 'jellyfin-bot for NPM' workingDir: ./apiclient/generated/typescript/axios diff --git a/apiclient/templates/typescript/axios/unstable.sh b/apiclient/templates/typescript/axios/unstable.sh index 83e4ba228a..5d85dc726c 100644 --- a/apiclient/templates/typescript/axios/unstable.sh +++ b/apiclient/templates/typescript/axios/unstable.sh @@ -6,4 +6,4 @@ java -jar openapi-generator-cli.jar generate \ --output ./apiclient/generated/typescript/axios \ --template-dir ./apiclient/templates/typescript/axios \ --ignore-file-override ./apiclient/.openapi-generator-ignore \ - --additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",npmName="axios",snapshotVersion="-SNAPSHOT.$(Build.BuildNumber)",npmRepository="https://dev.azure.com/jellyfin-project/jellyfin/_packaging" + --additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",npmName="axios",snapshotVersion="-SNAPSHOT.$(Build.BuildNumber)",npmRepository="https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/" From 86e06369a93e6d7047646470e3755ad5df4c8974 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 08:00:57 -0600 Subject: [PATCH 051/151] fix liniting errors --- .ci/azure-pipelines-api-client.yml | 52 +++++++++++++++--------------- .ci/azure-pipelines.yml | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index dbd70c7028..f6e870674d 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -9,6 +9,7 @@ jobs: - job: GenerateApiClients displayName: 'Generate Api Clients' + dependsOn: Test pool: vmImage: "${{ parameters.LinuxImage }}" @@ -21,42 +22,41 @@ jobs: artifact: "OpenAPI Spec" path: "$(System.ArtifactsDirectory)/openapispec" runVersion: "latest" - dependsOn: Test - task: CmdLine@2 - displayName: 'Download OpenApi Generator' - inputs: - scripts: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar" + displayName: 'Download OpenApi Generator' + inputs: + script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar" # Generate npm api client # Unstable - task: CmdLine@2 - displayName: 'Build unstable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') - inputs: - script: 'bash ./apiclient/templates/typescript/axios/unstable.sh' + displayName: 'Build unstable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + inputs: + script: 'bash ./apiclient/templates/typescript/axios/unstable.sh' - task: Npm@1 - displayName: 'Publish unstable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') - inputs: - command: publish - publishRegistry: useFeed - publishFeed: unstable - workingDir: ./apiclient/generated/typescript/axios + displayName: 'Publish unstable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + inputs: + command: publish + publishRegistry: useFeed + publishFeed: unstable + workingDir: ./apiclient/generated/typescript/axios # Stable - task: CmdLine@2 - displayName: 'Build stable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') - inputs: - script: 'bash ./apiclient/templates/typescript/axios/stable.sh' + displayName: 'Build stable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + inputs: + script: 'bash ./apiclient/templates/typescript/axios/stable.sh' - task: Npm@1 - displayName: 'Publish stable typescript axios client' - condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') - inputs: - command: publish - publishRegistry: useExternalRegistry - publishEndpoint: 'jellyfin-bot for NPM' - workingDir: ./apiclient/generated/typescript/axios + displayName: 'Publish stable typescript axios client' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + inputs: + command: publish + publishRegistry: useExternalRegistry + publishEndpoint: 'jellyfin-bot for NPM' + workingDir: ./apiclient/generated/typescript/axios diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index f3e515447d..4c5db80c10 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -57,4 +57,4 @@ jobs: - template: azure-pipelines-package.yml - ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: - - template: azure-pipelines-api-client.yml + - template: azure-pipelines-api-client.yml From b95533d6a947306e92b02ff77d17fe07ca78e87c Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 08:06:06 -0600 Subject: [PATCH 052/151] there I go changing paths again --- apiclient/templates/typescript/axios/stable.sh | 2 +- apiclient/templates/typescript/axios/unstable.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apiclient/templates/typescript/axios/stable.sh b/apiclient/templates/typescript/axios/stable.sh index 70c6409fb6..118ef219fe 100644 --- a/apiclient/templates/typescript/axios/stable.sh +++ b/apiclient/templates/typescript/axios/stable.sh @@ -1,7 +1,7 @@ #!/bin/bash java -jar openapi-generator-cli.jar generate \ - --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ + --input-spec $(System.ArtifactsDirectory)/openapispec/openapi.json \ --generator-name typescript-axios \ --output ./apiclient/generated/typescript/axios \ --template-dir ./apiclient/templates/typescript/axios \ diff --git a/apiclient/templates/typescript/axios/unstable.sh b/apiclient/templates/typescript/axios/unstable.sh index 5d85dc726c..be9f9be438 100644 --- a/apiclient/templates/typescript/axios/unstable.sh +++ b/apiclient/templates/typescript/axios/unstable.sh @@ -1,7 +1,7 @@ #!/bin/bash java -jar openapi-generator-cli.jar generate \ - --input-spec $(System.ArtifactsDirectory)/openapi/openapi.json \ + --input-spec $(System.ArtifactsDirectory)/openapispec/openapi.json \ --generator-name typescript-axios \ --output ./apiclient/generated/typescript/axios \ --template-dir ./apiclient/templates/typescript/axios \ From 0ffc58e25509c375654430e64a2208baf6d65be9 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Fri, 2 Oct 2020 08:21:28 -0600 Subject: [PATCH 053/151] Update .ci/azure-pipelines-api-client.yml Co-authored-by: Cameron --- .ci/azure-pipelines-api-client.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index f6e870674d..7f428aec15 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -4,7 +4,7 @@ default: "ubuntu-latest" - name: GeneratorVersion type: string - default: "4.3.1" + default: "5.0.0-beta2" jobs: - job: GenerateApiClients From 9aad772288145645d51f93b26a2493782f55f2d3 Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Fri, 2 Oct 2020 18:26:48 +0200 Subject: [PATCH 054/151] feat: implement CommaDelimitedArrayModelBinderProvider --- Jellyfin.Api/Controllers/SessionController.cs | 2 +- .../CommaDelimitedArrayModelBinderProvider.cs | 29 +++++++++++++++++++ .../ApiServiceCollectionExtensions.cs | 3 ++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinderProvider.cs diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 68cec14151..3dbf7ba0b7 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -379,7 +379,7 @@ namespace Jellyfin.Api.Controllers public ActionResult PostCapabilities( [FromQuery] string? id, [FromQuery] string? playableMediaTypes, - [FromQuery][ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, + [FromQuery] GeneralCommandType[] supportedCommands, [FromQuery] bool supportsMediaControl = false, [FromQuery] bool supportsSync = false, [FromQuery] bool supportsPersistentIdentifier = true) diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinderProvider.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinderProvider.cs new file mode 100644 index 0000000000..b9785a73b8 --- /dev/null +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinderProvider.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.ModelBinders +{ + /// + /// Comma delimited array model binder provider. + /// + public class CommaDelimitedArrayModelBinderProvider : IModelBinderProvider + { + private readonly IModelBinder _binder; + + /// + /// Initializes a new instance of the class. + /// + public CommaDelimitedArrayModelBinderProvider() + { + _binder = new CommaDelimitedArrayModelBinder(); + } + + /// + public IModelBinder? GetBinder(ModelBinderProviderContext context) + { + return context.Metadata.ModelType.IsArray ? _binder : null; + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 5bcf6d5f07..f867143df6 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -16,6 +16,7 @@ using Jellyfin.Api.Auth.LocalAccessPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; +using Jellyfin.Api.ModelBinders; using Jellyfin.Server.Configuration; using Jellyfin.Server.Filters; using Jellyfin.Server.Formatters; @@ -166,6 +167,8 @@ namespace Jellyfin.Server.Extensions opts.OutputFormatters.Add(new CssOutputFormatter()); opts.OutputFormatters.Add(new XmlOutputFormatter()); + + opts.ModelBinderProviders.Insert(0, new CommaDelimitedArrayModelBinderProvider()); }) // Clear app parts to avoid other assemblies being picked up From 754e859f6e13c0fad0ed91d694b7fca163f58ce1 Mon Sep 17 00:00:00 2001 From: Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Fri, 2 Oct 2020 12:05:39 -0500 Subject: [PATCH 055/151] Convert strings to ImageFormat --- Jellyfin.Api/Controllers/ImageController.cs | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 7afec1219e..6f925dec58 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -364,7 +364,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromQuery] string? tag, [FromQuery] bool? cropWhitespace, - [FromQuery] string? format, + [FromQuery] ImageFormat? format, [FromQuery] bool? addPlayedIndicator, [FromQuery] double? percentPlayed, [FromQuery] int? unplayedCount, @@ -443,7 +443,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? quality, [FromRoute, Required] string tag, [FromQuery] bool? cropWhitespace, - [FromRoute, Required] string format, + [FromRoute, Required] ImageFormat format, [FromQuery] bool? addPlayedIndicator, [FromRoute, Required] double percentPlayed, [FromRoute, Required] int unplayedCount, @@ -516,7 +516,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromQuery] string tag, - [FromQuery] string format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -595,7 +595,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromQuery] string tag, - [FromQuery] string format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -674,7 +674,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromQuery] string tag, - [FromQuery] string format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -753,7 +753,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromQuery] string tag, - [FromQuery] string format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -832,7 +832,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string name, [FromRoute, Required] ImageType imageType, [FromRoute, Required] string tag, - [FromRoute, Required] string format, + [FromRoute, Required] ImageFormat format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -911,7 +911,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromRoute, Required] ImageType imageType, [FromQuery] string? tag, - [FromQuery] string? format, + [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] double? percentPlayed, @@ -1038,7 +1038,7 @@ namespace Jellyfin.Api.Controllers ImageType imageType, int? imageIndex, string? tag, - string? format, + ImageFormat? format, int? maxWidth, int? maxHeight, double? percentPlayed, @@ -1128,12 +1128,11 @@ namespace Jellyfin.Api.Controllers isHeadRequest).ConfigureAwait(false); } - private ImageFormat[] GetOutputFormats(string? format) + private ImageFormat[] GetOutputFormats(ImageFormat? format) { - if (!string.IsNullOrWhiteSpace(format) - && Enum.TryParse(format, true, out ImageFormat parsedFormat)) + if (format.HasValue) { - return new[] { parsedFormat }; + return new[] { format.Value }; } return GetClientSupportedFormats(); @@ -1157,7 +1156,7 @@ namespace Jellyfin.Api.Controllers var acceptParam = Request.Query[HeaderNames.Accept]; - var supportsWebP = SupportsFormat(supportedFormats, acceptParam, "webp", false); + var supportsWebP = SupportsFormat(supportedFormats, acceptParam, ImageFormat.Webp, false); if (!supportsWebP) { @@ -1179,7 +1178,7 @@ namespace Jellyfin.Api.Controllers formats.Add(ImageFormat.Jpg); formats.Add(ImageFormat.Png); - if (SupportsFormat(supportedFormats, acceptParam, "gif", true)) + if (SupportsFormat(supportedFormats, acceptParam, ImageFormat.Gif, true)) { formats.Add(ImageFormat.Gif); } @@ -1187,9 +1186,10 @@ namespace Jellyfin.Api.Controllers return formats.ToArray(); } - private bool SupportsFormat(IReadOnlyCollection requestAcceptTypes, string acceptParam, string format, bool acceptAll) + private bool SupportsFormat(IReadOnlyCollection requestAcceptTypes, string acceptParam, ImageFormat format, bool acceptAll) { - var mimeType = "image/" + format; + var normalized = format.ToString().ToLowerInvariant(); + var mimeType = "image/" + normalized; if (requestAcceptTypes.Contains(mimeType)) { @@ -1201,7 +1201,7 @@ namespace Jellyfin.Api.Controllers return true; } - return string.Equals(acceptParam, format, StringComparison.OrdinalIgnoreCase); + return string.Equals(acceptParam, normalized, StringComparison.OrdinalIgnoreCase); } private async Task GetImageResult( From 8535a718b7e9307dfcb2e04d198b0cabbdb15205 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 2 Oct 2020 19:43:34 +0200 Subject: [PATCH 056/151] Add tests for deserializing guids --- .../Json/JsonGuidConverterTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs diff --git a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs new file mode 100644 index 0000000000..aef5b3a7d7 --- /dev/null +++ b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs @@ -0,0 +1,22 @@ +using System; +using System.Text.Json; +using MediaBrowser.Common.Json.Converters; +using Xunit; + +namespace Jellyfin.Common.Tests.Extensions +{ + public static class JsonGuidConverterTests + { + [Fact] + public static void Deserialize_Valid_Success() + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonGuidConverter()); + Guid value = JsonSerializer.Deserialize(@"""a852a27afe324084ae66db579ee3ee18""", options); + Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value); + + value = JsonSerializer.Deserialize(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", options); + Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value); + } + } +} From 6a32385588889074496dd94a6160614d01bdd63d Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 2 Oct 2020 13:30:31 -0600 Subject: [PATCH 057/151] Allow server to return .data files --- Jellyfin.Server/Startup.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 2f4620aa63..62ffe174cd 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using System.Net.Http.Headers; +using System.Net.Mime; using Jellyfin.Api.TypeConverters; using Jellyfin.Server.Extensions; using Jellyfin.Server.Implementations; @@ -11,6 +12,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; @@ -123,10 +125,15 @@ namespace Jellyfin.Server mainApp.UseStaticFiles(); if (appConfig.HostWebClient()) { + var extensionProvider = new FileExtensionContentTypeProvider(); + + // subtitles octopus requires .data files. + extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet); mainApp.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath), - RequestPath = "/web" + RequestPath = "/web", + ContentTypeProvider = extensionProvider }); } From 33f80dc3c167f07348cd817f38a33a78302bda6b Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Sat, 3 Oct 2020 01:09:15 +0200 Subject: [PATCH 058/151] feat(CommaDelimitedArrayModelBinder): add none result check --- Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs index 208566dc8e..13469194a0 100644 --- a/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs +++ b/Jellyfin.Api/ModelBinders/CommaDelimitedArrayModelBinder.cs @@ -18,6 +18,11 @@ namespace Jellyfin.Api.ModelBinders var elementType = bindingContext.ModelType.GetElementType(); var converter = TypeDescriptor.GetConverter(elementType); + if (valueProviderResult == ValueProviderResult.None) + { + return Task.CompletedTask; + } + if (valueProviderResult.Length > 1) { var result = Array.CreateInstance(elementType, valueProviderResult.Length); From b3b98a5cc8d49174dda4d4784a7e9297b5961f68 Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Sat, 3 Oct 2020 01:09:28 +0200 Subject: [PATCH 059/151] test: add TestType enum --- .../Jellyfin.Api.Tests/ModelBinders/TestType.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs b/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs new file mode 100644 index 0000000000..544a74637a --- /dev/null +++ b/tests/Jellyfin.Api.Tests/ModelBinders/TestType.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Jellyfin.Api.Tests.ModelBinders +{ + public enum TestType + { +#pragma warning disable SA1602 // Enumeration items should be documented + How, + Much, + Is, + The, + Fish +#pragma warning restore SA1602 // Enumeration items should be documented + } +} From 1bd80a634fc941c51b13b624530ab12ce6551820 Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Sat, 3 Oct 2020 01:09:45 +0200 Subject: [PATCH 060/151] test: add CommaDelimitedArrayModelBinder tests --- .../CommaDelimitedArrayModelBinderTests.cs | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs new file mode 100644 index 0000000000..b05be6a16a --- /dev/null +++ b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Text; +using System.Threading.Tasks; +using Jellyfin.Api.ModelBinders; +using MediaBrowser.Controller.Entities; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Primitives; +using Moq; +using Xunit; +using Xunit.Sdk; + +namespace Jellyfin.Api.Tests.ModelBinders +{ + public sealed class CommaDelimitedArrayModelBinderTests + { + [Fact] + public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedStringArrayQuery() + { + var queryParamName = "test"; + var queryParamValues = new string[] { "lol", "xd" }; + var queryParamString = "lol,xd"; + var queryParamType = typeof(string[]); + + var modelBinder = new CommaDelimitedArrayModelBinder(); + var valueProvider = new QueryStringValueProvider( + new BindingSource(string.Empty, string.Empty, false, false), + new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + CultureInfo.InvariantCulture); + var bindingContextMock = new Mock(); + bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); + bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); + bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); + bindingContextMock.SetupProperty(b => b.Result); + + await modelBinder.BindModelAsync(bindingContextMock.Object); + + Assert.True(bindingContextMock.Object.Result.IsModelSet); + Assert.Equal((string[])bindingContextMock.Object.Result.Model, queryParamValues); + } + + [Fact] + public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedIntArrayQuery() + { + var queryParamName = "test"; + var queryParamValues = new int[] { 42, 0 }; + var queryParamString = "42,0"; + var queryParamType = typeof(int[]); + + var modelBinder = new CommaDelimitedArrayModelBinder(); + var valueProvider = new QueryStringValueProvider( + new BindingSource(string.Empty, string.Empty, false, false), + new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + CultureInfo.InvariantCulture); + var bindingContextMock = new Mock(); + bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); + bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); + bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); + bindingContextMock.SetupProperty(b => b.Result); + + await modelBinder.BindModelAsync(bindingContextMock.Object); + + Assert.True(bindingContextMock.Object.Result.IsModelSet); + Assert.Equal((int[])bindingContextMock.Object.Result.Model, queryParamValues); + } + + [Fact] + public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery() + { + var queryParamName = "test"; + var queryParamValues = new TestType[] { TestType.How, TestType.Much }; + var queryParamString = "How,Much"; + var queryParamType = typeof(TestType[]); + + var modelBinder = new CommaDelimitedArrayModelBinder(); + var valueProvider = new QueryStringValueProvider( + new BindingSource(string.Empty, string.Empty, false, false), + new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + CultureInfo.InvariantCulture); + var bindingContextMock = new Mock(); + bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); + bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); + bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); + bindingContextMock.SetupProperty(b => b.Result); + + await modelBinder.BindModelAsync(bindingContextMock.Object); + + Assert.True(bindingContextMock.Object.Result.IsModelSet); + Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues); + } + + [Fact] + public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery2() + { + var queryParamName = "test"; + var queryParamValues = new TestType[] { TestType.How, TestType.Much }; + var queryParamString = "How,,Much"; + var queryParamType = typeof(TestType[]); + + var modelBinder = new CommaDelimitedArrayModelBinder(); + var valueProvider = new QueryStringValueProvider( + new BindingSource(string.Empty, string.Empty, false, false), + new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + CultureInfo.InvariantCulture); + var bindingContextMock = new Mock(); + bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); + bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); + bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); + bindingContextMock.SetupProperty(b => b.Result); + + await modelBinder.BindModelAsync(bindingContextMock.Object); + + Assert.True(bindingContextMock.Object.Result.IsModelSet); + Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues); + } + + [Fact] + public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery() + { + var queryParamName = "test"; + var queryParamValues = new TestType[] { TestType.How, TestType.Much }; + var queryParamString1 = "How"; + var queryParamString2 = "Much"; + var queryParamType = typeof(TestType[]); + + var modelBinder = new CommaDelimitedArrayModelBinder(); + + var valueProvider = new QueryStringValueProvider( + new BindingSource(string.Empty, string.Empty, false, false), + new QueryCollection(new Dictionary() + { + { queryParamName, new StringValues(new string[] { queryParamString1, queryParamString2 }) }, + }), + CultureInfo.InvariantCulture); + var bindingContextMock = new Mock(); + bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); + bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); + bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); + bindingContextMock.SetupProperty(b => b.Result); + + await modelBinder.BindModelAsync(bindingContextMock.Object); + + Assert.True(bindingContextMock.Object.Result.IsModelSet); + Assert.Equal((TestType[])bindingContextMock.Object.Result.Model, queryParamValues); + } + + [Fact] + public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery2() + { + var queryParamName = "test"; + var queryParamValues = Array.Empty(); + var queryParamType = typeof(TestType[]); + + var modelBinder = new CommaDelimitedArrayModelBinder(); + + var valueProvider = new QueryStringValueProvider( + new BindingSource(string.Empty, string.Empty, false, false), + new QueryCollection(new Dictionary() + { + { queryParamName, new StringValues(value: null) }, + }), + CultureInfo.InvariantCulture); + var bindingContextMock = new Mock(); + bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); + bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); + bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); + bindingContextMock.SetupProperty(b => b.Result); + + await modelBinder.BindModelAsync(bindingContextMock.Object); + + Assert.False(bindingContextMock.Object.Result.IsModelSet); + } + + [Fact] + public async Task BindModelAsync_ThrowsIfCommaDelimitedEnumArrayQueryIsInvalid() + { + var queryParamName = "test"; + var queryParamString = "🔥,😢"; + var queryParamType = typeof(TestType[]); + + var modelBinder = new CommaDelimitedArrayModelBinder(); + var valueProvider = new QueryStringValueProvider( + new BindingSource(string.Empty, string.Empty, false, false), + new QueryCollection(new Dictionary() { { queryParamName, new StringValues(queryParamString) } }), + CultureInfo.InvariantCulture); + var bindingContextMock = new Mock(); + bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); + bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); + bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); + bindingContextMock.SetupProperty(b => b.Result); + + Func act = async () => await modelBinder.BindModelAsync(bindingContextMock.Object); + + await Assert.ThrowsAsync(act); + } + + [Fact] + public async Task BindModelAsync_ThrowsIfCommaDelimitedEnumArrayQueryIsInvalid2() + { + var queryParamName = "test"; + var queryParamValues = new TestType[] { TestType.How, TestType.Much }; + var queryParamString1 = "How"; + var queryParamString2 = "😱"; + var queryParamType = typeof(TestType[]); + + var modelBinder = new CommaDelimitedArrayModelBinder(); + + var valueProvider = new QueryStringValueProvider( + new BindingSource(string.Empty, string.Empty, false, false), + new QueryCollection(new Dictionary() + { + { queryParamName, new StringValues(new string[] { queryParamString1, queryParamString2 }) }, + }), + CultureInfo.InvariantCulture); + var bindingContextMock = new Mock(); + bindingContextMock.Setup(b => b.ValueProvider).Returns(valueProvider); + bindingContextMock.Setup(b => b.ModelName).Returns(queryParamName); + bindingContextMock.Setup(b => b.ModelType).Returns(queryParamType); + bindingContextMock.SetupProperty(b => b.Result); + + Func act = async () => await modelBinder.BindModelAsync(bindingContextMock.Object); + + await Assert.ThrowsAsync(act); + } + } +} From 04cdc89a5c9f92c70078520eb5c63fd2606f2a22 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Fri, 2 Oct 2020 17:14:57 -0700 Subject: [PATCH 061/151] Make MusicBrainzAlbumProvider thread safe --- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index abfa1c6e71..2d37422d04 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -46,6 +46,7 @@ namespace MediaBrowser.Providers.Music private readonly string _musicBrainzBaseUrl; + private SemaphoreSlim _apiRequestLock = new SemaphoreSlim(1, 1); private Stopwatch _stopWatchMusicBrainz = new Stopwatch(); public MusicBrainzAlbumProvider( @@ -742,45 +743,55 @@ namespace MediaBrowser.Providers.Music /// internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { - using var options = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url); - - // MusicBrainz request a contact email address is supplied, as comment, in user agent field: - // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent - options.Headers.UserAgent.ParseAdd(string.Format( - CultureInfo.InvariantCulture, - "{0} ( {1} )", - _appHost.ApplicationUserAgent, - _appHost.ApplicationUserAgentAddress)); - HttpResponseMessage response; var attempts = 0u; + var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url; - do + await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try { - attempts++; - - if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) + do { - // MusicBrainz is extremely adamant about limiting to one request per second - var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; - await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); + attempts++; + + if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs) + { + // MusicBrainz is extremely adamant about limiting to one request per second + var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; + await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); + } + + // Write time since last request to debug log as evidence we're meeting rate limit + // requirement, before resetting stopwatch back to zero. + _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); + _stopWatchMusicBrainz.Restart(); + + using var request = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url); + + // MusicBrainz request a contact email address is supplied, as comment, in user agent field: + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent + request.Headers.UserAgent.ParseAdd(string.Format( + CultureInfo.InvariantCulture, + "{0} ( {1} )", + _appHost.ApplicationUserAgent, + _appHost.ApplicationUserAgentAddress)); + + response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(request).ConfigureAwait(false); + + // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) } - - // Write time since last request to debug log as evidence we're meeting rate limit - // requirement, before resetting stopwatch back to zero. - _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); - _stopWatchMusicBrainz.Restart(); - - response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options).ConfigureAwait(false); - - // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) + while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); + } + finally + { + _apiRequestLock.Release(); } - while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); // Log error if unable to query MB database due to throttling if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) { - _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.RequestUri); + _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); } return response; From db2e667936a3c982ad28fb383efd427f24e4e37d Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Fri, 2 Oct 2020 17:19:35 -0700 Subject: [PATCH 062/151] expand try finally --- .../Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 2d37422d04..5fe9ef7fa6 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -782,18 +782,18 @@ namespace MediaBrowser.Providers.Music // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) } while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); + + // Log error if unable to query MB database due to throttling + if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) + { + _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); + } } finally { _apiRequestLock.Release(); } - // Log error if unable to query MB database due to throttling - if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable) - { - _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); - } - return response; } From 7841378506a04dd02d8e8459a274e740ed5da3f5 Mon Sep 17 00:00:00 2001 From: Gary Wilber Date: Fri, 2 Oct 2020 17:27:43 -0700 Subject: [PATCH 063/151] cleaner --- .../MusicBrainz/MusicBrainzAlbumProvider.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 5fe9ef7fa6..bf3088aa83 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -743,14 +743,14 @@ namespace MediaBrowser.Providers.Music /// internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { - HttpResponseMessage response; - var attempts = 0u; - var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url; - await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false); try { + HttpResponseMessage response; + var attempts = 0u; + var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url; + do { attempts++; @@ -767,7 +767,7 @@ namespace MediaBrowser.Providers.Music _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); _stopWatchMusicBrainz.Restart(); - using var request = new HttpRequestMessage(HttpMethod.Get, _musicBrainzBaseUrl.TrimEnd('/') + url); + using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl); // MusicBrainz request a contact email address is supplied, as comment, in user agent field: // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent @@ -788,13 +788,13 @@ namespace MediaBrowser.Providers.Music { _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl); } + + return response; } finally { _apiRequestLock.Release(); } - - return response; } /// From 86cbefb059e55680bbf40769cdf13849e19b31bb Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Fri, 2 Oct 2020 20:32:52 -0400 Subject: [PATCH 064/151] Remove Windows legacy files --- windows/build-jellyfin.ps1 | 190 --------- windows/dependencies.txt | 2 - windows/dialogs/confirmation.nsddef | 24 -- windows/dialogs/confirmation.nsdinc | 61 --- windows/dialogs/service-config.nsddef | 13 - windows/dialogs/service-config.nsdinc | 56 --- windows/dialogs/setuptype.nsddef | 12 - windows/dialogs/setuptype.nsdinc | 50 --- windows/helpers/ShowError.nsh | 10 - windows/helpers/StrSlash.nsh | 47 --- windows/jellyfin.nsi | 575 -------------------------- windows/legacy/install-jellyfin.ps1 | 460 --------------------- windows/legacy/install.bat | 1 - 13 files changed, 1501 deletions(-) delete mode 100644 windows/build-jellyfin.ps1 delete mode 100644 windows/dependencies.txt delete mode 100644 windows/dialogs/confirmation.nsddef delete mode 100644 windows/dialogs/confirmation.nsdinc delete mode 100644 windows/dialogs/service-config.nsddef delete mode 100644 windows/dialogs/service-config.nsdinc delete mode 100644 windows/dialogs/setuptype.nsddef delete mode 100644 windows/dialogs/setuptype.nsdinc delete mode 100644 windows/helpers/ShowError.nsh delete mode 100644 windows/helpers/StrSlash.nsh delete mode 100644 windows/jellyfin.nsi delete mode 100644 windows/legacy/install-jellyfin.ps1 delete mode 100644 windows/legacy/install.bat diff --git a/windows/build-jellyfin.ps1 b/windows/build-jellyfin.ps1 deleted file mode 100644 index b65e619ee1..0000000000 --- a/windows/build-jellyfin.ps1 +++ /dev/null @@ -1,190 +0,0 @@ -[CmdletBinding()] -param( - [switch]$MakeNSIS, - [switch]$InstallNSIS, - [switch]$InstallFFMPEG, - [switch]$InstallNSSM, - [switch]$SkipJellyfinBuild, - [switch]$GenerateZip, - [string]$InstallLocation = "./dist/jellyfin-win-nsis", - [string]$UXLocation = "../jellyfin-ux", - [switch]$InstallTrayApp, - [ValidateSet('Debug','Release')][string]$BuildType = 'Release', - [ValidateSet('Quiet','Minimal', 'Normal')][string]$DotNetVerbosity = 'Minimal', - [ValidateSet('win','win7', 'win8','win81','win10')][string]$WindowsVersion = 'win', - [ValidateSet('x64','x86', 'arm', 'arm64')][string]$Architecture = 'x64' -) - -$ProgressPreference = 'SilentlyContinue' # Speedup all downloads by hiding progress bars. - -#PowershellCore and *nix check to make determine which temp dir to use. -if(($PSVersionTable.PSEdition -eq 'Core') -and (-not $IsWindows)){ - $TempDir = mktemp -d -}else{ - $TempDir = $env:Temp -} -#Create staging dir -New-Item -ItemType Directory -Force -Path $InstallLocation -$ResolvedInstallLocation = Resolve-Path $InstallLocation -$ResolvedUXLocation = Resolve-Path $UXLocation - -function Build-JellyFin { - if(($Architecture -eq 'arm64') -and ($WindowsVersion -ne 'win10')){ - Write-Error "arm64 only supported with Windows10 Version" - exit - } - if(($Architecture -eq 'arm') -and ($WindowsVersion -notin @('win10','win81','win8'))){ - Write-Error "arm only supported with Windows 8 or higher" - exit - } - Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture" - Write-Verbose "InstallLocation: $ResolvedInstallLocation" - Write-Verbose "DotNetVerbosity: $DotNetVerbosity" - dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=true -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server -} - -function Install-FFMPEG { - param( - [string]$ResolvedInstallLocation, - [string]$Architecture, - [string]$FFMPEGVersionX86 = "ffmpeg-4.3-win32-shared" - ) - Write-Verbose "Checking Architecture" - if($Architecture -notin @('x86','x64')){ - Write-Warning "No builds available for your selected architecture of $Architecture" - Write-Warning "FFMPEG will not be installed" - }elseif($Architecture -eq 'x64'){ - Write-Verbose "Downloading 64 bit FFMPEG" - Invoke-WebRequest -Uri https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose - }else{ - Write-Verbose "Downloading 32 bit FFMPEG" - Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/shared/$FFMPEGVersionX86.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose - } - - Expand-Archive "$tempdir/ffmpeg.zip" -DestinationPath "$tempdir/ffmpeg/" -Force | Write-Verbose - if($Architecture -eq 'x64'){ - Write-Verbose "Copying Binaries to Jellyfin location" - Get-ChildItem "$tempdir/ffmpeg" | ForEach-Object { - Copy-Item $_.FullName -Destination $installLocation | Write-Verbose - } - }else{ - Write-Verbose "Copying Binaries to Jellyfin location" - Get-ChildItem "$tempdir/ffmpeg/$FFMPEGVersionX86/bin" | ForEach-Object { - Copy-Item $_.FullName -Destination $installLocation | Write-Verbose - } - } - Remove-Item "$tempdir/ffmpeg/" -Recurse -Force -ErrorAction Continue | Write-Verbose - Remove-Item "$tempdir/ffmpeg.zip" -Force -ErrorAction Continue | Write-Verbose -} - -function Install-NSSM { - param( - [string]$ResolvedInstallLocation, - [string]$Architecture - ) - Write-Verbose "Checking Architecture" - if($Architecture -notin @('x86','x64')){ - Write-Warning "No builds available for your selected architecture of $Architecture" - Write-Warning "NSSM will not be installed" - }else{ - Write-Verbose "Downloading NSSM" - # [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - # Temporary workaround, file is hosted in an azure blob with a custom domain in front for brevity - Invoke-WebRequest -Uri http://files.evilt.win/nssm/nssm-2.24-101-g897c7ad.zip -UseBasicParsing -OutFile "$tempdir/nssm.zip" | Write-Verbose - } - - Expand-Archive "$tempdir/nssm.zip" -DestinationPath "$tempdir/nssm/" -Force | Write-Verbose - if($Architecture -eq 'x64'){ - Write-Verbose "Copying Binaries to Jellyfin location" - Get-ChildItem "$tempdir/nssm/nssm-2.24-101-g897c7ad/win64" | ForEach-Object { - Copy-Item $_.FullName -Destination $installLocation | Write-Verbose - } - }else{ - Write-Verbose "Copying Binaries to Jellyfin location" - Get-ChildItem "$tempdir/nssm/nssm-2.24-101-g897c7ad/win32" | ForEach-Object { - Copy-Item $_.FullName -Destination $installLocation | Write-Verbose - } - } - Remove-Item "$tempdir/nssm/" -Recurse -Force -ErrorAction Continue | Write-Verbose - Remove-Item "$tempdir/nssm.zip" -Force -ErrorAction Continue | Write-Verbose -} - -function Make-NSIS { - param( - [string]$ResolvedInstallLocation - ) - - $env:InstallLocation = $ResolvedInstallLocation - if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ - & "$tempdir/nsis/nsis-3.04/makensis.exe" /D$Architecture /DUXPATH=$ResolvedUXLocation ".\deployment\windows\jellyfin.nsi" - } else { - & "makensis" /D$Architecture /DUXPATH=$ResolvedUXLocation ".\deployment\windows\jellyfin.nsi" - } - Copy-Item .\deployment\windows\jellyfin_*.exe $ResolvedInstallLocation\..\ -} - - -function Install-NSIS { - Write-Verbose "Downloading NSIS" - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest -Uri https://nchc.dl.sourceforge.net/project/nsis/NSIS%203/3.04/nsis-3.04.zip -UseBasicParsing -OutFile "$tempdir/nsis.zip" | Write-Verbose - - Expand-Archive "$tempdir/nsis.zip" -DestinationPath "$tempdir/nsis/" -Force | Write-Verbose -} - -function Cleanup-NSIS { - Remove-Item "$tempdir/nsis/" -Recurse -Force -ErrorAction Continue | Write-Verbose - Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose -} - -function Install-TrayApp { - param( - [string]$ResolvedInstallLocation, - [string]$Architecture - ) - Write-Verbose "Checking Architecture" - if($Architecture -ne 'x64'){ - Write-Warning "No builds available for your selected architecture of $Architecture" - Write-Warning "The tray app will not be available." - }else{ - Write-Verbose "Downloading Tray App and copying to Jellyfin location" - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest -Uri https://github.com/jellyfin/jellyfin-windows-tray/releases/latest/download/JellyfinTray.exe -UseBasicParsing -OutFile "$installLocation/JellyfinTray.exe" | Write-Verbose - } -} - -if(-not $SkipJellyfinBuild.IsPresent -and -not ($InstallNSIS -eq $true)){ - Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture" - Build-JellyFin -} -if($InstallFFMPEG.IsPresent -or ($InstallFFMPEG -eq $true)){ - Write-Verbose "Starting FFMPEG Install" - Install-FFMPEG $ResolvedInstallLocation $Architecture -} -if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){ - Write-Verbose "Starting NSSM Install" - Install-NSSM $ResolvedInstallLocation $Architecture -} -if($InstallTrayApp.IsPresent -or ($InstallTrayApp -eq $true)){ - Write-Verbose "Downloading Windows Tray App" - Install-TrayApp $ResolvedInstallLocation $Architecture -} -#Copy-Item .\deployment\windows\install-jellyfin.ps1 $ResolvedInstallLocation\install-jellyfin.ps1 -#Copy-Item .\deployment\windows\install.bat $ResolvedInstallLocation\install.bat -Copy-Item .\LICENSE $ResolvedInstallLocation\LICENSE -if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ - Write-Verbose "Installing NSIS" - Install-NSIS -} -if($MakeNSIS.IsPresent -or ($MakeNSIS -eq $true)){ - Write-Verbose "Starting NSIS Package creation" - Make-NSIS $ResolvedInstallLocation -} -if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){ - Write-Verbose "Cleanup NSIS" - Cleanup-NSIS -} -if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){ - Compress-Archive -Path $ResolvedInstallLocation -DestinationPath "$ResolvedInstallLocation/jellyfin.zip" -Force -} -Write-Verbose "Finished" diff --git a/windows/dependencies.txt b/windows/dependencies.txt deleted file mode 100644 index 16f77cce7c..0000000000 --- a/windows/dependencies.txt +++ /dev/null @@ -1,2 +0,0 @@ -dotnet -nsis diff --git a/windows/dialogs/confirmation.nsddef b/windows/dialogs/confirmation.nsddef deleted file mode 100644 index 969ebacd62..0000000000 --- a/windows/dialogs/confirmation.nsddef +++ /dev/null @@ -1,24 +0,0 @@ - - - - !include "helpers\StrSlash.nsh" - ${StrSlash} '$0' $INSTDIR - - ${StrSlash} '$1' $_JELLYFINDATADIR_ - - ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ - \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ - Installation Folder:\b0 $0\line\b \ - Service install:\b0 $_INSTALLSERVICE_\line\b \ - Service start:\b0 $_SERVICESTART_\line\b \ - Service account:\b0 $_SERVICEACCOUNTTYPE_\line\b \ - Jellyfin Data Folder:\b0 $1\par \ -\ - \pard\sa200\sl276\slmult1\f1\lang1043\par \ - }" - - diff --git a/windows/dialogs/confirmation.nsdinc b/windows/dialogs/confirmation.nsdinc deleted file mode 100644 index f00e9b43ab..0000000000 --- a/windows/dialogs/confirmation.nsdinc +++ /dev/null @@ -1,61 +0,0 @@ -; ========================================================= -; This file was generated by NSISDialogDesigner 1.4.4.0 -; http://coolsoft.altervista.org/nsisdialogdesigner -; -; Do not edit it manually, use NSISDialogDesigner instead! -; Modified by EraYaN (2019-09-01) -; ========================================================= - -; handle variables -Var hCtl_confirmation -Var hCtl_confirmation_ConfirmRichText - -; HeaderCustomScript -!include "helpers\StrSlash.nsh" - - - -; dialog create function -Function fnc_confirmation_Create - - ; === confirmation (type: Dialog) === - nsDialogs::Create 1018 - Pop $hCtl_confirmation - ${If} $hCtl_confirmation == error - Abort - ${EndIf} - !insertmacro MUI_HEADER_TEXT "Confirmation Page" "Please confirm your choices for Jellyfin Server installation" - - ; === ConfirmRichText (type: RichText) === - nsDialogs::CreateControl /NOUNLOAD "RichEdit20A" ${ES_READONLY}|${WS_VISIBLE}|${WS_CHILD}|${WS_TABSTOP}|${WS_VSCROLL}|${ES_MULTILINE}|${ES_WANTRETURN} ${WS_EX_STATICEDGE} 8u 7u 280u 126u "" - Pop $hCtl_confirmation_ConfirmRichText - ${NSD_AddExStyle} $hCtl_confirmation_ConfirmRichText ${WS_EX_STATICEDGE} - - ; CreateFunctionCustomScript - ${StrSlash} '$0' $INSTDIR - - ${StrSlash} '$1' $_JELLYFINDATADIR_ - - ${If} $_INSTALLSERVICE_ == "Yes" - ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ - \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ - Installation Folder:\b0 $0\line\b \ - Service install:\b0 $_INSTALLSERVICE_\line\b \ - Service start:\b0 $_SERVICESTART_\line\b \ - Service account:\b0 $_SERVICEACCOUNTTYPE_\line\b \ - Jellyfin Data Folder:\b0 $1\par \ - \ - \pard\sa200\sl276\slmult1\f1\lang1043\par \ - }" - ${Else} - ${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \ - \pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \ - Installation Folder:\b0 $0\line\b \ - Service install:\b0 $_INSTALLSERVICE_\line\b \ - Jellyfin Data Folder:\b0 $1\par \ - \ - \pard\sa200\sl276\slmult1\f1\lang1043\par \ - }" - ${EndIf} - -FunctionEnd diff --git a/windows/dialogs/service-config.nsddef b/windows/dialogs/service-config.nsddef deleted file mode 100644 index 3509ada249..0000000000 --- a/windows/dialogs/service-config.nsddef +++ /dev/null @@ -1,13 +0,0 @@ - - - - - \ No newline at end of file diff --git a/windows/dialogs/service-config.nsdinc b/windows/dialogs/service-config.nsdinc deleted file mode 100644 index 58c350f2ec..0000000000 --- a/windows/dialogs/service-config.nsdinc +++ /dev/null @@ -1,56 +0,0 @@ -; ========================================================= -; This file was generated by NSISDialogDesigner 1.4.4.0 -; http://coolsoft.altervista.org/nsisdialogdesigner -; -; Do not edit it manually, use NSISDialogDesigner instead! -; ========================================================= - -; handle variables -Var hCtl_service_config -Var hCtl_service_config_StartServiceAfterInstall -Var hCtl_service_config_LocalSystemAccountLabel -Var hCtl_service_config_NetworkServiceAccountLabel -Var hCtl_service_config_UseLocalSystemAccount -Var hCtl_service_config_UseNetworkServiceAccount -Var hCtl_service_config_Font1 - - -; dialog create function -Function fnc_service_config_Create - - ; custom font definitions - CreateFont $hCtl_service_config_Font1 "Microsoft Sans Serif" "8.25" "700" - - ; === service_config (type: Dialog) === - nsDialogs::Create 1018 - Pop $hCtl_service_config - ${If} $hCtl_service_config == error - Abort - ${EndIf} - !insertmacro MUI_HEADER_TEXT "Configure the service" "This controls what type of access the server gets to this system." - - ; === StartServiceAfterInstall (type: Checkbox) === - ${NSD_CreateCheckbox} 8u 118u 280u 15u "Start Service after Install" - Pop $hCtl_service_config_StartServiceAfterInstall - ${NSD_Check} $hCtl_service_config_StartServiceAfterInstall - - ; === LocalSystemAccountLabel (type: Label) === - ${NSD_CreateLabel} 8u 71u 280u 28u "The Local System account has full access to every resource and file on the system. This can have very real security implications, do not use unless absolutely neseccary." - Pop $hCtl_service_config_LocalSystemAccountLabel - - ; === NetworkServiceAccountLabel (type: Label) === - ${NSD_CreateLabel} 8u 24u 280u 28u "The NetworkService account is a predefined local account used by the service control manager. It is the recommended way to install the Jellyfin Server service." - Pop $hCtl_service_config_NetworkServiceAccountLabel - - ; === UseLocalSystemAccount (type: RadioButton) === - ${NSD_CreateRadioButton} 8u 54u 280u 15u "Use Local System account" - Pop $hCtl_service_config_UseLocalSystemAccount - ${NSD_AddStyle} $hCtl_service_config_UseLocalSystemAccount ${WS_GROUP} - - ; === UseNetworkServiceAccount (type: RadioButton) === - ${NSD_CreateRadioButton} 8u 7u 280u 15u "Use Network Service account (Recommended)" - Pop $hCtl_service_config_UseNetworkServiceAccount - SendMessage $hCtl_service_config_UseNetworkServiceAccount ${WM_SETFONT} $hCtl_service_config_Font1 0 - ${NSD_Check} $hCtl_service_config_UseNetworkServiceAccount - -FunctionEnd diff --git a/windows/dialogs/setuptype.nsddef b/windows/dialogs/setuptype.nsddef deleted file mode 100644 index b55ceeaaa6..0000000000 --- a/windows/dialogs/setuptype.nsddef +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/windows/dialogs/setuptype.nsdinc b/windows/dialogs/setuptype.nsdinc deleted file mode 100644 index 8746ad2cc6..0000000000 --- a/windows/dialogs/setuptype.nsdinc +++ /dev/null @@ -1,50 +0,0 @@ -; ========================================================= -; This file was generated by NSISDialogDesigner 1.4.4.0 -; http://coolsoft.altervista.org/nsisdialogdesigner -; -; Do not edit it manually, use NSISDialogDesigner instead! -; ========================================================= - -; handle variables -Var hCtl_setuptype -Var hCtl_setuptype_InstallasaServiceLabel -Var hCtl_setuptype_InstallasaService -Var hCtl_setuptype_BasicInstallLabel -Var hCtl_setuptype_BasicInstall -Var hCtl_setuptype_Font1 - - -; dialog create function -Function fnc_setuptype_Create - - ; custom font definitions - CreateFont $hCtl_setuptype_Font1 "Microsoft Sans Serif" "8.25" "700" - - ; === setuptype (type: Dialog) === - nsDialogs::Create 1018 - Pop $hCtl_setuptype - ${If} $hCtl_setuptype == error - Abort - ${EndIf} - !insertmacro MUI_HEADER_TEXT "Setup Type" "Control how Jellyfin is installed." - - ; === InstallasaServiceLabel (type: Label) === - ${NSD_CreateLabel} 8u 71u 280u 28u "Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." - Pop $hCtl_setuptype_InstallasaServiceLabel - - ; === InstallasaService (type: RadioButton) === - ${NSD_CreateRadioButton} 8u 54u 280u 15u "Install as a Service (Advanced Users)" - Pop $hCtl_setuptype_InstallasaService - ${NSD_AddStyle} $hCtl_setuptype_InstallasaService ${WS_GROUP} - - ; === BasicInstallLabel (type: Label) === - ${NSD_CreateLabel} 8u 24u 280u 28u "The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4." - Pop $hCtl_setuptype_BasicInstallLabel - - ; === BasicInstall (type: RadioButton) === - ${NSD_CreateRadioButton} 8u 7u 280u 15u "Basic Install (Recommended)" - Pop $hCtl_setuptype_BasicInstall - SendMessage $hCtl_setuptype_BasicInstall ${WM_SETFONT} $hCtl_setuptype_Font1 0 - ${NSD_Check} $hCtl_setuptype_BasicInstall - -FunctionEnd diff --git a/windows/helpers/ShowError.nsh b/windows/helpers/ShowError.nsh deleted file mode 100644 index 6e09b1e407..0000000000 --- a/windows/helpers/ShowError.nsh +++ /dev/null @@ -1,10 +0,0 @@ -; Show error -!macro ShowError TEXT RETRYLABEL - MessageBox MB_ABORTRETRYIGNORE|MB_ICONSTOP "${TEXT}" IDIGNORE +2 IDRETRY ${RETRYLABEL} - Abort -!macroend - -!macro ShowErrorFinal TEXT - MessageBox MB_OK|MB_ICONSTOP "${TEXT}" - Abort -!macroend diff --git a/windows/helpers/StrSlash.nsh b/windows/helpers/StrSlash.nsh deleted file mode 100644 index b8aa771aa6..0000000000 --- a/windows/helpers/StrSlash.nsh +++ /dev/null @@ -1,47 +0,0 @@ -; Adapted from: https://nsis.sourceforge.io/Another_String_Replace_(and_Slash/BackSlash_Converter) (2019-08-31) - -!macro _StrSlashConstructor out in - Push "${in}" - Push "\" - Call StrSlash - Pop ${out} -!macroend - -!define StrSlash '!insertmacro "_StrSlashConstructor"' - -; Push $filenamestring (e.g. 'c:\this\and\that\filename.htm') -; Push "\" -; Call StrSlash -; Pop $R0 -; ;Now $R0 contains 'c:/this/and/that/filename.htm' -Function StrSlash - Exch $R3 ; $R3 = needle ("\" or "/") - Exch - Exch $R1 ; $R1 = String to replacement in (haystack) - Push $R2 ; Replaced haystack - Push $R4 ; $R4 = not $R3 ("/" or "\") - Push $R6 - Push $R7 ; Scratch reg - StrCpy $R2 "" - StrLen $R6 $R1 - StrCpy $R4 "\" - StrCmp $R3 "/" loop - StrCpy $R4 "/" -loop: - StrCpy $R7 $R1 1 - StrCpy $R1 $R1 $R6 1 - StrCmp $R7 $R3 found - StrCpy $R2 "$R2$R7" - StrCmp $R1 "" done loop -found: - StrCpy $R2 "$R2$R4" - StrCmp $R1 "" done loop -done: - StrCpy $R3 $R2 - Pop $R7 - Pop $R6 - Pop $R4 - Pop $R2 - Pop $R1 - Exch $R3 -FunctionEnd diff --git a/windows/jellyfin.nsi b/windows/jellyfin.nsi deleted file mode 100644 index fada62d981..0000000000 --- a/windows/jellyfin.nsi +++ /dev/null @@ -1,575 +0,0 @@ -!verbose 3 -SetCompressor /SOLID bzip2 -ShowInstDetails show -ShowUninstDetails show -Unicode True - -;-------------------------------- -!define SF_USELECTED 0 ; used to check selected options status, rest are inherited from Sections.nsh - - !include "MUI2.nsh" - !include "Sections.nsh" - !include "LogicLib.nsh" - - !include "helpers\ShowError.nsh" - -; Global variables that we'll use - Var _JELLYFINVERSION_ - Var _JELLYFINDATADIR_ - Var _SETUPTYPE_ - Var _INSTALLSERVICE_ - Var _SERVICESTART_ - Var _SERVICEACCOUNTTYPE_ - Var _EXISTINGINSTALLATION_ - Var _EXISTINGSERVICE_ - Var _MAKESHORTCUTS_ - Var _FOLDEREXISTS_ -; -!ifdef x64 - !define ARCH "x64" - !define NAMESUFFIX "(64 bit)" - !define INSTALL_DIRECTORY "$PROGRAMFILES64\Jellyfin\Server" -!endif - -!ifdef x84 - !define ARCH "x86" - !define NAMESUFFIX "(32 bit)" - !define INSTALL_DIRECTORY "$PROGRAMFILES32\Jellyfin\Server" -!endif - -!ifndef ARCH - !error "Set the Arch with /Dx86 or /Dx64" -!endif - -;-------------------------------- - - !define REG_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\JellyfinServer" ;Registry to show up in Add/Remove Programs - !define REG_CONFIG_KEY "Software\Jellyfin\Server" ;Registry to store all configuration - - !getdllversion "$%InstallLocation%\jellyfin.dll" ver_ ;Align installer version with jellyfin.dll version - - Name "Jellyfin Server ${ver_1}.${ver_2}.${ver_3} ${NAMESUFFIX}" ; This is referred in various header text labels - OutFile "jellyfin_${ver_1}.${ver_2}.${ver_3}_windows-${ARCH}.exe" ; Naming convention jellyfin_{version}_windows-{arch].exe - BrandingText "Jellyfin Server ${ver_1}.${ver_2}.${ver_3} Installer" ; This shows in just over the buttons - -; installer attributes, these show up in details tab on installer properties - VIProductVersion "${ver_1}.${ver_2}.${ver_3}.0" ; VIProductVersion format, should be X.X.X.X - VIFileVersion "${ver_1}.${ver_2}.${ver_3}.0" ; VIFileVersion format, should be X.X.X.X - VIAddVersionKey "ProductName" "Jellyfin Server" - VIAddVersionKey "FileVersion" "${ver_1}.${ver_2}.${ver_3}.0" - VIAddVersionKey "LegalCopyright" "(c) 2019 Jellyfin Contributors. Code released under the GNU General Public License" - VIAddVersionKey "FileDescription" "Jellyfin Server: The Free Software Media System" - -;TODO, check defaults - InstallDir ${INSTALL_DIRECTORY} ;Default installation folder - InstallDirRegKey HKLM "${REG_CONFIG_KEY}" "InstallFolder" ;Read the registry for install folder, - - RequestExecutionLevel admin ; ask it upfront for service control, and installing in priv folders - - CRCCheck on ; make sure the installer wasn't corrupted while downloading - - !define MUI_ABORTWARNING ;Prompts user in case of aborting install - -; TODO: Replace with nice Jellyfin Icons -!ifdef UXPATH - !define MUI_ICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Installer Icon - !define MUI_UNICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Uninstaller Icon - - !define MUI_HEADERIMAGE - !define MUI_HEADERIMAGE_BITMAP "${UXPATH}\branding\NSIS\installer-header.bmp" - !define MUI_WELCOMEFINISHPAGE_BITMAP "${UXPATH}\branding\NSIS\installer-right.bmp" - !define MUI_UNWELCOMEFINISHPAGE_BITMAP "${UXPATH}\branding\NSIS\installer-right.bmp" -!endif - -;-------------------------------- -;Pages - -; Welcome Page - !define MUI_WELCOMEPAGE_TEXT "The installer will ask for details to install Jellyfin Server." - !insertmacro MUI_PAGE_WELCOME -; License Page - !insertmacro MUI_PAGE_LICENSE "$%InstallLocation%\LICENSE" ; picking up generic GPL - -; Setup Type Page - Page custom ShowSetupTypePage SetupTypePage_Config - -; Components Page - !define MUI_PAGE_CUSTOMFUNCTION_PRE HideComponentsPage - !insertmacro MUI_PAGE_COMPONENTS - !define MUI_PAGE_CUSTOMFUNCTION_PRE HideInstallDirectoryPage ; Controls when to hide / show - !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Install folder" ; shows just above the folder selection dialog - !insertmacro MUI_PAGE_DIRECTORY - -; Data folder Page - !define MUI_PAGE_CUSTOMFUNCTION_PRE HideDataDirectoryPage ; Controls when to hide / show - !define MUI_PAGE_HEADER_TEXT "Choose Data Location" - !define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install the Jellyfin Server data." - !define MUI_DIRECTORYPAGE_TEXT_TOP "The installer will set the following folder for Jellyfin Server data. To install in a different folder, click Browse and select another folder. Please make sure the folder exists and is accessible. Click Next to continue." - !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Data folder" - !define MUI_DIRECTORYPAGE_VARIABLE $_JELLYFINDATADIR_ - !insertmacro MUI_PAGE_DIRECTORY - -; Custom Dialogs - !include "dialogs\setuptype.nsdinc" - !include "dialogs\service-config.nsdinc" - !include "dialogs\confirmation.nsdinc" - -; Select service account type - #!define MUI_PAGE_CUSTOMFUNCTION_PRE HideServiceConfigPage ; Controls when to hide / show (This does not work for Page, might need to go PageEx) - #!define MUI_PAGE_CUSTOMFUNCTION_SHOW fnc_service_config_Show - #!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ServiceConfigPage_Config - #!insertmacro MUI_PAGE_CUSTOM ServiceAccountType - Page custom ShowServiceConfigPage ServiceConfigPage_Config - -; Confirmation Page - Page custom ShowConfirmationPage ; just letting the user know what they chose to install - -; Actual Installion Page - !insertmacro MUI_PAGE_INSTFILES - - !insertmacro MUI_UNPAGE_CONFIRM - !insertmacro MUI_UNPAGE_INSTFILES - #!insertmacro MUI_UNPAGE_FINISH - -;-------------------------------- -;Languages; Add more languages later here if needed - !insertmacro MUI_LANGUAGE "English" - -;-------------------------------- -;Installer Sections -Section "!Jellyfin Server (required)" InstallJellyfinServer - SectionIn RO ; Mandatory section, isn't this the whole purpose to run the installer. - - StrCmp "$_EXISTINGINSTALLATION_" "Yes" RunUninstaller CarryOn ; Silently uninstall in case of previous installation - - RunUninstaller: - DetailPrint "Looking for uninstaller at $INSTDIR" - FindFirst $0 $1 "$INSTDIR\Uninstall.exe" - FindClose $0 - StrCmp $1 "" CarryOn ; the registry key was there but uninstaller was not found - - DetailPrint "Silently running the uninstaller at $INSTDIR" - ExecWait '"$INSTDIR\Uninstall.exe" /S _?=$INSTDIR' $0 - DetailPrint "Uninstall finished, $0" - - CarryOn: - ${If} $_EXISTINGSERVICE_ == 'Yes' - ExecWait '"$INSTDIR\nssm.exe" stop JellyfinServer' $0 - ${If} $0 <> 0 - MessageBox MB_OK|MB_ICONSTOP "Could not stop the Jellyfin Server service." - Abort - ${EndIf} - DetailPrint "Stopped Jellyfin Server service, $0" - ${EndIf} - - SetOutPath "$INSTDIR" - - File "/oname=icon.ico" "${UXPATH}\branding\NSIS\modern-install.ico" - File /r $%InstallLocation%\* - - -; Write the InstallFolder, DataFolder, Network Service info into the registry for later use - WriteRegExpandStr HKLM "${REG_CONFIG_KEY}" "InstallFolder" "$INSTDIR" - WriteRegExpandStr HKLM "${REG_CONFIG_KEY}" "DataFolder" "$_JELLYFINDATADIR_" - WriteRegStr HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" "$_SERVICEACCOUNTTYPE_" - - !getdllversion "$%InstallLocation%\jellyfin.dll" ver_ - StrCpy $_JELLYFINVERSION_ "${ver_1}.${ver_2}.${ver_3}" ; - -; Write the uninstall keys for Windows - WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayName" "Jellyfin Server $_JELLYFINVERSION_ ${NAMESUFFIX}" - WriteRegExpandStr HKLM "${REG_UNINST_KEY}" "UninstallString" '"$INSTDIR\Uninstall.exe"' - WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayIcon" '"$INSTDIR\Uninstall.exe",0' - WriteRegStr HKLM "${REG_UNINST_KEY}" "Publisher" "The Jellyfin Project" - WriteRegStr HKLM "${REG_UNINST_KEY}" "URLInfoAbout" "https://jellyfin.org/" - WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayVersion" "$_JELLYFINVERSION_" - WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoModify" 1 - WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoRepair" 1 - -;Create uninstaller - WriteUninstaller "$INSTDIR\Uninstall.exe" -SectionEnd - -Section "Jellyfin Server Service" InstallService -${If} $_INSTALLSERVICE_ == "Yes" ; Only run this if we're going to install the service! - ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 - DetailPrint "Jellyfin Server service statuscode, $0" - ${If} $0 == 0 - InstallRetry: - ExecWait '"$INSTDIR\nssm.exe" install JellyfinServer "$INSTDIR\jellyfin.exe" --service --datadir \"$_JELLYFINDATADIR_\"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not install the Jellyfin Server service." InstallRetry - ${EndIf} - DetailPrint "Jellyfin Server Service install, $0" - ${Else} - DetailPrint "Jellyfin Server Service exists, updating..." - - ConfigureApplicationRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Application "$INSTDIR\jellyfin.exe"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureApplicationRetry - ${EndIf} - DetailPrint "Jellyfin Server Service setting (Application), $0" - - ConfigureAppParametersRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppParameters --service --datadir \"$_JELLYFINDATADIR_\"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureAppParametersRetry - ${EndIf} - DetailPrint "Jellyfin Server Service setting (AppParameters), $0" - ${EndIf} - - - Sleep 3000 ; Give time for Windows to catchup - ConfigureStartRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Start SERVICE_DELAYED_AUTO_START' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureStartRetry - ${EndIf} - DetailPrint "Jellyfin Server Service setting (Start), $0" - - ConfigureDescriptionRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Description "Jellyfin Server: The Free Software Media System"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureDescriptionRetry - ${EndIf} - DetailPrint "Jellyfin Server Service setting (Description), $0" - ConfigureDisplayNameRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer DisplayName "Jellyfin Server"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureDisplayNameRetry - - ${EndIf} - DetailPrint "Jellyfin Server Service setting (DisplayName), $0" - - Sleep 3000 - ${If} $_SERVICEACCOUNTTYPE_ == "NetworkService" ; the default install using NSSM is Local System - ConfigureNetworkServiceRetry: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer Objectname "Network Service"' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service account." ConfigureNetworkServiceRetry - ${EndIf} - DetailPrint "Jellyfin Server service account change, $0" - ${EndIf} - - Sleep 3000 - ConfigureDefaultAppExit: - ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppExit Default Exit' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not configure the Jellyfin Server service app exit action." ConfigureDefaultAppExit - ${EndIf} - DetailPrint "Jellyfin Server service exit action set, $0" -${EndIf} - -SectionEnd - -Section "-start service" StartService -${If} $_SERVICESTART_ == "Yes" -${AndIf} $_INSTALLSERVICE_ == "Yes" - StartRetry: - ExecWait '"$INSTDIR\nssm.exe" start JellyfinServer' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not start the Jellyfin Server service." StartRetry - ${EndIf} - DetailPrint "Jellyfin Server service start, $0" -${EndIf} -SectionEnd - -Section "Create Shortcuts" CreateWinShortcuts - ${If} $_MAKESHORTCUTS_ == "Yes" - CreateDirectory "$SMPROGRAMS\Jellyfin Server" - CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin (View Console).lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMAXIMIZED - CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin Tray App.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0 - ;CreateShortCut "$DESKTOP\Jellyfin Server.lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMINIMIZED - CreateShortCut "$DESKTOP\Jellyfin Server\Jellyfin Server.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0 - ${EndIf} -SectionEnd - -;-------------------------------- -;Descriptions - -;Language strings - LangString DESC_InstallJellyfinServer ${LANG_ENGLISH} "Install Jellyfin Server" - LangString DESC_InstallService ${LANG_ENGLISH} "Install As a Service" - -;Assign language strings to sections - !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN - !insertmacro MUI_DESCRIPTION_TEXT ${InstallJellyfinServer} $(DESC_InstallJellyfinServer) - !insertmacro MUI_DESCRIPTION_TEXT ${InstallService} $(DESC_InstallService) - !insertmacro MUI_FUNCTION_DESCRIPTION_END - -;-------------------------------- -;Uninstaller Section - -Section "Uninstall" - - ReadRegStr $INSTDIR HKLM "${REG_CONFIG_KEY}" "InstallFolder" ; read the installation folder - ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; read the data folder - ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; read the account name - - DetailPrint "Jellyfin Install location: $INSTDIR" - DetailPrint "Jellyfin Data folder: $_JELLYFINDATADIR_" - - MessageBox MB_YESNO|MB_ICONINFORMATION "Do you want to retain the Jellyfin Server data folder? The media will not be touched. $\r$\nIf unsure choose YES." /SD IDYES IDYES PreserveData - - RMDir /r /REBOOTOK "$_JELLYFINDATADIR_" - - PreserveData: - - ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 - DetailPrint "Jellyfin Server service statuscode, $0" - IntCmp $0 0 NoServiceUninstall ; service doesn't exist, may be run from desktop shortcut - - Sleep 3000 ; Give time for Windows to catchup - - UninstallStopRetry: - ExecWait '"$INSTDIR\nssm.exe" stop JellyfinServer' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not stop the Jellyfin Server service." UninstallStopRetry - ${EndIf} - DetailPrint "Stopped Jellyfin Server service, $0" - - UninstallRemoveRetry: - ExecWait '"$INSTDIR\nssm.exe" remove JellyfinServer confirm' $0 - ${If} $0 <> 0 - !insertmacro ShowError "Could not remove the Jellyfin Server service." UninstallRemoveRetry - ${EndIf} - DetailPrint "Removed Jellyfin Server service, $0" - - Sleep 3000 ; Give time for Windows to catchup - - NoServiceUninstall: ; existing install was present but no service was detected. Remove shortcuts if account is set to none - ${If} $_SERVICEACCOUNTTYPE_ == "None" - RMDir /r "$SMPROGRAMS\Jellyfin Server" - Delete "$DESKTOP\Jellyfin Server.lnk" - DetailPrint "Removed old shortcuts..." - ${EndIf} - - Delete "$INSTDIR\*.*" - RMDir /r /REBOOTOK "$INSTDIR\jellyfin-web" - Delete "$INSTDIR\Uninstall.exe" - RMDir /r /REBOOTOK "$INSTDIR" - - DeleteRegKey HKLM "Software\Jellyfin" - DeleteRegKey HKLM "${REG_UNINST_KEY}" - -SectionEnd - -Function .onInit -; Setting up defaults - StrCpy $_INSTALLSERVICE_ "Yes" - StrCpy $_SERVICESTART_ "Yes" - StrCpy $_SERVICEACCOUNTTYPE_ "NetworkService" - StrCpy $_EXISTINGINSTALLATION_ "No" - StrCpy $_EXISTINGSERVICE_ "No" - StrCpy $_MAKESHORTCUTS_ "No" - - SetShellVarContext current - StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server" - - System::Call 'kernel32::CreateMutex(p 0, i 0, t "JellyfinServerMutex") p .r1 ?e' - Pop $R0 - - StrCmp $R0 0 +3 - !insertmacro ShowErrorFinal "The installer is already running." - -;Detect if Jellyfin is already installed. -; In case it is installed, let the user choose either -; 1. Exit installer -; 2. Upgrade without messing with data -; 2a. Don't ask for any details, uninstall and install afresh with old settings - -; Read Registry for previous installation - ClearErrors - ReadRegStr "$0" HKLM "${REG_CONFIG_KEY}" "InstallFolder" - IfErrors NoExisitingInstall - - DetailPrint "Existing Jellyfin Server detected at: $0" - StrCpy "$INSTDIR" "$0" ; set the location fro registry as new default - - StrCpy $_EXISTINGINSTALLATION_ "Yes" ; Set our flag to be used later - SectionSetText ${InstallJellyfinServer} "Upgrade Jellyfin Server (required)" ; Change install text to "Upgrade" - - ; check if service was run using Network Service account - ClearErrors - ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; in case of error _SERVICEACCOUNTTYPE_ will be NetworkService as default - - ClearErrors - ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; in case of error, the default holds - - ; Hide sections which will not be needed in case of previous install - ; SectionSetText ${InstallService} "" - -; check if there is a service called Jellyfin, there should be -; hack : nssm statuscode Jellyfin will return non zero return code in case it exists - ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0 - DetailPrint "Jellyfin Server service statuscode, $0" - IntCmp $0 0 NoService ; service doesn't exist, may be run from desktop shortcut - - ; if service was detected, set defaults going forward. - StrCpy $_EXISTINGSERVICE_ "Yes" - StrCpy $_INSTALLSERVICE_ "Yes" - StrCpy $_SERVICESTART_ "Yes" - StrCpy $_MAKESHORTCUTS_ "No" - SectionSetText ${CreateWinShortcuts} "" - - - NoService: ; existing install was present but no service was detected - ${If} $_SERVICEACCOUNTTYPE_ == "None" - StrCpy $_SETUPTYPE_ "Basic" - StrCpy $_INSTALLSERVICE_ "No" - StrCpy $_SERVICESTART_ "No" - StrCpy $_MAKESHORTCUTS_ "Yes" - ${EndIf} - -; Let the user know that we'll upgrade and provide an option to quit. - MessageBox MB_OKCANCEL|MB_ICONINFORMATION "Existing installation of Jellyfin Server was detected, it'll be upgraded, settings will be retained. \ - $\r$\nClick OK to proceed, Cancel to exit installer." /SD IDOK IDOK ProceedWithUpgrade - Quit ; Quit if the user is not sure about upgrade - - ProceedWithUpgrade: - - NoExisitingInstall: ; by this time, the variables have been correctly set to reflect previous install details - -FunctionEnd - -Function HideInstallDirectoryPage - ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder - Abort - ${EndIf} -FunctionEnd - -Function HideDataDirectoryPage - ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder - Abort - ${EndIf} -FunctionEnd - -Function HideServiceConfigPage - ${If} $_INSTALLSERVICE_ == "No" ; Not running as a service, don't ask for service type - ${OrIf} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder - Abort - ${EndIf} -FunctionEnd - -Function HideConfirmationPage - ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for InstallFolder - Abort - ${EndIf} -FunctionEnd - -Function HideSetupTypePage - ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for SetupType - Abort - ${EndIf} -FunctionEnd - -Function HideComponentsPage - ${If} $_SETUPTYPE_ == "Basic" ; Basic installation chosen, don't show components choice - Abort - ${EndIf} -FunctionEnd - -; Setup Type dialog show function -Function ShowSetupTypePage - Call HideSetupTypePage - Call fnc_setuptype_Create - nsDialogs::Show -FunctionEnd - -; Service Config dialog show function -Function ShowServiceConfigPage - Call HideServiceConfigPage - Call fnc_service_config_Create - nsDialogs::Show -FunctionEnd - -; Confirmation dialog show function -Function ShowConfirmationPage - Call HideConfirmationPage - Call fnc_confirmation_Create - nsDialogs::Show -FunctionEnd - -; Declare temp variables to read the options from the custom page. -Var StartServiceAfterInstall -Var UseNetworkServiceAccount -Var UseLocalSystemAccount -Var BasicInstall - - -Function SetupTypePage_Config -${NSD_GetState} $hCtl_setuptype_BasicInstall $BasicInstall - IfFileExists "$LOCALAPPDATA\Jellyfin" folderfound foldernotfound ; if the folder exists, use this, otherwise, go with new default - folderfound: - StrCpy $_FOLDEREXISTS_ "Yes" - Goto InstallCheck - foldernotfound: - StrCpy $_FOLDEREXISTS_ "No" - Goto InstallCheck - -InstallCheck: -${If} $BasicInstall == 1 - StrCpy $_SETUPTYPE_ "Basic" - StrCpy $_INSTALLSERVICE_ "No" - StrCpy $_SERVICESTART_ "No" - StrCpy $_SERVICEACCOUNTTYPE_ "None" - StrCpy $_MAKESHORTCUTS_ "Yes" - ${If} $_FOLDEREXISTS_ == "Yes" - StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\Jellyfin\" - ${EndIf} -${Else} - StrCpy $_SETUPTYPE_ "Advanced" - StrCpy $_INSTALLSERVICE_ "Yes" - StrCpy $_MAKESHORTCUTS_ "No" - ${If} $_FOLDEREXISTS_ == "Yes" - MessageBox MB_OKCANCEL|MB_ICONINFORMATION "An existing data folder was detected.\ - $\r$\nBasic Setup is highly recommended.\ - $\r$\nIf you proceed, you will need to set up Jellyfin again." IDOK GoAhead IDCANCEL GoBack - GoBack: - Abort - ${EndIf} - GoAhead: - StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server" - SectionSetText ${CreateWinShortcuts} "" -${EndIf} - -FunctionEnd - -Function ServiceConfigPage_Config -${NSD_GetState} $hCtl_service_config_StartServiceAfterInstall $StartServiceAfterInstall -${If} $StartServiceAfterInstall == 1 - StrCpy $_SERVICESTART_ "Yes" -${Else} - StrCpy $_SERVICESTART_ "No" -${EndIf} -${NSD_GetState} $hCtl_service_config_UseNetworkServiceAccount $UseNetworkServiceAccount -${NSD_GetState} $hCtl_service_config_UseLocalSystemAccount $UseLocalSystemAccount - -${If} $UseNetworkServiceAccount == 1 - StrCpy $_SERVICEACCOUNTTYPE_ "NetworkService" -${ElseIf} $UseLocalSystemAccount == 1 - StrCpy $_SERVICEACCOUNTTYPE_ "LocalSystem" -${Else} - !insertmacro ShowErrorFinal "Service account type not properly configured." -${EndIf} - -FunctionEnd - -; This function handles the choices during component selection -Function .onSelChange - -; If we are not installing service, we don't need to set the NetworkService account or StartService - SectionGetFlags ${InstallService} $0 - ${If} $0 = ${SF_SELECTED} - StrCpy $_INSTALLSERVICE_ "Yes" - ${Else} - StrCpy $_INSTALLSERVICE_ "No" - StrCpy $_SERVICESTART_ "No" - StrCpy $_SERVICEACCOUNTTYPE_ "None" - ${EndIf} -FunctionEnd - -Function .onInstSuccess - #ExecShell "open" "http://localhost:8096" -FunctionEnd diff --git a/windows/legacy/install-jellyfin.ps1 b/windows/legacy/install-jellyfin.ps1 deleted file mode 100644 index e909a0468e..0000000000 --- a/windows/legacy/install-jellyfin.ps1 +++ /dev/null @@ -1,460 +0,0 @@ -[CmdletBinding()] - -param( - [Switch]$Quiet, - [Switch]$InstallAsService, - [System.Management.Automation.pscredential]$ServiceUser, - [switch]$CreateDesktopShorcut, - [switch]$LaunchJellyfin, - [switch]$MigrateEmbyLibrary, - [string]$InstallLocation, - [string]$EmbyLibraryLocation, - [string]$JellyfinLibraryLocation -) -<# This form was created using POSHGUI.com a free online gui designer for PowerShell -.NAME - Install-Jellyfin -#> - -#This doesn't need to be used by default anymore, but I am keeping it in as a function for future use. -function Elevate-Window { - # Get the ID and security principal of the current user account - $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent() - $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID) - - # Get the security principal for the Administrator role - $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator - - # Check to see if we are currently running "as Administrator" - if ($myWindowsPrincipal.IsInRole($adminRole)) - { - # We are running "as Administrator" - so change the title and background color to indicate this - $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)" - $Host.UI.RawUI.BackgroundColor = "DarkBlue" - clear-host - } - else - { - # We are not running "as Administrator" - so relaunch as administrator - - # Create a new process object that starts PowerShell - $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell"; - - # Specify the current script path and name as a parameter - $newProcess.Arguments = $myInvocation.MyCommand.Definition; - - # Indicate that the process should be elevated - $newProcess.Verb = "runas"; - - # Start the new process - [System.Diagnostics.Process]::Start($newProcess); - - # Exit from the current, unelevated, process - exit - } -} - -#FIXME The install methods should be a function that takes all the params, the quiet flag should be a paramset - -if($Quiet.IsPresent -or $Quiet -eq $true){ - if([string]::IsNullOrEmpty($JellyfinLibraryLocation)){ - $Script:JellyfinDataDir = "$env:LOCALAPPDATA\jellyfin\" - }else{ - $Script:JellyfinDataDir = $JellyfinLibraryLocation - } - if([string]::IsNullOrEmpty($InstallLocation)){ - $Script:DefaultJellyfinInstallDirectory = "$env:Appdata\jellyfin\" - }else{ - $Script:DefaultJellyfinInstallDirectory = $InstallLocation - } - - if([string]::IsNullOrEmpty($EmbyLibraryLocation)){ - $Script:defaultEmbyDataDir = "$env:Appdata\Emby-Server\data\" - }else{ - $Script:defaultEmbyDataDir = $EmbyLibraryLocation - } - - if($InstallAsService.IsPresent -or $InstallAsService -eq $true){ - $Script:InstallAsService = $true - }else{$Script:InstallAsService = $false} - if($null -eq $ServiceUser){ - $Script:InstallServiceAsUser = $false - }else{ - $Script:InstallServiceAsUser = $true - $Script:UserCredentials = $ServiceUser - $Script:JellyfinDataDir = "$env:HOMEDRIVE\Users\$($Script:UserCredentials.UserName)\Appdata\Local\jellyfin\"} - if($CreateDesktopShorcut.IsPresent -or $CreateDesktopShorcut -eq $true) {$Script:CreateShortcut = $true}else{$Script:CreateShortcut = $false} - if($MigrateEmbyLibrary.IsPresent -or $MigrateEmbyLibrary -eq $true){$Script:MigrateLibrary = $true}else{$Script:MigrateLibrary = $false} - if($LaunchJellyfin.IsPresent -or $LaunchJellyfin -eq $true){$Script:StartJellyfin = $true}else{$Script:StartJellyfin = $false} - - if(-not (Test-Path $Script:DefaultJellyfinInstallDirectory)){ - mkdir $Script:DefaultJellyfinInstallDirectory - } - Copy-Item -Path $PSScriptRoot/* -DestinationPath "$Script:DefaultJellyfinInstallDirectory/" -Force -Recurse - if($Script:InstallAsService){ - if($Script:InstallServiceAsUser){ - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" - Start-Sleep -Milliseconds 500 - &sc.exe config Jellyfin obj=".\$($Script:UserCredentials.UserName)" password="$($Script:UserCredentials.GetNetworkCredential().Password)" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START - }else{ - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" - Start-Sleep -Milliseconds 500 - #&"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin ObjectName $Script:UserCredentials.UserName $Script:UserCredentials.GetNetworkCredential().Password - #Set-Service -Name Jellyfin -Credential $Script:UserCredentials - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START - } - } - if($Script:MigrateLibrary){ - Copy-Item -Path $Script:defaultEmbyDataDir/config -Destination $Script:JellyfinDataDir -force -Recurse - Copy-Item -Path $Script:defaultEmbyDataDir/cache -Destination $Script:JellyfinDataDir -force -Recurse - Copy-Item -Path $Script:defaultEmbyDataDir/data -Destination $Script:JellyfinDataDir -force -Recurse - Copy-Item -Path $Script:defaultEmbyDataDir/metadata -Destination $Script:JellyfinDataDir -force -Recurse - Copy-Item -Path $Script:defaultEmbyDataDir/root -Destination $Script:JellyfinDataDir -force -Recurse - } - if($Script:CreateShortcut){ - $WshShell = New-Object -comObject WScript.Shell - $Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Jellyfin.lnk") - $Shortcut.TargetPath = "$Script:DefaultJellyfinInstallDirectory\jellyfin.exe" - $Shortcut.Save() - } - if($Script:StartJellyfin){ - if($Script:InstallAsService){ - Get-Service Jellyfin | Start-Service - }else{ - Start-Process -FilePath $Script:DefaultJellyfinInstallDirectory\jellyfin.exe -PassThru - } - } -}else{ - -} -Add-Type -AssemblyName System.Windows.Forms -[System.Windows.Forms.Application]::EnableVisualStyles() - -$Script:JellyFinDataDir = "$env:LOCALAPPDATA\jellyfin\" -$Script:DefaultJellyfinInstallDirectory = "$env:Appdata\jellyfin\" -$Script:defaultEmbyDataDir = "$env:Appdata\Emby-Server\" -$Script:InstallAsService = $False -$Script:InstallServiceAsUser = $false -$Script:CreateShortcut = $false -$Script:MigrateLibrary = $false -$Script:StartJellyfin = $false - -function InstallJellyfin { - Write-Host "Install as service: $Script:InstallAsService" - Write-Host "Install as serviceuser: $Script:InstallServiceAsUser" - Write-Host "Create Shortcut: $Script:CreateShortcut" - Write-Host "MigrateLibrary: $Script:MigrateLibrary" - $GUIElementsCollection | ForEach-Object { - $_.Enabled = $false - } - Write-Host "Making Jellyfin directory" - $ProgressBar.Minimum = 1 - $ProgressBar.Maximum = 100 - $ProgressBar.Value = 1 - if($Script:DefaultJellyfinInstallDirectory -ne $InstallLocationBox.Text){ - Write-Host "Custom Install Location Chosen: $($InstallLocationBox.Text)" - $Script:DefaultJellyfinInstallDirectory = $InstallLocationBox.Text - } - if($Script:JellyfinDataDir -ne $CustomLibraryBox.Text){ - Write-Host "Custom Library Location Chosen: $($CustomLibraryBox.Text)" - $Script:JellyfinDataDir = $CustomLibraryBox.Text - } - if(-not (Test-Path $Script:DefaultJellyfinInstallDirectory)){ - mkdir $Script:DefaultJellyfinInstallDirectory - } - Write-Host "Copying Jellyfin Data" - $progressbar.Value = 10 - Copy-Item -Path $PSScriptRoot/* -Destination $Script:DefaultJellyfinInstallDirectory/ -Force -Recurse - Write-Host "Finished Copying" - $ProgressBar.Value = 50 - if($Script:InstallAsService){ - if($Script:InstallServiceAsUser){ - Write-Host "Installing Service as user $($Script:UserCredentials.UserName)" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" - Start-Sleep -Milliseconds 2000 - &sc.exe config Jellyfin obj=".\$($Script:UserCredentials.UserName)" password="$($Script:UserCredentials.GetNetworkCredential().Password)" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START - }else{ - Write-Host "Installing Service as LocalSystem" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" - Start-Sleep -Milliseconds 2000 - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START - } - } - $progressbar.Value = 60 - if($Script:MigrateLibrary){ - if($Script:defaultEmbyDataDir -ne $LibraryLocationBox.Text){ - Write-Host "Custom location defined for emby library: $($LibraryLocationBox.Text)" - $Script:defaultEmbyDataDir = $LibraryLocationBox.Text - } - Write-Host "Copying emby library from $Script:defaultEmbyDataDir to $Script:JellyFinDataDir" - Write-Host "This could take a while depending on the size of your library. Please be patient" - Write-Host "Copying config" - Copy-Item -Path $Script:defaultEmbyDataDir/config -Destination $Script:JellyfinDataDir -force -Recurse - Write-Host "Copying cache" - Copy-Item -Path $Script:defaultEmbyDataDir/cache -Destination $Script:JellyfinDataDir -force -Recurse - Write-Host "Copying data" - Copy-Item -Path $Script:defaultEmbyDataDir/data -Destination $Script:JellyfinDataDir -force -Recurse - Write-Host "Copying metadata" - Copy-Item -Path $Script:defaultEmbyDataDir/metadata -Destination $Script:JellyfinDataDir -force -Recurse - Write-Host "Copying root dir" - Copy-Item -Path $Script:defaultEmbyDataDir/root -Destination $Script:JellyfinDataDir -force -Recurse - } - $progressbar.Value = 80 - if($Script:CreateShortcut){ - Write-Host "Creating Shortcut" - $WshShell = New-Object -comObject WScript.Shell - $Shortcut = $WshShell.CreateShortcut("$Home\Desktop\Jellyfin.lnk") - $Shortcut.TargetPath = "$Script:DefaultJellyfinInstallDirectory\jellyfin.exe" - $Shortcut.Save() - } - $ProgressBar.Value = 90 - if($Script:StartJellyfin){ - if($Script:InstallAsService){ - Write-Host "Starting Jellyfin Service" - Get-Service Jellyfin | Start-Service - }else{ - Write-Host "Starting Jellyfin" - Start-Process -FilePath $Script:DefaultJellyfinInstallDirectory\jellyfin.exe -PassThru - } - } - $progressbar.Value = 100 - Write-Host Finished - $wshell = New-Object -ComObject Wscript.Shell - $wshell.Popup("Operation Completed",0,"Done",0x1) - $InstallForm.Close() -} -function ServiceBoxCheckChanged { - if($InstallAsServiceCheck.Checked){ - $Script:InstallAsService = $true - $ServiceUserLabel.Visible = $true - $ServiceUserLabel.Enabled = $true - $ServiceUserBox.Visible = $true - $ServiceUserBox.Enabled = $true - }else{ - $Script:InstallAsService = $false - $ServiceUserLabel.Visible = $false - $ServiceUserLabel.Enabled = $false - $ServiceUserBox.Visible = $false - $ServiceUserBox.Enabled = $false - } -} -function UserSelect { - if($ServiceUserBox.Text -eq 'Local System') - { - $Script:InstallServiceAsUser = $false - $Script:UserCredentials = $null - $ServiceUserBox.Items.RemoveAt(1) - $ServiceUserBox.Items.Add("Custom User") - }elseif($ServiceUserBox.Text -eq 'Custom User'){ - $Script:InstallServiceAsUser = $true - $Script:UserCredentials = Get-Credential -Message "Please enter the credentials of the user you with to run Jellyfin Service as" -UserName $env:USERNAME - $ServiceUserBox.Items[1] = "$($Script:UserCredentials.UserName)" - } -} -function CreateShortcutBoxCheckChanged { - if($CreateShortcutCheck.Checked){ - $Script:CreateShortcut = $true - }else{ - $Script:CreateShortcut = $False - } -} -function StartJellyFinBoxCheckChanged { - if($StartProgramCheck.Checked){ - $Script:StartJellyfin = $true - }else{ - $Script:StartJellyfin = $false - } -} - -function CustomLibraryCheckChanged { - if($CustomLibraryCheck.Checked){ - $Script:UseCustomLibrary = $true - $CustomLibraryBox.Enabled = $true - }else{ - $Script:UseCustomLibrary = $false - $CustomLibraryBox.Enabled = $false - } -} - -function MigrateLibraryCheckboxChanged { - - if($MigrateLibraryCheck.Checked){ - $Script:MigrateLibrary = $true - $LibraryMigrationLabel.Visible = $true - $LibraryMigrationLabel.Enabled = $true - $LibraryLocationBox.Visible = $true - $LibraryLocationBox.Enabled = $true - }else{ - $Script:MigrateLibrary = $false - $LibraryMigrationLabel.Visible = $false - $LibraryMigrationLabel.Enabled = $false - $LibraryLocationBox.Visible = $false - $LibraryLocationBox.Enabled = $false - } - -} - - -#region begin GUI{ - -$InstallForm = New-Object system.Windows.Forms.Form -$InstallForm.ClientSize = '320,240' -$InstallForm.text = "Terrible Jellyfin Installer" -$InstallForm.TopMost = $false - -$GUIElementsCollection = @() - -$InstallButton = New-Object system.Windows.Forms.Button -$InstallButton.text = "Install" -$InstallButton.width = 60 -$InstallButton.height = 30 -$InstallButton.location = New-Object System.Drawing.Point(5,5) -$InstallButton.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $InstallButton - -$ProgressBar = New-Object system.Windows.Forms.ProgressBar -$ProgressBar.width = 245 -$ProgressBar.height = 30 -$ProgressBar.location = New-Object System.Drawing.Point(70,5) - -$InstallLocationLabel = New-Object system.Windows.Forms.Label -$InstallLocationLabel.text = "Install Location" -$InstallLocationLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$InstallLocationLabel.AutoSize = $true -$InstallLocationLabel.width = 100 -$InstallLocationLabel.height = 20 -$InstallLocationLabel.location = New-Object System.Drawing.Point(5,50) -$InstallLocationLabel.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $InstallLocationLabel - -$InstallLocationBox = New-Object system.Windows.Forms.TextBox -$InstallLocationBox.multiline = $false -$InstallLocationBox.width = 205 -$InstallLocationBox.height = 20 -$InstallLocationBox.location = New-Object System.Drawing.Point(110,50) -$InstallLocationBox.Text = $Script:DefaultJellyfinInstallDirectory -$InstallLocationBox.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $InstallLocationBox - -$CustomLibraryCheck = New-Object system.Windows.Forms.CheckBox -$CustomLibraryCheck.text = "Custom Library Location:" -$CustomLibraryCheck.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$CustomLibraryCheck.AutoSize = $false -$CustomLibraryCheck.width = 180 -$CustomLibraryCheck.height = 20 -$CustomLibraryCheck.location = New-Object System.Drawing.Point(5,75) -$CustomLibraryCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $CustomLibraryCheck - -$CustomLibraryBox = New-Object system.Windows.Forms.TextBox -$CustomLibraryBox.multiline = $false -$CustomLibraryBox.width = 130 -$CustomLibraryBox.height = 20 -$CustomLibraryBox.location = New-Object System.Drawing.Point(185,75) -$CustomLibraryBox.Text = $Script:JellyFinDataDir -$CustomLibraryBox.Font = 'Microsoft Sans Serif,10' -$CustomLibraryBox.Enabled = $false -$GUIElementsCollection += $CustomLibraryBox - -$InstallAsServiceCheck = New-Object system.Windows.Forms.CheckBox -$InstallAsServiceCheck.text = "Install as Service" -$InstallAsServiceCheck.AutoSize = $false -$InstallAsServiceCheck.width = 140 -$InstallAsServiceCheck.height = 20 -$InstallAsServiceCheck.location = New-Object System.Drawing.Point(5,125) -$InstallAsServiceCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $InstallAsServiceCheck - -$ServiceUserLabel = New-Object system.Windows.Forms.Label -$ServiceUserLabel.text = "Run Service As:" -$ServiceUserLabel.AutoSize = $true -$ServiceUserLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$ServiceUserLabel.width = 100 -$ServiceUserLabel.height = 20 -$ServiceUserLabel.location = New-Object System.Drawing.Point(15,145) -$ServiceUserLabel.Font = 'Microsoft Sans Serif,10' -$ServiceUserLabel.Visible = $false -$ServiceUserLabel.Enabled = $false -$GUIElementsCollection += $ServiceUserLabel - -$ServiceUserBox = New-Object system.Windows.Forms.ComboBox -$ServiceUserBox.text = "Run Service As" -$ServiceUserBox.width = 195 -$ServiceUserBox.height = 20 -@('Local System','Custom User') | ForEach-Object {[void] $ServiceUserBox.Items.Add($_)} -$ServiceUserBox.location = New-Object System.Drawing.Point(120,145) -$ServiceUserBox.Font = 'Microsoft Sans Serif,10' -$ServiceUserBox.Visible = $false -$ServiceUserBox.Enabled = $false -$ServiceUserBox.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList -$GUIElementsCollection += $ServiceUserBox - -$MigrateLibraryCheck = New-Object system.Windows.Forms.CheckBox -$MigrateLibraryCheck.text = "Import Emby/Old JF Library" -$MigrateLibraryCheck.AutoSize = $false -$MigrateLibraryCheck.width = 160 -$MigrateLibraryCheck.height = 20 -$MigrateLibraryCheck.location = New-Object System.Drawing.Point(5,170) -$MigrateLibraryCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $MigrateLibraryCheck - -$LibraryMigrationLabel = New-Object system.Windows.Forms.Label -$LibraryMigrationLabel.text = "Emby/Old JF Library Path" -$LibraryMigrationLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft -$LibraryMigrationLabel.AutoSize = $false -$LibraryMigrationLabel.width = 120 -$LibraryMigrationLabel.height = 20 -$LibraryMigrationLabel.location = New-Object System.Drawing.Point(15,190) -$LibraryMigrationLabel.Font = 'Microsoft Sans Serif,10' -$LibraryMigrationLabel.Visible = $false -$LibraryMigrationLabel.Enabled = $false -$GUIElementsCollection += $LibraryMigrationLabel - -$LibraryLocationBox = New-Object system.Windows.Forms.TextBox -$LibraryLocationBox.multiline = $false -$LibraryLocationBox.width = 175 -$LibraryLocationBox.height = 20 -$LibraryLocationBox.location = New-Object System.Drawing.Point(140,190) -$LibraryLocationBox.Text = $Script:defaultEmbyDataDir -$LibraryLocationBox.Font = 'Microsoft Sans Serif,10' -$LibraryLocationBox.Visible = $false -$LibraryLocationBox.Enabled = $false -$GUIElementsCollection += $LibraryLocationBox - -$CreateShortcutCheck = New-Object system.Windows.Forms.CheckBox -$CreateShortcutCheck.text = "Desktop Shortcut" -$CreateShortcutCheck.AutoSize = $false -$CreateShortcutCheck.width = 150 -$CreateShortcutCheck.height = 20 -$CreateShortcutCheck.location = New-Object System.Drawing.Point(5,215) -$CreateShortcutCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $CreateShortcutCheck - -$StartProgramCheck = New-Object system.Windows.Forms.CheckBox -$StartProgramCheck.text = "Start Jellyfin" -$StartProgramCheck.AutoSize = $false -$StartProgramCheck.width = 160 -$StartProgramCheck.height = 20 -$StartProgramCheck.location = New-Object System.Drawing.Point(160,215) -$StartProgramCheck.Font = 'Microsoft Sans Serif,10' -$GUIElementsCollection += $StartProgramCheck - -$InstallForm.controls.AddRange($GUIElementsCollection) -$InstallForm.Controls.Add($ProgressBar) - -#region gui events { -$InstallButton.Add_Click({ InstallJellyfin }) -$CustomLibraryCheck.Add_CheckedChanged({CustomLibraryCheckChanged}) -$InstallAsServiceCheck.Add_CheckedChanged({ServiceBoxCheckChanged}) -$ServiceUserBox.Add_SelectedValueChanged({ UserSelect }) -$MigrateLibraryCheck.Add_CheckedChanged({MigrateLibraryCheckboxChanged}) -$CreateShortcutCheck.Add_CheckedChanged({CreateShortcutBoxCheckChanged}) -$StartProgramCheck.Add_CheckedChanged({StartJellyFinBoxCheckChanged}) -#endregion events } - -#endregion GUI } - - -[void]$InstallForm.ShowDialog() diff --git a/windows/legacy/install.bat b/windows/legacy/install.bat deleted file mode 100644 index e21479a79a..0000000000 --- a/windows/legacy/install.bat +++ /dev/null @@ -1 +0,0 @@ -powershell.exe -executionpolicy Bypass -file install-jellyfin.ps1 From 2124bc2e1894e5a09e5843cfcf19ab189e70eac1 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sat, 3 Oct 2020 16:04:39 +0800 Subject: [PATCH 065/151] enhance workload when tone mapping with AMF zscale filter is required. --- .../MediaEncoding/EncodingHelper.cs | 78 +++++++++++-------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 790b26f699..4a55840d52 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -859,29 +859,44 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { - switch (encodingOptions.EncoderPreset) + var videoStream = state.VideoStream; + var isColorDepth10 = IsColorDepth10(state); + + if (isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && encodingOptions.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) { - case "veryslow": - case "slow": - case "slower": - param += "-quality quality"; - break; + // Enhance quality and workload when tone mapping with AMF + param += "-quality quality -preanalysis true"; + } + else + { + switch (encodingOptions.EncoderPreset) + { + case "veryslow": + case "slow": + case "slower": + param += "-quality quality"; + break; - case "medium": - param += "-quality balanced"; - break; + case "medium": + param += "-quality balanced"; + break; - case "fast": - case "faster": - case "veryfast": - case "superfast": - case "ultrafast": - param += "-quality speed"; - break; + case "fast": + case "faster": + case "veryfast": + case "superfast": + case "ultrafast": + param += "-quality speed"; + break; - default: - param += "-quality speed"; - break; + default: + param += "-quality speed"; + break; + } } } else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm @@ -2123,19 +2138,18 @@ namespace MediaBrowser.Controller.MediaEncoding if (isSwDecoder || isD3d11vaDecoder) { isScalingInAdvance = true; - // Add scaling filter before tonemapping filter for performance. - filters.AddRange( - GetScalingFilters( - state, - inputWidth, - inputHeight, - threeDFormat, - videoDecoder, - outputVideoCodec, - request.Width, - request.Height, - request.MaxWidth, - request.MaxHeight)); + // Add zscale filter before tone mapping filter for performance. + var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); + if (width.HasValue && height.HasValue) + { + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "zscale=s={0}x{1}", + width.Value, + height.Value)); + } + // Convert to hardware pixel format p010 when using SW decoder. filters.Add("format=p010"); } From 9fbf725a6de88b8381f3d1607a8c722a59b92fbd Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sat, 3 Oct 2020 17:53:10 +0800 Subject: [PATCH 066/151] Enhance workload when tone mapping on some APUs --- .../MediaEncoding/EncodingHelper.cs | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 4a55840d52..0125e909fe 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -859,6 +859,31 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { + switch (encodingOptions.EncoderPreset) + { + case "veryslow": + case "slow": + case "slower": + param += "-quality quality"; + break; + + case "medium": + param += "-quality balanced"; + break; + + case "fast": + case "faster": + case "veryfast": + case "superfast": + case "ultrafast": + param += "-quality speed"; + break; + + default: + param += "-quality speed"; + break; + } + var videoStream = state.VideoStream; var isColorDepth10 = IsColorDepth10(state); @@ -868,35 +893,8 @@ namespace MediaBrowser.Controller.MediaEncoding && !string.IsNullOrEmpty(videoStream.VideoRange) && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) { - // Enhance quality and workload when tone mapping with AMF - param += "-quality quality -preanalysis true"; - } - else - { - switch (encodingOptions.EncoderPreset) - { - case "veryslow": - case "slow": - case "slower": - param += "-quality quality"; - break; - - case "medium": - param += "-quality balanced"; - break; - - case "fast": - case "faster": - case "veryfast": - case "superfast": - case "ultrafast": - param += "-quality speed"; - break; - - default: - param += "-quality speed"; - break; - } + // Enhance workload when tone mapping with AMF on some APUs + param += " -preanalysis true"; } } else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm From ec0ff5d02fa53ca5b902d0bd5b477199170f3d28 Mon Sep 17 00:00:00 2001 From: "github@esslinger.dev" Date: Sat, 3 Oct 2020 12:40:28 +0200 Subject: [PATCH 067/151] test: use descriptive test method names --- .../ModelBinders/CommaDelimitedArrayModelBinderTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs index b05be6a16a..c801b4a523 100644 --- a/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs +++ b/tests/Jellyfin.Api.Tests/ModelBinders/CommaDelimitedArrayModelBinderTests.cs @@ -93,7 +93,7 @@ namespace Jellyfin.Api.Tests.ModelBinders } [Fact] - public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQuery2() + public async Task BindModelAsync_CorrectlyBindsValidCommaDelimitedEnumArrayQueryWithDoubleCommas() { var queryParamName = "test"; var queryParamValues = new TestType[] { TestType.How, TestType.Much }; @@ -148,7 +148,7 @@ namespace Jellyfin.Api.Tests.ModelBinders } [Fact] - public async Task BindModelAsync_CorrectlyBindsValidEnumArrayQuery2() + public async Task BindModelAsync_CorrectlyBindsEmptyEnumArrayQuery() { var queryParamName = "test"; var queryParamValues = Array.Empty(); From 211c9cd60850c6c33d1211cc5a7e35a94b19bab4 Mon Sep 17 00:00:00 2001 From: KonH Date: Sat, 3 Oct 2020 22:03:23 +0700 Subject: [PATCH 068/151] Remove unnecessary null checks in some places Related to https://github.com/jellyfin/jellyfin/issues/2149 --- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 2 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 5 +---- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 1207fb5134..e78f63b256 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -554,7 +554,7 @@ namespace Jellyfin.Api.Helpers private long? GetMaxBitrate(long? clientMaxBitrate, User user, string ipAddress) { var maxBitrate = clientMaxBitrate; - var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0; + var remoteClientMaxBitrate = user.RemoteClientBitrateLimit ?? 0; if (remoteClientMaxBitrate <= 0) { diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 64d1227f7c..0db1fabffe 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -740,10 +740,7 @@ namespace Jellyfin.Api.Helpers /// The state. private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state) { - if (job != null) - { - job.HasExited = true; - } + job.HasExited = true; _logger.LogDebug("Disposing stream resources"); state.Dispose(); diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 8f04baa089..dfd7ee99da 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -799,7 +799,7 @@ namespace Jellyfin.Server.Implementations.Users private IList GetPasswordResetProviders(User user) { - var passwordResetProviderId = user?.PasswordResetProviderId; + var passwordResetProviderId = user.PasswordResetProviderId; var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); if (!string.IsNullOrEmpty(passwordResetProviderId)) From e01209a6f5305084fa4857cf634a0287fd916fd3 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sat, 3 Oct 2020 17:14:09 +0200 Subject: [PATCH 069/151] Log stream type and codec for missing direct play profile --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index d9e7e4fbb4..fc0aad0727 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -455,9 +455,10 @@ namespace MediaBrowser.Model.Dlna if (directPlayProfile == null) { - _logger.LogInformation("Profile: {0}, No direct play profiles found for Path: {1}", + _logger.LogInformation("Profile: {0}, No audio direct play profiles found for {1} with codec {2}", options.Profile.Name ?? "Unknown Profile", - item.Path ?? "Unknown path"); + item.Path ?? "Unknown path", + audioStream.Codec ?? "Unknown codec"); return (Enumerable.Empty(), GetTranscodeReasonsFromDirectPlayProfile(item, null, audioStream, options.Profile.DirectPlayProfiles)); } @@ -972,9 +973,10 @@ namespace MediaBrowser.Model.Dlna if (directPlay == null) { - _logger.LogInformation("Profile: {0}, No direct play profiles found for Path: {1}", + _logger.LogInformation("Profile: {0}, No video direct play profiles found for {1} with codec {2}", profile.Name ?? "Unknown Profile", - mediaSource.Path ?? "Unknown path"); + mediaSource.Path ?? "Unknown path", + videoStream.Codec ?? "Unknown codec"); return (null, GetTranscodeReasonsFromDirectPlayProfile(mediaSource, videoStream, audioStream, profile.DirectPlayProfiles)); } From e9524f89d63894ea9af62cd1b61ddd89cb8b9e82 Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 24 Sep 2020 19:49:35 +0200 Subject: [PATCH 070/151] Migrate the TMDb providers to the TMDbLib library --- .../ApplicationHost.cs | 2 + .../Extensions/EnumerableExtensions.cs | 46 ++ .../MediaBrowser.Providers.csproj | 1 + .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 143 ++--- .../Tmdb/BoxSets/TmdbBoxSetProvider.cs | 260 ++------ .../Models/Collections/CollectionImages.cs | 14 - .../Models/Collections/CollectionResult.cs | 23 - .../Plugins/Tmdb/Models/Collections/Part.cs | 17 - .../Plugins/Tmdb/Models/General/Backdrop.cs | 21 - .../Plugins/Tmdb/Models/General/Crew.cs | 19 - .../Tmdb/Models/General/ExternalIds.cs | 17 - .../Plugins/Tmdb/Models/General/Genre.cs | 11 - .../Plugins/Tmdb/Models/General/Images.cs | 13 - .../Plugins/Tmdb/Models/General/Keyword.cs | 11 - .../Plugins/Tmdb/Models/General/Keywords.cs | 11 - .../Plugins/Tmdb/Models/General/Poster.cs | 21 - .../Plugins/Tmdb/Models/General/Profile.cs | 17 - .../Plugins/Tmdb/Models/General/Still.cs | 23 - .../Tmdb/Models/General/StillImages.cs | 11 - .../Plugins/Tmdb/Models/General/Video.cs | 23 - .../Plugins/Tmdb/Models/General/Videos.cs | 11 - .../Tmdb/Models/Movies/BelongsToCollection.cs | 15 - .../Plugins/Tmdb/Models/Movies/Cast.cs | 19 - .../Plugins/Tmdb/Models/Movies/Casts.cs | 14 - .../Plugins/Tmdb/Models/Movies/Country.cs | 15 - .../Plugins/Tmdb/Models/Movies/MovieResult.cs | 80 --- .../Tmdb/Models/Movies/ProductionCompany.cs | 11 - .../Tmdb/Models/Movies/ProductionCountry.cs | 11 - .../Plugins/Tmdb/Models/Movies/Releases.cs | 11 - .../Tmdb/Models/Movies/SpokenLanguage.cs | 11 - .../Plugins/Tmdb/Models/Movies/Trailers.cs | 11 - .../Plugins/Tmdb/Models/Movies/Youtube.cs | 13 - .../Tmdb/Models/People/PersonImages.cs | 12 - .../Tmdb/Models/People/PersonResult.cs | 38 -- .../Models/Search/ExternalIdLookupResult.cs | 11 - .../Plugins/Tmdb/Models/Search/MovieResult.cs | 78 --- .../Tmdb/Models/Search/PersonSearchResult.cs | 31 - .../Tmdb/Models/Search/TmdbSearchResult.cs | 33 - .../Plugins/Tmdb/Models/Search/TvResult.cs | 25 - .../Plugins/Tmdb/Models/TV/Cast.cs | 19 - .../Plugins/Tmdb/Models/TV/ContentRating.cs | 11 - .../Plugins/Tmdb/Models/TV/ContentRatings.cs | 11 - .../Plugins/Tmdb/Models/TV/CreatedBy.cs | 13 - .../Plugins/Tmdb/Models/TV/Credits.cs | 14 - .../Plugins/Tmdb/Models/TV/Episode.cs | 23 - .../Plugins/Tmdb/Models/TV/EpisodeCredits.cs | 16 - .../Plugins/Tmdb/Models/TV/EpisodeResult.cs | 38 -- .../Plugins/Tmdb/Models/TV/GuestStar.cs | 19 - .../Plugins/Tmdb/Models/TV/Network.cs | 11 - .../Plugins/Tmdb/Models/TV/Season.cs | 17 - .../Plugins/Tmdb/Models/TV/SeasonImages.cs | 12 - .../Plugins/Tmdb/Models/TV/SeasonResult.cs | 33 - .../Plugins/Tmdb/Models/TV/SeriesResult.cs | 71 --- .../Tmdb/Movies/GenericTmdbMovieInfo.cs | 309 ---------- .../Plugins/Tmdb/Movies/TmdbImageProvider.cs | 212 ------- .../Plugins/Tmdb/Movies/TmdbImageSettings.cs | 22 - .../Tmdb/Movies/TmdbMovieExternalId.cs | 3 +- .../Tmdb/Movies/TmdbMovieImageProvider.cs | 128 ++++ .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 574 ++++++++---------- .../Plugins/Tmdb/Movies/TmdbSearch.cs | 302 --------- .../Tmdb/Music/TmdbMusicVideoProvider.cs | 34 -- .../Tmdb/People/TmdbPersonImageProvider.cs | 104 +--- .../Plugins/Tmdb/People/TmdbPersonProvider.cs | 248 ++------ .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 112 ++-- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 277 +++++---- .../Tmdb/TV/TmdbEpisodeProviderBase.cs | 156 ----- .../Tmdb/TV/TmdbSeasonImageProvider.cs | 112 +--- .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 259 +++----- .../Tmdb/TV/TmdbSeriesImageProvider.cs | 165 ++--- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 525 ++++++---------- .../Plugins/Tmdb/TmdbClientManager.cs | 469 ++++++++++++++ .../Plugins/Tmdb/TmdbUtils.cs | 90 ++- .../Tmdb/Trailers/TmdbTrailerProvider.cs | 43 -- 73 files changed, 1677 insertions(+), 3929 deletions(-) create mode 100644 MediaBrowser.Model/Extensions/EnumerableExtensions.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageSettings.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs delete mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7a46fdf2e7..0c8b0339bd 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -97,6 +97,7 @@ using MediaBrowser.Model.Tasks; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.TheTvdb; +using MediaBrowser.Providers.Plugins.Tmdb; using MediaBrowser.Providers.Subtitles; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Mvc; @@ -537,6 +538,7 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton(_fileSystemManager); ServiceCollection.AddSingleton(); + ServiceCollection.AddSingleton(); ServiceCollection.AddSingleton(_networkManager); diff --git a/MediaBrowser.Model/Extensions/EnumerableExtensions.cs b/MediaBrowser.Model/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000000..712fa381ee --- /dev/null +++ b/MediaBrowser.Model/Extensions/EnumerableExtensions.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Model.Extensions +{ + /// + /// Extension methods for . + /// + public static class EnumerableExtensions + { + /// + /// Orders by requested language in descending order, prioritizing "en" over other non-matches. + /// + /// The remote image infos. + /// The requested language for the images. + /// The ordered remote image infos. + public static IEnumerable OrderByLanguageDescending(this IEnumerable remoteImageInfos, string requestedLanguage) + { + var isRequestedLanguageEn = string.Equals(requestedLanguage, "en", StringComparison.OrdinalIgnoreCase); + + return remoteImageInfos.OrderByDescending(i => + { + if (string.Equals(requestedLanguage, i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + + if (!isRequestedLanguageEn && string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 2; + } + + if (string.IsNullOrEmpty(i.Language)) + { + return isRequestedLanguageEn ? 3 : 2; + } + + return 0; + }) + .ThenByDescending(i => i.CommunityRating ?? 0) + .ThenByDescending(i => i.VoteCount ?? 0); + } + } +} diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 813dd441f5..11e30940fd 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -21,6 +21,7 @@ + diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index f6592afe46..df1e12240d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net.Http; using System.Threading; @@ -12,25 +13,25 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; -using MediaBrowser.Providers.Plugins.Tmdb.Models.Collections; -using MediaBrowser.Providers.Plugins.Tmdb.Models.General; -using MediaBrowser.Providers.Plugins.Tmdb.Movies; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; + private readonly TmdbClientManager _tmdbClientManager; - public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory) + public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; + _tmdbClientManager = tmdbClientManager; } - public string Name => ProviderName; + public string Name => TmdbUtils.ProviderName; - public static string ProviderName => TmdbUtils.ProviderName; + public int Order => 0; public bool Supports(BaseItem item) { @@ -48,112 +49,60 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) { - var tmdbId = item.GetProviderId(MetadataProvider.Tmdb); + var tmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); - if (!string.IsNullOrEmpty(tmdbId)) + if (tmdbId <= 0) { - var language = item.GetPreferredMetadataLanguage(); - - var mainResult = await TmdbBoxSetProvider.Current.GetMovieDbResult(tmdbId, null, cancellationToken).ConfigureAwait(false); - - if (mainResult != null) - { - var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - - var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); - - return GetImages(mainResult, language, tmdbImageUrl); - } + return Enumerable.Empty(); } - return new List(); - } + var language = item.GetPreferredMetadataLanguage(); - private IEnumerable GetImages(CollectionResult obj, string language, string baseUrl) - { - var list = new List(); + var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false); - var images = obj.Images ?? new CollectionImages(); - - list.AddRange(GetPosters(images).Select(i => new RemoteImageInfo + if (collection?.Images == null) { - Url = baseUrl + i.File_Path, - CommunityRating = i.Vote_Average, - VoteCount = i.Vote_Count, - Width = i.Width, - Height = i.Height, - Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language), - ProviderName = Name, - Type = ImageType.Primary, - RatingType = RatingType.Score - })); + return Enumerable.Empty(); + } - list.AddRange(GetBackdrops(images).Select(i => new RemoteImageInfo + var remoteImages = new List(); + + for (var i = 0; i < collection.Images.Posters.Count; i++) { - Url = baseUrl + i.File_Path, - CommunityRating = i.Vote_Average, - VoteCount = i.Vote_Count, - Width = i.Width, - Height = i.Height, - ProviderName = Name, - Type = ImageType.Backdrop, - RatingType = RatingType.Score - })); + var poster = collection.Images.Posters[i]; + remoteImages.Add(new RemoteImageInfo + { + Url = _tmdbClientManager.GetPosterUrl(poster.FilePath), + CommunityRating = poster.VoteAverage, + VoteCount = poster.VoteCount, + Width = poster.Width, + Height = poster.Height, + Language = TmdbUtils.AdjustImageLanguage(poster.Iso_639_1, language), + ProviderName = Name, + Type = ImageType.Primary, + RatingType = RatingType.Score + }); + } - var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase); - - return list.OrderByDescending(i => + for (var i = 0; i < collection.Images.Backdrops.Count; i++) { - if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) + var backdrop = collection.Images.Backdrops[i]; + remoteImages.Add(new RemoteImageInfo { - return 3; - } + Url = _tmdbClientManager.GetBackdropUrl(backdrop.FilePath), + CommunityRating = backdrop.VoteAverage, + VoteCount = backdrop.VoteCount, + Width = backdrop.Width, + Height = backdrop.Height, + ProviderName = Name, + Type = ImageType.Backdrop, + RatingType = RatingType.Score + }); + } - if (!isLanguageEn) - { - if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) - { - return 2; - } - } - - if (string.IsNullOrEmpty(i.Language)) - { - return isLanguageEn ? 3 : 2; - } - - return 0; - }) - .ThenByDescending(i => i.CommunityRating ?? 0) - .ThenByDescending(i => i.VoteCount ?? 0); + return remoteImages.OrderByLanguageDescending(language); } - /// - /// Gets the posters. - /// - /// The images. - /// IEnumerable{MovieDbProvider.Poster}. - private IEnumerable GetPosters(CollectionImages images) - { - return images.Posters ?? new List(); - } - - /// - /// Gets the backdrops. - /// - /// The images. - /// IEnumerable{MovieDbProvider.Backdrop}. - private IEnumerable GetBackdrops(CollectionImages images) - { - var eligibleBackdrops = images.Backdrops == null ? new List() : - images.Backdrops; - - return eligibleBackdrops.OrderByDescending(i => i.Vote_Average) - .ThenByDescending(i => i.Vote_Count); - } - - public int Order => 0; - public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs index e7328b5535..fcd8e614c1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -3,270 +3,118 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Net.Http; -using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Providers.Plugins.Tmdb.Models.Collections; -using MediaBrowser.Providers.Plugins.Tmdb.Models.General; -using MediaBrowser.Providers.Plugins.Tmdb.Movies; -using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { public class TmdbBoxSetProvider : IRemoteMetadataProvider { - private const string GetCollectionInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/collection/{0}?api_key={1}&append_to_response=images"; - - internal static TmdbBoxSetProvider Current; - - private readonly ILogger _logger; - private readonly IJsonSerializer _json; - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; private readonly IHttpClientFactory _httpClientFactory; - private readonly ILibraryManager _libraryManager; + private readonly TmdbClientManager _tmdbClientManager; - public TmdbBoxSetProvider( - ILogger logger, - IJsonSerializer json, - IServerConfigurationManager config, - IFileSystem fileSystem, - IHttpClientFactory httpClientFactory, - ILibraryManager libraryManager) + public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { - _logger = logger; - _json = json; - _config = config; - _fileSystem = fileSystem; _httpClientFactory = httpClientFactory; - _libraryManager = libraryManager; - Current = this; + _tmdbClientManager = tmdbClientManager; } - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + public string Name => TmdbUtils.ProviderName; public async Task> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken) { - var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb); + var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); + var language = searchInfo.MetadataLanguage; - if (!string.IsNullOrEmpty(tmdbId)) + if (tmdbId > 0) { - await EnsureInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false); - var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, searchInfo.MetadataLanguage); - var info = _json.DeserializeFromFile(dataFilePath); - - var images = (info.Images ?? new CollectionImages()).Posters ?? new List(); - - var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); - - var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); + if (collection == null) + { + return Enumerable.Empty(); + } var result = new RemoteSearchResult { - Name = info.Name, - SearchProviderName = Name, - ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].File_Path) + Name = collection.Name, + SearchProviderName = Name }; - result.SetProviderId(MetadataProvider.Tmdb, info.Id.ToString(_usCulture)); + if (collection.Images != null) + { + result.ImageUrl = _tmdbClientManager.GetPosterUrl(collection.PosterPath); + } + + result.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture)); return new[] { result }; } - return await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false); + var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false); + + var collections = new List(); + for (var i = 0; i < collectionSearchResults.Count; i++) + { + var collection = new RemoteSearchResult + { + Name = collectionSearchResults[i].Name, + SearchProviderName = Name + }; + collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture)); + + collections.Add(collection); + } + + return collections; } public async Task> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken) { - var tmdbId = id.GetProviderId(MetadataProvider.Tmdb); - + var tmdbId = Convert.ToInt32(id.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); + var language = id.MetadataLanguage; // We don't already have an Id, need to fetch it - if (string.IsNullOrEmpty(tmdbId)) + if (tmdbId <= 0) { - var searchResults = await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(id, cancellationToken).ConfigureAwait(false); + var searchResults = await _tmdbClientManager.SearchCollectionAsync(id.Name, language, cancellationToken).ConfigureAwait(false); - var searchResult = searchResults.FirstOrDefault(); - - if (searchResult != null) + if (searchResults != null && searchResults.Count > 0) { - tmdbId = searchResult.GetProviderId(MetadataProvider.Tmdb); + tmdbId = searchResults[0].Id; } } var result = new MetadataResult(); - if (!string.IsNullOrEmpty(tmdbId)) + if (tmdbId > 0) { - var mainResult = await GetMovieDbResult(tmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false); - if (mainResult != null) + if (collection != null) { + var item = new BoxSet + { + Name = collection.Name, + Overview = collection.Overview + }; + + item.SetProviderId(MetadataProvider.Tmdb, collection.Id.ToString(CultureInfo.InvariantCulture)); + result.HasMetadata = true; - result.Item = GetItem(mainResult); + result.Item = item; } } return result; } - internal async Task GetMovieDbResult(string tmdbId, string language, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(tmdbId)) - { - throw new ArgumentNullException(nameof(tmdbId)); - } - - await EnsureInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); - - var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, language); - - if (!string.IsNullOrEmpty(dataFilePath)) - { - return _json.DeserializeFromFile(dataFilePath); - } - - return null; - } - - private BoxSet GetItem(CollectionResult obj) - { - var item = new BoxSet - { - Name = obj.Name, - Overview = obj.Overview - }; - - item.SetProviderId(MetadataProvider.Tmdb, obj.Id.ToString(_usCulture)); - - return item; - } - - private async Task DownloadInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) - { - var mainResult = await FetchMainResult(tmdbId, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); - - if (mainResult == null) - { - return; - } - - var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); - - Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); - - _json.SerializeToFile(mainResult, dataFilePath); - } - - private async Task FetchMainResult(string id, string language, CancellationToken cancellationToken) - { - var url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey); - - if (!string.IsNullOrEmpty(language)) - { - url += string.Format(CultureInfo.InvariantCulture, "&language={0}", TmdbMovieProvider.NormalizeLanguage(language)); - - // Get images in english and with no language - url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url); - foreach (var header in TmdbUtils.AcceptHeaders) - { - requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); - } - - using var mainResponse = await TmdbMovieProvider.Current.GetMovieDbResponse(requestMessage, cancellationToken).ConfigureAwait(false); - await using var stream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); - var mainResult = await _json.DeserializeFromStreamAsync(stream).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - if (mainResult != null && string.IsNullOrEmpty(mainResult.Name)) - { - if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) - { - url = string.Format(CultureInfo.InvariantCulture, GetCollectionInfo3, id, TmdbUtils.ApiKey) + "&language=en"; - - if (!string.IsNullOrEmpty(language)) - { - // Get images in english and with no language - url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language); - } - - using var langRequestMessage = new HttpRequestMessage(HttpMethod.Get, url); - foreach (var header in TmdbUtils.AcceptHeaders) - { - langRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(header)); - } - - await using var langStream = await mainResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); - mainResult = await _json.DeserializeFromStreamAsync(langStream).ConfigureAwait(false); - } - } - - return mainResult; - } - - internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) - { - var path = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); - - var fileInfo = _fileSystem.GetFileSystemInfo(path); - - if (fileInfo.Exists) - { - // If it's recent or automatic updates are enabled, don't re-download - if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) - { - return Task.CompletedTask; - } - } - - return DownloadInfo(tmdbId, preferredMetadataLanguage, cancellationToken); - } - - public string Name => TmdbUtils.ProviderName; - - private static string GetDataFilePath(IApplicationPaths appPaths, string tmdbId, string preferredLanguage) - { - var path = GetDataPath(appPaths, tmdbId); - - var filename = string.Format(CultureInfo.InvariantCulture, "all-{0}.json", preferredLanguage ?? string.Empty); - - return Path.Combine(path, filename); - } - - private static string GetDataPath(IApplicationPaths appPaths, string tmdbId) - { - var dataPath = GetCollectionsDataPath(appPaths); - - return Path.Combine(dataPath, tmdbId); - } - - private static string GetCollectionsDataPath(IApplicationPaths appPaths) - { - var dataPath = Path.Combine(appPaths.CachePath, "tmdb-collections"); - - return dataPath; - } - public Task GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs deleted file mode 100644 index 0a8994d540..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs +++ /dev/null @@ -1,14 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; -using MediaBrowser.Providers.Plugins.Tmdb.Models.General; - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections -{ - public class CollectionImages - { - public List Backdrops { get; set; } - - public List Posters { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs deleted file mode 100644 index c6b851c237..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections -{ - public class CollectionResult - { - public int Id { get; set; } - - public string Name { get; set; } - - public string Overview { get; set; } - - public string Poster_Path { get; set; } - - public string Backdrop_Path { get; set; } - - public List Parts { get; set; } - - public CollectionImages Images { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs deleted file mode 100644 index a48124b3e1..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs +++ /dev/null @@ -1,17 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections -{ - public class Part - { - public string Title { get; set; } - - public int Id { get; set; } - - public string Release_Date { get; set; } - - public string Poster_Path { get; set; } - - public string Backdrop_Path { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs deleted file mode 100644 index 5b7627f6e8..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Backdrop - { - public double Aspect_Ratio { get; set; } - - public string File_Path { get; set; } - - public int Height { get; set; } - - public string Iso_639_1 { get; set; } - - public double Vote_Average { get; set; } - - public int Vote_Count { get; set; } - - public int Width { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs deleted file mode 100644 index 339ecb6285..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs +++ /dev/null @@ -1,19 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Crew - { - public int Id { get; set; } - - public string Credit_Id { get; set; } - - public string Name { get; set; } - - public string Department { get; set; } - - public string Job { get; set; } - - public string Profile_Path { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs deleted file mode 100644 index aac4420e8b..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs +++ /dev/null @@ -1,17 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class ExternalIds - { - public string Imdb_Id { get; set; } - - public object Freebase_Id { get; set; } - - public string Freebase_Mid { get; set; } - - public int? Tvdb_Id { get; set; } - - public int? Tvrage_Id { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs deleted file mode 100644 index 9ba1c15c65..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Genre - { - public int Id { get; set; } - - public string Name { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs deleted file mode 100644 index 0538cf174d..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs +++ /dev/null @@ -1,13 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Images - { - public List Backdrops { get; set; } - - public List Posters { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs deleted file mode 100644 index fff86931be..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Keyword - { - public int Id { get; set; } - - public string Name { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs deleted file mode 100644 index 235ecb5682..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Keywords - { - public List Results { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs deleted file mode 100644 index 4f61e978be..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs +++ /dev/null @@ -1,21 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Poster - { - public double Aspect_Ratio { get; set; } - - public string File_Path { get; set; } - - public int Height { get; set; } - - public string Iso_639_1 { get; set; } - - public double Vote_Average { get; set; } - - public int Vote_Count { get; set; } - - public int Width { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs deleted file mode 100644 index 0a1f8843eb..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs +++ /dev/null @@ -1,17 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Profile - { - public string File_Path { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public object Iso_639_1 { get; set; } - - public double Aspect_Ratio { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs deleted file mode 100644 index 61de819b93..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Still - { - public double Aspect_Ratio { get; set; } - - public string File_Path { get; set; } - - public int Height { get; set; } - - public string Id { get; set; } - - public string Iso_639_1 { get; set; } - - public double Vote_Average { get; set; } - - public int Vote_Count { get; set; } - - public int Width { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs deleted file mode 100644 index 59ab18b7bf..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class StillImages - { - public List Stills { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs deleted file mode 100644 index ebd5c7acee..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Video - { - public string Id { get; set; } - - public string Iso_639_1 { get; set; } - - public string Iso_3166_1 { get; set; } - - public string Key { get; set; } - - public string Name { get; set; } - - public string Site { get; set; } - - public string Size { get; set; } - - public string Type { get; set; } - } -} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs deleted file mode 100644 index 1c673fdbd6..0000000000 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 - -using System.Collections.Generic; - -namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General -{ - public class Videos - { - public IReadOnlyList