aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-compat.yml2
-rw-r--r--.ci/azure-pipelines-main.yml21
-rw-r--r--.ci/azure-pipelines-windows.yml82
-rw-r--r--.ci/azure-pipelines.yml5
-rw-r--r--.gitignore1
-rw-r--r--CONTRIBUTORS.md2
-rw-r--r--Dockerfile7
-rw-r--r--Dockerfile.arm10
-rw-r--r--Dockerfile.arm648
-rw-r--r--Emby.Dlna/Api/DlnaServerService.cs10
-rw-r--r--Emby.Dlna/Api/DlnaService.cs3
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs111
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs194
-rw-r--r--Emby.Dlna/Didl/StringWriterWithEncoding.cs2
-rw-r--r--Emby.Dlna/PlayTo/Device.cs49
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs150
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs6
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs10
-rw-r--r--Emby.Naming/Common/NamingOptions.cs61
-rw-r--r--Emby.Naming/Video/ExtraResolver.cs9
-rw-r--r--Emby.Naming/Video/ExtraRule.cs13
-rw-r--r--Emby.Naming/Video/ExtraRuleType.cs13
-rw-r--r--Emby.Notifications/Api/NotificationsService.cs12
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs85
-rw-r--r--Emby.Server.Implementations/ConfigurationOptions.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteExtensions.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs412
-rw-r--r--Emby.Server.Implementations/Diagnostics/CommonProcess.cs152
-rw-r--r--Emby.Server.Implementations/Diagnostics/ProcessFactory.cs14
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs19
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj6
-rw-r--r--Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs15
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs44
-rw-r--r--Emby.Server.Implementations/IStartupOptions.cs17
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs8
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs34
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs72
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json28
-rw-r--r--Emby.Server.Implementations/Localization/Core/da.json30
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-GB.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-AR.json66
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_DO.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/fa.json26
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json58
-rw-r--r--Emby.Server.Implementations/Localization/Core/fil.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr-CA.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/hu.json28
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json23
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/lv.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/mr.json61
-rw-r--r--Emby.Server.Implementations/Localization/Core/nb.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json15
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json26
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json23
-rw-r--r--Emby.Server.Implementations/Localization/Core/tr.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/ur_PK.json117
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-CN.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs5
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs70
-rw-r--r--Jellyfin.Server/Program.cs19
-rw-r--r--Jellyfin.Server/Properties/launchSettings.json8
-rw-r--r--Jellyfin.Server/StartupOptions.cs11
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs29
-rw-r--r--MediaBrowser.Api/BaseApiService.cs58
-rw-r--r--MediaBrowser.Api/ChannelService.cs22
-rw-r--r--MediaBrowser.Api/Devices/DeviceService.cs16
-rw-r--r--MediaBrowser.Api/EnvironmentService.cs7
-rw-r--r--MediaBrowser.Api/FilterService.cs4
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs26
-rw-r--r--MediaBrowser.Api/Images/RemoteImageService.cs26
-rw-r--r--MediaBrowser.Api/ItemLookupService.cs11
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs28
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs195
-rw-r--r--MediaBrowser.Api/Library/LibraryStructureService.cs12
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs12
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs2
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs406
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs73
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs10
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs23
-rw-r--r--MediaBrowser.Api/Playback/UniversalAudioService.cs4
-rw-r--r--MediaBrowser.Api/PluginService.cs4
-rw-r--r--MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs8
-rw-r--r--MediaBrowser.Api/SearchService.cs98
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs15
-rw-r--r--MediaBrowser.Api/System/SystemService.cs7
-rw-r--r--MediaBrowser.Api/TranscodingJob.cs5
-rw-r--r--MediaBrowser.Api/TvShowsService.cs13
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs34
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs33
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs13
-rw-r--r--MediaBrowser.Api/UserLibrary/UserLibraryService.cs3
-rw-r--r--MediaBrowser.Api/VideosService.cs13
-rw-r--r--MediaBrowser.Common/Cryptography/CryptoExtensions.cs (renamed from MediaBrowser.Common/Cryptography/Extensions.cs)2
-rw-r--r--MediaBrowser.Common/Extensions/ProcessExtensions.cs80
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj2
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs8
-rw-r--r--MediaBrowser.Common/Net/HttpResponseInfo.cs2
-rw-r--r--MediaBrowser.Common/Updates/IInstallationManager.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs44
-rw-r--r--MediaBrowser.Controller/Entities/InternalPeopleQuery.cs10
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs65
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs65
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs1
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs81
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs147
-rw-r--r--MediaBrowser.Model/Diagnostics/IProcess.cs23
-rw-r--r--MediaBrowser.Model/Diagnostics/IProcessFactory.cs24
-rw-r--r--MediaBrowser.Model/Dlna/DirectPlayProfile.cs4
-rw-r--r--MediaBrowser.Model/Entities/ExtraType.cs1
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj2
-rw-r--r--deployment/windows/build-jellyfin.ps1190
-rw-r--r--deployment/windows/dependencies.txt2
-rw-r--r--deployment/windows/dialogs/confirmation.nsddef24
-rw-r--r--deployment/windows/dialogs/confirmation.nsdinc61
-rw-r--r--deployment/windows/dialogs/service-config.nsddef13
-rw-r--r--deployment/windows/dialogs/service-config.nsdinc56
-rw-r--r--deployment/windows/dialogs/setuptype.nsddef12
-rw-r--r--deployment/windows/dialogs/setuptype.nsdinc50
-rw-r--r--deployment/windows/helpers/ShowError.nsh10
-rw-r--r--deployment/windows/helpers/StrSlash.nsh47
-rw-r--r--deployment/windows/jellyfin.nsi575
-rw-r--r--deployment/windows/legacy/install-jellyfin.ps1460
-rw-r--r--deployment/windows/legacy/install.bat1
-rw-r--r--nuget.config6
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj2
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs6
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs66
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj2
147 files changed, 2270 insertions, 3586 deletions
diff --git a/.ci/azure-pipelines-compat.yml b/.ci/azure-pipelines-compat.yml
index 762bbdcb2..de60d2ccb 100644
--- a/.ci/azure-pipelines-compat.yml
+++ b/.ci/azure-pipelines-compat.yml
@@ -23,7 +23,7 @@ jobs:
NugetPackageName: ${{ Package.value.NugetPackageName }}
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
maxParallel: 2
- dependsOn: MainBuild
+ dependsOn: Build
steps:
- checkout: none
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
index 09901b2a6..d155624ab 100644
--- a/.ci/azure-pipelines-main.yml
+++ b/.ci/azure-pipelines-main.yml
@@ -4,15 +4,14 @@ parameters:
DotNetSdkVersion: 3.1.100
jobs:
- - job: MainBuild
- displayName: Main Build
+ - job: Build
+ displayName: Build
strategy:
matrix:
Release:
BuildConfiguration: Release
Debug:
BuildConfiguration: Debug
- maxParallel: 2
pool:
vmImage: "${{ parameters.LinuxImage }}"
steps:
@@ -22,13 +21,13 @@ jobs:
persistCredentials: true
- task: CmdLine@2
- displayName: "Clone Web Client (Master, Release, or Tag)"
+ displayName: "Clone Web Branch"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
- task: CmdLine@2
- displayName: "Clone Web Client (PR)"
+ displayName: "Clone Web Target"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
inputs:
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
@@ -37,7 +36,7 @@ jobs:
displayName: "Install Node"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
- versionSpec: "10.x"
+ versionSpec: "12.x"
- task: CmdLine@2
displayName: "Build Web Client"
@@ -69,33 +68,33 @@ jobs:
command: publish
publishWebProjects: false
projects: "${{ parameters.RestoreBuildProjects }}"
- arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
+ arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)"
zipAfterPublish: false
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Naming"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
- targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
+ targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll"
artifactName: "Jellyfin.Naming"
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Controller"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
- targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
+ targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
artifactName: "Jellyfin.Controller"
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Model"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
- targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
+ targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
artifactName: "Jellyfin.Model"
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Common"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
- targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
+ targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
artifactName: "Jellyfin.Common"
diff --git a/.ci/azure-pipelines-windows.yml b/.ci/azure-pipelines-windows.yml
deleted file mode 100644
index 32d1d1382..000000000
--- a/.ci/azure-pipelines-windows.yml
+++ /dev/null
@@ -1,82 +0,0 @@
-parameters:
- WindowsImage: "windows-latest"
- TestProjects: "tests/**/*Tests.csproj"
- DotNetSdkVersion: 3.1.100
-
-jobs:
- - job: PublishWindows
- displayName: Publish Windows
- pool:
- vmImage: ${{ parameters.WindowsImage }}
- steps:
- - checkout: self
- clean: true
- submodules: true
- persistCredentials: true
-
- - task: CmdLine@2
- displayName: "Clone Web Client (Master, Release, or Tag)"
- condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
-
- - task: CmdLine@2
- displayName: "Clone Web Client (PR)"
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest'))
- inputs:
- script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
-
- - task: NodeTool@0
- displayName: "Install Node"
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- versionSpec: "10.x"
-
- - task: CmdLine@2
- displayName: "Build Web Client"
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- script: yarn install
- workingDirectory: $(Agent.TempDirectory)/jellyfin-web
-
- - task: CopyFiles@2
- displayName: "Copy Web Client"
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
- contents: "**"
- targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
- cleanTargetFolder: true
- overWrite: true
- flattenFolders: false
-
- - task: CmdLine@2
- displayName: "Clone UX Repository"
- inputs:
- script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
-
- - task: PowerShell@2
- displayName: "Build NSIS Installer"
- inputs:
- targetType: "filePath"
- filePath: ./deployment/windows/build-jellyfin.ps1
- arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
- errorActionPreference: "stop"
- workingDirectory: $(Build.SourcesDirectory)
-
- - task: CopyFiles@2
- displayName: "Copy NSIS Installer"
- inputs:
- sourceFolder: $(Build.SourcesDirectory)/deployment/windows/
- contents: "jellyfin*.exe"
- targetFolder: $(System.ArtifactsDirectory)/setup
- cleanTargetFolder: true
- overWrite: true
- flattenFolders: true
-
- - task: PublishPipelineArtifact@0
- displayName: "Publish Artifact Setup"
- condition: succeeded()
- inputs:
- targetPath: "$(build.artifactstagingdirectory)/setup"
- artifactName: "Jellyfin Server Setup"
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index f79a85b21..3522cbf00 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -27,11 +27,6 @@ jobs:
Windows: "windows-latest"
macOS: "macos-latest"
- - template: azure-pipelines-windows.yml
- parameters:
- WindowsImage: "windows-latest"
- TestProjects: $(TestProjects)
-
- template: azure-pipelines-compat.yml
parameters:
Packages:
diff --git a/.gitignore b/.gitignore
index 42243f01a..523c45a7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,7 @@ ProgramData*/
CorePlugins*/
ProgramData-Server*/
ProgramData-UI*/
+MediaBrowser.WebDashboard/jellyfin-web/**
#################
## Visual Studio
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index f195c125f..ce956176e 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -22,6 +22,7 @@
- [cvium](https://github.com/cvium)
- [dannymichel](https://github.com/dannymichel)
- [DaveChild](https://github.com/DaveChild)
+ - [Delgan](https://github.com/Delgan)
- [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung)
- [dinki](https://github.com/dinki)
@@ -128,6 +129,7 @@
- [xosdy](https://github.com/xosdy)
- [XVicarious](https://github.com/XVicarious)
- [YouKnowBlom](https://github.com/YouKnowBlom)
+ - [KristupasSavickas](https://github.com/KristupasSavickas)
# Emby Contributors
diff --git a/Dockerfile b/Dockerfile
index 01b3dd1c2..caac7500a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -45,15 +45,20 @@ RUN apt-get update \
ca-certificates \
vainfo \
i965-va-driver \
+ locales \
&& apt-get clean autoclean -y\
&& apt-get autoremove -y\
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \
&& ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
- && ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
+ && ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
EXPOSE 8096
VOLUME /cache /config /media
diff --git a/Dockerfile.arm b/Dockerfile.arm
index 434280855..39beaa479 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -52,20 +52,26 @@ RUN apt-get update \
libraspberrypi0 \
vainfo \
libva2 \
+ locales \
&& apt-get remove curl gnupg -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media
+ && chmod 777 /cache /config /media \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
+
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
- "--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index 15421a889..1a691b572 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -42,15 +42,21 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge
libfreetype6 \
libomxil-bellagio0 \
libomxil-bellagio-bin \
+ locales \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media
+ && chmod 777 /cache /config /media \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
+
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
EXPOSE 8096
VOLUME /cache /config /media
diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs
index b7d018921..7fba2184a 100644
--- a/Emby.Dlna/Api/DlnaServerService.cs
+++ b/Emby.Dlna/Api/DlnaServerService.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using System.Threading.Tasks;
@@ -151,6 +152,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes)));
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetContentDirectory request)
{
var xml = ContentDirectory.GetServiceXml();
@@ -158,6 +160,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetMediaReceiverRegistrar request)
{
var xml = MediaReceiverRegistrar.GetServiceXml();
@@ -165,6 +168,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetConnnectionManager request)
{
var xml = ConnectionManager.GetServiceXml();
@@ -313,31 +317,37 @@ namespace Emby.Dlna.Api
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream));
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessContentDirectoryEventRequest request)
{
return ProcessEventRequest(ContentDirectory);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessConnectionManagerEventRequest request)
{
return ProcessEventRequest(ConnectionManager);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
{
return ProcessEventRequest(MediaReceiverRegistrar);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessContentDirectoryEventRequest request)
{
return ProcessEventRequest(ContentDirectory);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessConnectionManagerEventRequest request)
{
return ProcessEventRequest(ConnectionManager);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
{
return ProcessEventRequest(MediaReceiverRegistrar);
diff --git a/Emby.Dlna/Api/DlnaService.cs b/Emby.Dlna/Api/DlnaService.cs
index 7d6b8f78e..5f984bb33 100644
--- a/Emby.Dlna/Api/DlnaService.cs
+++ b/Emby.Dlna/Api/DlnaService.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
@@ -52,6 +53,7 @@ namespace Emby.Dlna.Api
_dlnaManager = dlnaManager;
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetProfileInfos request)
{
return _dlnaManager.GetProfileInfos().ToArray();
@@ -62,6 +64,7 @@ namespace Emby.Dlna.Api
return _dlnaManager.GetProfile(request.Id);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetDefaultProfile request)
{
return _dlnaManager.GetDefaultProfile();
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 41f4fbbd3..28888f031 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -78,7 +78,18 @@ namespace Emby.Dlna.ContentDirectory
_profile = profile;
_config = config;
- _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, mediaEncoder);
+ _didlBuilder = new DidlBuilder(
+ profile,
+ user,
+ imageProcessor,
+ serverAddress,
+ accessToken,
+ userDataManager,
+ localization,
+ mediaSourceManager,
+ Logger,
+ mediaEncoder,
+ libraryManager);
}
/// <inheritdoc />
@@ -153,7 +164,7 @@ namespace Emby.Dlna.ContentDirectory
{
var id = sparams["ObjectID"];
- var serverItem = GetItemFromObjectId(id, _user);
+ var serverItem = GetItemFromObjectId(id);
var item = serverItem.Item;
@@ -276,7 +287,7 @@ namespace Emby.Dlna.ContentDirectory
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
- var serverItem = GetItemFromObjectId(id, _user);
+ var serverItem = GetItemFromObjectId(id);
var item = serverItem.Item;
@@ -293,7 +304,7 @@ namespace Emby.Dlna.ContentDirectory
else
{
var dlnaOptions = _config.GetDlnaConfiguration();
- _didlBuilder.WriteItemElement(dlnaOptions, writer, item, _user, null, null, deviceId, filter);
+ _didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter);
}
provided++;
@@ -320,7 +331,7 @@ namespace Emby.Dlna.ContentDirectory
}
else
{
- _didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
+ _didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
}
}
}
@@ -387,7 +398,7 @@ namespace Emby.Dlna.ContentDirectory
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
- var serverItem = GetItemFromObjectId(sparams["ContainerID"], _user);
+ var serverItem = GetItemFromObjectId(sparams["ContainerID"]);
var item = serverItem.Item;
@@ -406,7 +417,7 @@ namespace Emby.Dlna.ContentDirectory
}
else
{
- _didlBuilder.WriteItemElement(dlnaOptions, writer, i, _user, item, serverItem.StubType, deviceId, filter);
+ _didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter);
}
}
@@ -512,11 +523,11 @@ namespace Emby.Dlna.ContentDirectory
}
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
- return GetFolders(item, user, stubType, sort, startIndex, limit);
+ return GetFolders(user, startIndex, limit);
}
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
- return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
+ return GetLiveTvChannels(user, sort, startIndex, limit);
}
}
@@ -547,7 +558,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(queryResult);
}
- private QueryResult<ServerItem> GetLiveTvChannels(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
+ private QueryResult<ServerItem> GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
{
@@ -579,7 +590,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Playlists)
{
- return GetMusicPlaylists(item, user, query);
+ return GetMusicPlaylists(user, query);
}
if (stubType.HasValue && stubType.Value == StubType.Albums)
@@ -707,7 +718,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Collections)
{
- return GetMovieCollections(item, user, query);
+ return GetMovieCollections(user, query);
}
if (stubType.HasValue && stubType.Value == StubType.Favorites)
@@ -720,46 +731,42 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query);
}
- var list = new List<ServerItem>();
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.ContinueWatching
- });
-
- list.Add(new ServerItem(item)
+ var array = new ServerItem[]
{
- StubType = StubType.Latest
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Movies
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Collections
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Favorites
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Genres
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.ContinueWatching
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Latest
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Movies
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Collections
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Favorites
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Genres
+ }
+ };
return new QueryResult<ServerItem>
{
- Items = list,
- TotalRecordCount = list.Count
+ Items = array,
+ TotalRecordCount = array.Length
};
}
- private QueryResult<ServerItem> GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
+ private QueryResult<ServerItem> GetFolders(User user, int? startIndex, int? limit)
{
var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.OrderBy(i => i.SortName)
@@ -792,7 +799,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.NextUp)
{
- return GetNextUp(item, user, query);
+ return GetNextUp(item, query);
}
if (stubType.HasValue && stubType.Value == StubType.Latest)
@@ -910,7 +917,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
- private QueryResult<ServerItem> GetMovieCollections(BaseItem parent, User user, InternalItemsQuery query)
+ private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
{
query.Recursive = true;
//query.Parent = parent;
@@ -1105,7 +1112,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
- private QueryResult<ServerItem> GetMusicPlaylists(BaseItem parent, User user, InternalItemsQuery query)
+ private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
{
query.Parent = null;
query.IncludeItemTypes = new[] { typeof(Playlist).Name };
@@ -1134,7 +1141,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(items);
}
- private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query)
+ private QueryResult<ServerItem> GetNextUp(BaseItem parent, InternalItemsQuery query)
{
query.OrderBy = Array.Empty<(string, SortOrder)>();
@@ -1289,15 +1296,15 @@ namespace Emby.Dlna.ContentDirectory
return result;
}
- private ServerItem GetItemFromObjectId(string id, User user)
+ private ServerItem GetItemFromObjectId(string id)
{
return DidlBuilder.IsIdRoot(id)
? new ServerItem(_libraryManager.GetUserRootFolder())
- : ParseItemId(id, user);
+ : ParseItemId(id);
}
- private ServerItem ParseItemId(string id, User user)
+ private ServerItem ParseItemId(string id)
{
StubType? stubType = null;
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index efc86f333..88cc00e30 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -45,6 +45,7 @@ namespace Emby.Dlna.Didl
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder;
+ private readonly ILibraryManager _libraryManager;
public DidlBuilder(
DeviceProfile profile,
@@ -56,7 +57,8 @@ namespace Emby.Dlna.Didl
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
ILogger logger,
- IMediaEncoder mediaEncoder)
+ IMediaEncoder mediaEncoder,
+ ILibraryManager libraryManager)
{
_profile = profile;
_user = user;
@@ -68,6 +70,7 @@ namespace Emby.Dlna.Didl
_mediaSourceManager = mediaSourceManager;
_logger = logger;
_mediaEncoder = mediaEncoder;
+ _libraryManager = libraryManager;
}
public static string NormalizeDlnaMediaUrl(string url)
@@ -75,7 +78,7 @@ namespace Emby.Dlna.Didl
return url + "&dlnaheaders=true";
}
- public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
+ public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
{
var settings = new XmlWriterSettings
{
@@ -100,7 +103,7 @@ namespace Emby.Dlna.Didl
WriteXmlRootAttributes(_profile, writer);
- WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo);
+ WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement();
//writer.WriteEndDocument();
@@ -127,7 +130,6 @@ namespace Emby.Dlna.Didl
}
public void WriteItemElement(
- DlnaOptions options,
XmlWriter writer,
BaseItem item,
User user,
@@ -164,25 +166,23 @@ namespace Emby.Dlna.Didl
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
- var hasMediaSources = item as IHasMediaSources;
-
- if (hasMediaSources != null)
+ if (item is IHasMediaSources)
{
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
- AddAudioResource(options, writer, item, deviceId, filter, streamInfo);
+ AddAudioResource(writer, item, deviceId, filter, streamInfo);
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
- AddVideoResource(options, writer, item, deviceId, filter, streamInfo);
+ AddVideoResource(writer, item, deviceId, filter, streamInfo);
}
}
- AddCover(item, context, null, writer);
+ AddCover(item, null, writer);
writer.WriteFullEndElement();
}
- private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
if (streamInfo == null)
{
@@ -226,7 +226,7 @@ namespace Emby.Dlna.Didl
foreach (var contentFeature in contentFeatureList)
{
- AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
+ AddVideoResource(writer, filter, contentFeature, streamInfo);
}
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
@@ -283,7 +283,10 @@ namespace Emby.Dlna.Didl
else
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
- var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
+ var protocolInfo = string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:text/{0}:*",
+ info.Format.ToLowerInvariant());
writer.WriteAttributeString("protocolInfo", protocolInfo);
writer.WriteString(info.Url);
@@ -293,7 +296,7 @@ namespace Emby.Dlna.Didl
return true;
}
- private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
+ private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@@ -335,7 +338,13 @@ namespace Emby.Dlna.Didl
{
if (targetWidth.HasValue && targetHeight.HasValue)
{
- writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
+ writer.WriteAttributeString(
+ "resolution",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}x{1}",
+ targetWidth.Value,
+ targetHeight.Value));
}
}
@@ -369,17 +378,19 @@ namespace Emby.Dlna.Didl
streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC);
- var filename = url.Substring(0, url.IndexOf('?'));
+ var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- mimeType,
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ mimeType,
+ contentFeatures));
writer.WriteString(url);
@@ -420,7 +431,10 @@ namespace Emby.Dlna.Didl
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{
- return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("ValueSpecialEpisodeName"),
+ item.Name);
}
if (item.IndexNumber.HasValue)
@@ -435,11 +449,34 @@ namespace Emby.Dlna.Didl
return number + " - " + item.Name;
}
}
+ else if (item is Episode ep)
+ {
+ var parent = ep.GetParent();
+ var name = parent.Name + " - ";
+
+ if (ep.ParentIndexNumber.HasValue)
+ {
+ name += "S" + ep.ParentIndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+ }
+ else if (!item.IndexNumber.HasValue)
+ {
+ return name + " - " + item.Name;
+ }
+
+ name += "E" + ep.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+ if (ep.IndexNumberEnd.HasValue)
+ {
+ name += "-" + ep.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
+ }
+
+ name += " - " + item.Name;
+ return name;
+ }
return item.Name;
}
- private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@@ -505,7 +542,7 @@ namespace Emby.Dlna.Didl
targetSampleRate,
targetAudioBitDepth);
- var filename = url.Substring(0, url.IndexOf('?'));
+ var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
@@ -521,11 +558,13 @@ namespace Emby.Dlna.Didl
streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo);
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- mimeType,
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ mimeType,
+ contentFeatures));
writer.WriteString(url);
@@ -548,7 +587,7 @@ namespace Emby.Dlna.Didl
var clientId = GetClientId(folder, stubType);
- if (string.Equals(requestedId, "0"))
+ if (string.Equals(requestedId, "0", StringComparison.Ordinal))
{
writer.WriteAttributeString("id", "0");
writer.WriteAttributeString("parentID", "-1");
@@ -577,7 +616,7 @@ namespace Emby.Dlna.Didl
AddGeneralProperties(folder, stubType, context, writer, filter);
- AddCover(folder, context, stubType, writer);
+ AddCover(folder, stubType, writer);
writer.WriteFullEndElement();
}
@@ -610,7 +649,10 @@ namespace Emby.Dlna.Didl
if (playbackPositionTicks > 0)
{
- var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture));
+ var elementValue = string.Format(
+ CultureInfo.InvariantCulture,
+ "BM={0}",
+ Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds));
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
}
}
@@ -763,37 +805,36 @@ namespace Emby.Dlna.Didl
private void AddPeople(BaseItem item, XmlWriter writer)
{
- //var types = new[]
- //{
- // PersonType.Director,
- // PersonType.Writer,
- // PersonType.Producer,
- // PersonType.Composer,
- // "Creator"
- //};
-
- //var people = _libraryManager.GetPeople(item);
-
- //var index = 0;
-
- //// Seeing some LG models locking up due content with large lists of people
- //// The actual issue might just be due to processing a more metadata than it can handle
- //var limit = 6;
+ if (!item.SupportsPeople)
+ {
+ return;
+ }
- //foreach (var actor in people)
- //{
- // var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
- // ?? PersonType.Actor;
+ var types = new[]
+ {
+ PersonType.Director,
+ PersonType.Writer,
+ PersonType.Producer,
+ PersonType.Composer,
+ "creator"
+ };
- // AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
+ // Seeing some LG models locking up due content with large lists of people
+ // The actual issue might just be due to processing a more metadata than it can handle
+ var people = _libraryManager.GetPeople(
+ new InternalPeopleQuery
+ {
+ ItemId = item.Id,
+ Limit = 6
+ });
- // index++;
+ foreach (var actor in people)
+ {
+ var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
+ ?? PersonType.Actor;
- // if (index >= limit)
- // {
- // break;
- // }
- //}
+ AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
+ }
}
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
@@ -870,7 +911,7 @@ namespace Emby.Dlna.Didl
}
}
- private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
+ private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer)
{
ImageDownloadInfo imageInfo = GetImageInfo(item);
@@ -915,17 +956,8 @@ namespace Emby.Dlna.Didl
}
- private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
- {
- writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
- writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
- writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg");
- writer.WriteFullEndElement();
-
- writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg");
- }
-
- private void AddImageResElement(BaseItem item,
+ private void AddImageResElement(
+ BaseItem item,
XmlWriter writer,
int maxWidth,
int maxHeight,
@@ -951,13 +983,17 @@ namespace Emby.Dlna.Didl
var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- MimeTypes.GetMimeType("file." + format),
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ MimeTypes.GetMimeType("file." + format),
+ contentFeatures));
- writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width, height));
+ writer.WriteAttributeString(
+ "resolution",
+ string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
writer.WriteString(albumartUrlInfo.Url);
@@ -1096,7 +1132,9 @@ namespace Emby.Dlna.Didl
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
{
- var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
+ var url = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
_serverAddress,
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
info.Type,
diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs
index 67fc56ec0..896fe992b 100644
--- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs
+++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs
@@ -53,6 +53,6 @@ namespace Emby.Dlna.Didl
_encoding = encoding;
}
- public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding;
+ public override Encoding Encoding => _encoding ?? base.Encoding;
}
}
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index b77a2bbac..6abc3a82c 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -346,7 +346,12 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- return new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1));
+ return new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ avCommands.BuildPost(command, service.ServiceType, 1),
+ cancellationToken: cancellationToken);
}
public async Task SetPlay(CancellationToken cancellationToken)
@@ -515,8 +520,12 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ rendererCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
{
@@ -561,8 +570,12 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ rendererCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
return;
@@ -588,8 +601,12 @@ namespace Emby.Dlna.PlayTo
return null;
}
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ avCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
{
@@ -599,7 +616,7 @@ namespace Emby.Dlna.PlayTo
var transportState =
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
- var transportStateValue = transportState == null ? null : transportState.Value;
+ var transportStateValue = transportState?.Value;
if (transportStateValue != null
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
@@ -626,8 +643,12 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ rendererCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
{
@@ -689,8 +710,12 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ rendererCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
{
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index cf978d742..43e983054 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -27,6 +27,8 @@ namespace Emby.Dlna.PlayTo
{
public class PlayToController : ISessionController, IDisposable
{
+ private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
+
private Device _device;
private readonly SessionInfo _session;
private readonly ISessionManager _sessionManager;
@@ -45,9 +47,10 @@ namespace Emby.Dlna.PlayTo
private readonly string _serverAddress;
private readonly string _accessToken;
- public bool IsSessionActive => !_disposed && _device != null;
+ private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
+ private int _currentPlaylistIndex;
- public bool SupportsMediaControl => IsSessionActive;
+ private bool _disposed;
public PlayToController(
SessionInfo session,
@@ -83,18 +86,22 @@ namespace Emby.Dlna.PlayTo
_mediaEncoder = mediaEncoder;
}
+ public bool IsSessionActive => !_disposed && _device != null;
+
+ public bool SupportsMediaControl => IsSessionActive;
+
public void Init(Device device)
{
_device = device;
_device.OnDeviceUnavailable = OnDeviceUnavailable;
- _device.PlaybackStart += _device_PlaybackStart;
- _device.PlaybackProgress += _device_PlaybackProgress;
- _device.PlaybackStopped += _device_PlaybackStopped;
- _device.MediaChanged += _device_MediaChanged;
+ _device.PlaybackStart += OnDevicePlaybackStart;
+ _device.PlaybackProgress += OnDevicePlaybackProgress;
+ _device.PlaybackStopped += OnDevicePlaybackStopped;
+ _device.MediaChanged += OnDeviceMediaChanged;
_device.Start();
- _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
+ _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
}
private void OnDeviceUnavailable()
@@ -110,7 +117,7 @@ namespace Emby.Dlna.PlayTo
}
}
- void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
+ private void OnDeviceDiscoveryDeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
var info = e.Argument;
@@ -125,7 +132,7 @@ namespace Emby.Dlna.PlayTo
}
}
- async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
+ private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e)
{
if (_disposed)
{
@@ -137,15 +144,15 @@ namespace Emby.Dlna.PlayTo
var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item != null)
{
- var positionTicks = GetProgressPositionTicks(e.OldMediaInfo, streamInfo);
+ var positionTicks = GetProgressPositionTicks(streamInfo);
- ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks);
+ ReportPlaybackStopped(streamInfo, positionTicks);
}
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item == null) return;
- var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo);
+ var newItemProgress = GetProgressInfo(streamInfo);
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
}
@@ -155,7 +162,7 @@ namespace Emby.Dlna.PlayTo
}
}
- async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e)
+ private async void OnDevicePlaybackStopped(object sender, PlaybackStoppedEventArgs e)
{
if (_disposed)
{
@@ -168,9 +175,9 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.Item == null) return;
- var positionTicks = GetProgressPositionTicks(e.MediaInfo, streamInfo);
+ var positionTicks = GetProgressPositionTicks(streamInfo);
- ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks);
+ ReportPlaybackStopped(streamInfo, positionTicks);
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
@@ -194,7 +201,7 @@ namespace Emby.Dlna.PlayTo
}
else
{
- Playlist.Clear();
+ _playlist.Clear();
}
}
catch (Exception ex)
@@ -203,7 +210,7 @@ namespace Emby.Dlna.PlayTo
}
}
- private async void ReportPlaybackStopped(uBaseObject mediaInfo, StreamParams streamInfo, long? positionTicks)
+ private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks)
{
try
{
@@ -222,7 +229,7 @@ namespace Emby.Dlna.PlayTo
}
}
- async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e)
+ private async void OnDevicePlaybackStart(object sender, PlaybackStartEventArgs e)
{
if (_disposed)
{
@@ -235,7 +242,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null)
{
- var progress = GetProgressInfo(e.MediaInfo, info);
+ var progress = GetProgressInfo(info);
await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
}
@@ -246,7 +253,7 @@ namespace Emby.Dlna.PlayTo
}
}
- async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
+ private async void OnDevicePlaybackProgress(object sender, PlaybackProgressEventArgs e)
{
if (_disposed)
{
@@ -266,7 +273,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null)
{
- var progress = GetProgressInfo(e.MediaInfo, info);
+ var progress = GetProgressInfo(info);
await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false);
}
@@ -277,7 +284,7 @@ namespace Emby.Dlna.PlayTo
}
}
- private long? GetProgressPositionTicks(uBaseObject mediaInfo, StreamParams info)
+ private long? GetProgressPositionTicks(StreamParams info)
{
var ticks = _device.Position.Ticks;
@@ -289,13 +296,13 @@ namespace Emby.Dlna.PlayTo
return ticks;
}
- private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info)
+ private PlaybackStartInfo GetProgressInfo(StreamParams info)
{
return new PlaybackStartInfo
{
ItemId = info.ItemId,
SessionId = _session.Id,
- PositionTicks = GetProgressPositionTicks(mediaInfo, info),
+ PositionTicks = GetProgressPositionTicks(info),
IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused,
MediaSourceId = info.MediaSourceId,
@@ -310,9 +317,7 @@ namespace Emby.Dlna.PlayTo
};
}
- #region SendCommands
-
- public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
+ public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{
_logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
@@ -350,11 +355,12 @@ namespace Emby.Dlna.PlayTo
if (command.PlayCommand == PlayCommand.PlayLast)
{
- Playlist.AddRange(playlist);
+ _playlist.AddRange(playlist);
}
+
if (command.PlayCommand == PlayCommand.PlayNext)
{
- Playlist.AddRange(playlist);
+ _playlist.AddRange(playlist);
}
if (!command.ControllingUserId.Equals(Guid.Empty))
@@ -363,7 +369,7 @@ namespace Emby.Dlna.PlayTo
_session.DeviceName, _session.RemoteEndPoint, user);
}
- await PlayItems(playlist).ConfigureAwait(false);
+ return PlayItems(playlist, cancellationToken);
}
private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
@@ -371,7 +377,7 @@ namespace Emby.Dlna.PlayTo
switch (command.Command)
{
case PlaystateCommand.Stop:
- Playlist.Clear();
+ _playlist.Clear();
return _device.SetStop(CancellationToken.None);
case PlaystateCommand.Pause:
@@ -387,10 +393,10 @@ namespace Emby.Dlna.PlayTo
return Seek(command.SeekPositionTicks ?? 0);
case PlaystateCommand.NextTrack:
- return SetPlaylistIndex(_currentPlaylistIndex + 1);
+ return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken);
case PlaystateCommand.PreviousTrack:
- return SetPlaylistIndex(_currentPlaylistIndex - 1);
+ return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken);
}
return Task.CompletedTask;
@@ -426,14 +432,6 @@ namespace Emby.Dlna.PlayTo
return info.IsDirectStream;
}
- #endregion
-
- #region Playlist
-
- private int _currentPlaylistIndex;
- private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
- private List<PlaylistItem> Playlist => _playlist;
-
private void AddItemFromId(Guid id, List<BaseItem> list)
{
var item = _libraryManager.GetItemById(id);
@@ -451,7 +449,7 @@ namespace Emby.Dlna.PlayTo
_dlnaManager.GetDefaultProfile();
var mediaSources = item is IHasMediaSources
- ? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
+ ? _mediaSourceManager.GetStaticMediaSources(item, true, user)
: new List<MediaSourceInfo>();
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
@@ -459,8 +457,19 @@ namespace Emby.Dlna.PlayTo
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
- var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder)
- .GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
+ var itemXml = new DidlBuilder(
+ profile,
+ user,
+ _imageProcessor,
+ _serverAddress,
+ _accessToken,
+ _userDataManager,
+ _localization,
+ _mediaSourceManager,
+ _logger,
+ _mediaEncoder,
+ _libraryManager)
+ .GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
playlistItem.Didl = itemXml;
@@ -570,30 +579,31 @@ namespace Emby.Dlna.PlayTo
/// Plays the items.
/// </summary>
/// <param name="items">The items.</param>
- /// <returns></returns>
- private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns><c>true</c> on success.</returns>
+ private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items, CancellationToken cancellationToken = default)
{
- Playlist.Clear();
- Playlist.AddRange(items);
- _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count);
+ _playlist.Clear();
+ _playlist.AddRange(items);
+ _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count);
- await SetPlaylistIndex(0).ConfigureAwait(false);
+ await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false);
return true;
}
- private async Task SetPlaylistIndex(int index)
+ private async Task SetPlaylistIndex(int index, CancellationToken cancellationToken = default)
{
- if (index < 0 || index >= Playlist.Count)
+ if (index < 0 || index >= _playlist.Count)
{
- Playlist.Clear();
- await _device.SetStop(CancellationToken.None);
+ _playlist.Clear();
+ await _device.SetStop(cancellationToken).ConfigureAwait(false);
return;
}
_currentPlaylistIndex = index;
- var currentitem = Playlist[index];
+ var currentitem = _playlist[index];
- await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, CancellationToken.None);
+ await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
var streamInfo = currentitem.StreamInfo;
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
@@ -602,10 +612,7 @@ namespace Emby.Dlna.PlayTo
}
}
- #endregion
-
- private bool _disposed;
-
+ /// <inheritdoc />
public void Dispose()
{
Dispose(true);
@@ -624,19 +631,17 @@ namespace Emby.Dlna.PlayTo
_device.Dispose();
}
- _device.PlaybackStart -= _device_PlaybackStart;
- _device.PlaybackProgress -= _device_PlaybackProgress;
- _device.PlaybackStopped -= _device_PlaybackStopped;
- _device.MediaChanged -= _device_MediaChanged;
- _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
+ _device.PlaybackStart -= OnDevicePlaybackStart;
+ _device.PlaybackProgress -= OnDevicePlaybackProgress;
+ _device.PlaybackStopped -= OnDevicePlaybackStopped;
+ _device.MediaChanged -= OnDeviceMediaChanged;
+ _deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft;
_device.OnDeviceUnavailable = null;
_device = null;
_disposed = true;
}
- private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
-
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
@@ -713,7 +718,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null)
{
- var newPosition = GetProgressPositionTicks(media, info) ?? 0;
+ var newPosition = GetProgressPositionTicks(info) ?? 0;
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
@@ -738,7 +743,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null)
{
- var newPosition = GetProgressPositionTicks(media, info) ?? 0;
+ var newPosition = GetProgressPositionTicks(info) ?? 0;
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
@@ -852,8 +857,11 @@ namespace Emby.Dlna.PlayTo
return request;
}
- var index = url.IndexOf('?');
- if (index == -1) return request;
+ var index = url.IndexOf('?', StringComparison.Ordinal);
+ if (index == -1)
+ {
+ return request;
+ }
var query = url.Substring(index + 1);
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index b8a47c44c..bbedd1485 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -23,7 +23,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo
{
- public class PlayToManager : IDisposable
+ public sealed class PlayToManager : IDisposable
{
private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
@@ -231,6 +231,7 @@ namespace Emby.Dlna.PlayTo
}
}
+ /// <inheritdoc />
public void Dispose()
{
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
@@ -244,6 +245,9 @@ namespace Emby.Dlna.PlayTo
}
+ _sessionLock.Dispose();
+ _disposeCancellationTokenSource.Dispose();
+
_disposed = true;
}
}
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index dab5f29bd..8c1362007 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -32,18 +32,15 @@ namespace Emby.Dlna.PlayTo
DeviceService service,
string command,
string postData,
- bool logRequest = true,
- string header = null)
+ string header = null,
+ CancellationToken cancellationToken = default)
{
- var cancellationToken = CancellationToken.None;
-
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
using (var response = await PostSoapDataAsync(
url,
$"\"{service.ServiceType}#{command}\"",
postData,
header,
- logRequest,
cancellationToken)
.ConfigureAwait(false))
using (var stream = response.Content)
@@ -63,7 +60,7 @@ namespace Emby.Dlna.PlayTo
return serviceUrl;
}
- if (!serviceUrl.StartsWith("/"))
+ if (!serviceUrl.StartsWith("/", StringComparison.Ordinal))
{
serviceUrl = "/" + serviceUrl;
}
@@ -127,7 +124,6 @@ namespace Emby.Dlna.PlayTo
string soapAction,
string postData,
string header,
- bool logRequest,
CancellationToken cancellationToken)
{
if (soapAction[0] != '\"')
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 793847f84..a2d75d0b8 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -136,7 +136,8 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
- @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
+ @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*",
+ @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
};
CleanStrings = new[]
@@ -505,7 +506,63 @@ namespace Emby.Naming.Common
RuleType = ExtraRuleType.Suffix,
Token = "-short",
MediaType = MediaType.Video
- }
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.BehindTheScenes,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "behind the scenes",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.DeletedScene,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "deleted scenes",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Interview,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "interviews",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Scene,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "scenes",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Sample,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "samples",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Clip,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "shorts",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Clip,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "featurettes",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Unknown,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "extras",
+ MediaType = MediaType.Video,
+ },
};
Format3DRules = new[]
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index 42a5c88b3..fc0424faa 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -80,6 +80,15 @@ namespace Emby.Naming.Video
result.Rule = rule;
}
}
+ else if (rule.RuleType == ExtraRuleType.DirectoryName)
+ {
+ var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
+ if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
+ }
return result;
}
diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs
index cb58a3934..7c9702e24 100644
--- a/Emby.Naming/Video/ExtraRule.cs
+++ b/Emby.Naming/Video/ExtraRule.cs
@@ -5,30 +5,29 @@ using MediaType = Emby.Naming.Common.MediaType;
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// A rule used to match a file path with an <see cref="MediaBrowser.Model.Entities.ExtraType"/>.
+ /// </summary>
public class ExtraRule
{
/// <summary>
- /// Gets or sets the token.
+ /// Gets or sets the token to use for matching against the file path.
/// </summary>
- /// <value>The token.</value>
public string Token { get; set; }
/// <summary>
- /// Gets or sets the type of the extra.
+ /// Gets or sets the type of the extra to return when matched.
/// </summary>
- /// <value>The type of the extra.</value>
public ExtraType ExtraType { get; set; }
/// <summary>
/// Gets or sets the type of the rule.
/// </summary>
- /// <value>The type of the rule.</value>
public ExtraRuleType RuleType { get; set; }
/// <summary>
- /// Gets or sets the type of the media.
+ /// Gets or sets the type of the media to return when matched.
/// </summary>
- /// <value>The type of the media.</value>
public MediaType MediaType { get; set; }
}
}
diff --git a/Emby.Naming/Video/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs
index b021a04a3..e89876f4a 100644
--- a/Emby.Naming/Video/ExtraRuleType.cs
+++ b/Emby.Naming/Video/ExtraRuleType.cs
@@ -5,18 +5,23 @@ namespace Emby.Naming.Video
public enum ExtraRuleType
{
/// <summary>
- /// The suffix
+ /// Match <see cref="ExtraRule.Token"/> against a suffix in the file name.
/// </summary>
Suffix = 0,
/// <summary>
- /// The filename
+ /// Match <see cref="ExtraRule.Token"/> against the file name, excluding the file extension.
/// </summary>
Filename = 1,
/// <summary>
- /// The regex
+ /// Match <see cref="ExtraRule.Token"/> against the file name, including the file extension.
/// </summary>
- Regex = 2
+ Regex = 2,
+
+ /// <summary>
+ /// Match <see cref="ExtraRule.Token"/> against the name of the directory containing the file.
+ /// </summary>
+ DirectoryName = 3,
}
}
diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs
index 67401c1f5..788750796 100644
--- a/Emby.Notifications/Api/NotificationsService.cs
+++ b/Emby.Notifications/Api/NotificationsService.cs
@@ -134,19 +134,19 @@ namespace Emby.Notifications.Api
_userManager = userManager;
}
- [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotificationTypes request)
{
return _notificationManager.GetNotificationTypes();
}
- [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotificationServices request)
{
return _notificationManager.GetNotificationServices().ToList();
}
- [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotificationsSummary request)
{
return new NotificationsSummary
@@ -170,17 +170,17 @@ namespace Emby.Notifications.Api
return _notificationManager.SendNotification(notification, CancellationToken.None);
}
- [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public void Post(MarkRead request)
{
}
- [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public void Post(MarkUnread request)
{
}
- [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotifications request)
{
return new NotificationResult();
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index c959cc974..81a80ddb2 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -30,7 +30,6 @@ using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Cryptography;
using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Devices;
-using Emby.Server.Implementations.Diagnostics;
using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.Security;
@@ -86,7 +85,6 @@ using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
@@ -118,6 +116,11 @@ namespace Emby.Server.Implementations
/// </summary>
public abstract class ApplicationHost : IServerApplicationHost, IDisposable
{
+ /// <summary>
+ /// The environment variable prefixes to log at server startup.
+ /// </summary>
+ private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
+
private SqliteUserRepository _userRepository;
private SqliteDisplayPreferencesRepository _displayPreferencesRepository;
@@ -332,8 +335,6 @@ namespace Emby.Server.Implementations
internal IImageEncoder ImageEncoder { get; private set; }
- protected IProcessFactory ProcessFactory { get; private set; }
-
protected readonly IXmlSerializer XmlSerializer;
protected ISocketFactory SocketFactory { get; private set; }
@@ -675,9 +676,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(XmlSerializer);
- ProcessFactory = new ProcessFactory();
- serviceCollection.AddSingleton(ProcessFactory);
-
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
var cryptoProvider = new CryptographyProvider();
@@ -738,7 +736,6 @@ namespace Emby.Server.Implementations
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
ServerConfigurationManager,
FileSystemManager,
- ProcessFactory,
LocalizationManager,
() => SubtitleEncoder,
startupConfig,
@@ -852,8 +849,7 @@ namespace Emby.Server.Implementations
FileSystemManager,
MediaEncoder,
HttpClient,
- MediaSourceManager,
- ProcessFactory);
+ MediaSourceManager);
serviceCollection.AddSingleton(SubtitleEncoder);
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
@@ -889,18 +885,18 @@ namespace Emby.Server.Implementations
.GetCommandLineArgs()
.Distinct();
- // Get all 'JELLYFIN_' prefixed environment variables
+ // Get all relevant environment variables
var allEnvVars = Environment.GetEnvironmentVariables();
- var jellyfinEnvVars = new Dictionary<object, object>();
+ var relevantEnvVars = new Dictionary<object, object>();
foreach (var key in allEnvVars.Keys)
{
- if (key.ToString().StartsWith("JELLYFIN_", StringComparison.OrdinalIgnoreCase))
+ if (_relevantEnvVarPrefixes.Any(prefix => key.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
{
- jellyfinEnvVars.Add(key, allEnvVars[key]);
+ relevantEnvVars.Add(key, allEnvVars[key]);
}
}
- logger.LogInformation("Environment Variables: {EnvVars}", jellyfinEnvVars);
+ logger.LogInformation("Environment Variables: {EnvVars}", relevantEnvVars);
logger.LogInformation("Arguments: {Args}", commandLineArgs);
logger.LogInformation("Operating system: {OS}", OperatingSystem.Name);
logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture);
@@ -1010,48 +1006,12 @@ namespace Emby.Server.Implementations
AuthenticatedAttribute.AuthService = AuthService;
}
- private async void PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> args)
- {
- string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
- var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
- .Select(Assembly.LoadFrom)
- .SelectMany(x => x.ExportedTypes)
- .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
- .ToArray();
-
- int oldLen = _allConcreteTypes.Length;
- Array.Resize(ref _allConcreteTypes, oldLen + types.Length);
- types.CopyTo(_allConcreteTypes, oldLen);
-
- var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin)))
- .Select(CreateInstanceSafe)
- .Where(x => x != null)
- .Cast<IPlugin>()
- .Select(LoadPlugin)
- .Where(x => x != null)
- .ToArray();
-
- oldLen = _plugins.Length;
- Array.Resize(ref _plugins, oldLen + plugins.Length);
- plugins.CopyTo(_plugins, oldLen);
-
- var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint)))
- .Select(CreateInstanceSafe)
- .Where(x => x != null)
- .Cast<IServerEntryPoint>()
- .ToList();
-
- await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false);
- await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false);
- }
-
/// <summary>
/// Finds the parts.
/// </summary>
public void FindParts()
{
InstallationManager = ServiceProvider.GetService<IInstallationManager>();
- InstallationManager.PluginInstalled += PluginInstalled;
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
{
@@ -1709,15 +1669,17 @@ namespace Emby.Server.Implementations
throw new NotSupportedException();
}
- var process = ProcessFactory.Create(new ProcessOptions
+ var process = new Process
{
- FileName = url,
- EnableRaisingEvents = true,
- UseShellExecute = true,
- ErrorDialog = false
- });
-
- process.Exited += ProcessExited;
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = url,
+ UseShellExecute = true,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ };
+ process.Exited += (sender, args) => ((Process)sender).Dispose();
try
{
@@ -1730,11 +1692,6 @@ namespace Emby.Server.Implementations
}
}
- private static void ProcessExited(object sender, EventArgs e)
- {
- ((IProcess)sender).Dispose();
- }
-
public virtual void EnableLoopback(string appName)
{
}
diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
index 4574a64fd..20bdd18e7 100644
--- a/Emby.Server.Implementations/ConfigurationOptions.cs
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Emby.Server.Implementations.HttpServer;
+using Emby.Server.Implementations.Updates;
using MediaBrowser.Providers.Music;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
@@ -17,6 +18,7 @@ namespace Emby.Server.Implementations
{
{ HostWebClientKey, bool.TrueString },
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
+ { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index c87793072..e7c5270b9 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.Data
}
}
- public static void TryBind(this IStatement statement, string name, byte[] value)
+ public static void TryBind(this IStatement statement, string name, ReadOnlySpan<byte> value)
{
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
{
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index e3242f7b4..33ff74bb5 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -3315,7 +3315,7 @@ namespace Emby.Server.Implementations.Data
for (int i = 0; i < str.Length; i++)
{
- if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
+ if (!char.IsLetter(str[i]) && !char.IsNumber(str[i]))
{
return false;
}
@@ -3339,7 +3339,7 @@ namespace Emby.Server.Implementations.Data
return IsAlphaNumeric(value);
}
- private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
+ private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement)
{
if (query.IsResumable ?? false)
{
@@ -3351,27 +3351,27 @@ namespace Emby.Server.Implementations.Data
if (query.IsHD.HasValue)
{
- var threshold = 1200;
+ const int Threshold = 1200;
if (query.IsHD.Value)
{
- minWidth = threshold;
+ minWidth = Threshold;
}
else
{
- maxWidth = threshold - 1;
+ maxWidth = Threshold - 1;
}
}
if (query.Is4K.HasValue)
{
- var threshold = 3800;
+ const int Threshold = 3800;
if (query.Is4K.Value)
{
- minWidth = threshold;
+ minWidth = Threshold;
}
else
{
- maxWidth = threshold - 1;
+ maxWidth = Threshold - 1;
}
}
@@ -3380,93 +3380,61 @@ namespace Emby.Server.Implementations.Data
if (minWidth.HasValue)
{
whereClauses.Add("Width>=@MinWidth");
- if (statement != null)
- {
- statement.TryBind("@MinWidth", minWidth);
- }
+ statement?.TryBind("@MinWidth", minWidth);
}
+
if (query.MinHeight.HasValue)
{
whereClauses.Add("Height>=@MinHeight");
- if (statement != null)
- {
- statement.TryBind("@MinHeight", query.MinHeight);
- }
+ statement?.TryBind("@MinHeight", query.MinHeight);
}
+
if (maxWidth.HasValue)
{
whereClauses.Add("Width<=@MaxWidth");
- if (statement != null)
- {
- statement.TryBind("@MaxWidth", maxWidth);
- }
+ statement?.TryBind("@MaxWidth", maxWidth);
}
+
if (query.MaxHeight.HasValue)
{
whereClauses.Add("Height<=@MaxHeight");
- if (statement != null)
- {
- statement.TryBind("@MaxHeight", query.MaxHeight);
- }
+ statement?.TryBind("@MaxHeight", query.MaxHeight);
}
if (query.IsLocked.HasValue)
{
whereClauses.Add("IsLocked=@IsLocked");
- if (statement != null)
- {
- statement.TryBind("@IsLocked", query.IsLocked);
- }
+ statement?.TryBind("@IsLocked", query.IsLocked);
}
var tags = query.Tags.ToList();
var excludeTags = query.ExcludeTags.ToList();
- if (query.IsMovie ?? false)
+ if (query.IsMovie == true)
{
- var alternateTypes = new List<string>();
- if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
- {
- alternateTypes.Add(typeof(Movie).FullName);
- }
- if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
+ if (query.IncludeItemTypes.Length == 0
+ || query.IncludeItemTypes.Contains(nameof(Movie))
+ || query.IncludeItemTypes.Contains(nameof(Trailer)))
{
- alternateTypes.Add(typeof(Trailer).FullName);
- }
-
- var programAttribtues = new List<string>();
- if (alternateTypes.Count == 0)
- {
- programAttribtues.Add("IsMovie=@IsMovie");
+ whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
}
else
{
- programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)");
- }
-
- if (statement != null)
- {
- statement.TryBind("@IsMovie", true);
+ whereClauses.Add("IsMovie=@IsMovie");
}
- whereClauses.Add("(" + string.Join(" OR ", programAttribtues) + ")");
+ statement?.TryBind("@IsMovie", true);
}
else if (query.IsMovie.HasValue)
{
whereClauses.Add("IsMovie=@IsMovie");
- if (statement != null)
- {
- statement.TryBind("@IsMovie", query.IsMovie);
- }
+ statement?.TryBind("@IsMovie", query.IsMovie);
}
if (query.IsSeries.HasValue)
{
whereClauses.Add("IsSeries=@IsSeries");
- if (statement != null)
- {
- statement.TryBind("@IsSeries", query.IsSeries);
- }
+ statement?.TryBind("@IsSeries", query.IsSeries);
}
if (query.IsSports.HasValue)
@@ -3518,10 +3486,7 @@ namespace Emby.Server.Implementations.Data
if (query.IsFolder.HasValue)
{
whereClauses.Add("IsFolder=@IsFolder");
- if (statement != null)
- {
- statement.TryBind("@IsFolder", query.IsFolder);
- }
+ statement?.TryBind("@IsFolder", query.IsFolder);
}
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
@@ -3532,10 +3497,7 @@ namespace Emby.Server.Implementations.Data
if (excludeTypes.Length == 1)
{
whereClauses.Add("type<>@type");
- if (statement != null)
- {
- statement.TryBind("@type", excludeTypes[0]);
- }
+ statement?.TryBind("@type", excludeTypes[0]);
}
else if (excludeTypes.Length > 1)
{
@@ -3546,10 +3508,7 @@ namespace Emby.Server.Implementations.Data
else if (includeTypes.Length == 1)
{
whereClauses.Add("type=@type");
- if (statement != null)
- {
- statement.TryBind("@type", includeTypes[0]);
- }
+ statement?.TryBind("@type", includeTypes[0]);
}
else if (includeTypes.Length > 1)
{
@@ -3560,10 +3519,7 @@ namespace Emby.Server.Implementations.Data
if (query.ChannelIds.Length == 1)
{
whereClauses.Add("ChannelId=@ChannelId");
- if (statement != null)
- {
- statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
- }
+ statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
}
else if (query.ChannelIds.Length > 1)
{
@@ -3574,98 +3530,65 @@ namespace Emby.Server.Implementations.Data
if (!query.ParentId.Equals(Guid.Empty))
{
whereClauses.Add("ParentId=@ParentId");
- if (statement != null)
- {
- statement.TryBind("@ParentId", query.ParentId);
- }
+ statement?.TryBind("@ParentId", query.ParentId);
}
if (!string.IsNullOrWhiteSpace(query.Path))
{
whereClauses.Add("Path=@Path");
- if (statement != null)
- {
- statement.TryBind("@Path", GetPathToSave(query.Path));
- }
+ statement?.TryBind("@Path", GetPathToSave(query.Path));
}
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
{
whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
- if (statement != null)
- {
- statement.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
- }
+ statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
}
if (query.MinCommunityRating.HasValue)
{
whereClauses.Add("CommunityRating>=@MinCommunityRating");
- if (statement != null)
- {
- statement.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
- }
+ statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
}
if (query.MinIndexNumber.HasValue)
{
whereClauses.Add("IndexNumber>=@MinIndexNumber");
- if (statement != null)
- {
- statement.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
- }
+ statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
}
if (query.MinDateCreated.HasValue)
{
whereClauses.Add("DateCreated>=@MinDateCreated");
- if (statement != null)
- {
- statement.TryBind("@MinDateCreated", query.MinDateCreated.Value);
- }
+ statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value);
}
if (query.MinDateLastSaved.HasValue)
{
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
- if (statement != null)
- {
- statement.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
- }
+ statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
}
if (query.MinDateLastSavedForUser.HasValue)
{
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
- if (statement != null)
- {
- statement.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
- }
+ statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
}
if (query.IndexNumber.HasValue)
{
whereClauses.Add("IndexNumber=@IndexNumber");
- if (statement != null)
- {
- statement.TryBind("@IndexNumber", query.IndexNumber.Value);
- }
+ statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
}
if (query.ParentIndexNumber.HasValue)
{
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
- if (statement != null)
- {
- statement.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
- }
+ statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
}
if (query.ParentIndexNumberNotEquals.HasValue)
{
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
- if (statement != null)
- {
- statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
- }
+ statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
}
var minEndDate = query.MinEndDate;
@@ -3686,73 +3609,59 @@ namespace Emby.Server.Implementations.Data
if (minEndDate.HasValue)
{
whereClauses.Add("EndDate>=@MinEndDate");
- if (statement != null)
- {
- statement.TryBind("@MinEndDate", minEndDate.Value);
- }
+ statement?.TryBind("@MinEndDate", minEndDate.Value);
}
if (maxEndDate.HasValue)
{
whereClauses.Add("EndDate<=@MaxEndDate");
- if (statement != null)
- {
- statement.TryBind("@MaxEndDate", maxEndDate.Value);
- }
+ statement?.TryBind("@MaxEndDate", maxEndDate.Value);
}
if (query.MinStartDate.HasValue)
{
whereClauses.Add("StartDate>=@MinStartDate");
- if (statement != null)
- {
- statement.TryBind("@MinStartDate", query.MinStartDate.Value);
- }
+ statement?.TryBind("@MinStartDate", query.MinStartDate.Value);
}
if (query.MaxStartDate.HasValue)
{
whereClauses.Add("StartDate<=@MaxStartDate");
- if (statement != null)
- {
- statement.TryBind("@MaxStartDate", query.MaxStartDate.Value);
- }
+ statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value);
}
if (query.MinPremiereDate.HasValue)
{
whereClauses.Add("PremiereDate>=@MinPremiereDate");
- if (statement != null)
- {
- statement.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
- }
+ statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
}
+
if (query.MaxPremiereDate.HasValue)
{
whereClauses.Add("PremiereDate<=@MaxPremiereDate");
- if (statement != null)
- {
- statement.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
- }
+ statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
}
- if (query.TrailerTypes.Length > 0)
+ var trailerTypes = query.TrailerTypes;
+ int trailerTypesLen = trailerTypes.Length;
+ if (trailerTypesLen > 0)
{
- var clauses = new List<string>();
- var index = 0;
- foreach (var type in query.TrailerTypes)
+ const string Or = " OR ";
+ StringBuilder clause = new StringBuilder("(", trailerTypesLen * 32);
+ for (int i = 0; i < trailerTypesLen; i++)
{
- var paramName = "@TrailerTypes" + index;
-
- clauses.Add("TrailerTypes like " + paramName);
- if (statement != null)
- {
- statement.TryBind(paramName, "%" + type + "%");
- }
- index++;
+ var paramName = "@TrailerTypes" + i;
+ clause.Append("TrailerTypes like ")
+ .Append(paramName)
+ .Append(Or);
+ statement?.TryBind(paramName, "%" + trailerTypes[i] + "%");
}
- var clause = "(" + string.Join(" OR ", clauses) + ")";
- whereClauses.Add(clause);
+
+ // Remove last " OR "
+ clause.Length -= Or.Length;
+ clause.Append(')');
+
+ whereClauses.Add(clause.ToString());
}
if (query.IsAiring.HasValue)
@@ -3760,24 +3669,15 @@ namespace Emby.Server.Implementations.Data
if (query.IsAiring.Value)
{
whereClauses.Add("StartDate<=@MaxStartDate");
- if (statement != null)
- {
- statement.TryBind("@MaxStartDate", DateTime.UtcNow);
- }
+ statement?.TryBind("@MaxStartDate", DateTime.UtcNow);
whereClauses.Add("EndDate>=@MinEndDate");
- if (statement != null)
- {
- statement.TryBind("@MinEndDate", DateTime.UtcNow);
- }
+ statement?.TryBind("@MinEndDate", DateTime.UtcNow);
}
else
{
whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
- if (statement != null)
- {
- statement.TryBind("@IsAiringDate", DateTime.UtcNow);
- }
+ statement?.TryBind("@IsAiringDate", DateTime.UtcNow);
}
}
@@ -3792,13 +3692,10 @@ namespace Emby.Server.Implementations.Data
var paramName = "@PersonId" + index;
clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))");
-
- if (statement != null)
- {
- statement.TryBind(paramName, personId.ToByteArray());
- }
+ statement?.TryBind(paramName, personId.ToByteArray());
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -3806,47 +3703,31 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrWhiteSpace(query.Person))
{
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
- if (statement != null)
- {
- statement.TryBind("@PersonName", query.Person);
- }
+ statement?.TryBind("@PersonName", query.Person);
}
if (!string.IsNullOrWhiteSpace(query.MinSortName))
{
whereClauses.Add("SortName>=@MinSortName");
- if (statement != null)
- {
- statement.TryBind("@MinSortName", query.MinSortName);
- }
+ statement?.TryBind("@MinSortName", query.MinSortName);
}
if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
{
whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
- if (statement != null)
- {
- statement.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
- }
+ statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
}
if (!string.IsNullOrWhiteSpace(query.ExternalId))
{
whereClauses.Add("ExternalId=@ExternalId");
- if (statement != null)
- {
- statement.TryBind("@ExternalId", query.ExternalId);
- }
+ statement?.TryBind("@ExternalId", query.ExternalId);
}
if (!string.IsNullOrWhiteSpace(query.Name))
{
whereClauses.Add("CleanName=@Name");
-
- if (statement != null)
- {
- statement.TryBind("@Name", GetCleanValue(query.Name));
- }
+ statement?.TryBind("@Name", GetCleanValue(query.Name));
}
// These are the same, for now
@@ -3865,28 +3746,21 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
{
whereClauses.Add("SortName like @NameStartsWith");
- if (statement != null)
- {
- statement.TryBind("@NameStartsWith", query.NameStartsWith + "%");
- }
+ statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%");
}
+
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
{
whereClauses.Add("SortName >= @NameStartsWithOrGreater");
// lowercase this because SortName is stored as lowercase
- if (statement != null)
- {
- statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
- }
+ statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
}
+
if (!string.IsNullOrWhiteSpace(query.NameLessThan))
{
whereClauses.Add("SortName < @NameLessThan");
// lowercase this because SortName is stored as lowercase
- if (statement != null)
- {
- statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
- }
+ statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
}
if (query.ImageTypes.Length > 0)
@@ -3902,18 +3776,12 @@ namespace Emby.Server.Implementations.Data
if (query.IsLiked.Value)
{
whereClauses.Add("rating>=@UserRating");
- if (statement != null)
- {
- statement.TryBind("@UserRating", UserItemData.MinLikeValue);
- }
+ statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
}
else
{
whereClauses.Add("(rating is null or rating<@UserRating)");
- if (statement != null)
- {
- statement.TryBind("@UserRating", UserItemData.MinLikeValue);
- }
+ statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
}
}
@@ -3927,10 +3795,8 @@ namespace Emby.Server.Implementations.Data
{
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
}
- if (statement != null)
- {
- statement.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
- }
+
+ statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
}
if (query.IsFavorite.HasValue)
@@ -3943,10 +3809,8 @@ namespace Emby.Server.Implementations.Data
{
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
}
- if (statement != null)
- {
- statement.TryBind("@IsFavorite", query.IsFavorite.Value);
- }
+
+ statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
}
if (EnableJoinUserData(query))
@@ -3975,10 +3839,8 @@ namespace Emby.Server.Implementations.Data
{
whereClauses.Add("(played is null or played=@IsPlayed)");
}
- if (statement != null)
- {
- statement.TryBind("@IsPlayed", query.IsPlayed.Value);
- }
+
+ statement?.TryBind("@IsPlayed", query.IsPlayed.Value);
}
}
}
@@ -4010,6 +3872,7 @@ namespace Emby.Server.Implementations.Data
}
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4029,6 +3892,7 @@ namespace Emby.Server.Implementations.Data
}
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4762,18 +4626,22 @@ namespace Emby.Server.Implementations.Data
{
list.Add(typeof(Person).Name);
}
+
if (IsTypeInQuery(typeof(Genre).Name, query))
{
list.Add(typeof(Genre).Name);
}
+
if (IsTypeInQuery(typeof(MusicGenre).Name, query))
{
list.Add(typeof(MusicGenre).Name);
}
+
if (IsTypeInQuery(typeof(MusicArtist).Name, query))
{
list.Add(typeof(MusicArtist).Name);
}
+
if (IsTypeInQuery(typeof(Studio).Name, query))
{
list.Add(typeof(Studio).Name);
@@ -4847,7 +4715,7 @@ namespace Emby.Server.Implementations.Data
return false;
}
- private static readonly Type[] KnownTypes =
+ private static readonly Type[] _knownTypes =
{
typeof(LiveTvProgram),
typeof(LiveTvChannel),
@@ -4916,7 +4784,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
- foreach (var t in KnownTypes)
+ foreach (var t in _knownTypes)
{
dict[t.Name] = new[] { t.FullName };
}
@@ -4928,7 +4796,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
// Not crazy about having this all the way down here, but at least it's in one place
- readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
+ private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
private string[] MapIncludeItemTypes(string value)
{
@@ -4945,7 +4813,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return Array.Empty<string>();
}
- public void DeleteItem(Guid id, CancellationToken cancellationToken)
+ public void DeleteItem(Guid id)
{
if (id == Guid.Empty)
{
@@ -4981,7 +4849,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
}
- private void ExecuteWithSingleParam(IDatabaseConnection db, string query, byte[] value)
+ private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan<byte> value)
{
using (var statement = PrepareStatement(db, query))
{
@@ -5011,6 +4879,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
commandText += " order by ListOrder";
+ if (query.Limit > 0)
+ {
+ commandText += " LIMIT " + query.Limit;
+ }
+
using (var connection = GetConnection(true))
{
var list = new List<string>();
@@ -5049,6 +4922,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
commandText += " order by ListOrder";
+ if (query.Limit > 0)
+ {
+ commandText += " LIMIT " + query.Limit;
+ }
+
using (var connection = GetConnection(true))
{
var list = new List<PersonInfo>();
@@ -5531,6 +5409,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
GetWhereClauses(typeSubQuery, null);
}
+
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
GetWhereClauses(innerQuery, statement);
@@ -5572,7 +5451,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
- .ToLookup(i => i);
+ .ToLookup(x => x);
foreach (var type in allTypes)
{
@@ -5663,30 +5542,26 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
{
+ const int Limit = 100;
var startIndex = 0;
- var limit = 100;
while (startIndex < values.Count)
{
var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
- var endIndex = Math.Min(values.Count, startIndex + limit);
- var isSubsequentRow = false;
+ var endIndex = Math.Min(values.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
- if (isSubsequentRow)
- {
- insertText.Append(',');
- }
-
insertText.AppendFormat(
CultureInfo.InvariantCulture,
- "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
+ "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),",
i);
- isSubsequentRow = true;
}
+ // Remove last comma
+ insertText.Length--;
+
using (var statement = PrepareStatement(db, insertText.ToString()))
{
statement.TryBind("@ItemId", idBlob);
@@ -5714,7 +5589,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.MoveNext();
}
- startIndex += limit;
+ startIndex += Limit;
}
}
@@ -5749,28 +5624,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
private void InsertPeople(byte[] idBlob, List<PersonInfo> people, IDatabaseConnection db)
{
+ const int Limit = 100;
var startIndex = 0;
- var limit = 100;
var listIndex = 0;
while (startIndex < people.Count)
{
var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
- var endIndex = Math.Min(people.Count, startIndex + limit);
- var isSubsequentRow = false;
-
+ var endIndex = Math.Min(people.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
- if (isSubsequentRow)
- {
- insertText.Append(',');
- }
-
- insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture));
- isSubsequentRow = true;
+ insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture));
}
+ // Remove last comma
+ insertText.Length--;
+
using (var statement = PrepareStatement(db, insertText.ToString()))
{
statement.TryBind("@ItemId", idBlob);
@@ -5794,16 +5664,17 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.MoveNext();
}
- startIndex += limit;
+ startIndex += Limit;
}
}
private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader)
{
- var item = new PersonInfo();
-
- item.ItemId = reader.GetGuid(0);
- item.Name = reader.GetString(1);
+ var item = new PersonInfo
+ {
+ ItemId = reader.GetGuid(0),
+ Name = reader.GetString(1)
+ };
if (!reader.IsDBNull(2))
{
@@ -5910,20 +5781,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db)
{
+ const int Limit = 10;
var startIndex = 0;
- var limit = 10;
while (startIndex < streams.Count)
{
- var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns)));
+ var insertText = new StringBuilder("insert into mediastreams (");
+ foreach (var column in _mediaStreamSaveColumns)
+ {
+ insertText.Append(column).Append(',');
+ }
- var endIndex = Math.Min(streams.Count, startIndex + limit);
+ // Remove last comma
+ insertText.Length--;
+ insertText.Append(") values ");
+
+ var endIndex = Math.Min(streams.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
if (i != startIndex)
{
- insertText.Append(",");
+ insertText.Append(',');
}
var index = i.ToString(CultureInfo.InvariantCulture);
@@ -5931,11 +5810,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
foreach (var column in _mediaStreamSaveColumns.Skip(1))
{
- insertText.Append("@" + column + index + ",");
+ insertText.Append('@').Append(column).Append(index).Append(',');
}
+
insertText.Length -= 1; // Remove the last comma
- insertText.Append(")");
+ insertText.Append(')');
}
using (var statement = PrepareStatement(db, insertText.ToString()))
@@ -5997,7 +5877,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.MoveNext();
}
- startIndex += limit;
+ startIndex += Limit;
}
}
@@ -6014,7 +5894,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
Index = reader[1].ToInt()
};
- item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader[2].ToString(), true);
+ item.Type = Enum.Parse<MediaStreamType>(reader[2].ToString(), true);
if (reader[3].SQLiteType != SQLiteType.Null)
{
diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
deleted file mode 100644
index bfa49ac5f..000000000
--- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
+++ /dev/null
@@ -1,152 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Diagnostics;
-
-namespace Emby.Server.Implementations.Diagnostics
-{
- public class CommonProcess : IProcess
- {
- private readonly Process _process;
-
- private bool _disposed = false;
- private bool _hasExited;
-
- public CommonProcess(ProcessOptions options)
- {
- StartInfo = options;
-
- var startInfo = new ProcessStartInfo
- {
- Arguments = options.Arguments,
- FileName = options.FileName,
- WorkingDirectory = options.WorkingDirectory,
- UseShellExecute = options.UseShellExecute,
- CreateNoWindow = options.CreateNoWindow,
- RedirectStandardError = options.RedirectStandardError,
- RedirectStandardInput = options.RedirectStandardInput,
- RedirectStandardOutput = options.RedirectStandardOutput,
- ErrorDialog = options.ErrorDialog
- };
-
-
- if (options.IsHidden)
- {
- startInfo.WindowStyle = ProcessWindowStyle.Hidden;
- }
-
- _process = new Process
- {
- StartInfo = startInfo
- };
-
- if (options.EnableRaisingEvents)
- {
- _process.EnableRaisingEvents = true;
- _process.Exited += OnProcessExited;
- }
- }
-
- public event EventHandler Exited;
-
- public ProcessOptions StartInfo { get; }
-
- public StreamWriter StandardInput => _process.StandardInput;
-
- public StreamReader StandardError => _process.StandardError;
-
- public StreamReader StandardOutput => _process.StandardOutput;
-
- public int ExitCode => _process.ExitCode;
-
- private bool HasExited
- {
- get
- {
- if (_hasExited)
- {
- return true;
- }
-
- try
- {
- _hasExited = _process.HasExited;
- }
- catch (InvalidOperationException)
- {
- _hasExited = true;
- }
-
- return _hasExited;
- }
- }
-
- public void Start()
- {
- _process.Start();
- }
-
- public void Kill()
- {
- _process.Kill();
- }
-
- public bool WaitForExit(int timeMs)
- {
- return _process.WaitForExit(timeMs);
- }
-
- public Task<bool> WaitForExitAsync(int timeMs)
- {
- // Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
-
- if (HasExited)
- {
- return Task.FromResult(true);
- }
-
- timeMs = Math.Max(0, timeMs);
-
- var tcs = new TaskCompletionSource<bool>();
-
- var cancellationToken = new CancellationTokenSource(timeMs).Token;
-
- _process.Exited += (sender, args) => tcs.TrySetResult(true);
-
- cancellationToken.Register(() => tcs.TrySetResult(HasExited));
-
- return tcs.Task;
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- _process?.Dispose();
- }
-
- _disposed = true;
- }
-
- private void OnProcessExited(object sender, EventArgs e)
- {
- _hasExited = true;
- Exited?.Invoke(this, e);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
deleted file mode 100644
index 02ad3c1a8..000000000
--- a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Diagnostics;
-
-namespace Emby.Server.Implementations.Diagnostics
-{
- public class ProcessFactory : IProcessFactory
- {
- public IProcess Create(ProcessOptions options)
- {
- return new CommonProcess(options);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 65711e89d..34a342cf7 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1056,30 +1056,19 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.SpecialFeatureCount))
{
- if (allExtras == null)
- {
- allExtras = item.GetExtras().ToArray();
- }
-
+ allExtras = item.GetExtras().ToArray();
dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value));
}
if (options.ContainsField(ItemFields.LocalTrailerCount))
{
- int trailerCount = 0;
- if (allExtras == null)
- {
- allExtras = item.GetExtras().ToArray();
- }
-
- trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer);
+ allExtras ??= item.GetExtras().ToArray();
+ dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
if (item is IHasTrailers hasTrailers)
{
- trailerCount += hasTrailers.GetTrailerCount();
+ dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
}
-
- dto.LocalTrailerCount = trailerCount;
}
// Add EpisodeInfo
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index d302d8984..3d065f70a 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -32,11 +32,11 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
- <PackageReference Include="Mono.Nat" Version="2.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
+ <PackageReference Include="Mono.Nat" Version="2.0.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
- <PackageReference Include="sharpcompress" Version="0.24.0" />
+ <PackageReference Include="sharpcompress" Version="0.25.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
- <PackageReference Include="System.Interactive.Async" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
index 45fa03cdd..882bfe2f6 100644
--- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
@@ -96,13 +96,13 @@ namespace Emby.Server.Implementations.HttpClientManager
switch (options.DecompressionMethod)
{
- case CompressionMethod.Deflate | CompressionMethod.Gzip:
+ case CompressionMethods.Deflate | CompressionMethods.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" });
break;
- case CompressionMethod.Deflate:
+ case CompressionMethods.Deflate:
request.Headers.Add(HeaderNames.AcceptEncoding, "deflate");
break;
- case CompressionMethod.Gzip:
+ case CompressionMethods.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, "gzip");
break;
default:
@@ -239,15 +239,10 @@ namespace Emby.Server.Implementations.HttpClientManager
var httpWebRequest = GetRequestMessage(options, httpMethod);
- if (options.RequestContentBytes != null
- || !string.IsNullOrEmpty(options.RequestContent)
+ if (!string.IsNullOrEmpty(options.RequestContent)
|| httpMethod == HttpMethod.Post)
{
- if (options.RequestContentBytes != null)
- {
- httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes);
- }
- else if (options.RequestContent != null)
+ if (options.RequestContent != null)
{
httpWebRequest.Content = new StringContent(
options.RequestContent,
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 7a812f320..5ae65a4e3 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -23,6 +23,7 @@ using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ServiceStack.Text.Jsv;
@@ -48,6 +49,8 @@ namespace Emby.Server.Implementations.HttpServer
private readonly string _baseUrlPrefix;
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
+ private readonly IHostEnvironment _hostEnvironment;
+
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
private bool _disposed = false;
@@ -61,7 +64,8 @@ namespace Emby.Server.Implementations.HttpServer
IXmlSerializer xmlSerializer,
IHttpListener socketListener,
ILocalizationManager localizationManager,
- ServiceController serviceController)
+ ServiceController serviceController,
+ IHostEnvironment hostEnvironment)
{
_appHost = applicationHost;
_logger = logger;
@@ -75,6 +79,7 @@ namespace Emby.Server.Implementations.HttpServer
ServiceController = serviceController;
_socketListener.WebSocketConnected = OnWebSocketConnected;
+ _hostEnvironment = hostEnvironment;
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
@@ -234,7 +239,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace)
+ private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, string urlToLog)
{
try
{
@@ -242,11 +247,11 @@ namespace Emby.Server.Implementations.HttpServer
if (logExceptionStackTrace)
{
- _logger.LogError(ex, "Error processing request");
+ _logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
}
else
{
- _logger.LogError("Error processing request: {Message}", ex.Message);
+ _logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
}
var httpRes = httpReq.Response;
@@ -266,7 +271,7 @@ namespace Emby.Server.Implementations.HttpServer
}
catch (Exception errorEx)
{
- _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
+ _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response). URL: {Url}", urlToLog);
}
}
@@ -451,7 +456,7 @@ namespace Emby.Server.Implementations.HttpServer
var stopWatch = new Stopwatch();
stopWatch.Start();
var httpRes = httpReq.Response;
- string urlToLog = null;
+ string urlToLog = GetUrlToLog(urlString);
string remoteIp = httpReq.RemoteIp;
try
@@ -497,8 +502,6 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- urlToLog = GetUrlToLog(urlString);
-
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
@@ -530,22 +533,25 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
- await ErrorHandler(new FileNotFoundException(), httpReq, false).ConfigureAwait(false);
+ throw new FileNotFoundException();
}
}
- catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
- {
- await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
- }
- catch (SecurityException ex)
- {
- await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
- }
catch (Exception ex)
{
- var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
+ // Do not handle exceptions manually when in development mode
+ // The framework-defined development exception page will be returned instead
+ if (_hostEnvironment.IsDevelopment())
+ {
+ throw;
+ }
- await ErrorHandler(ex, httpReq, logException).ConfigureAwait(false);
+ bool ignoreStackTrace =
+ ex is SocketException
+ || ex is IOException
+ || ex is OperationCanceledException
+ || ex is SecurityException
+ || ex is FileNotFoundException;
+ await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
}
finally
{
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index 6e915de3d..16b68170b 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -3,33 +3,38 @@ namespace Emby.Server.Implementations
public interface IStartupOptions
{
/// <summary>
- /// --ffmpeg
+ /// Gets the value of the --ffmpeg command line option.
/// </summary>
string FFmpegPath { get; }
/// <summary>
- /// --service
+ /// Gets the value of the --service command line option.
/// </summary>
bool IsService { get; }
/// <summary>
- /// --noautorunwebapp
+ /// Gets the value of the --noautorunwebapp command line option.
/// </summary>
bool NoAutoRunWebApp { get; }
/// <summary>
- /// --package-name
+ /// Gets the value of the --package-name command line option.
/// </summary>
string PackageName { get; }
/// <summary>
- /// --restartpath
+ /// Gets the value of the --restartpath command line option.
/// </summary>
string RestartPath { get; }
/// <summary>
- /// --restartargs
+ /// Gets the value of the --restartargs command line option.
/// </summary>
string RestartArgs { get; }
+
+ /// <summary>
+ /// Gets the value of the --plugin-manifest-url command line option.
+ /// </summary>
+ string PluginManifestUrl { get; }
}
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 8ec4d08be..4e2cf334c 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -437,10 +437,10 @@ namespace Emby.Server.Implementations.Library
item.SetParent(null);
- ItemRepository.DeleteItem(item.Id, CancellationToken.None);
+ ItemRepository.DeleteItem(item.Id);
foreach (var child in children)
{
- ItemRepository.DeleteItem(child.Id, CancellationToken.None);
+ ItemRepository.DeleteItem(child.Id);
}
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
@@ -2609,14 +2609,12 @@ namespace Emby.Server.Implementations.Library
}).OrderBy(i => i.Path);
}
- private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" };
-
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ .Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 7b17cc913..15076a194 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -264,6 +264,7 @@ namespace Emby.Server.Implementations.Library
{
if (string.IsNullOrWhiteSpace(username))
{
+ _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint);
throw new ArgumentNullException(nameof(username));
}
@@ -319,11 +320,13 @@ namespace Emby.Server.Implementations.Library
if (user == null)
{
+ _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("Invalid username or password entered.");
}
if (user.Policy.IsDisabled)
{
+ _logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException(
string.Format(
CultureInfo.InvariantCulture,
@@ -333,11 +336,13 @@ namespace Emby.Server.Implementations.Library
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
+ _logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
+ _logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("User is not allowed access at this time.");
}
@@ -351,14 +356,14 @@ namespace Emby.Server.Implementations.Library
}
ResetInvalidLoginAttemptCount(user);
+ _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name);
}
else
{
IncrementInvalidLoginAttemptCount(user);
+ _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint);
}
- _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
-
return success ? user : null;
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index e2bff97c8..2e13a3bb3 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
UserAgent = "Emby/3.0",
// Shouldn't matter but may cause issues
- DecompressionMethod = CompressionMethod.None
+ DecompressionMethod = CompressionMethods.None
};
using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 139aa19a4..900f12062 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
@@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly IProcessFactory _processFactory;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IStreamHelper _streamHelper;
@@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
ILibraryManager libraryManager,
ILibraryMonitor libraryMonitor,
IProviderManager providerManager,
- IMediaEncoder mediaEncoder,
- IProcessFactory processFactory)
+ IMediaEncoder mediaEncoder)
{
Current = this;
@@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_libraryMonitor = libraryMonitor;
_providerManager = providerManager;
_mediaEncoder = mediaEncoder;
- _processFactory = processFactory;
_liveTvManager = (LiveTvManager)liveTvManager;
_jsonSerializer = jsonSerializer;
_mediaSourceManager = mediaSourceManager;
@@ -1662,7 +1659,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
{
- return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
+ return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
}
return new DirectRecorder(_logger, _httpClient, _streamHelper);
@@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
- var process = _processFactory.Create(new ProcessOptions
+ var process = new Process
{
- Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
- CreateNoWindow = true,
- EnableRaisingEvents = true,
- ErrorDialog = false,
- FileName = options.RecordingPostProcessor,
- IsHidden = true,
- UseShellExecute = false
- });
+ StartInfo = new ProcessStartInfo
+ {
+ Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
+ CreateNoWindow = true,
+ ErrorDialog = false,
+ FileName = options.RecordingPostProcessor,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ UseShellExecute = false
+ },
+ EnableRaisingEvents = true
+ };
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void Process_Exited(object sender, EventArgs e)
{
- using (var process = (IProcess)sender)
+ using (var process = (Process)sender)
{
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
-
- process.Dispose();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index d24fc6792..bc86cc59a 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
@@ -13,7 +14,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
@@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private bool _hasExited;
private Stream _logFileStream;
private string _targetPath;
- private IProcess _process;
- private readonly IProcessFactory _processFactory;
+ private Process _process;
private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _config;
@@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IMediaEncoder mediaEncoder,
IServerApplicationPaths appPaths,
IJsonSerializer json,
- IProcessFactory processFactory,
IServerConfigurationManager config)
{
_logger = logger;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
- _processFactory = processFactory;
_config = config;
}
@@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_targetPath = targetFile;
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
- var process = _processFactory.Create(new ProcessOptions
+ var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
@@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
FileName = _mediaEncoder.EncoderPath,
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
- IsHidden = true,
- ErrorDialog = false,
- EnableRaisingEvents = true
- });
-
- _process = process;
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ };
- var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
+ var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
_logger.LogInformation(commandLineLogMessage);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
@@ -109,16 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
- process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
+ _process = new Process
+ {
+ StartInfo = processStartInfo,
+ EnableRaisingEvents = true
+ };
+ _process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
- process.Start();
+ _process.Start();
cancellationToken.Register(Stop);
onStarted();
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
+ StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
@@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Processes the exited.
/// </summary>
- private void OnFfMpegProcessExited(IProcess process, string inputFile)
+ private void OnFfMpegProcessExited(Process process, string inputFile)
{
- _hasExited = true;
+ using (process)
+ {
+ _hasExited = true;
- _logFileStream?.Dispose();
- _logFileStream = null;
+ _logFileStream?.Dispose();
+ _logFileStream = null;
- var exitCode = process.ExitCode;
+ var exitCode = process.ExitCode;
- _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
+ _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
- if (exitCode == 0)
- {
- _taskCompletionSource.TrySetResult(true);
- }
- else
- {
- _taskCompletionSource.TrySetException(
- new Exception(
- string.Format(
- CultureInfo.InvariantCulture,
- "Recording for {0} failed. Exit code {1}",
- _targetPath,
- exitCode)));
+ if (exitCode == 0)
+ {
+ _taskCompletionSource.TrySetResult(true);
+ }
+ else
+ {
+ _taskCompletionSource.TrySetException(
+ new Exception(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Recording for {0} failed. Exit code {1}",
+ _targetPath,
+ exitCode)));
+ }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 00f469d83..89b81fd96 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -635,7 +635,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
- options.DecompressionMethod = CompressionMethod.Deflate;
+ options.DecompressionMethod = CompressionMethods.Deflate;
try
{
@@ -665,7 +665,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
- options.DecompressionMethod = CompressionMethod.Deflate;
+ options.DecompressionMethod = CompressionMethods.Deflate;
try
{
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index 609b397da..07f8539c5 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
CancellationToken = cancellationToken,
Url = path,
- DecompressionMethod = CompressionMethod.Gzip,
+ DecompressionMethod = CompressionMethods.Gzip,
},
HttpMethod.Get).ConfigureAwait(false))
using (var stream = res.Content)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index 9ced65cca..d63588bbd 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Url = url,
CancellationToken = CancellationToken.None,
BufferContent = false,
- DecompressionMethod = CompressionMethod.None
+ DecompressionMethod = CompressionMethods.None
};
foreach (var header in mediaSource.RequiredHttpHeaders)
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index 7fffe7b83..f313039a6 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -90,7 +90,29 @@
"UserPolicyUpdatedWithName": "تم تحديث سياسة المستخدم {0}",
"UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}",
"UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}",
- "ValueHasBeenAddedToLibrary": "{0} تم اضافتها الى مكتبة الوسائط",
- "ValueSpecialEpisodeName": "مميز - {0}",
- "VersionNumber": "الإصدار رقم {0}"
+ "ValueHasBeenAddedToLibrary": "تمت اضافت {0} إلى مكتبة الوسائط",
+ "ValueSpecialEpisodeName": "خاص - {0}",
+ "VersionNumber": "النسخة {0}",
+ "TaskCleanCacheDescription": "يحذف ملفات ذاكرة التخزين المؤقت التي لم يعد النظام بحاجة إليها.",
+ "TaskCleanCache": "احذف مجلد ذاكرة التخزين المؤقت",
+ "TasksChannelsCategory": "قنوات الإنترنت",
+ "TasksLibraryCategory": "مكتبة",
+ "TasksMaintenanceCategory": "صيانة",
+ "TaskRefreshLibraryDescription": "يقوم بفصح مكتبة الوسائط الخاصة بك بحثًا عن ملفات جديدة وتحديث البيانات الوصفية.",
+ "TaskRefreshLibrary": "افحص مكتبة الوسائط",
+ "TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.",
+ "TaskRefreshChapterImages": "استخراج صور الفصل",
+ "TasksApplicationCategory": "تطبيق",
+ "TaskDownloadMissingSubtitlesDescription": "ابحث في الإنترنت على الترجمات المفقودة إستنادا على الميتاداتا.",
+ "TaskDownloadMissingSubtitles": "تحميل الترجمات المفقودة",
+ "TaskRefreshChannelsDescription": "تحديث معلومات قنوات الإنترنت.",
+ "TaskRefreshChannels": "إعادة تحديث القنوات",
+ "TaskCleanTranscodeDescription": "حذف ملفات الترميز الأقدم من يوم واحد.",
+ "TaskCleanTranscode": "حذف سجلات الترميز",
+ "TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
+ "TaskUpdatePlugins": "تحديث الإضافات",
+ "TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
+ "TaskRefreshPeople": "إعادة تحميل الأشخاص",
+ "TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.",
+ "TaskCleanLogs": "حذف دليل السجل"
}
diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json
index 94437d237..f5397b62c 100644
--- a/Emby.Server.Implementations/Localization/Core/da.json
+++ b/Emby.Server.Implementations/Localization/Core/da.json
@@ -1,5 +1,5 @@
{
- "Albums": "Album",
+ "Albums": "Albums",
"AppDeviceValues": "App: {0}, Enhed: {1}",
"Application": "Applikation",
"Artists": "Kunstnere",
@@ -35,8 +35,8 @@
"Latest": "Seneste",
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
- "MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguration sektion {0} er blevet opdateret",
+ "MessageServerConfigurationUpdated": "Server konfigurationen er blevet opdateret",
"MixedContent": "Blandet indhold",
"Movies": "Film",
"Music": "Musik",
@@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
"ValueSpecialEpisodeName": "Special - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiguration.",
+ "TaskDownloadMissingSubtitles": "Download manglende undertekster",
+ "TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
+ "TaskUpdatePlugins": "Opdater Plugins",
+ "TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
+ "TaskCleanLogs": "Ryd Log Mappe",
+ "TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdaterer metadata.",
+ "TaskRefreshLibrary": "Scan Medie Bibliotek",
+ "TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
+ "TaskCleanCache": "Ryd Cache Mappe",
+ "TasksChannelsCategory": "Internet Kanaler",
+ "TasksApplicationCategory": "Applikation",
+ "TasksLibraryCategory": "Bibliotek",
+ "TasksMaintenanceCategory": "Vedligeholdelse",
+ "TaskRefreshChapterImages": "Udtræk Kapitel billeder",
+ "TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.",
+ "TaskRefreshChannelsDescription": "Genopfrisker internet kanal information.",
+ "TaskRefreshChannels": "Genopfrisk Kanaler",
+ "TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.",
+ "TaskCleanTranscode": "Rengør Transcode Mappen",
+ "TaskRefreshPeople": "Genopfrisk Personer",
+ "TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek."
}
diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json
index dc4f0b212..544c38cfa 100644
--- a/Emby.Server.Implementations/Localization/Core/en-GB.json
+++ b/Emby.Server.Implementations/Localization/Core/en-GB.json
@@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "Special - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
+ "TaskDownloadMissingSubtitles": "Download missing subtitles",
+ "TaskRefreshChannelsDescription": "Refreshes internet channel information.",
+ "TaskRefreshChannels": "Refresh Channels",
+ "TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
+ "TaskCleanTranscode": "Clean Transcode Directory",
+ "TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
+ "TaskUpdatePlugins": "Update Plugins",
+ "TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
+ "TaskRefreshPeople": "Refresh People",
+ "TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
+ "TaskCleanLogs": "Clean Log Directory",
+ "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
+ "TaskRefreshLibrary": "Scan Media Library",
+ "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
+ "TaskRefreshChapterImages": "Extract Chapter Images",
+ "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
+ "TaskCleanCache": "Clean Cache Directory",
+ "TasksChannelsCategory": "Internet Channels",
+ "TasksApplicationCategory": "Application",
+ "TasksLibraryCategory": "Library",
+ "TasksMaintenanceCategory": "Maintenance"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json
index 154c72bc6..1b6c6b5ae 100644
--- a/Emby.Server.Implementations/Localization/Core/es-AR.json
+++ b/Emby.Server.Implementations/Localization/Core/es-AR.json
@@ -11,20 +11,20 @@
"Collections": "Colecciones",
"DeviceOfflineWithName": "{0} se ha desconectado",
"DeviceOnlineWithName": "{0} está conectado",
- "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión desde {0}",
+ "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión de {0}",
"Favorites": "Favoritos",
"Folders": "Carpetas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Artistas de álbumes",
+ "HeaderAlbumArtists": "Artistas de álbum",
"HeaderCameraUploads": "Subidas de cámara",
- "HeaderContinueWatching": "Continuar viendo",
+ "HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Programas favoritos",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en vivo",
- "HeaderNextUp": "Continuar Viendo",
+ "HeaderNextUp": "A Continuación",
"HeaderRecordingGroups": "Grupos de grabación",
"HomeVideos": "Videos caseros",
"Inherit": "Heredar",
@@ -35,47 +35,47 @@
"Latest": "Últimos",
"MessageApplicationUpdated": "El servidor Jellyfin fue actualizado",
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Fue actualizada la sección {0} de la configuración del servidor",
- "MessageServerConfigurationUpdated": "Fue actualizada la configuración del servidor",
- "MixedContent": "Contenido mixto",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor",
+ "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
+ "MixedContent": "Contenido mezclado",
"Movies": "Películas",
"Music": "Música",
"MusicVideos": "Videos musicales",
- "NameInstallFailed": "{0} error de instalación",
+ "NameInstallFailed": "{0} instalación fallida",
"NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada desconocida",
- "NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.",
+ "NewVersionIsAvailable": "Una nueva versión del Servidor Jellyfin está disponible para descargar.",
"NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible",
"NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada",
"NotificationOptionAudioPlayback": "Se inició la reproducción de audio",
"NotificationOptionAudioPlaybackStopped": "Se detuvo la reproducción de audio",
- "NotificationOptionCameraImageUploaded": "Imagen de la cámara cargada",
+ "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
"NotificationOptionInstallationFailed": "Error de instalación",
"NotificationOptionNewLibraryContent": "Nuevo contenido añadido",
- "NotificationOptionPluginError": "Error en plugin",
- "NotificationOptionPluginInstalled": "Plugin instalado",
- "NotificationOptionPluginUninstalled": "Plugin desinstalado",
- "NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada",
- "NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor",
- "NotificationOptionTaskFailed": "Error de tarea programada",
+ "NotificationOptionPluginError": "Falla de complemento",
+ "NotificationOptionPluginInstalled": "Complemento instalado",
+ "NotificationOptionPluginUninstalled": "Complemento desinstalado",
+ "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada",
+ "NotificationOptionServerRestartRequired": "Se necesita reiniciar el Servidor",
+ "NotificationOptionTaskFailed": "Falla de tarea programada",
"NotificationOptionUserLockedOut": "Usuario bloqueado",
"NotificationOptionVideoPlayback": "Se inició la reproducción de video",
"NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida",
"Photos": "Fotos",
"Playlists": "Listas de reproducción",
- "Plugin": "Plugin",
+ "Plugin": "Complemento",
"PluginInstalledWithName": "{0} fue instalado",
"PluginUninstalledWithName": "{0} fue desinstalado",
"PluginUpdatedWithName": "{0} fue actualizado",
"ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló",
- "ScheduledTaskStartedWithName": "{0} iniciada",
+ "ScheduledTaskStartedWithName": "{0} iniciado",
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Series",
"Songs": "Canciones",
- "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
+ "StartupEmbyServerIsLoading": "El servidor Jellyfin se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}",
+ "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtitulos desde {0} para {1}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Series de TV",
@@ -87,10 +87,32 @@
"UserOfflineFromDevice": "{0} se ha desconectado de {1}",
"UserOnlineFromDevice": "{0} está en línea desde {1}",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
- "UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}",
+ "UserPolicyUpdatedWithName": "Las política de usuario ha sido actualizada para {0}",
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia",
"ValueSpecialEpisodeName": "Especial - {0}",
- "VersionNumber": "Versión {0}"
+ "VersionNumber": "Versión {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten basándose en la configuración de los metadatos.",
+ "TaskDownloadMissingSubtitles": "Descargar subtítulos extraviados",
+ "TaskRefreshChannelsDescription": "Actualizar información de canales de internet.",
+ "TaskRefreshChannels": "Actualizar canales",
+ "TaskCleanTranscodeDescription": "Eliminar archivos transcodificados con mas de un día de antigüedad.",
+ "TaskCleanTranscode": "Limpiar directorio de Transcodificado",
+ "TaskUpdatePluginsDescription": "Descargar e instalar actualizaciones para complementos que estén configurados en actualizar automáticamente.",
+ "TaskUpdatePlugins": "Actualizar complementos",
+ "TaskRefreshPeopleDescription": "Actualizar metadatos de actores y directores en su librería multimedia.",
+ "TaskRefreshPeople": "Actualizar personas",
+ "TaskCleanLogsDescription": "Eliminar archivos de registro que tengan mas de {0} días de antigüedad.",
+ "TaskCleanLogs": "Limpiar directorio de registros",
+ "TaskRefreshLibraryDescription": "Escanear su librería multimedia por nuevos archivos y refrescar metadatos.",
+ "TaskRefreshLibrary": "Escanear librería multimedia",
+ "TaskRefreshChapterImagesDescription": "Crear miniaturas de videos que tengan capítulos.",
+ "TaskRefreshChapterImages": "Extraer imágenes de capitulo",
+ "TaskCleanCacheDescription": "Eliminar archivos de cache que no se necesiten en el sistema.",
+ "TaskCleanCache": "Limpiar directorio Cache",
+ "TasksChannelsCategory": "Canales de Internet",
+ "TasksApplicationCategory": "Solicitud",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Mantenimiento"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 24fde8e62..e0bbe90b3 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducirse {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} se han añadido a su biblioteca de medios",
"ValueSpecialEpisodeName": "Especial - {0}",
- "VersionNumber": "Versión {0}"
+ "VersionNumber": "Versión {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Buscar subtítulos de internet basado en configuración de metadatos.",
+ "TaskDownloadMissingSubtitles": "Descargar subtítulos perdidos",
+ "TaskRefreshChannelsDescription": "Refrescar información de canales de internet.",
+ "TaskRefreshChannels": "Actualizar canales",
+ "TaskCleanTranscodeDescription": "Eliminar archivos transcodificados que tengan mas de un día.",
+ "TaskCleanTranscode": "Limpiar directorio de transcodificado",
+ "TaskUpdatePluginsDescription": "Descargar y actualizar complementos que están configurados para actualizarse automáticamente.",
+ "TaskUpdatePlugins": "Actualizar complementos",
+ "TaskRefreshPeopleDescription": "Actualizar datos de actores y directores en su librería multimedia.",
+ "TaskRefreshPeople": "Refrescar persona",
+ "TaskCleanLogsDescription": "Eliminar archivos de registro con mas de {0} días.",
+ "TaskCleanLogs": "Directorio de logo limpio",
+ "TaskRefreshLibraryDescription": "Escanear su librería multimedia para nuevos archivos y refrescar metadatos.",
+ "TaskRefreshLibrary": "Escanear librería multimerdia",
+ "TaskRefreshChapterImagesDescription": "Crear miniaturas para videos con capítulos.",
+ "TaskRefreshChapterImages": "Extraer imágenes de capítulos",
+ "TaskCleanCacheDescription": "Eliminar archivos cache que ya no se necesiten por el sistema.",
+ "TaskCleanCache": "Limpiar directorio cache",
+ "TasksChannelsCategory": "Canales de Internet",
+ "TasksApplicationCategory": "Aplicación",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Mantenimiento"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json
index 1a7b57c53..0ef16542f 100644
--- a/Emby.Server.Implementations/Localization/Core/es_DO.json
+++ b/Emby.Server.Implementations/Localization/Core/es_DO.json
@@ -5,7 +5,7 @@
"Collections": "Colecciones",
"Artists": "Artistas",
"DeviceOnlineWithName": "{0} está conectado",
- "DeviceOfflineWithName": "{0} ha desconectado",
+ "DeviceOfflineWithName": "{0} se ha desconectado",
"ChapterNameValue": "Capítulo {0}",
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json
index 45e74b8eb..be6f87ee3 100644
--- a/Emby.Server.Implementations/Localization/Core/fa.json
+++ b/Emby.Server.Implementations/Localization/Core/fa.json
@@ -23,7 +23,7 @@
"HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه",
"HeaderFavoriteShows": "سریال‌های مورد علاقه",
"HeaderFavoriteSongs": "آهنگ‌های مورد علاقه",
- "HeaderLiveTV": "پخش زنده تلویزیون",
+ "HeaderLiveTV": "تلویزیون زنده",
"HeaderNextUp": "قسمت بعدی",
"HeaderRecordingGroups": "گروه‌های ضبط",
"HomeVideos": "ویدیوهای خانگی",
@@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
"ValueHasBeenAddedToLibrary": "{0} به کتابخانه‌ی رسانه‌ی شما افزوده شد",
"ValueSpecialEpisodeName": "ویژه - {0}",
- "VersionNumber": "نسخه {0}"
+ "VersionNumber": "نسخه {0}",
+ "TaskCleanTranscodeDescription": "فایل‌های کدگذاری که قدیمی‌تر از یک روز هستند را حذف می‌کند.",
+ "TaskCleanTranscode": "پاکسازی مسیر کد گذاری",
+ "TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونه‌هایی که برای به روز رسانی خودکار پیکربندی شده‌اند.",
+ "TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویس‌های ناموجود در اینترنت بر اساس پیکربندی ابرداده‌ها.",
+ "TaskDownloadMissingSubtitles": "دانلود زیرنویس‌های ناموجود",
+ "TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی می‌کند.",
+ "TaskRefreshChannels": "تازه سازی کانال‌ها",
+ "TaskUpdatePlugins": "به روز رسانی افزونه‌ها",
+ "TaskRefreshPeopleDescription": "ابرداده‌ها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.",
+ "TaskRefreshPeople": "تازه سازی افراد",
+ "TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.",
+ "TaskCleanLogs": "پاکسازی مسیر واقعه نگار",
+ "TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن می‌کند و ابرداده‌ها را تازه سازی می‌کند.",
+ "TaskRefreshLibrary": "اسکن کتابخانه رسانه",
+ "TaskRefreshChapterImagesDescription": "عکس‌های کوچک برای ویدیوهایی که سکانس دارند ایجاد می‌کند.",
+ "TaskRefreshChapterImages": "استخراج عکس‌های سکانس",
+ "TaskCleanCacheDescription": "فایل‌های حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف می‌شوند.",
+ "TaskCleanCache": "پاکسازی مسیر حافظه موقت",
+ "TasksChannelsCategory": "کانال‌های داخلی",
+ "TasksApplicationCategory": "برنامه",
+ "TasksLibraryCategory": "کتابخانه",
+ "TasksMaintenanceCategory": "تعمیر"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index bf5fc05c4..b39adefe7 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -1,5 +1,5 @@
{
- "HeaderLiveTV": "TV-lähetykset",
+ "HeaderLiveTV": "Suorat lähetykset",
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
"NameSeasonUnknown": "Tuntematon Kausi",
"NameSeasonNumber": "Kausi {0}",
@@ -19,12 +19,12 @@
"ItemAddedWithName": "{0} lisättiin kirjastoon",
"Inherit": "Periytyä",
"HomeVideos": "Kotivideot",
- "HeaderRecordingGroups": "Nauhoitusryhmät",
+ "HeaderRecordingGroups": "Nauhoiteryhmät",
"HeaderNextUp": "Seuraavaksi",
"HeaderFavoriteSongs": "Lempikappaleet",
"HeaderFavoriteShows": "Lempisarjat",
"HeaderFavoriteEpisodes": "Lempijaksot",
- "HeaderCameraUploads": "Kameralataukset",
+ "HeaderCameraUploads": "Kamerasta Lähetetyt",
"HeaderFavoriteArtists": "Lempiartistit",
"HeaderFavoriteAlbums": "Lempialbumit",
"HeaderContinueWatching": "Jatka katsomista",
@@ -63,10 +63,10 @@
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
"UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
- "UserLockedOutWithName": "Käyttäjä {0} kirjautui ulos",
- "UserDownloadingItemWithValues": "{0} latautumassa {1}",
- "UserDeletedWithName": "Poistettiin käyttäjä {0}",
- "UserCreatedWithName": "Luotiin käyttäjä {0}",
+ "UserLockedOutWithName": "Käyttäjä {0} lukittu",
+ "UserDownloadingItemWithValues": "{0} lataa {1}",
+ "UserDeletedWithName": "Käyttäjä {0} poistettu",
+ "UserCreatedWithName": "Käyttäjä {0} luotu",
"TvShows": "TV-Ohjelmat",
"Sync": "Synkronoi",
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
@@ -74,22 +74,44 @@
"Songs": "Kappaleet",
"Shows": "Ohjelmat",
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
- "ProviderValue": "Palveluntarjoaja: {0}",
+ "ProviderValue": "Tarjoaja: {0}",
"Plugin": "Liitännäinen",
- "NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty",
- "NotificationOptionVideoPlayback": "Videon toistaminen aloitettu",
- "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
- "NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma",
+ "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
+ "NotificationOptionVideoPlayback": "Videon toisto aloitettu",
+ "NotificationOptionUserLockedOut": "Käyttäjä lukittu",
+ "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
- "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
+ "NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu",
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
"NotificationOptionPluginError": "Ongelma liitännäisessä",
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
- "NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta",
- "NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty",
- "NotificationOptionAudioPlayback": "Audion toisto aloitettu",
- "NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu",
- "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla"
+ "NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
+ "NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
+ "NotificationOptionAudioPlayback": "Toistetaan ääntä",
+ "NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu",
+ "NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla",
+ "TasksMaintenanceCategory": "Ylläpito",
+ "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
+ "TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
+ "TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
+ "TaskRefreshChannels": "Päivitä kanavat",
+ "TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
+ "TaskCleanTranscode": "Puhdista transkoodaushakemisto",
+ "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
+ "TaskUpdatePlugins": "Päivitä liitännäiset",
+ "TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
+ "TaskRefreshPeople": "Päivitä henkilöt",
+ "TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
+ "TaskCleanLogs": "Puhdista lokihakemisto",
+ "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
+ "TaskRefreshLibrary": "Skannaa mediakirjasto",
+ "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
+ "TaskRefreshChapterImages": "Eristä lukujen kuvat",
+ "TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
+ "TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
+ "TasksChannelsCategory": "Internet kanavat",
+ "TasksApplicationCategory": "Sovellus",
+ "TasksLibraryCategory": "Kirjasto"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json
index 86a6d1836..47daf2044 100644
--- a/Emby.Server.Implementations/Localization/Core/fil.json
+++ b/Emby.Server.Implementations/Localization/Core/fil.json
@@ -90,5 +90,13 @@
"Artists": "Artista",
"Application": "Aplikasyon",
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
- "Albums": "Albums"
+ "Albums": "Albums",
+ "TaskRefreshLibrary": "Suriin ang nasa librerya",
+ "TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata",
+ "TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",
+ "TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.",
+ "TasksChannelsCategory": "Palabas sa internet",
+ "TasksLibraryCategory": "Librerya",
+ "TasksMaintenanceCategory": "Pagpapanatili",
+ "HomeVideos": "Sariling pelikula"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json
index dcc8f17a4..2c9dae6a1 100644
--- a/Emby.Server.Implementations/Localization/Core/fr-CA.json
+++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json
@@ -92,5 +92,7 @@
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
"ValueSpecialEpisodeName": "Spécial - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TasksLibraryCategory": "Bibliothèque",
+ "TasksMaintenanceCategory": "Entretien"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index 88a7ac190..d1403c494 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -5,7 +5,7 @@
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
"Books": "Livres",
- "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}",
+ "CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}",
"Channels": "Chaînes",
"ChapterNameValue": "Chapitre {0}",
"Collections": "Collections",
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index 8f1288a55..c5c3844e3 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -7,7 +7,7 @@
"Books": "Könyvek",
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre innen: {0}",
"Channels": "Csatornák",
- "ChapterNameValue": "Jelenet {0}",
+ "ChapterNameValue": "{0}. jelenet",
"Collections": "Gyűjtemények",
"DeviceOfflineWithName": "{0} kijelentkezett",
"DeviceOnlineWithName": "{0} belépett",
@@ -71,7 +71,7 @@
"ScheduledTaskFailedWithName": "{0} sikertelen",
"ScheduledTaskStartedWithName": "{0} elkezdve",
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
- "Shows": "Műsorok",
+ "Shows": "Sorozatok",
"Songs": "Dalok",
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
@@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} befejezte {1} lejátászását itt: {2}",
"ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
"ValueSpecialEpisodeName": "Special - {0}",
- "VersionNumber": "Verzió: {0}"
+ "VersionNumber": "Verzió: {0}",
+ "TaskCleanTranscode": "Átkódolási könyvtár ürítése",
+ "TaskUpdatePluginsDescription": "Letölti és telepíti a frissítéseket azokhoz a bővítményekhez, amelyeknél az automatikus frissítés engedélyezve van.",
+ "TaskUpdatePlugins": "Bővítmények frissítése",
+ "TaskRefreshPeopleDescription": "Frissíti a szereplők és a stábok metaadatait a könyvtáradban.",
+ "TaskRefreshPeople": "Személyek frissítése",
+ "TaskCleanLogsDescription": "Törli azokat a naplófájlokat, amelyek {0} napnál régebbiek.",
+ "TaskCleanLogs": "Naplózási könyvtár ürítése",
+ "TaskRefreshLibraryDescription": "Átvizsgálja a könyvtáraidat új fájlokért és frissíti a metaadatokat.",
+ "TaskRefreshLibrary": "Média könyvtár beolvasása",
+ "TaskRefreshChapterImagesDescription": "Miniatűröket generál olyan videókhoz, amely tartalmaz fejezeteket.",
+ "TaskRefreshChapterImages": "Fejezetek képeinek generálása",
+ "TaskCleanCacheDescription": "Törli azokat a gyorsítótárazott fájlokat, amikre a rendszernek már nincs szüksége.",
+ "TaskCleanCache": "Gyorsítótár könyvtárának ürítése",
+ "TasksChannelsCategory": "Internetes csatornák",
+ "TasksApplicationCategory": "Alkalmazás",
+ "TasksLibraryCategory": "Könyvtár",
+ "TasksMaintenanceCategory": "Karbantartás",
+ "TaskDownloadMissingSubtitlesDescription": "A metaadat konfiguráció alapján ellenőrzi és letölti a hiányzó feliratokat az internetről.",
+ "TaskDownloadMissingSubtitles": "Hiányzó feliratok letöltése",
+ "TaskRefreshChannelsDescription": "Frissíti az internetes csatornák adatait.",
+ "TaskRefreshChannels": "Csatornák frissítése",
+ "TaskCleanTranscodeDescription": "Törli az egy napnál régebbi átkódolási fájlokat."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index 1ec4a0668..5e017d4c4 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -91,5 +91,26 @@
"UserStoppedPlayingItemWithValues": "{0} は{2}で{1} の再生が終わりました",
"ValueHasBeenAddedToLibrary": "{0}はあなたのメディアライブラリに追加されました",
"ValueSpecialEpisodeName": "スペシャル - {0}",
- "VersionNumber": "バージョン {0}"
+ "VersionNumber": "バージョン {0}",
+ "TaskCleanLogsDescription": "{0} 日以上前のログを消去します。",
+ "TaskCleanLogs": "ログの掃除",
+ "TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータをリフレッシュします。",
+ "TaskRefreshLibrary": "メディアライブラリのスキャン",
+ "TaskCleanCacheDescription": "不要なキャッシュを消去します。",
+ "TaskCleanCache": "キャッシュの掃除",
+ "TasksChannelsCategory": "ネットチャンネル",
+ "TasksApplicationCategory": "アプリケーション",
+ "TasksLibraryCategory": "ライブラリ",
+ "TasksMaintenanceCategory": "メンテナンス",
+ "TaskRefreshChannelsDescription": "ネットチャンネルの情報をリフレッシュします。",
+ "TaskRefreshChannels": "チャンネルのリフレッシュ",
+ "TaskCleanTranscodeDescription": "一日以上前のトランスコードを消去します。",
+ "TaskCleanTranscode": "トランスコード用のディレクトリの掃除",
+ "TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
+ "TaskUpdatePlugins": "プラグインの更新",
+ "TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
+ "TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
+ "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
+ "TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
+ "TaskRefreshChapterImages": "チャプター画像を抽出する"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index c4b22901e..9e3ecd5a8 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{2}에서 {0}이 {1} 재생을 마침",
"ValueHasBeenAddedToLibrary": "{0}가 미디어 라이브러리에 추가되었습니다",
"ValueSpecialEpisodeName": "스페셜 - {0}",
- "VersionNumber": "버전 {0}"
+ "VersionNumber": "버전 {0}",
+ "TasksApplicationCategory": "어플리케이션",
+ "TasksMaintenanceCategory": "유지 보수",
+ "TaskDownloadMissingSubtitlesDescription": "메타 데이터 기반으로 누락 된 자막이 있는지 인터넷을 검색합니다.",
+ "TaskDownloadMissingSubtitles": "누락 된 자막 다운로드",
+ "TaskRefreshChannelsDescription": "인터넷 채널 정보를 새로 고칩니다.",
+ "TaskRefreshChannels": "채널 새로고침",
+ "TaskCleanTranscodeDescription": "하루 이상 지난 트랜스 코드 파일을 삭제합니다.",
+ "TaskCleanTranscode": "트랜스코드 폴더 청소",
+ "TaskUpdatePluginsDescription": "자동으로 업데이트되도록 구성된 플러그인 업데이트를 다운로드하여 설치합니다.",
+ "TaskUpdatePlugins": "플러그인 업데이트",
+ "TaskRefreshPeopleDescription": "미디어 라이브러리에서 배우 및 감독의 메타 데이터를 업데이트합니다.",
+ "TaskRefreshPeople": "인물 새로고침",
+ "TaskCleanLogsDescription": "{0} 일이 지난 로그 파일을 삭제합니다.",
+ "TaskCleanLogs": "로그 폴더 청소",
+ "TaskRefreshLibraryDescription": "미디어 라이브러리에서 새 파일을 검색하고 메타 데이터를 새로 고칩니다.",
+ "TaskRefreshLibrary": "미디어 라이브러리 스캔",
+ "TaskRefreshChapterImagesDescription": "챕터가있는 비디오의 썸네일을 만듭니다.",
+ "TaskRefreshChapterImages": "챕터 이미지 추출",
+ "TaskCleanCacheDescription": "시스템에서 더 이상 필요하지 않은 캐시 파일을 삭제합니다.",
+ "TaskCleanCache": "캐시 폴더 청소",
+ "TasksChannelsCategory": "인터넷 채널",
+ "TasksLibraryCategory": "라이브러리"
}
diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json
index e4a06c0f0..dbcf17287 100644
--- a/Emby.Server.Implementations/Localization/Core/lv.json
+++ b/Emby.Server.Implementations/Localization/Core/lv.json
@@ -91,5 +91,27 @@
"HeaderFavoriteShows": "Raidījumu Favorīti",
"HeaderFavoriteEpisodes": "Episožu Favorīti",
"HeaderFavoriteArtists": "Izpildītāju Favorīti",
- "HeaderFavoriteAlbums": "Albumu Favorīti"
+ "HeaderFavoriteAlbums": "Albumu Favorīti",
+ "TaskCleanCacheDescription": "Nodzēš keša datnes, kas vairs nav sistēmai vajadzīgas.",
+ "TaskRefreshChapterImages": "Izvilkt Nodaļu Attēlus",
+ "TasksApplicationCategory": "Lietotne",
+ "TasksLibraryCategory": "Bibliotēka",
+ "TaskDownloadMissingSubtitlesDescription": "Internetā meklē trūkstošus subtitrus pēc metadatu uzstādījumiem.",
+ "TaskDownloadMissingSubtitles": "Lejupielādēt trūkstošus subtitrus",
+ "TaskRefreshChannelsDescription": "Atjauno interneta kanālu informāciju.",
+ "TaskRefreshChannels": "Atjaunot Kanālus",
+ "TaskCleanTranscodeDescription": "Izdzēš trans-kodēšanas datnes, kas ir vecākas par vienu dienu.",
+ "TaskCleanTranscode": "Iztīrīt Trans-kodēšanas Mapi",
+ "TaskUpdatePluginsDescription": "Lejupielādē un uzstāda atjauninājumus paplašinājumiem, kam ir uzstādīta automātiskā atjaunināšana.",
+ "TaskUpdatePlugins": "Atjaunot Paplašinājumus",
+ "TaskRefreshPeopleDescription": "Atjauno metadatus priekš aktieriem un direktoriem tavā mediju bibliotēkā.",
+ "TaskRefreshPeople": "Atjaunot Cilvēkus",
+ "TaskCleanLogsDescription": "Nodzēš log datnes, kas ir vairāk par {0} dienām vecas.",
+ "TaskCleanLogs": "Iztīrīt Logdatņu Mapi",
+ "TaskRefreshLibraryDescription": "Skenē tavas mediju bibliotēkas priekš jaunām datnēm un atjauno metadatus.",
+ "TaskRefreshLibrary": "Skanēt Mediju Bibliotēku",
+ "TaskRefreshChapterImagesDescription": "Izveido sīktēlus priekš video ar sadaļām.",
+ "TaskCleanCache": "Iztīrīt Kešošanas Mapi",
+ "TasksChannelsCategory": "Interneta Kanāli",
+ "TasksMaintenanceCategory": "Apkope"
}
diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json
new file mode 100644
index 000000000..50b6360d8
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/mr.json
@@ -0,0 +1,61 @@
+{
+ "Books": "पुस्तकं",
+ "Artists": "संगीतकार",
+ "Albums": "अल्बम",
+ "Playlists": "प्लेलिस्ट",
+ "HeaderAlbumArtists": "अल्बम संगीतकार",
+ "Folders": "फोल्डर",
+ "HeaderFavoriteEpisodes": "आवडते भाग",
+ "HeaderFavoriteSongs": "आवडती गाणी",
+ "Movies": "चित्रपट",
+ "HeaderFavoriteArtists": "आवडते संगीतकार",
+ "Shows": "कार्यक्रम",
+ "HeaderFavoriteAlbums": "आवडते अल्बम",
+ "Channels": "वाहिन्या",
+ "ValueSpecialEpisodeName": "विशेष - {0}",
+ "HeaderFavoriteShows": "आवडते कार्यक्रम",
+ "Favorites": "आवडीचे",
+ "HeaderNextUp": "यानंतर",
+ "Songs": "गाणी",
+ "HeaderLiveTV": "लाइव्ह टीव्ही",
+ "Genres": "जाँनरे",
+ "Photos": "चित्र",
+ "TaskDownloadMissingSubtitles": "नसलेले सबटायटल डाउनलोड करा",
+ "TaskCleanTranscodeDescription": "एक दिवसापेक्षा जुन्या ट्रान्सकोड फायली काढून टाका.",
+ "TaskCleanTranscode": "ट्रान्सकोड डिरेक्टरी साफ करून टाका",
+ "TaskUpdatePlugins": "प्लगइन अपडेट करा",
+ "TaskCleanLogs": "लॉग डिरेक्टरी साफ करून टाका",
+ "TaskCleanCache": "कॅश डिरेक्टरी साफ करून टाका",
+ "TasksChannelsCategory": "इंटरनेट वाहिन्या",
+ "TasksApplicationCategory": "अ‍ॅप्लिकेशन",
+ "TasksLibraryCategory": "संग्रहालय",
+ "VersionNumber": "आवृत्ती {0}",
+ "UserPasswordChangedWithName": "{0} या प्रयोक्त्याचे पासवर्ड बदलण्यात आले आहे",
+ "UserOnlineFromDevice": "{0} हे {1} येथून ऑनलाइन आहेत",
+ "UserDeletedWithName": "प्रयोक्ता {0} काढून टाकण्यात आले आहे",
+ "UserCreatedWithName": "प्रयोक्ता {0} बनवण्यात आले आहे",
+ "User": "प्रयोक्ता",
+ "TvShows": "टीव्ही कार्यक्रम",
+ "StartupEmbyServerIsLoading": "जेलिफिन सर्व्हर लोड होत आहे. कृपया थोड्या वेळात पुन्हा प्रयत्न करा.",
+ "Plugin": "प्लगइन",
+ "NotificationOptionCameraImageUploaded": "कॅमेरा चित्र अपलोड केले आहे",
+ "NotificationOptionApplicationUpdateInstalled": "अ‍ॅप्लिकेशन अपडेट इन्स्टॉल केले आहे",
+ "NotificationOptionApplicationUpdateAvailable": "अ‍ॅप्लिकेशन अपडेट उपलब्ध आहे",
+ "NewVersionIsAvailable": "जेलिफिन सर्व्हरची एक नवीन आवृत्ती डाउनलोड करण्यास उपलब्ध आहे.",
+ "NameSeasonUnknown": "अज्ञात सीझन",
+ "NameSeasonNumber": "सीझन {0}",
+ "MusicVideos": "संगीत व्हिडीयो",
+ "Music": "संगीत",
+ "MessageApplicationUpdatedTo": "जेलिफिन सर्व्हर अपडेट होऊन {0} आवृत्तीवर पोहोचला आहे",
+ "MessageApplicationUpdated": "जेलिफिन सर्व्हर अपडेट केला गेला आहे",
+ "Latest": "नवीनतम",
+ "LabelIpAddressValue": "आयपी पत्ता: {0}",
+ "ItemRemovedWithName": "{0} हे संग्रहालयातून काढून टाकण्यात आले",
+ "ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले",
+ "HomeVideos": "घरचे व्हिडीयो",
+ "HeaderRecordingGroups": "रेकॉर्डिंग गट",
+ "HeaderCameraUploads": "कॅमेरा अपलोड",
+ "CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
+ "Application": "अ‍ॅप्लिकेशन",
+ "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json
index 175735997..e523ae90b 100644
--- a/Emby.Server.Implementations/Localization/Core/nb.json
+++ b/Emby.Server.Implementations/Localization/Core/nb.json
@@ -92,5 +92,9 @@
"UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}",
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
- "VersionNumber": "Versjon {0}"
+ "VersionNumber": "Versjon {0}",
+ "TasksChannelsCategory": "Internett kanaler",
+ "TasksApplicationCategory": "Applikasjon",
+ "TasksLibraryCategory": "Bibliotek",
+ "TasksMaintenanceCategory": "Vedlikehold"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index bc36cbdd3..3bc9c2a77 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -92,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
"ValueSpecialEpisodeName": "Speciaal - {0}",
- "VersionNumber": "Versie {0}"
+ "VersionNumber": "Versie {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar missende ondertitels gebaseerd op metadata configuratie.",
+ "TaskDownloadMissingSubtitles": "Download missende ondertitels",
+ "TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
+ "TaskRefreshChannels": "Vernieuw Kanalen",
+ "TaskCleanTranscodeDescription": "Verwijder transcode bestanden ouder dan 1 dag.",
+ "TaskCleanLogs": "Log Folder Opschonen",
+ "TaskCleanTranscode": "Transcode Folder Opschonen",
+ "TaskUpdatePluginsDescription": "Download en installeert updates voor plugins waar automatisch updaten aan staat.",
+ "TaskUpdatePlugins": "Update Plugins",
+ "TaskRefreshPeopleDescription": "Update metadata for acteurs en regisseurs in de media bibliotheek.",
+ "TaskRefreshPeople": "Vernieuw Personen",
+ "TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
+ "TaskRefreshLibraryDescription": "Scant de media bibliotheek voor nieuwe bestanden en vernieuwt de metadata.",
+ "TaskRefreshLibrary": "Scan Media Bibliotheek",
+ "TaskRefreshChapterImagesDescription": "Maakt thumbnails aan voor videos met hoofdstukken.",
+ "TaskRefreshChapterImages": "Hoofdstukafbeeldingen Uitpakken",
+ "TaskCleanCacheDescription": "Verwijder gecachte bestanden die het systeem niet langer nodig heeft.",
+ "TaskCleanCache": "Cache Folder Opschonen",
+ "TasksChannelsCategory": "Internet Kanalen",
+ "TasksApplicationCategory": "Applicatie",
+ "TasksLibraryCategory": "Bibliotheek",
+ "TasksMaintenanceCategory": "Onderhoud"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index 3d5f7cab2..661ee8603 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -91,5 +91,9 @@
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
"Application": "Aplicação",
- "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}"
+ "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}",
+ "TaskCleanCache": "Limpar Diretório de Cache",
+ "TasksApplicationCategory": "Aplicação",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Manutenção"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index db863ebc5..699dd26da 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -91,5 +91,27 @@
"Artists": "Artiști",
"Application": "Aplicație",
"AppDeviceValues": "Aplicație: {0}, Dispozitiv: {1}",
- "Albums": "Albume"
+ "Albums": "Albume",
+ "TaskDownloadMissingSubtitlesDescription": "Caută pe internet subtitrările lipsă pe baza configurației metadatelor.",
+ "TaskDownloadMissingSubtitles": "Descarcă subtitrările lipsă",
+ "TaskRefreshChannelsDescription": "Actualizează informațiile despre canalul de internet.",
+ "TaskRefreshChannels": "Actualizează canale",
+ "TaskCleanTranscodeDescription": "Șterge fișierele de transcodare mai vechi de o zi.",
+ "TaskCleanTranscode": "Curățați directorul de transcodare",
+ "TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru pluginuri care sunt configurate să se actualizeze automat.",
+ "TaskUpdatePlugins": "Actualizați plugin-uri",
+ "TaskRefreshPeopleDescription": "Actualizează metadatele pentru actori și regizori din biblioteca media.",
+ "TaskRefreshPeople": "Actualizează oamenii",
+ "TaskCleanLogsDescription": "Șterge fișierele jurnal care au mai mult de {0} zile.",
+ "TaskCleanLogs": "Curățare director jurnal",
+ "TaskRefreshLibraryDescription": "Scanează biblioteca media pentru fișiere noi și reîmprospătează metadatele.",
+ "TaskRefreshLibrary": "Scanează Biblioteca Media",
+ "TaskRefreshChapterImagesDescription": "Creează miniaturi pentru videourile care au capitole.",
+ "TaskRefreshChapterImages": "Extrage Imaginile de Capitol",
+ "TaskCleanCacheDescription": "Șterge fișierele cache care nu mai sunt necesare sistemului.",
+ "TaskCleanCache": "Curățați directorul cache",
+ "TasksChannelsCategory": "Canale de pe Internet",
+ "TasksApplicationCategory": "Aplicație",
+ "TasksLibraryCategory": "Librărie",
+ "TasksMaintenanceCategory": "Mentenanță"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index c46aa5c30..442e09791 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -92,5 +92,18 @@
"UserStoppedPlayingItemWithValues": "{0} - воспр. «{1}» ост-но на {2}",
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
"ValueSpecialEpisodeName": "Спецэпизод - {0}",
- "VersionNumber": "Версия {0}"
+ "VersionNumber": "Версия {0}",
+ "TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров",
+ "TaskRefreshChannels": "Подновить каналы",
+ "TaskCleanTranscode": "Очистка каталога перекодировки",
+ "TaskUpdatePlugins": "Обновление плагинов",
+ "TaskRefreshPeople": "Подновить людей",
+ "TaskCleanLogs": "Очистка каталога журналов",
+ "TaskRefreshLibrary": "Сканирование медиатеки",
+ "TaskRefreshChapterImages": "Извлечение рисунков сцен",
+ "TaskCleanCache": "Очистка каталога кеша",
+ "TasksChannelsCategory": "Интернет-каналы",
+ "TasksApplicationCategory": "Приложение",
+ "TasksLibraryCategory": "Медиатека",
+ "TasksMaintenanceCategory": "Обслуживание"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
index 9d3445ba6..5f3cbb1c8 100644
--- a/Emby.Server.Implementations/Localization/Core/sr.json
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -81,7 +81,7 @@
"Favorites": "Омиљено",
"FailedLoginAttemptWithUserName": "Неуспела пријава са {0}",
"DeviceOnlineWithName": "{0} се повезао",
- "DeviceOfflineWithName": "{0} се одвезао",
+ "DeviceOfflineWithName": "{0} је прекинуо везу",
"Collections": "Колекције",
"ChapterNameValue": "Поглавље {0}",
"Channels": "Канали",
@@ -91,5 +91,27 @@
"Artists": "Извођач",
"Application": "Апликација",
"AppDeviceValues": "Апл: {0}, уређај: {1}",
- "Albums": "Албуми"
+ "Albums": "Албуми",
+ "TaskDownloadMissingSubtitlesDescription": "Претражује интернет за недостајуће титлове на основу конфигурације метаподатака.",
+ "TaskDownloadMissingSubtitles": "Преузмите недостајуће титлове",
+ "TaskRefreshChannelsDescription": "Освежава информације о интернет каналу.",
+ "TaskRefreshChannels": "Освежи канале",
+ "TaskCleanTranscodeDescription": "Брише датотеке за кодирање старије од једног дана.",
+ "TaskCleanTranscode": "Очистите директоријум преноса",
+ "TaskUpdatePluginsDescription": "Преузима и инсталира исправке за додатке који су конфигурисани за аутоматско ажурирање.",
+ "TaskUpdatePlugins": "Ажурирајте додатке",
+ "TaskRefreshPeopleDescription": "Ажурира метаподатке за глумце и редитеље у вашој медијској библиотеци.",
+ "TaskRefreshPeople": "Освежите људе",
+ "TaskCleanLogsDescription": "Брише логове старије од {0} дана.",
+ "TaskCleanLogs": "Очистите директоријум логова",
+ "TaskRefreshLibraryDescription": "Скенира вашу медијску библиотеку за нове датотеке и освежава метаподатке.",
+ "TaskRefreshLibrary": "Скенирај Библиотеку Медија",
+ "TaskRefreshChapterImagesDescription": "Ствара сличице за видео записе који имају поглавља.",
+ "TaskRefreshChapterImages": "Издвоји слике из поглавља",
+ "TaskCleanCacheDescription": "Брише Кеш фајлове који више нису потребни систему.",
+ "TaskCleanCache": "Очистите Кеш Директоријум",
+ "TasksChannelsCategory": "Интернет канали",
+ "TasksApplicationCategory": "Апликација",
+ "TasksLibraryCategory": "Библиотека",
+ "TasksMaintenanceCategory": "Одржавање"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 96891f994..b7c50394a 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -92,5 +92,26 @@
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
"ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserad på metadatas konfiguration.",
+ "TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
+ "TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
+ "TaskRefreshChannels": "Uppdatera kanaler",
+ "TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
+ "TaskCleanTranscode": "Töm transkodningskatalog",
+ "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
+ "TaskUpdatePlugins": "Uppdatera insticksprogram",
+ "TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
+ "TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.",
+ "TaskCleanLogs": "Töm loggkatalog",
+ "TaskRefreshLibraryDescription": "Söker igenom ditt mediabibliotek efter nya filer och förnyar metadata.",
+ "TaskRefreshLibrary": "Genomsök mediabibliotek",
+ "TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.",
+ "TaskRefreshChapterImages": "Extrahera kapitelbilder",
+ "TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.",
+ "TaskCleanCache": "Rensa cachekatalog",
+ "TasksChannelsCategory": "Internetkanaler",
+ "TasksApplicationCategory": "Applikation",
+ "TasksLibraryCategory": "Bibliotek",
+ "TasksMaintenanceCategory": "Underhåll"
}
diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json
index 1d13b0354..62d205516 100644
--- a/Emby.Server.Implementations/Localization/Core/tr.json
+++ b/Emby.Server.Implementations/Localization/Core/tr.json
@@ -92,5 +92,10 @@
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
"ValueSpecialEpisodeName": "Özel - {0}",
- "VersionNumber": "Versiyon {0}"
+ "VersionNumber": "Versiyon {0}",
+ "TaskCleanCache": "Geçici dosya klasörünü temizle",
+ "TasksChannelsCategory": "İnternet kanalları",
+ "TasksApplicationCategory": "Yazılım",
+ "TasksLibraryCategory": "Kütüphane",
+ "TasksMaintenanceCategory": "Onarım"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json
new file mode 100644
index 000000000..9a5874e29
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json
@@ -0,0 +1,117 @@
+{
+ "HeaderFavoriteAlbums": "پسندیدہ البمز",
+ "HeaderNextUp": "اگلا",
+ "HeaderFavoriteArtists": "پسندیدہ فنکار",
+ "HeaderAlbumArtists": "البم کے فنکار",
+ "Movies": "فلمیں",
+ "HeaderFavoriteEpisodes": "پسندیدہ اقساط",
+ "Collections": "مجموعہ",
+ "Folders": "فولڈرز",
+ "HeaderLiveTV": "براہ راست ٹی وی",
+ "Channels": "چینل",
+ "HeaderContinueWatching": "دیکھنا جاری رکھیں",
+ "Playlists": "پلے لسٹس",
+ "ValueSpecialEpisodeName": "خاص - {0}",
+ "Shows": "شوز",
+ "Genres": "انواع",
+ "Artists": "فنکار",
+ "Sync": "مطابقت",
+ "Photos": "تصوریں",
+ "Albums": "البم",
+ "Favorites": "پسندیدہ",
+ "Songs": "گانے",
+ "Books": "کتابیں",
+ "HeaderFavoriteSongs": "پسندیدہ گانے",
+ "HeaderFavoriteShows": "پسندیدہ شوز",
+ "TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔",
+ "TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں",
+ "TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔",
+ "TaskRefreshChannels": "چینلز ریفریش کریں",
+ "TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔",
+ "TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں",
+ "TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔",
+ "TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں",
+ "TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔",
+ "TaskRefreshPeople": "لوگوں کو تروتازہ کریں",
+ "TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔",
+ "TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں",
+ "TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.",
+ "TaskRefreshLibrary": "اسکین میڈیا لائبریری",
+ "TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔",
+ "TaskRefreshChapterImages": "باب کی تصاویر نکالیں",
+ "TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔",
+ "TaskCleanCache": "کیش ڈائرکٹری کلیر کریں",
+ "TasksChannelsCategory": "انٹرنیٹ چینلز",
+ "TasksApplicationCategory": "پروگرام",
+ "TasksLibraryCategory": "لآیبریری",
+ "TasksMaintenanceCategory": "مرمت",
+ "VersionNumber": "ورژن {0}",
+ "ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے",
+ "UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے",
+ "UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے",
+ "UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے",
+ "UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے",
+ "UserOnlineFromDevice": "{0} آن لائن ہے {1} سے",
+ "UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}",
+ "UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے",
+ "UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}",
+ "UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے",
+ "UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے",
+ "User": "صارف",
+ "TvShows": "ٹی وی کے پروگرام",
+ "System": "نظام",
+ "SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے",
+ "StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔",
+ "ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے",
+ "ScheduledTaskStartedWithName": "{0} شروع",
+ "ScheduledTaskFailedWithName": "{0} ناکام",
+ "ProviderValue": "فراہم کرنے والا: {0}",
+ "PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی",
+ "PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا",
+ "PluginInstalledWithName": "{0} انسٹال کیا گیا تھا",
+ "Plugin": "پلگن",
+ "NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا",
+ "NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا",
+ "NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا",
+ "NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی",
+ "NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے",
+ "NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال",
+ "NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا",
+ "NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا",
+ "NotificationOptionPluginError": "پلگ ان کی ناکامی",
+ "NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا",
+ "NotificationOptionInstallationFailed": "تنصیب کی ناکامی",
+ "NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی",
+ "NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا",
+ "NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا",
+ "NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے",
+ "NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے",
+ "NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔",
+ "NameSeasonUnknown": "نامعلوم باب",
+ "NameSeasonNumber": "باب {0}",
+ "NameInstallFailed": "{0} تنصیب ناکام ہوگئی",
+ "MusicVideos": "موسیقی ویڈیو",
+ "Music": "موسیقی",
+ "MixedContent": "مخلوط مواد",
+ "MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے",
+ "MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے",
+ "MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}",
+ "MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے",
+ "Latest": "تازہ ترین",
+ "LabelRunningTimeValue": "چلانے کی مدت",
+ "LabelIpAddressValue": "ای پی پتے {0}",
+ "ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے",
+ "ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے",
+ "Inherit": "وراثت میں",
+ "HomeVideos": "ہوم ویڈیو",
+ "HeaderRecordingGroups": "ریکارڈنگ گروپس",
+ "HeaderCameraUploads": "کیمرہ اپلوڈز",
+ "FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
+ "DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
+ "DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",
+ "ChapterNameValue": "باب",
+ "AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے",
+ "CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
+ "Application": "پروگرام",
+ "AppDeviceValues": "پروگرام:{0}, آلہ:{1}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index 9d23f60cc..6b563a9b1 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -3,7 +3,7 @@
"AppDeviceValues": "应用: {0}, 设备: {1}",
"Application": "应用程序",
"Artists": "艺术家",
- "AuthenticationSucceededWithUserName": "成功验证{0} ",
+ "AuthenticationSucceededWithUserName": "{0} 认证成功",
"Books": "书籍",
"CameraImageUploadedFrom": "新的相机图像已从 {0} 上传",
"Channels": "频道",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index 21034b76f..c423c7ea7 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -20,7 +20,7 @@
"HeaderContinueWatching": "繼續觀賞",
"HeaderFavoriteAlbums": "最愛專輯",
"HeaderFavoriteArtists": "最愛演出者",
- "HeaderFavoriteEpisodes": "最愛級數",
+ "HeaderFavoriteEpisodes": "最愛影集",
"HeaderFavoriteShows": "最愛節目",
"HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播",
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
index 588944d0e..6a1afced7 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
@@ -55,9 +55,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
{
progress.Report(0);
- var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
- .ToListAsync(cancellationToken)
- .ConfigureAwait(false);
+ var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken);
+ var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList();
progress.Report(10);
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index c897036eb..25f70471a 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -3,8 +3,10 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
@@ -18,6 +20,7 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Updates
@@ -28,6 +31,11 @@ namespace Emby.Server.Implementations.Updates
public class InstallationManager : IInstallationManager
{
/// <summary>
+ /// The key for a setting that specifies a URL for the plugin repository JSON manifest.
+ /// </summary>
+ public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
+
+ /// <summary>
/// The _logger.
/// </summary>
private readonly ILogger _logger;
@@ -44,6 +52,7 @@ namespace Emby.Server.Implementations.Updates
private readonly IApplicationHost _applicationHost;
private readonly IZipClient _zipClient;
+ private readonly IConfiguration _appConfig;
private readonly object _currentInstallationsLock = new object();
@@ -65,7 +74,8 @@ namespace Emby.Server.Implementations.Updates
IJsonSerializer jsonSerializer,
IServerConfigurationManager config,
IFileSystem fileSystem,
- IZipClient zipClient)
+ IZipClient zipClient,
+ IConfiguration appConfig)
{
if (logger == null)
{
@@ -83,6 +93,7 @@ namespace Emby.Server.Implementations.Updates
_config = config;
_fileSystem = fileSystem;
_zipClient = zipClient;
+ _appConfig = appConfig;
}
/// <inheritdoc />
@@ -112,19 +123,43 @@ namespace Emby.Server.Implementations.Updates
/// <inheritdoc />
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
{
- using (var response = await _httpClient.SendAsync(
- new HttpRequestOptions
+ var manifestUrl = _appConfig.GetValue<string>(PluginManifestUrlKey);
+
+ try
+ {
+ using (var response = await _httpClient.SendAsync(
+ new HttpRequestOptions
+ {
+ Url = manifestUrl,
+ CancellationToken = cancellationToken,
+ CacheMode = CacheMode.Unconditional,
+ CacheLength = TimeSpan.FromMinutes(3)
+ },
+ HttpMethod.Get).ConfigureAwait(false))
+ using (Stream stream = response.Content)
{
- Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
- CancellationToken = cancellationToken,
- CacheMode = CacheMode.Unconditional,
- CacheLength = TimeSpan.FromMinutes(3)
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (Stream stream = response.Content)
+ try
+ {
+ return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
+ }
+ catch (SerializationException ex)
+ {
+ const string LogTemplate =
+ "Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " +
+ "have specified a custom plugin repository manifest URL with --plugin-manifest-url or " +
+ PluginManifestUrlKey + ", please ensure that it is correct.";
+ _logger.LogError(ex, LogTemplate, manifestUrl);
+ throw;
+ }
+ }
+ }
+ catch (UriFormatException ex)
{
- return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(
- stream).ConfigureAwait(false);
+ const string LogTemplate =
+ "The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " +
+ "Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey;
+ _logger.LogError(ex, LogTemplate, manifestUrl);
+ throw;
}
}
@@ -189,16 +224,17 @@ namespace Emby.Server.Implementations.Updates
}
/// <inheritdoc />
- public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
{
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
+ return GetAvailablePluginUpdates(catalog);
+ }
- var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
-
- // Figure out what needs to be installed
+ private IEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
+ {
foreach (var plugin in _applicationHost.Plugins)
{
- var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel);
+ var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version, _applicationHost.SystemUpdateLevel);
var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
if (version != null
&& !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 4abdd59aa..e55b0d4ed 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -25,6 +25,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Serilog;
@@ -259,7 +260,7 @@ namespace Jellyfin.Server
IApplicationPaths appPaths)
{
return new WebHostBuilder()
- .UseKestrel(options =>
+ .UseKestrel((builderContext, options) =>
{
var addresses = appHost.ServerConfigurationManager
.Configuration
@@ -282,6 +283,14 @@ namespace Jellyfin.Server
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
}
+ else if (builderContext.HostingEnvironment.IsDevelopment())
+ {
+ options.Listen(address, appHost.HttpsPort, listenOptions =>
+ {
+ listenOptions.UseHttps();
+ listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
+ });
+ }
}
}
else
@@ -297,6 +306,14 @@ namespace Jellyfin.Server
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
});
}
+ else if (builderContext.HostingEnvironment.IsDevelopment())
+ {
+ options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
+ {
+ listenOptions.UseHttps();
+ listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
+ });
+ }
}
})
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig))
diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json
index 53d9fe165..b6e2bcf97 100644
--- a/Jellyfin.Server/Properties/launchSettings.json
+++ b/Jellyfin.Server/Properties/launchSettings.json
@@ -1,10 +1,16 @@
{
"profiles": {
"Jellyfin.Server": {
- "commandName": "Project"
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
},
"Jellyfin.Server (nowebclient)": {
"commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
"commandLineArgs": "--nowebclient"
}
}
diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs
index c93577d3e..6e15d058f 100644
--- a/Jellyfin.Server/StartupOptions.cs
+++ b/Jellyfin.Server/StartupOptions.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
-using System.Globalization;
using CommandLine;
using Emby.Server.Implementations;
+using Emby.Server.Implementations.Updates;
using MediaBrowser.Controller.Extensions;
namespace Jellyfin.Server
@@ -76,6 +76,10 @@ namespace Jellyfin.Server
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
public string? RestartArgs { get; set; }
+ /// <inheritdoc />
+ [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")]
+ public string? PluginManifestUrl { get; set; }
+
/// <summary>
/// Gets the command line options as a dictionary that can be used in the .NET configuration system.
/// </summary>
@@ -84,6 +88,11 @@ namespace Jellyfin.Server
{
var config = new Dictionary<string, string>();
+ if (PluginManifestUrl != null)
+ {
+ config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl);
+ }
+
if (NoWebClient)
{
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 4bd13df00..6691080bc 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -86,12 +86,9 @@ namespace MediaBrowser.Api
return Array.Empty<string>();
}
- if (removeEmpty)
- {
- return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- return value.Split(separator);
+ return removeEmpty
+ ? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
+ : value.Split(separator);
}
public SemaphoreSlim GetTranscodingLock(string outputPath)
@@ -258,7 +255,7 @@ namespace MediaBrowser.Api
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
- var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
+ var ticks = transcodingPosition?.Ticks;
if (job != null)
{
@@ -487,16 +484,9 @@ namespace MediaBrowser.Api
/// <returns>Task.</returns>
internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
{
- return KillTranscodingJobs(j =>
- {
- if (!string.IsNullOrWhiteSpace(playSessionId))
- {
- return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
- }
-
- return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase);
-
- }, deleteFiles);
+ return KillTranscodingJobs(j => string.IsNullOrWhiteSpace(playSessionId)
+ ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
+ : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
}
/// <summary>
@@ -561,10 +551,7 @@ namespace MediaBrowser.Api
lock (job.ProcessLock)
{
- if (job.TranscodingThrottler != null)
- {
- job.TranscodingThrottler.Stop().GetAwaiter().GetResult();
- }
+ job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
var process = job.Process;
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index 112ee8f79..1a1d86362 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -58,12 +58,9 @@ namespace MediaBrowser.Api
public static string[] SplitValue(string value, char delim)
{
- if (value == null)
- {
- return Array.Empty<string>();
- }
-
- return value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
+ return value == null
+ ? Array.Empty<string>()
+ : value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
}
public static Guid[] GetGuids(string value)
@@ -97,19 +94,10 @@ namespace MediaBrowser.Api
var authenticatedUser = auth.User;
// If they're going to update the record of another user, they must be an administrator
- if (!userId.Equals(auth.UserId))
- {
- if (!authenticatedUser.Policy.IsAdministrator)
- {
- throw new SecurityException("Unauthorized access.");
- }
- }
- else if (restrictUserPreferences)
+ if ((!userId.Equals(auth.UserId) && !authenticatedUser.Policy.IsAdministrator)
+ || (restrictUserPreferences && !authenticatedUser.Policy.EnableUserPreferenceAccess))
{
- if (!authenticatedUser.Policy.EnableUserPreferenceAccess)
- {
- throw new SecurityException("Unauthorized access.");
- }
+ throw new SecurityException("Unauthorized access.");
}
}
@@ -138,8 +126,8 @@ namespace MediaBrowser.Api
options.Fields = hasFields.GetItemFields();
}
- if (!options.ContainsField(Model.Querying.ItemFields.RecursiveItemCount)
- || !options.ContainsField(Model.Querying.ItemFields.ChildCount))
+ if (!options.ContainsField(ItemFields.RecursiveItemCount)
+ || !options.ContainsField(ItemFields.ChildCount))
{
var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -150,7 +138,7 @@ namespace MediaBrowser.Api
int oldLen = options.Fields.Length;
var arr = new ItemFields[oldLen + 1];
options.Fields.CopyTo(arr, 0);
- arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
+ arr[oldLen] = ItemFields.RecursiveItemCount;
options.Fields = arr;
}
@@ -166,7 +154,7 @@ namespace MediaBrowser.Api
int oldLen = options.Fields.Length;
var arr = new ItemFields[oldLen + 1];
options.Fields.CopyTo(arr, 0);
- arr[oldLen] = Model.Querying.ItemFields.ChildCount;
+ arr[oldLen] = ItemFields.ChildCount;
options.Fields = arr;
}
}
@@ -282,27 +270,21 @@ namespace MediaBrowser.Api
}).OfType<T>().FirstOrDefault();
- if (result == null)
+ result ??= libraryManager.GetItemList(new InternalItemsQuery
{
- result = libraryManager.GetItemList(new InternalItemsQuery
- {
- Name = name.Replace(BaseItem.SlugChar, '/'),
- IncludeItemTypes = new[] { typeof(T).Name },
- DtoOptions = dtoOptions
+ Name = name.Replace(BaseItem.SlugChar, '/'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
- }).OfType<T>().FirstOrDefault();
- }
+ }).OfType<T>().FirstOrDefault();
- if (result == null)
+ result ??= libraryManager.GetItemList(new InternalItemsQuery
{
- result = libraryManager.GetItemList(new InternalItemsQuery
- {
- Name = name.Replace(BaseItem.SlugChar, '?'),
- IncludeItemTypes = new[] { typeof(T).Name },
- DtoOptions = dtoOptions
+ Name = name.Replace(BaseItem.SlugChar, '?'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
- }).OfType<T>().FirstOrDefault();
- }
+ }).OfType<T>().FirstOrDefault();
return result;
}
diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs
index 6139ba156..fd9b8c396 100644
--- a/MediaBrowser.Api/ChannelService.cs
+++ b/MediaBrowser.Api/ChannelService.cs
@@ -116,12 +116,9 @@ namespace MediaBrowser.Api
{
var val = Filters;
- if (string.IsNullOrEmpty(val))
- {
- return new ItemFilter[] { };
- }
-
- return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
+ return string.IsNullOrEmpty(val)
+ ? Array.Empty<ItemFilter>()
+ : val.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
}
/// <summary>
@@ -173,14 +170,9 @@ namespace MediaBrowser.Api
/// <returns>IEnumerable{ItemFilter}.</returns>
public IEnumerable<ItemFilter> GetFilters()
{
- var val = Filters;
-
- if (string.IsNullOrEmpty(val))
- {
- return new ItemFilter[] { };
- }
-
- return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
+ return string.IsNullOrEmpty(Filters)
+ ? Array.Empty<ItemFilter>()
+ : Filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
}
}
@@ -241,7 +233,7 @@ namespace MediaBrowser.Api
{
Limit = request.Limit,
StartIndex = request.StartIndex,
- ChannelIds = new Guid[] { new Guid(request.Id) },
+ ChannelIds = new[] { new Guid(request.Id) },
ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId),
OrderBy = request.GetOrderBy(),
DtoOptions = new Controller.Dto.DtoOptions
diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs
index 8b63decd2..7004a2559 100644
--- a/MediaBrowser.Api/Devices/DeviceService.cs
+++ b/MediaBrowser.Api/Devices/DeviceService.cs
@@ -155,16 +155,14 @@ namespace MediaBrowser.Api.Devices
Id = id
});
}
- else
+
+ return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
{
- return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
- {
- MimeType = Request.ContentType,
- Album = album,
- Name = name,
- Id = id
- });
- }
+ MimeType = Request.ContentType,
+ Album = album,
+ Name = name,
+ Id = id
+ });
}
}
}
diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs
index 36b03f09c..d199ce154 100644
--- a/MediaBrowser.Api/EnvironmentService.cs
+++ b/MediaBrowser.Api/EnvironmentService.cs
@@ -258,12 +258,7 @@ namespace MediaBrowser.Api
return false;
}
- if (!request.IncludeDirectories && isDirectory)
- {
- return false;
- }
-
- return true;
+ return request.IncludeDirectories || !isDirectory;
});
return entries.Select(f => new FileSystemEntryInfo
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index 25f23bcd1..5eb72cdb1 100644
--- a/MediaBrowser.Api/FilterService.cs
+++ b/MediaBrowser.Api/FilterService.cs
@@ -133,7 +133,7 @@ namespace MediaBrowser.Api
// Non recursive not yet supported for library folders
if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
{
- genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new Guid[] { parentItem.Id };
+ genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new[] { parentItem.Id };
}
else
{
@@ -231,7 +231,7 @@ namespace MediaBrowser.Api
EnableTotalRecordCount = false,
DtoOptions = new Controller.Dto.DtoOptions
{
- Fields = new ItemFields[] { ItemFields.Genres, ItemFields.Tags },
+ Fields = new[] { ItemFields.Genres, ItemFields.Tags },
EnableImages = false,
EnableUserData = false
}
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index af455987b..e8ea31251 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -650,7 +650,7 @@ namespace MediaBrowser.Api.Images
if (!string.IsNullOrWhiteSpace(request.Format)
&& Enum.TryParse(request.Format, true, out ImageFormat format))
{
- return new ImageFormat[] { format };
+ return new[] { format };
}
return GetClientSupportedFormats();
@@ -743,24 +743,22 @@ namespace MediaBrowser.Api.Images
/// <returns>Task.</returns>
public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
{
- using (var reader = new StreamReader(inputStream))
- {
- var text = await reader.ReadToEndAsync().ConfigureAwait(false);
+ using var reader = new StreamReader(inputStream);
+ var text = await reader.ReadToEndAsync().ConfigureAwait(false);
- var bytes = Convert.FromBase64String(text);
+ var bytes = Convert.FromBase64String(text);
- var memoryStream = new MemoryStream(bytes)
- {
- Position = 0
- };
+ var memoryStream = new MemoryStream(bytes)
+ {
+ Position = 0
+ };
- // Handle image/png; charset=utf-8
- mimeType = mimeType.Split(';').FirstOrDefault();
+ // Handle image/png; charset=utf-8
+ mimeType = mimeType.Split(';').FirstOrDefault();
- await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
+ await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
- entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
- }
+ entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
}
}
}
diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs
index f03f5efd8..222bb34d3 100644
--- a/MediaBrowser.Api/Images/RemoteImageService.cs
+++ b/MediaBrowser.Api/Images/RemoteImageService.cs
@@ -261,27 +261,25 @@ namespace MediaBrowser.Api.Images
/// <returns>Task.</returns>
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
{
- using (var result = await _httpClient.GetResponse(new HttpRequestOptions
+ using var result = await _httpClient.GetResponse(new HttpRequestOptions
{
Url = url,
BufferContent = false
- }).ConfigureAwait(false))
- {
- var ext = result.ContentType.Split('/').Last();
-
- var fullCachePath = GetFullCachePath(urlHash + "." + ext);
+ }).ConfigureAwait(false);
+ var ext = result.ContentType.Split('/').Last();
- Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
- using (var stream = result.Content)
- using (var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
- {
- await stream.CopyToAsync(filestream).ConfigureAwait(false);
- }
+ var fullCachePath = GetFullCachePath(urlHash + "." + ext);
- Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
- File.WriteAllText(pointerCachePath, fullCachePath);
+ Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+ using (var stream = result.Content)
+ {
+ using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ await stream.CopyToAsync(filestream).ConfigureAwait(false);
}
+
+ Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+ File.WriteAllText(pointerCachePath, fullCachePath);
}
/// <summary>
diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs
index a76369a15..0bbe7e1cf 100644
--- a/MediaBrowser.Api/ItemLookupService.cs
+++ b/MediaBrowser.Api/ItemLookupService.cs
@@ -305,9 +305,16 @@ namespace MediaBrowser.Api
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
using (var stream = result.Content)
- using (var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
{
- await stream.CopyToAsync(filestream).ConfigureAwait(false);
+ using var fileStream = new FileStream(
+ fullCachePath,
+ FileMode.Create,
+ FileAccess.Write,
+ FileShare.Read,
+ IODefaults.FileStreamBufferSize,
+ true);
+
+ await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index c81e89ca3..2db6d717a 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -263,8 +263,7 @@ namespace MediaBrowser.Api
item.Overview = request.Overview;
item.Genres = request.Genres;
- var episode = item as Episode;
- if (episode != null)
+ if (item is Episode episode)
{
episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
@@ -302,14 +301,12 @@ namespace MediaBrowser.Api
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
- var hasDisplayOrder = item as IHasDisplayOrder;
- if (hasDisplayOrder != null)
+ if (item is IHasDisplayOrder hasDisplayOrder)
{
hasDisplayOrder.DisplayOrder = request.DisplayOrder;
}
- var hasAspectRatio = item as IHasAspectRatio;
- if (hasAspectRatio != null)
+ if (item is IHasAspectRatio hasAspectRatio)
{
hasAspectRatio.AspectRatio = request.AspectRatio;
}
@@ -337,16 +334,14 @@ namespace MediaBrowser.Api
item.ProviderIds = request.ProviderIds;
- var video = item as Video;
- if (video != null)
+ if (item is Video video)
{
video.Video3DFormat = request.Video3DFormat;
}
if (request.AlbumArtists != null)
{
- var hasAlbumArtists = item as IHasAlbumArtist;
- if (hasAlbumArtists != null)
+ if (item is IHasAlbumArtist hasAlbumArtists)
{
hasAlbumArtists.AlbumArtists = request
.AlbumArtists
@@ -357,8 +352,7 @@ namespace MediaBrowser.Api
if (request.ArtistItems != null)
{
- var hasArtists = item as IHasArtist;
- if (hasArtists != null)
+ if (item is IHasArtist hasArtists)
{
hasArtists.Artists = request
.ArtistItems
@@ -367,20 +361,17 @@ namespace MediaBrowser.Api
}
}
- var song = item as Audio;
- if (song != null)
+ if (item is Audio song)
{
song.Album = request.Album;
}
- var musicVideo = item as MusicVideo;
- if (musicVideo != null)
+ if (item is MusicVideo musicVideo)
{
musicVideo.Album = request.Album;
}
- var series = item as Series;
- if (series != null)
+ if (item is Series series)
{
series.Status = GetSeriesStatus(request);
@@ -400,7 +391,6 @@ namespace MediaBrowser.Api
}
return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true);
-
}
}
}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index 15284958d..a54640b2f 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -348,28 +348,19 @@ namespace MediaBrowser.Api.Library
private string[] GetRepresentativeItemTypes(string contentType)
{
- switch (contentType)
+ return contentType switch
{
- case CollectionType.BoxSets:
- return new string[] { "BoxSet" };
- case CollectionType.Playlists:
- return new string[] { "Playlist" };
- case CollectionType.Movies:
- return new string[] { "Movie" };
- case CollectionType.TvShows:
- return new string[] { "Series", "Season", "Episode" };
- case CollectionType.Books:
- return new string[] { "Book" };
- case CollectionType.Music:
- return new string[] { "MusicAlbum", "MusicArtist", "Audio", "MusicVideo" };
- case CollectionType.HomeVideos:
- case CollectionType.Photos:
- return new string[] { "Video", "Photo" };
- case CollectionType.MusicVideos:
- return new string[] { "MusicVideo" };
- default:
- return new string[] { "Series", "Season", "Episode", "Movie" };
- }
+ CollectionType.BoxSets => new[] {"BoxSet"},
+ CollectionType.Playlists => new[] {"Playlist"},
+ CollectionType.Movies => new[] {"Movie"},
+ CollectionType.TvShows => new[] {"Series", "Season", "Episode"},
+ CollectionType.Books => new[] {"Book"},
+ CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"},
+ CollectionType.HomeVideos => new[] {"Video", "Photo"},
+ CollectionType.Photos => new[] {"Video", "Photo"},
+ CollectionType.MusicVideos => new[] {"MusicVideo"},
+ _ => new[] {"Series", "Season", "Episode", "Movie"}
+ };
}
private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
@@ -397,54 +388,22 @@ namespace MediaBrowser.Api.Library
{
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- return true;
- }
- else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase))
- {
- return true;
+ return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
}
- return false;
+ return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
}
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
.Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
.ToArray();
- if (metadataOptions.Length == 0)
- {
- return true;
- }
-
- return metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
+ return metadataOptions.Length == 0
+ || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
}
private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
@@ -453,50 +412,17 @@ namespace MediaBrowser.Api.Library
{
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- return true;
- }
- else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase))
- {
- return true;
+ return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase);
}
- return false;
+ return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase);
}
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
@@ -561,8 +487,7 @@ namespace MediaBrowser.Api.Library
foreach (var type in types)
{
- ImageOption[] defaultImageOptions = null;
- TypeOptions.DefaultImageOptions.TryGetValue(type, out defaultImageOptions);
+ TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions);
typeOptions.Add(new LibraryTypeOptions
{
@@ -609,8 +534,6 @@ namespace MediaBrowser.Api.Library
public object Get(GetSimilarItems request)
{
- var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
-
var item = string.IsNullOrEmpty(request.Id) ?
(!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
@@ -668,7 +591,7 @@ namespace MediaBrowser.Api.Library
// ExcludeArtistIds
if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
{
- query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds);
+ query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
}
List<BaseItem> itemsResult;
@@ -689,7 +612,6 @@ namespace MediaBrowser.Api.Library
var result = new QueryResult<BaseItemDto>
{
Items = returnList,
-
TotalRecordCount = itemsResult.Count
};
@@ -919,12 +841,10 @@ namespace MediaBrowser.Api.Library
private BaseItem TranslateParentItem(BaseItem item, User user)
{
- if (item.GetParent() is AggregateFolder)
- {
- return _libraryManager.GetUserRootFolder().GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
- }
-
- return item;
+ return item.GetParent() is AggregateFolder
+ ? _libraryManager.GetUserRootFolder().GetChildren(user, true)
+ .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path))
+ : item;
}
/// <summary>
@@ -1086,7 +1006,7 @@ namespace MediaBrowser.Api.Library
var item = string.IsNullOrEmpty(request.Id)
? (!request.UserId.Equals(Guid.Empty)
? _libraryManager.GetUserRootFolder()
- : (Folder)_libraryManager.RootFolder)
+ : _libraryManager.RootFolder)
: _libraryManager.GetItemById(request.Id);
if (item == null)
@@ -1094,18 +1014,13 @@ namespace MediaBrowser.Api.Library
throw new ResourceNotFoundException("Item not found.");
}
- BaseItem[] themeItems = Array.Empty<BaseItem>();
+ IEnumerable<BaseItem> themeItems;
while (true)
{
- themeItems = item.GetThemeSongs().ToArray();
-
- if (themeItems.Length > 0)
- {
- break;
- }
+ themeItems = item.GetThemeSongs();
- if (!request.InheritFromParent)
+ if (themeItems.Any() || !request.InheritFromParent)
{
break;
}
@@ -1119,11 +1034,9 @@ namespace MediaBrowser.Api.Library
}
var dtoOptions = GetDtoOptions(_authContext, request);
-
- var dtos = themeItems
- .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
-
- var items = dtos.ToArray();
+ var items = themeItems
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
+ .ToArray();
return new ThemeMediaResult
{
@@ -1140,9 +1053,7 @@ namespace MediaBrowser.Api.Library
/// <returns>System.Object.</returns>
public object Get(GetThemeVideos request)
{
- var result = GetThemeVideos(request);
-
- return ToOptimizedResult(result);
+ return ToOptimizedResult(GetThemeVideos(request));
}
public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
@@ -1152,7 +1063,7 @@ namespace MediaBrowser.Api.Library
var item = string.IsNullOrEmpty(request.Id)
? (!request.UserId.Equals(Guid.Empty)
? _libraryManager.GetUserRootFolder()
- : (Folder)_libraryManager.RootFolder)
+ : _libraryManager.RootFolder)
: _libraryManager.GetItemById(request.Id);
if (item == null)
@@ -1160,18 +1071,13 @@ namespace MediaBrowser.Api.Library
throw new ResourceNotFoundException("Item not found.");
}
- BaseItem[] themeItems = Array.Empty<BaseItem>();
+ IEnumerable<BaseItem> themeItems;
while (true)
{
- themeItems = item.GetThemeVideos().ToArray();
-
- if (themeItems.Length > 0)
- {
- break;
- }
+ themeItems = item.GetThemeVideos();
- if (!request.InheritFromParent)
+ if (themeItems.Any() || !request.InheritFromParent)
{
break;
}
@@ -1186,10 +1092,9 @@ namespace MediaBrowser.Api.Library
var dtoOptions = GetDtoOptions(_authContext, request);
- var dtos = themeItems
- .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
-
- var items = dtos.ToArray();
+ var items = themeItems
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
+ .ToArray();
return new ThemeMediaResult
{
diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs
index c071b42f7..1e300814f 100644
--- a/MediaBrowser.Api/Library/LibraryStructureService.cs
+++ b/MediaBrowser.Api/Library/LibraryStructureService.cs
@@ -327,15 +327,11 @@ namespace MediaBrowser.Api.Library
try
{
- var mediaPath = request.PathInfo;
-
- if (mediaPath == null)
+ var mediaPath = request.PathInfo ?? new MediaPathInfo
{
- mediaPath = new MediaPathInfo
- {
- Path = request.Path
- };
- }
+ Path = request.Path
+ };
+
_libraryManager.AddMediaPath(request.Name, mediaPath);
}
finally
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 4b4496139..5fe4c0cca 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -885,11 +885,10 @@ namespace MediaBrowser.Api.LiveTv
{
// SchedulesDirect requires a SHA1 hash of the user's password
// https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
- using (SHA1 sha = SHA1.Create())
- {
- return Hex.Encode(
- sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
- }
+ using SHA1 sha = SHA1.Create();
+
+ return Hex.Encode(
+ sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
}
public void Delete(DeleteListingProvider request)
@@ -1050,8 +1049,7 @@ namespace MediaBrowser.Api.LiveTv
{
query.IsSeries = true;
- var series = _libraryManager.GetItemById(request.LibrarySeriesId) as Series;
- if (series != null)
+ if (_libraryManager.GetItemById(request.LibrarySeriesId) is Series series)
{
query.Name = series.Name;
}
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index 889ebc928..46da8b909 100644
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ b/MediaBrowser.Api/Movies/MoviesService.cs
@@ -394,7 +394,7 @@ namespace MediaBrowser.Api.Movies
{
var people = _libraryManager.GetPeople(new InternalPeopleQuery
{
- PersonTypes = new string[]
+ PersonTypes = new[]
{
PersonType.Director
}
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 5029ce0bb..eb44cb426 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -137,12 +137,9 @@ namespace MediaBrowser.Api.Playback
var ext = outputFileExtension.ToLowerInvariant();
var folder = ServerConfigurationManager.GetTranscodePath();
- if (EnableOutputInSubFolder)
- {
- return Path.Combine(folder, filename, filename + ext);
- }
-
- return Path.Combine(folder, filename + ext);
+ return EnableOutputInSubFolder
+ ? Path.Combine(folder, filename, filename + ext)
+ : Path.Combine(folder, filename + ext);
}
protected virtual string GetDefaultEncoderPreset()
@@ -248,14 +245,8 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null
&& string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- logFilePrefix = "ffmpeg-remux";
- }
- else
- {
- logFilePrefix = "ffmpeg-directstream";
- }
+ logFilePrefix = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)
+ ? "ffmpeg-remux" : "ffmpeg-directstream";
}
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
@@ -389,195 +380,181 @@ namespace MediaBrowser.Api.Playback
continue;
}
- if (i == 0)
- {
- request.DeviceProfileId = val;
- }
- else if (i == 1)
- {
- request.DeviceId = val;
- }
- else if (i == 2)
- {
- request.MediaSourceId = val;
- }
- else if (i == 3)
- {
- request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- else if (i == 4)
- {
- if (videoRequest != null)
- {
- videoRequest.VideoCodec = val;
- }
- }
- else if (i == 5)
- {
- request.AudioCodec = val;
- }
- else if (i == 6)
- {
- if (videoRequest != null)
- {
- videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 7)
- {
- if (videoRequest != null)
- {
- videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 8)
- {
- if (videoRequest != null)
- {
- videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 9)
- {
- request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 10)
- {
- request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 11)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 12)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 13)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 14)
- {
- request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 15)
- {
- if (videoRequest != null)
- {
- videoRequest.Level = val;
- }
- }
- else if (i == 16)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 17)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 18)
- {
- if (videoRequest != null)
- {
- videoRequest.Profile = val;
- }
- }
- else if (i == 19)
- {
- // cabac no longer used
- }
- else if (i == 20)
- {
- request.PlaySessionId = val;
- }
- else if (i == 21)
- {
- // api_key
- }
- else if (i == 22)
- {
- request.LiveStreamId = val;
- }
- else if (i == 23)
- {
- // Duplicating ItemId because of MediaMonkey
- }
- else if (i == 24)
- {
- if (videoRequest != null)
- {
- videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 25)
- {
- if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
- {
- if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
+ switch (i)
+ {
+ case 0:
+ request.DeviceProfileId = val;
+ break;
+ case 1:
+ request.DeviceId = val;
+ break;
+ case 2:
+ request.MediaSourceId = val;
+ break;
+ case 3:
+ request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ break;
+ case 4:
+ if (videoRequest != null)
{
- videoRequest.SubtitleMethod = method;
+ videoRequest.VideoCodec = val;
}
- }
- }
- else if (i == 26)
- {
- request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 27)
- {
- if (videoRequest != null)
- {
- videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 28)
- {
- request.Tag = val;
- }
- else if (i == 29)
- {
- if (videoRequest != null)
- {
- videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 30)
- {
- request.SubtitleCodec = val;
- }
- else if (i == 31)
- {
- if (videoRequest != null)
- {
- videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 32)
- {
- if (videoRequest != null)
- {
- videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 33)
- {
- request.TranscodeReasons = val;
+
+ break;
+ case 5:
+ request.AudioCodec = val;
+ break;
+ case 6:
+ if (videoRequest != null)
+ {
+ videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 7:
+ if (videoRequest != null)
+ {
+ videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 8:
+ if (videoRequest != null)
+ {
+ videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 9:
+ request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 10:
+ request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 11:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 12:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 13:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 14:
+ request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 15:
+ if (videoRequest != null)
+ {
+ videoRequest.Level = val;
+ }
+
+ break;
+ case 16:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 17:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 18:
+ if (videoRequest != null)
+ {
+ videoRequest.Profile = val;
+ }
+
+ break;
+ case 19:
+ // cabac no longer used
+ break;
+ case 20:
+ request.PlaySessionId = val;
+ break;
+ case 21:
+ // api_key
+ break;
+ case 22:
+ request.LiveStreamId = val;
+ break;
+ case 23:
+ // Duplicating ItemId because of MediaMonkey
+ break;
+ case 24:
+ if (videoRequest != null)
+ {
+ videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 25:
+ if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
+ {
+ if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
+ {
+ videoRequest.SubtitleMethod = method;
+ }
+ }
+
+ break;
+ case 26:
+ request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 27:
+ if (videoRequest != null)
+ {
+ videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 28:
+ request.Tag = val;
+ break;
+ case 29:
+ if (videoRequest != null)
+ {
+ videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 30:
+ request.SubtitleCodec = val;
+ break;
+ case 31:
+ if (videoRequest != null)
+ {
+ videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 32:
+ if (videoRequest != null)
+ {
+ videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 33:
+ request.TranscodeReasons = val;
+ break;
}
}
}
@@ -630,14 +607,9 @@ namespace MediaBrowser.Api.Playback
throw new ArgumentException("Invalid timeseek header");
}
int index = value.IndexOf('-');
- if (index == -1)
- {
- value = value.Substring(Npt.Length);
- }
- else
- {
- value = value.Substring(Npt.Length, index - Npt.Length);
- }
+ value = index == -1
+ ? value.Substring(Npt.Length)
+ : value.Substring(Npt.Length, index - Npt.Length);
if (value.IndexOf(':') == -1)
{
@@ -856,21 +828,11 @@ namespace MediaBrowser.Api.Playback
{
state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
}
- else
+ else if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
{
- if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
- {
- var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
+ var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
- if (caps != null)
- {
- state.DeviceProfile = caps.DeviceProfile;
- }
- else
- {
- state.DeviceProfile = DlnaManager.GetProfile(headers);
- }
- }
+ state.DeviceProfile = caps == null ? DlnaManager.GetProfile(headers) : caps.DeviceProfile;
}
var profile = state.DeviceProfile;
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 0cbfe4bdf..52962366c 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (isLive)
{
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
if (job != null)
{
@@ -156,7 +156,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
if (job != null)
{
@@ -168,22 +168,19 @@ namespace MediaBrowser.Api.Playback.Hls
private string GetLivePlaylistText(string path, int segmentLength)
{
- using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
- {
- using (var reader = new StreamReader(stream))
- {
- var text = reader.ReadToEnd();
+ using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ using var reader = new StreamReader(stream);
- text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
+ var text = reader.ReadToEnd();
- var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
+ text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
- text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
- //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
- return text;
- }
- }
+ text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+
+ return text;
}
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
@@ -212,29 +209,25 @@ namespace MediaBrowser.Api.Playback.Hls
try
{
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
- using (var fileStream = GetPlaylistFileStream(playlist))
+ using var fileStream = GetPlaylistFileStream(playlist);
+ using var reader = new StreamReader(fileStream);
+ var count = 0;
+
+ while (!reader.EndOfStream)
{
- using (var reader = new StreamReader(fileStream))
- {
- var count = 0;
+ var line = reader.ReadLine();
- while (!reader.EndOfStream)
+ if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ count++;
+ if (count >= segmentCount)
{
- var line = reader.ReadLine();
-
- if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
- {
- count++;
- if (count >= segmentCount)
- {
- Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
- return;
- }
- }
+ Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
+ return;
}
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
}
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
catch (IOException)
{
@@ -247,17 +240,13 @@ namespace MediaBrowser.Api.Playback.Hls
protected Stream GetPlaylistFileStream(string path)
{
- var tmpPath = path + ".tmp";
- tmpPath = path;
-
- try
- {
- return new FileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
- }
- catch (IOException)
- {
- return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
- }
+ return new FileStream(
+ path,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.SequentialScan);
}
protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 3348a3187..20e18cc26 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -284,7 +284,7 @@ namespace MediaBrowser.Api.Playback.Hls
//}
Logger.LogDebug("returning {0} [general case]", segmentPath);
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
@@ -438,8 +438,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var segmentId = "0";
- var segmentRequest = request as GetHlsVideoSegment;
- if (segmentRequest != null)
+ if (request is GetHlsVideoSegment segmentRequest)
{
segmentId = segmentRequest.SegmentId;
}
@@ -690,8 +689,7 @@ namespace MediaBrowser.Api.Playback.Hls
return false;
}
- var request = state.Request as IMasterHlsRequest;
- if (request != null && !request.EnableAdaptiveBitrateStreaming)
+ if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming)
{
return false;
}
@@ -936,7 +934,7 @@ namespace MediaBrowser.Api.Playback.Hls
var framerate = state.VideoStream?.RealFrameRate;
- if (framerate != null && framerate.HasValue)
+ if (framerate.HasValue)
{
// This is to make sure keyframe interval is limited to our segment,
// as forcing keyframes is not enough.
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 040022a3d..db24eaca6 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -234,7 +234,7 @@ namespace MediaBrowser.Api.Playback
OpenToken = mediaSource.OpenToken
}).ConfigureAwait(false);
- info.MediaSources = new MediaSourceInfo[] { openStreamResult.MediaSource };
+ info.MediaSources = new[] { openStreamResult.MediaSource };
}
}
@@ -289,7 +289,7 @@ namespace MediaBrowser.Api.Playback
{
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
- mediaSources = new MediaSourceInfo[] { mediaSource };
+ mediaSources = new[] { mediaSource };
}
if (mediaSources.Length == 0)
@@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Playback
var options = new VideoOptions
{
- MediaSources = new MediaSourceInfo[] { mediaSource },
+ MediaSources = new[] { mediaSource },
Context = EncodingContext.Streaming,
DeviceId = auth.DeviceId,
ItemId = item.Id,
@@ -569,8 +569,7 @@ namespace MediaBrowser.Api.Playback
{
attachment.DeliveryUrl = string.Format(
CultureInfo.InvariantCulture,
- "{0}/Videos/{1}/{2}/Attachments/{3}",
- ServerConfigurationManager.Configuration.BaseUrl,
+ "/Videos/{0}/{1}/Attachments/{2}",
item.Id,
mediaSource.Id,
attachment.Index);
@@ -580,7 +579,7 @@ namespace MediaBrowser.Api.Playback
private long? GetMaxBitrate(long? clientMaxBitrate, User user)
{
var maxBitrate = clientMaxBitrate;
- var remoteClientMaxBitrate = user == null ? 0 : user.Policy.RemoteClientBitrateLimit;
+ var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0;
if (remoteClientMaxBitrate <= 0)
{
@@ -659,17 +658,9 @@ namespace MediaBrowser.Api.Playback
};
}).ThenBy(i =>
{
- if (maxBitrate.HasValue)
+ if (maxBitrate.HasValue && i.Bitrate.HasValue)
{
- if (i.Bitrate.HasValue)
- {
- if (i.Bitrate.Value <= maxBitrate.Value)
- {
- return 0;
- }
-
- return 2;
- }
+ return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
}
return 1;
diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs
index cbf981dfe..cebd4b49a 100644
--- a/MediaBrowser.Api/Playback/UniversalAudioService.cs
+++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs
@@ -167,7 +167,7 @@ namespace MediaBrowser.Api.Playback
AudioCodec = request.AudioCodec,
Protocol = request.TranscodingProtocol,
BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
- MaxAudioChannels = request.TranscodingAudioChannels.HasValue ? request.TranscodingAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : null
+ MaxAudioChannels = request.TranscodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
}
};
@@ -300,7 +300,7 @@ namespace MediaBrowser.Api.Playback
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
// TODO: remove this when we switch back to the segment muxer
- var supportedHLSContainers = new string[] { "mpegts", "fmp4" };
+ var supportedHLSContainers = new[] { "mpegts", "fmp4" };
var newRequest = new GetMasterHlsAudioPlaylist
{
diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs
index 16d3268b9..7f74511ee 100644
--- a/MediaBrowser.Api/PluginService.cs
+++ b/MediaBrowser.Api/PluginService.cs
@@ -243,9 +243,7 @@ namespace MediaBrowser.Api
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
var id = Guid.Parse(GetPathValue(1));
- var plugin = _appHost.Plugins.First(p => p.Id == id) as IHasPluginConfiguration;
-
- if (plugin == null)
+ if (!(_appHost.Plugins.First(p => p.Id == id) is IHasPluginConfiguration plugin))
{
throw new FileNotFoundException();
}
diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
index 2bd387229..e08a8482e 100644
--- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
+++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
@@ -123,9 +123,7 @@ namespace MediaBrowser.Api.ScheduledTasks
{
var isHidden = false;
- var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
-
- if (configurableTask != null)
+ if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
{
isHidden = configurableTask.IsHidden;
}
@@ -142,9 +140,7 @@ namespace MediaBrowser.Api.ScheduledTasks
{
var isEnabled = true;
- var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
-
- if (configurableTask != null)
+ if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
{
isEnabled = configurableTask.IsEnabled;
}
diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs
index 0a3dc19dc..e9d339c6e 100644
--- a/MediaBrowser.Api/SearchService.cs
+++ b/MediaBrowser.Api/SearchService.cs
@@ -234,59 +234,48 @@ namespace MediaBrowser.Api
SetThumbImageInfo(result, item);
SetBackdropImageInfo(result, item);
- var program = item as LiveTvProgram;
- if (program != null)
+ switch (item)
{
- result.StartDate = program.StartDate;
- }
-
- var hasSeries = item as IHasSeries;
- if (hasSeries != null)
- {
- result.Series = hasSeries.SeriesName;
- }
-
- var series = item as Series;
- if (series != null)
- {
- if (series.Status.HasValue)
- {
- result.Status = series.Status.Value.ToString();
- }
- }
-
- var album = item as MusicAlbum;
-
- if (album != null)
- {
- result.Artists = album.Artists;
- result.AlbumArtist = album.AlbumArtist;
- }
-
- var song = item as Audio;
-
- if (song != null)
- {
- result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
- result.Artists = song.Artists;
-
- album = song.AlbumEntity;
-
- if (album != null)
- {
- result.Album = album.Name;
- result.AlbumId = album.Id;
- }
- else
- {
- result.Album = song.Album;
- }
+ case IHasSeries hasSeries:
+ result.Series = hasSeries.SeriesName;
+ break;
+ case LiveTvProgram program:
+ result.StartDate = program.StartDate;
+ break;
+ case Series series:
+ if (series.Status.HasValue)
+ {
+ result.Status = series.Status.Value.ToString();
+ }
+
+ break;
+ case MusicAlbum album:
+ result.Artists = album.Artists;
+ result.AlbumArtist = album.AlbumArtist;
+ break;
+ case Audio song:
+ result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
+ result.Artists = song.Artists;
+
+ MusicAlbum musicAlbum = song.AlbumEntity;
+
+ if (musicAlbum != null)
+ {
+ result.Album = musicAlbum.Name;
+ result.AlbumId = musicAlbum.Id;
+ }
+ else
+ {
+ result.Album = song.Album;
+ }
+
+ break;
}
if (!item.ChannelId.Equals(Guid.Empty))
{
var channel = _libraryManager.GetItemById(item.ChannelId);
- result.ChannelName = channel == null ? null : channel.Name;
+ result.ChannelName = channel?.Name;
}
return result;
@@ -296,12 +285,9 @@ namespace MediaBrowser.Api
{
var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null;
- if (itemWithImage == null)
+ if (itemWithImage == null && item is Episode)
{
- if (item is Episode)
- {
- itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
- }
+ itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
}
if (itemWithImage == null)
@@ -323,12 +309,8 @@ namespace MediaBrowser.Api
private void SetBackdropImageInfo(SearchHint hint, BaseItem item)
{
- var itemWithImage = item.HasImage(ImageType.Backdrop) ? item : null;
-
- if (itemWithImage == null)
- {
- itemWithImage = GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
- }
+ var itemWithImage = (item.HasImage(ImageType.Backdrop) ? item : null)
+ ?? GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
if (itemWithImage != null)
{
diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs
index c4a7ae78e..f2968c6b5 100644
--- a/MediaBrowser.Api/Subtitles/SubtitleService.cs
+++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs
@@ -230,17 +230,14 @@ namespace MediaBrowser.Api.Subtitles
if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
{
- using (var stream = await GetSubtitles(request).ConfigureAwait(false))
- {
- using (var reader = new StreamReader(stream))
- {
- var text = reader.ReadToEnd();
+ using var stream = await GetSubtitles(request).ConfigureAwait(false);
+ using var reader = new StreamReader(stream);
- text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+ var text = reader.ReadToEnd();
- return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
- }
- }
+ text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+
+ return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
}
return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format));
diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs
index 3a3eeb8b8..c57cc93d5 100644
--- a/MediaBrowser.Api/System/SystemService.cs
+++ b/MediaBrowser.Api/System/SystemService.cs
@@ -168,12 +168,9 @@ namespace MediaBrowser.Api.System
.First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
// For older files, assume fully static
- if (file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1))
- {
- return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.Read);
- }
+ var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite;
- return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);
+ return ResultFactory.GetStaticFileResult(Request, file.FullName, fileShare);
}
/// <summary>
diff --git a/MediaBrowser.Api/TranscodingJob.cs b/MediaBrowser.Api/TranscodingJob.cs
index 6d944d19e..8c24e3ce1 100644
--- a/MediaBrowser.Api/TranscodingJob.cs
+++ b/MediaBrowser.Api/TranscodingJob.cs
@@ -92,10 +92,7 @@ namespace MediaBrowser.Api
{
lock (_timerLock)
{
- if (KillTimer != null)
- {
- KillTimer.Change(Timeout.Infinite, Timeout.Infinite);
- }
+ KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
}
}
diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index 334d1db51..cd8e8dfbe 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -424,9 +424,7 @@ namespace MediaBrowser.Api
if (!string.IsNullOrWhiteSpace(request.SeasonId))
{
- var season = _libraryManager.GetItemById(new Guid(request.SeasonId)) as Season;
-
- if (season == null)
+ if (!(_libraryManager.GetItemById(new Guid(request.SeasonId)) is Season season))
{
throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
}
@@ -444,14 +442,7 @@ namespace MediaBrowser.Api
var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
- if (season == null)
- {
- episodes = new List<BaseItem>();
- }
- else
- {
- episodes = ((Season)season).GetEpisodes(user, dtoOptions);
- }
+ episodes = season == null ? new List<BaseItem>() : ((Season)season).GetEpisodes(user, dtoOptions);
}
else
{
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
index adb0a440f..3d08d5437 100644
--- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
@@ -126,12 +126,7 @@ namespace MediaBrowser.Api.UserLibrary
protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{
- if (request is GetAlbumArtists)
- {
- return LibraryManager.GetAlbumArtists(query);
- }
-
- return LibraryManager.GetArtists(query);
+ return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
index 9fa222d32..c4a52d5f5 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
@@ -82,8 +82,7 @@ namespace MediaBrowser.Api.UserLibrary
{
var parent = GetParentItem(request);
- var collectionFolder = parent as IHasCollectionType;
- if (collectionFolder != null)
+ if (parent is IHasCollectionType collectionFolder)
{
return collectionFolder.CollectionType;
}
@@ -274,7 +273,7 @@ namespace MediaBrowser.Api.UserLibrary
DtoOptions = dtoOptions
};
- Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
+ bool Filter(BaseItem i) => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
if (parentItem.IsFolder)
{
@@ -284,18 +283,18 @@ namespace MediaBrowser.Api.UserLibrary
{
items = request.Recursive ?
folder.GetRecursiveChildren(user, query).ToList() :
- folder.GetChildren(user, true).Where(filter).ToList();
+ folder.GetChildren(user, true).Where(Filter).ToList();
}
else
{
items = request.Recursive ?
- folder.GetRecursiveChildren(filter) :
- folder.Children.Where(filter).ToList();
+ folder.GetRecursiveChildren(Filter) :
+ folder.Children.Where(Filter).ToList();
}
}
else
{
- items = new[] { parentItem }.Where(filter).ToList();
+ items = new[] { parentItem }.Where(Filter).ToList();
}
var extractedItems = GetAllItems(request, items);
@@ -346,30 +345,21 @@ namespace MediaBrowser.Api.UserLibrary
private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
{
// Exclude item types
- if (excludeItemTypes.Length > 0)
+ if (excludeItemTypes.Length > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
{
- if (excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
+ return false;
}
// Include item types
- if (includeItemTypes.Length > 0)
+ if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
{
- if (!includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
+ return false;
}
// Include MediaTypes
- if (mediaTypes.Length > 0)
+ if (mediaTypes.Length > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
- if (!mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
+ return false;
}
return true;
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
index a26f59573..7561b5c89 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
@@ -396,12 +396,10 @@ namespace MediaBrowser.Api.UserLibrary
public VideoType[] GetVideoTypes()
{
- if (string.IsNullOrEmpty(VideoTypes))
- {
- return Array.Empty<VideoType>();
- }
-
- return VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
+ return string.IsNullOrEmpty(VideoTypes)
+ ? Array.Empty<VideoType>()
+ : VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(v => Enum.Parse<VideoType>(v, true)).ToArray();
}
/// <summary>
@@ -412,12 +410,10 @@ namespace MediaBrowser.Api.UserLibrary
{
var val = Filters;
- if (string.IsNullOrEmpty(val))
- {
- return new ItemFilter[] { };
- }
-
- return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)).ToArray();
+ return string.IsNullOrEmpty(val)
+ ? Array.Empty<ItemFilter>()
+ : val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(v => Enum.Parse<ItemFilter>(v, true)).ToArray();
}
/// <summary>
@@ -428,12 +424,9 @@ namespace MediaBrowser.Api.UserLibrary
{
var val = ImageTypes;
- if (string.IsNullOrEmpty(val))
- {
- return new ImageType[] { };
- }
-
- return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
+ return string.IsNullOrEmpty(val)
+ ? Array.Empty<ImageType>()
+ : val.Split(',').Select(v => Enum.Parse<ImageType>(v, true)).ToArray();
}
/// <summary>
@@ -469,7 +462,9 @@ namespace MediaBrowser.Api.UserLibrary
var sortOrderIndex = sortOrders.Length > i ? i : 0;
var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
- var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending;
+ var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
+ ? MediaBrowser.Model.Entities.SortOrder.Descending
+ : MediaBrowser.Model.Entities.SortOrder.Ascending;
result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
}
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index c7b505171..ac59c3030 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -199,21 +199,22 @@ namespace MediaBrowser.Api.UserLibrary
item = _libraryManager.GetUserRootFolder();
}
- Folder folder = item as Folder;
- if (folder == null)
+ if (!(item is Folder folder))
{
folder = _libraryManager.GetUserRootFolder();
}
- var hasCollectionType = folder as IHasCollectionType;
- if (hasCollectionType != null
+ if (folder is IHasCollectionType hasCollectionType
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
{
request.Recursive = true;
request.IncludeItemTypes = "Playlist";
}
- bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id);
+ bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)
+ // Assume all folders inside an EnabledChannel are enabled
+ || user.Policy.EnabledChannels.Any(i => new Guid(i) == item.Id);
+
var collectionFolders = _libraryManager.GetCollectionFolders(item);
foreach (var collectionFolder in collectionFolders)
{
@@ -225,7 +226,7 @@ namespace MediaBrowser.Api.UserLibrary
}
}
- if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder)
+ if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder && !user.Policy.EnableAllChannels)
{
Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name);
return new QueryResult<BaseItem>
diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
index 2ec08f578..7fa750adb 100644
--- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
+++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
@@ -361,7 +361,8 @@ namespace MediaBrowser.Api.UserLibrary
var dtoOptions = GetDtoOptions(_authContext, request);
- var dtos = item.GetDisplayExtras()
+ var dtos = item
+ .GetExtras(BaseItem.DisplayExtraTypes)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
return dtos.ToArray();
diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs
index 46b6d5a94..b11fd48d3 100644
--- a/MediaBrowser.Api/VideosService.cs
+++ b/MediaBrowser.Api/VideosService.cs
@@ -139,17 +139,11 @@ namespace MediaBrowser.Api
.ToList();
var primaryVersion = videosWithVersions.FirstOrDefault();
-
if (primaryVersion == null)
{
primaryVersion = items.OrderBy(i =>
{
- if (i.Video3DFormat.HasValue)
- {
- return 1;
- }
-
- if (i.VideoType != Model.Entities.VideoType.VideoFile)
+ if (i.Video3DFormat.HasValue || i.VideoType != Model.Entities.VideoType.VideoFile)
{
return 1;
}
@@ -158,10 +152,7 @@ namespace MediaBrowser.Api
})
.ThenByDescending(i =>
{
- var stream = i.GetDefaultVideoStream();
-
- return stream == null || stream.Width == null ? 0 : stream.Width.Value;
-
+ return i.GetDefaultVideoStream()?.Width ?? 0;
}).First();
}
diff --git a/MediaBrowser.Common/Cryptography/Extensions.cs b/MediaBrowser.Common/Cryptography/CryptoExtensions.cs
index 1e32a6d1a..157b0ed10 100644
--- a/MediaBrowser.Common/Cryptography/Extensions.cs
+++ b/MediaBrowser.Common/Cryptography/CryptoExtensions.cs
@@ -9,7 +9,7 @@ namespace MediaBrowser.Common.Cryptography
/// <summary>
/// Class containing extension methods for working with Jellyfin cryptography objects.
/// </summary>
- public static class Extensions
+ public static class CryptoExtensions
{
/// <summary>
/// Creates a new <see cref="PasswordHash" /> instance.
diff --git a/MediaBrowser.Common/Extensions/ProcessExtensions.cs b/MediaBrowser.Common/Extensions/ProcessExtensions.cs
new file mode 100644
index 000000000..c74787122
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/ProcessExtensions.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.Extensions
+{
+ /// <summary>
+ /// Extension methods for <see cref="Process"/>.
+ /// </summary>
+ public static class ProcessExtensions
+ {
+ /// <summary>
+ /// Asynchronously wait for the process to exit.
+ /// </summary>
+ /// <param name="process">The process to wait for.</param>
+ /// <param name="timeout">The duration to wait before cancelling waiting for the task.</param>
+ /// <returns>True if the task exited normally, false if the timeout elapsed before the process exited.</returns>
+ /// <exception cref="InvalidOperationException">If <see cref="Process.EnableRaisingEvents"/> is not set to true for the process.</exception>
+ public static async Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
+ {
+ using (var cancelTokenSource = new CancellationTokenSource(timeout))
+ {
+ return await WaitForExitAsync(process, cancelTokenSource.Token).ConfigureAwait(false);
+ }
+ }
+
+ /// <summary>
+ /// Asynchronously wait for the process to exit.
+ /// </summary>
+ /// <param name="process">The process to wait for.</param>
+ /// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the process to exit.</param>
+ /// <returns>True if the task exited normally, false if cancelled before the process exited.</returns>
+ public static async Task<bool> WaitForExitAsync(this Process process, CancellationToken cancelToken)
+ {
+ if (!process.EnableRaisingEvents)
+ {
+ throw new InvalidOperationException("EnableRisingEvents must be enabled to async wait for a task to exit.");
+ }
+
+ // Add an event handler for the process exit event
+ var tcs = new TaskCompletionSource<bool>();
+ process.Exited += (sender, args) => tcs.TrySetResult(true);
+
+ // Return immediately if the process has already exited
+ if (process.HasExitedSafe())
+ {
+ return true;
+ }
+
+ // Register with the cancellation token then await
+ using (var cancelRegistration = cancelToken.Register(() => tcs.TrySetResult(process.HasExitedSafe())))
+ {
+ return await tcs.Task.ConfigureAwait(false);
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the associated process has been terminated using
+ /// <see cref="Process.HasExited"/>. This is safe to call even if there is no operating system process
+ /// associated with the <see cref="Process"/>.
+ /// </summary>
+ /// <param name="process">The process to check the exit status for.</param>
+ /// <returns>
+ /// True if the operating system process referenced by the <see cref="Process"/> component has
+ /// terminated, or if there is no associated operating system process; otherwise, false.
+ /// </returns>
+ private static bool HasExitedSafe(this Process process)
+ {
+ try
+ {
+ return process.HasExited;
+ }
+ catch (InvalidOperationException)
+ {
+ return true;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 548c214dd..3b0347802 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -30,7 +30,7 @@
<!-- Code analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <!-- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<!-- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> -->
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs
index 51962001e..38274a80e 100644
--- a/MediaBrowser.Common/Net/HttpRequestOptions.cs
+++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Common.Net
RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
CacheMode = CacheMode.None;
- DecompressionMethod = CompressionMethod.Deflate;
+ DecompressionMethod = CompressionMethods.Deflate;
}
/// <summary>
@@ -29,7 +29,7 @@ namespace MediaBrowser.Common.Net
/// <value>The URL.</value>
public string Url { get; set; }
- public CompressionMethod DecompressionMethod { get; set; }
+ public CompressionMethods DecompressionMethod { get; set; }
/// <summary>
/// Gets or sets the accept header.
@@ -83,8 +83,6 @@ namespace MediaBrowser.Common.Net
public string RequestContent { get; set; }
- public byte[] RequestContentBytes { get; set; }
-
public bool BufferContent { get; set; }
public bool LogErrorResponseBody { get; set; }
@@ -112,7 +110,7 @@ namespace MediaBrowser.Common.Net
}
[Flags]
- public enum CompressionMethod
+ public enum CompressionMethods
{
None = 0b00000001,
Deflate = 0b00000010,
diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs
index 56a951ebf..d4fee6c78 100644
--- a/MediaBrowser.Common/Net/HttpResponseInfo.cs
+++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Common.Net
/// <summary>
/// Class HttpResponseInfo.
/// </summary>
- public class HttpResponseInfo : IDisposable
+ public sealed class HttpResponseInfo : IDisposable
{
#pragma warning disable CS1591
public HttpResponseInfo()
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
index 8ea492261..93f330e5b 100644
--- a/MediaBrowser.Common/Updates/IInstallationManager.cs
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -92,7 +92,7 @@ namespace MediaBrowser.Common.Updates
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The available plugin updates.</returns>
- IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
+ Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
/// <summary>
/// Installs the package.
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 7380e6da1..56a361e0e 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1326,8 +1326,9 @@ namespace MediaBrowser.Controller.Entities
}
// Use some hackery to get the extra type based on foldername
- Enum.TryParse(extraFolderName.Replace(" ", ""), true, out ExtraType extraType);
- item.ExtraType = extraType;
+ item.ExtraType = Enum.TryParse(extraFolderName.Replace(" ", string.Empty), true, out ExtraType extraType)
+ ? extraType
+ : Model.Entities.ExtraType.Unknown;
return item;
@@ -2877,14 +2878,29 @@ namespace MediaBrowser.Controller.Entities
/// <value>The remote trailers.</value>
public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
+ /// <summary>
+ /// Get all extras associated with this item, sorted by <see cref="SortName"/>.
+ /// </summary>
+ /// <returns>An enumerable containing the items.</returns>
public IEnumerable<BaseItem> GetExtras()
{
- return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
+ return ExtraIds
+ .Select(LibraryManager.GetItemById)
+ .Where(i => i != null)
+ .OrderBy(i => i.SortName);
}
+ /// <summary>
+ /// Get all extras with specific types that are associated with this item.
+ /// </summary>
+ /// <param name="extraTypes">The types of extras to retrieve.</param>
+ /// <returns>An enumerable containing the extras.</returns>
public IEnumerable<BaseItem> GetExtras(IReadOnlyCollection<ExtraType> extraTypes)
{
- return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i?.ExtraType != null && extraTypes.Contains(i.ExtraType.Value));
+ return ExtraIds
+ .Select(LibraryManager.GetItemById)
+ .Where(i => i != null)
+ .Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
}
public IEnumerable<BaseItem> GetTrailers()
@@ -2895,11 +2911,6 @@ namespace MediaBrowser.Controller.Entities
return Array.Empty<BaseItem>();
}
- public IEnumerable<BaseItem> GetDisplayExtras()
- {
- return GetExtras(DisplayExtraTypes);
- }
-
public virtual bool IsHD => Height >= 720;
public bool IsShortcut { get; set; }
@@ -2917,8 +2928,19 @@ namespace MediaBrowser.Controller.Entities
return RunTimeTicks ?? 0;
}
- // Possible types of extra videos
- public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new[] { Model.Entities.ExtraType.BehindTheScenes, Model.Entities.ExtraType.Clip, Model.Entities.ExtraType.DeletedScene, Model.Entities.ExtraType.Interview, Model.Entities.ExtraType.Sample, Model.Entities.ExtraType.Scene };
+ /// <summary>
+ /// Extra types that should be counted and displayed as "Special Features" in the UI.
+ /// </summary>
+ public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType>
+ {
+ Model.Entities.ExtraType.Unknown,
+ Model.Entities.ExtraType.BehindTheScenes,
+ Model.Entities.ExtraType.Clip,
+ Model.Entities.ExtraType.DeletedScene,
+ Model.Entities.ExtraType.Interview,
+ Model.Entities.ExtraType.Sample,
+ Model.Entities.ExtraType.Scene
+ };
public virtual bool SupportsExternalTransfer => false;
diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
index 1613531b5..011975dd2 100644
--- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
@@ -4,11 +4,21 @@ namespace MediaBrowser.Controller.Entities
{
public class InternalPeopleQuery
{
+ /// <summary>
+ /// Gets or sets the maximum number of items the query should return.
+ /// </summary>
+ public int Limit { get; set; }
+
public Guid ItemId { get; set; }
+
public string[] PersonTypes { get; set; }
+
public string[] ExcludePersonTypes { get; set; }
+
public int? MaxListOrder { get; set; }
+
public Guid AppearsInItemId { get; set; }
+
public string NameContains { get; set; }
public InternalPeopleQuery()
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index ce576a6c3..8fefdd770 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -78,8 +78,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(hwType)
&& encodingOptions.EnableHardwareEncoding
- && codecMap.ContainsKey(hwType)
- && CheckVaapi(state, hwType, encodingOptions))
+ && codecMap.ContainsKey(hwType))
{
var preferredEncoder = codecMap[hwType];
@@ -93,23 +92,6 @@ namespace MediaBrowser.Controller.MediaEncoding
return defaultEncoder;
}
- private bool CheckVaapi(EncodingJobInfo state, string hwType, EncodingOptions encodingOptions)
- {
- if (!string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase))
- {
- // No vaapi requested, return OK.
- return true;
- }
-
- if (string.IsNullOrEmpty(encodingOptions.VaapiDevice))
- {
- // No device specified, return OK.
- return true;
- }
-
- return IsVaapiSupported(state);
- }
-
private bool IsVaapiSupported(EncodingJobInfo state)
{
var videoStream = state.VideoStream;
@@ -424,7 +406,13 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{
- return "aac -strict experimental";
+ // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
+ if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
+ {
+ return "libfdk_aac";
+ }
+
+ return "aac";
}
if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
@@ -1605,7 +1593,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// For VAAPI and CUVID decoder
// these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
// thus needs to be manually adjusted.
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ if ((IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|| (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
{
var videoStream = state.VideoStream;
@@ -1636,7 +1624,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"";
// When the input may or may not be hardware VAAPI decodable
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
/*
[base]: HW scaling video to OutputSize
@@ -1648,7 +1636,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
- else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding)
+ else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
/*
[base]: SW scaling video to OutputSize
@@ -1996,14 +1985,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
// When the input may or may not be hardware VAAPI decodable
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
// When the input may or may not be hardware QSV decodable
- else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
+ else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
if (!hasTextSubs)
{
@@ -2013,25 +2002,29 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
-
- else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding)
+ else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
var codec = videoStream.Codec.ToLowerInvariant();
- var pixelFormat = videoStream.PixelFormat.ToLowerInvariant();
+ var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
+ || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
// Assert 10-bit hardware VAAPI decodable
- if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
- && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
- {
+ if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
+ {
+ /*
+ Download data from GPU to CPU as p010le format.
+ Colorspace conversion is unnecessary here as libx264 will handle it.
+ If this step is missing, it will fail on AMD but not on intel.
+ */
filters.Add("hwdownload");
filters.Add("format=p010le");
- filters.Add("format=nv12");
}
// Assert 8-bit hardware VAAPI decodable
- else if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) == -1)
+ else if (!isColorDepth10)
{
filters.Add("hwdownload");
filters.Add("format=nv12");
@@ -2077,7 +2070,7 @@ namespace MediaBrowser.Controller.MediaEncoding
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 subttiles (GH issue #642)
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
if (state.SubtitleStream != null
&& state.SubtitleStream.IsTextSubtitleStream
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index eb5a8ded8..75fc43a04 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -24,8 +24,7 @@ namespace MediaBrowser.Controller.Persistence
/// Deletes the item.
/// </summary>
/// <param name="id">The identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- void DeleteItem(Guid id, CancellationToken cancellationToken);
+ void DeleteItem(Guid id);
/// <summary>
/// Saves the items.
@@ -169,4 +168,3 @@ namespace MediaBrowser.Controller.Persistence
List<string> GetAllArtistNames();
}
}
-
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index 43d33c716..3f177a9fa 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -155,47 +155,44 @@ namespace MediaBrowser.MediaEncoding.Attachments
inputPath,
attachmentStreamIndex,
outputPath);
- var startInfo = new ProcessStartInfo
- {
- Arguments = processArgs,
- FileName = _mediaEncoder.EncoderPath,
- UseShellExecute = false,
- CreateNoWindow = true,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- };
- var process = new Process
- {
- StartInfo = startInfo
- };
- _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ int exitCode;
- process.Start();
+ using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ Arguments = processArgs,
+ FileName = _mediaEncoder.EncoderPath,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
+ {
+ _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
- var processTcs = new TaskCompletionSource<bool>();
- process.EnableRaisingEvents = true;
- process.Exited += (sender, args) => processTcs.TrySetResult(true);
- var unregister = cancellationToken.Register(() => processTcs.TrySetResult(process.HasExited));
- var ranToCompletion = await processTcs.Task.ConfigureAwait(false);
- unregister.Dispose();
+ process.Start();
- if (!ranToCompletion)
- {
- try
- {
- _logger.LogWarning("Killing ffmpeg attachment extraction process");
- process.Kill();
- }
- catch (Exception ex)
+ var ranToCompletion = await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
+
+ if (!ranToCompletion)
{
- _logger.LogError(ex, "Error killing attachment extraction process");
+ try
+ {
+ _logger.LogWarning("Killing ffmpeg attachment extraction process");
+ process.Kill();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error killing attachment extraction process");
+ }
}
- }
- var exitCode = ranToCompletion ? process.ExitCode : -1;
-
- process.Dispose();
+ exitCode = ranToCompletion ? process.ExitCode : -1;
+ }
var failed = false;
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index f5decdc0d..6e036d24c 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -42,6 +42,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"libvpx",
"libvpx-vp9",
"aac",
+ "libfdk_aac",
"libmp3lame",
"libopus",
"libvorbis",
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index f3f2b86ee..c2bfac9d6 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -13,7 +13,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
@@ -22,6 +21,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using System.Diagnostics;
namespace MediaBrowser.MediaEncoding.Encoder
{
@@ -38,7 +38,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly ILogger _logger;
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
- private readonly IProcessFactory _processFactory;
private readonly ILocalizationManager _localization;
private readonly Func<ISubtitleEncoder> _subtitleEncoder;
private readonly IConfiguration _configuration;
@@ -58,7 +57,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
ILogger<MediaEncoder> logger,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
- IProcessFactory processFactory,
ILocalizationManager localization,
Func<ISubtitleEncoder> subtitleEncoder,
IConfiguration configuration,
@@ -67,7 +65,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
- _processFactory = processFactory;
_localization = localization;
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
_subtitleEncoder = subtitleEncoder;
@@ -362,30 +359,33 @@ namespace MediaBrowser.MediaEncoding.Encoder
: "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
args = string.Format(args, probeSizeArgument, inputPath).Trim();
- var process = _processFactory.Create(new ProcessOptions
+ var process = new Process
{
- CreateNoWindow = true,
- UseShellExecute = false,
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
- // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
- RedirectStandardOutput = true,
+ // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
+ RedirectStandardOutput = true,
- FileName = _ffprobePath,
- Arguments = args,
+ FileName = _ffprobePath,
+ Arguments = args,
- IsHidden = true,
- ErrorDialog = false,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false,
+ },
EnableRaisingEvents = true
- });
+ };
if (forceEnableLogging)
{
- _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ _logger.LogInformation("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
}
else
{
- _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ _logger.LogDebug("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
}
using (var processWrapper = new ProcessWrapper(process, this))
@@ -571,18 +571,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- var process = _processFactory.Create(new ProcessOptions
+ var process = new Process
{
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = _ffmpegPath,
- Arguments = args,
- IsHidden = true,
- ErrorDialog = false,
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _ffmpegPath,
+ Arguments = args,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false,
+ },
EnableRaisingEvents = true
- });
+ };
- _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ _logger.LogDebug("{ProcessFileName} {ProcessArguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
using (var processWrapper = new ProcessWrapper(process, this))
{
@@ -599,7 +602,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
timeoutMs = DefaultImageExtractionTimeout;
}
- ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
+ ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMilliseconds(timeoutMs)).ConfigureAwait(false);
if (!ranToCompletion)
{
@@ -700,23 +703,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- var process = _processFactory.Create(new ProcessOptions
+ var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = _ffmpegPath,
Arguments = args,
- IsHidden = true,
- ErrorDialog = false,
- EnableRaisingEvents = true
- });
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ };
- _logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
+ _logger.LogInformation(processStartInfo.FileName + " " + processStartInfo.Arguments);
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
bool ranToCompletion = false;
+ var process = new Process
+ {
+ StartInfo = processStartInfo,
+ EnableRaisingEvents = true
+ };
using (var processWrapper = new ProcessWrapper(process, this))
{
try
@@ -732,7 +739,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
while (isResponsive)
{
- if (await process.WaitForExitAsync(30000).ConfigureAwait(false))
+ if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false))
{
ranToCompletion = true;
break;
@@ -949,14 +956,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
private bool _disposed = false;
- public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
+ public ProcessWrapper(Process process, MediaEncoder mediaEncoder)
{
Process = process;
_mediaEncoder = mediaEncoder;
Process.Exited += OnProcessExited;
}
- public IProcess Process { get; }
+ public Process Process { get; }
public bool HasExited { get; private set; }
@@ -964,7 +971,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
void OnProcessExited(object sender, EventArgs e)
{
- var process = (IProcess)sender;
+ var process = (Process)sender;
HasExited = true;
@@ -979,7 +986,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
DisposeProcess(process);
}
- private void DisposeProcess(IProcess process)
+ private void DisposeProcess(Process process)
{
lock (_mediaEncoder._runningProcessesLock)
{
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index b76b52941..ba171295e 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -12,7 +13,6 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -31,7 +31,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IMediaEncoder _mediaEncoder;
private readonly IHttpClient _httpClient;
private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IProcessFactory _processFactory;
public SubtitleEncoder(
ILibraryManager libraryManager,
@@ -40,8 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
IHttpClient httpClient,
- IMediaSourceManager mediaSourceManager,
- IProcessFactory processFactory)
+ IMediaSourceManager mediaSourceManager)
{
_libraryManager = libraryManager;
_logger = logger;
@@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_mediaEncoder = mediaEncoder;
_httpClient = httpClient;
_mediaSourceManager = mediaSourceManager;
- _processFactory = processFactory;
}
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
@@ -429,49 +426,53 @@ namespace MediaBrowser.MediaEncoding.Subtitles
encodingParam = " -sub_charenc " + encodingParam;
}
- var process = _processFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
- EnableRaisingEvents = true,
- IsHidden = true,
- ErrorDialog = false
- });
-
- _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error starting ffmpeg");
+ int exitCode;
- throw;
- }
-
- var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
+ using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
+ {
+ _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- if (!ranToCompletion)
- {
try
{
- _logger.LogInformation("Killing ffmpeg subtitle conversion process");
-
- process.Kill();
+ process.Start();
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error killing subtitle conversion process");
+ _logger.LogError(ex, "Error starting ffmpeg");
+
+ throw;
}
- }
- var exitCode = ranToCompletion ? process.ExitCode : -1;
+ var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
+
+ if (!ranToCompletion)
+ {
+ try
+ {
+ _logger.LogInformation("Killing ffmpeg subtitle conversion process");
- process.Dispose();
+ process.Kill();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error killing subtitle conversion process");
+ }
+ }
+
+ exitCode = ranToCompletion ? process.ExitCode : -1;
+ }
var failed = false;
@@ -578,49 +579,53 @@ namespace MediaBrowser.MediaEncoding.Subtitles
outputCodec,
outputPath);
- var process = _processFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- EnableRaisingEvents = true,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = processArgs,
- IsHidden = true,
- ErrorDialog = false
- });
-
- _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error starting ffmpeg");
+ int exitCode;
- throw;
- }
-
- var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
+ using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = processArgs,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
+ {
+ _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
- if (!ranToCompletion)
- {
try
{
- _logger.LogWarning("Killing ffmpeg subtitle extraction process");
-
- process.Kill();
+ process.Start();
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error killing subtitle extraction process");
+ _logger.LogError(ex, "Error starting ffmpeg");
+
+ throw;
}
- }
- var exitCode = ranToCompletion ? process.ExitCode : -1;
+ var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
+
+ if (!ranToCompletion)
+ {
+ try
+ {
+ _logger.LogWarning("Killing ffmpeg subtitle extraction process");
- process.Dispose();
+ process.Kill();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error killing subtitle extraction process");
+ }
+ }
+
+ exitCode = ranToCompletion ? process.ExitCode : -1;
+ }
var failed = false;
diff --git a/MediaBrowser.Model/Diagnostics/IProcess.cs b/MediaBrowser.Model/Diagnostics/IProcess.cs
deleted file mode 100644
index 514d1e737..000000000
--- a/MediaBrowser.Model/Diagnostics/IProcess.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Model.Diagnostics
-{
- public interface IProcess : IDisposable
- {
- event EventHandler Exited;
-
- void Kill();
- bool WaitForExit(int timeMs);
- Task<bool> WaitForExitAsync(int timeMs);
- int ExitCode { get; }
- void Start();
- StreamWriter StandardInput { get; }
- StreamReader StandardError { get; }
- StreamReader StandardOutput { get; }
- ProcessOptions StartInfo { get; }
- }
-}
diff --git a/MediaBrowser.Model/Diagnostics/IProcessFactory.cs b/MediaBrowser.Model/Diagnostics/IProcessFactory.cs
deleted file mode 100644
index 57082acc5..000000000
--- a/MediaBrowser.Model/Diagnostics/IProcessFactory.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Diagnostics
-{
- public interface IProcessFactory
- {
- IProcess Create(ProcessOptions options);
- }
-
- public class ProcessOptions
- {
- public string FileName { get; set; }
- public string Arguments { get; set; }
- public string WorkingDirectory { get; set; }
- public bool CreateNoWindow { get; set; }
- public bool UseShellExecute { get; set; }
- public bool EnableRaisingEvents { get; set; }
- public bool ErrorDialog { get; set; }
- public bool RedirectStandardError { get; set; }
- public bool RedirectStandardInput { get; set; }
- public bool RedirectStandardOutput { get; set; }
- public bool IsHidden { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
index a5947bbf4..b43f8633e 100644
--- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
+++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
@@ -25,12 +25,12 @@ namespace MediaBrowser.Model.Dlna
public bool SupportsVideoCodec(string codec)
{
- return ContainerProfile.ContainsContainer(VideoCodec, codec);
+ return Type == DlnaProfileType.Video && ContainerProfile.ContainsContainer(VideoCodec, codec);
}
public bool SupportsAudioCodec(string codec)
{
- return ContainerProfile.ContainsContainer(AudioCodec, codec);
+ return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec);
}
}
}
diff --git a/MediaBrowser.Model/Entities/ExtraType.cs b/MediaBrowser.Model/Entities/ExtraType.cs
index 857e92adb..aca4bd282 100644
--- a/MediaBrowser.Model/Entities/ExtraType.cs
+++ b/MediaBrowser.Model/Entities/ExtraType.cs
@@ -4,6 +4,7 @@ namespace MediaBrowser.Model.Entities
{
public enum ExtraType
{
+ Unknown = 0,
Clip = 1,
Trailer = 2,
BehindTheScenes = 3,
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 0fdfe5761..27486c68f 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -18,7 +18,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.3" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
- <PackageReference Include="System.Text.Json" Version="4.7.0" />
+ <PackageReference Include="System.Text.Json" Version="4.7.1" />
</ItemGroup>
<ItemGroup>
diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1
deleted file mode 100644
index c762137a7..000000000
--- a/deployment/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=false -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server
-}
-
-function Install-FFMPEG {
- param(
- [string]$ResolvedInstallLocation,
- [string]$Architecture,
- [string]$FFMPEGVersionX86 = "ffmpeg-4.2.1-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/deployment/windows/dependencies.txt b/deployment/windows/dependencies.txt
deleted file mode 100644
index 16f77cce7..000000000
--- a/deployment/windows/dependencies.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-dotnet
-nsis
diff --git a/deployment/windows/dialogs/confirmation.nsddef b/deployment/windows/dialogs/confirmation.nsddef
deleted file mode 100644
index 969ebacd6..000000000
--- a/deployment/windows/dialogs/confirmation.nsddef
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-This file was created by NSISDialogDesigner 1.4.4.0
-http://coolsoft.altervista.org/nsisdialogdesigner
-Do not edit manually!
--->
-<Dialog Name="confirmation" Title="Confirmation Page" Subtitle="Please confirm your choices for Jellyfin Server installation" GenerateShowFunction="False">
- <HeaderCustomScript>!include "helpers\StrSlash.nsh"</HeaderCustomScript>
- <CreateFunctionCustomScript>${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 \
- }"</CreateFunctionCustomScript>
- <RichText Name="ConfirmRichText" Location="12, 12" Size="426, 204" TabIndex="0" ExStyle="WS_EX_STATICEDGE" />
-</Dialog>
diff --git a/deployment/windows/dialogs/confirmation.nsdinc b/deployment/windows/dialogs/confirmation.nsdinc
deleted file mode 100644
index f00e9b43a..000000000
--- a/deployment/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/deployment/windows/dialogs/service-config.nsddef b/deployment/windows/dialogs/service-config.nsddef
deleted file mode 100644
index 3509ada24..000000000
--- a/deployment/windows/dialogs/service-config.nsddef
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-This file was created by NSISDialogDesigner 1.4.4.0
-http://coolsoft.altervista.org/nsisdialogdesigner
-Do not edit manually!
--->
-<Dialog Name="service_config" Title="CoOnfigure the service" Subtitle="This controls what type of access the server gets to this system." GenerateShowFunction="False">
- <CheckBox Name="StartServiceAfterInstall" Location="12, 192" Size="426, 24" Text="Start Service after Install" Checked="True" TabIndex="0" />
- <Label Name="LocalSystemAccountLabel" Location="12, 115" Size="426, 46" Text="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." TabIndex="1" />
- <Label Name="NetworkServiceAccountLabel" Location="12, 39" Size="426, 46" Text="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." TabIndex="2" />
- <RadioButton Name="UseLocalSystemAccount" Location="12, 88" Size="426, 24" Text="Use Local System account" TabIndex="3" />
- <RadioButton Name="UseNetworkServiceAccount" Location="12, 12" Size="426, 24" Text="Use Network Service account (Recommended)" Font="Microsoft Sans Serif, 8.25pt, style=Bold" Checked="True" TabIndex="4" />
-</Dialog> \ No newline at end of file
diff --git a/deployment/windows/dialogs/service-config.nsdinc b/deployment/windows/dialogs/service-config.nsdinc
deleted file mode 100644
index 58c350f2e..000000000
--- a/deployment/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/deployment/windows/dialogs/setuptype.nsddef b/deployment/windows/dialogs/setuptype.nsddef
deleted file mode 100644
index b55ceeaaa..000000000
--- a/deployment/windows/dialogs/setuptype.nsddef
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-This file was created by NSISDialogDesigner 1.4.4.0
-http://coolsoft.altervista.org/nsisdialogdesigner
-Do not edit manually!
--->
-<Dialog Name="setuptype" Title="Setup Type" Subtitle="Control how Jellyfin is installed.">
- <Label Name="InstallasaServiceLabel" Location="12, 115" Size="426, 46" Text="Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." TabIndex="0" />
- <RadioButton Name="InstallasaService" Location="12, 88" Size="426, 24" Text="Install as a Service (Advanced Users)" TabIndex="1" />
- <Label Name="BasicInstallLabel" Location="12, 39" Size="426, 46" Text="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." TabIndex="2" />
- <RadioButton Name="BasicInstall" Location="12, 12" Size="426, 24" Text="Basic Install (Recommended)" Font="Microsoft Sans Serif, 8.25pt, style=Bold" Checked="True" TabIndex="3" />
-</Dialog> \ No newline at end of file
diff --git a/deployment/windows/dialogs/setuptype.nsdinc b/deployment/windows/dialogs/setuptype.nsdinc
deleted file mode 100644
index 8746ad2cc..000000000
--- a/deployment/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/deployment/windows/helpers/ShowError.nsh b/deployment/windows/helpers/ShowError.nsh
deleted file mode 100644
index 6e09b1e40..000000000
--- a/deployment/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/deployment/windows/helpers/StrSlash.nsh b/deployment/windows/helpers/StrSlash.nsh
deleted file mode 100644
index b8aa771aa..000000000
--- a/deployment/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/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi
deleted file mode 100644
index fada62d98..000000000
--- a/deployment/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/deployment/windows/legacy/install-jellyfin.ps1 b/deployment/windows/legacy/install-jellyfin.ps1
deleted file mode 100644
index e909a0468..000000000
--- a/deployment/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/deployment/windows/legacy/install.bat b/deployment/windows/legacy/install.bat
deleted file mode 100644
index e21479a79..000000000
--- a/deployment/windows/legacy/install.bat
+++ /dev/null
@@ -1 +0,0 @@
-powershell.exe -executionpolicy Bypass -file install-jellyfin.ps1
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 000000000..326331f32
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <packageSources>
+ <add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
+ </packageSources>
+</configuration>
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index bb0357c06..b159db2bd 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -15,7 +15,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
<PackageReference Include="Moq" Version="4.13.1" />
</ItemGroup>
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index c81b820d9..81a2242e7 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -11,7 +11,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 06c10afe1..30994dee6 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -11,7 +11,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 52d28206d..78a020ad5 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -17,7 +17,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 4a583bcc7..f404b3e46 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -10,7 +10,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
index a2ef2dcd6..49cb2387b 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
@@ -39,11 +39,11 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData(@"[rec].mkv", "[rec].mkv", null)]
[InlineData(@"St. Vincent (2014)", "St. Vincent", 2014)]
[InlineData("Super movie(2009).mp4", "Super movie", 2009)]
- // FIXME: [InlineData("Drug War 2013.mp4", "Drug War", 2013)]
+ [InlineData("Drug War 2013.mp4", "Drug War", 2013)]
[InlineData("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997)]
- // FIXME: [InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
+ [InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
[InlineData("First Man (2018) 1080p.mkv", "First Man", 2018)]
- // FIXME: [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
+ [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
// FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
[InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
index 1646237a0..a64d17349 100644
--- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
@@ -7,6 +7,8 @@ namespace Jellyfin.Naming.Tests.Video
{
public class ExtraTests : BaseVideoTest
{
+ private readonly NamingOptions _videoOptions = new NamingOptions();
+
// Requirements
// movie-deleted = ExtraType deletedscene
@@ -15,42 +17,64 @@ namespace Jellyfin.Naming.Tests.Video
[Fact]
public void TestKodiExtras()
{
- var videoOptions = new NamingOptions();
-
- Test("trailer.mp4", ExtraType.Trailer, videoOptions);
- Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
+ Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
+ Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
- Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
+ Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
}
[Fact]
public void TestExpandedExtras()
{
- var videoOptions = new NamingOptions();
+ Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
+ Test("trailer.mp3", null, _videoOptions);
+ Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
- Test("trailer.mp4", ExtraType.Trailer, videoOptions);
- Test("trailer.mp3", null, videoOptions);
- Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
+ Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
+ Test("theme.mkv", null, _videoOptions);
- Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
- Test("theme.mkv", null, videoOptions);
+ Test("300-scene.mp4", ExtraType.Scene, _videoOptions);
+ Test("300-scene2.mp4", ExtraType.Scene, _videoOptions);
+ Test("300-clip.mp4", ExtraType.Clip, _videoOptions);
+
+ Test("300-deleted.mp4", ExtraType.DeletedScene, _videoOptions);
+ Test("300-deletedscene.mp4", ExtraType.DeletedScene, _videoOptions);
+ Test("300-interview.mp4", ExtraType.Interview, _videoOptions);
+ Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, _videoOptions);
+ }
- Test("300-scene.mp4", ExtraType.Scene, videoOptions);
- Test("300-scene2.mp4", ExtraType.Scene, videoOptions);
- Test("300-clip.mp4", ExtraType.Clip, videoOptions);
+ [Theory]
+ [InlineData(ExtraType.BehindTheScenes, "behind the scenes" )]
+ [InlineData(ExtraType.DeletedScene, "deleted scenes" )]
+ [InlineData(ExtraType.Interview, "interviews" )]
+ [InlineData(ExtraType.Scene, "scenes" )]
+ [InlineData(ExtraType.Sample, "samples" )]
+ [InlineData(ExtraType.Clip, "shorts" )]
+ [InlineData(ExtraType.Clip, "featurettes" )]
+ [InlineData(ExtraType.Unknown, "extras" )]
+ public void TestDirectories(ExtraType type, string dirName)
+ {
+ Test(dirName + "/300.mp4", type, _videoOptions);
+ Test("300/" + dirName + "/something.mkv", type, _videoOptions);
+ Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", type, _videoOptions);
+ }
- Test("300-deleted.mp4", ExtraType.DeletedScene, videoOptions);
- Test("300-deletedscene.mp4", ExtraType.DeletedScene, videoOptions);
- Test("300-interview.mp4", ExtraType.Interview, videoOptions);
- Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, videoOptions);
+ [Theory]
+ [InlineData("gibberish")]
+ [InlineData("not a scene")]
+ [InlineData("The Big Short")]
+ public void TestNonExtraDirectories(string dirName)
+ {
+ Test(dirName + "/300.mp4", null, _videoOptions);
+ Test("300/" + dirName + "/something.mkv", null, _videoOptions);
+ Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", null, _videoOptions);
+ Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null, _videoOptions);
}
[Fact]
public void TestSample()
{
- var videoOptions = new NamingOptions();
-
- Test("300-sample.mp4", ExtraType.Sample, videoOptions);
+ Test("300-sample.mp4", ExtraType.Sample, _videoOptions);
}
private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions)
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 29733a1c4..b7865439c 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -14,7 +14,7 @@
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.2.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
</ItemGroup>
<ItemGroup>