Merge branch 'master' into comparisons

This commit is contained in:
BaronGreenback 2021-05-05 23:22:54 +01:00 committed by GitHub
commit e682c230bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
264 changed files with 2907 additions and 2013 deletions

View File

@ -1,59 +0,0 @@
parameters:
- name: LinuxImage
type: string
default: "ubuntu-latest"
- name: GeneratorVersion
type: string
default: "5.0.1"
jobs:
- job: GenerateApiClients
displayName: 'Generate Api Clients'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
dependsOn: Test
pool:
vmImage: "${{ parameters.LinuxImage }}"
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec Artifact'
inputs:
source: 'current'
artifact: "OpenAPI Spec"
path: "$(System.ArtifactsDirectory)/openapispec"
runVersion: "latest"
- task: CmdLine@2
displayName: 'Download OpenApi Generator'
inputs:
script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
## Authenticate with npm registry
- task: npmAuthenticate@0
inputs:
workingFile: ./.npmrc
customEndpoint: 'jellyfin-bot for NPM'
## Generate npm api client
- task: CmdLine@2
displayName: 'Build stable typescript axios client'
inputs:
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)"
## Run npm install
- task: Npm@1
displayName: 'Install npm dependencies'
inputs:
command: install
workingDir: ./apiclient/generated/typescript/axios
## Publish npm packages
- task: Npm@1
displayName: 'Publish stable typescript axios client'
inputs:
command: custom
customCommand: publish --access public
publishRegistry: useExternalRegistry
publishEndpoint: 'jellyfin-bot for NPM'
workingDir: ./apiclient/generated/typescript/axios

View File

@ -61,6 +61,3 @@ jobs:
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}: - ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-package.yml - template: azure-pipelines-package.yml
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-api-client.yml

View File

@ -16,7 +16,7 @@ jobs:
label: stable backport label: stable backport
- name: Remove from 'Current Release' project - name: Remove from 'Current Release' project
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel if: (github.event.pull_request || github.event.issue.pull_request) && !steps.checkLabel.outputs.hasLabel
continue-on-error: true continue-on-error: true
with: with:
@ -25,7 +25,7 @@ jobs:
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.GH_TOKEN }}
- name: Add to 'Release Next' project - name: Add to 'Release Next' project
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened' if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
continue-on-error: true continue-on-error: true
with: with:
@ -34,7 +34,7 @@ jobs:
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.GH_TOKEN }}
- name: Add to 'Current Release' project - name: Add to 'Current Release' project
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel if: (github.event.pull_request || github.event.issue.pull_request) && steps.checkLabel.outputs.hasLabel
continue-on-error: true continue-on-error: true
with: with:
@ -48,7 +48,7 @@ jobs:
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)" run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
- name: Move issue to needs triage - name: Move issue to needs triage
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1 if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
continue-on-error: true continue-on-error: true
with: with:
@ -57,7 +57,7 @@ jobs:
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.GH_TOKEN }}
- name: Add issue to triage project - name: Add issue to triage project
uses: alex-page/github-project-automation-plus@v0.5.1 uses: alex-page/github-project-automation-plus@v0.7.1
if: github.event.issue.pull_request == '' && github.event.action == 'opened' if: github.event.issue.pull_request == '' && github.event.action == 'opened'
continue-on-error: true continue-on-error: true
with: with:

View File

@ -0,0 +1,43 @@
comment:
header: Hello @{{ issue.user.login }}
footer: "\
---\n\n
> This is an automated comment created by the [peaceiris/actions-label-commenter]. \
Responding to the bot or mentioning it won't have any effect.\n\n
[peaceiris/actions-label-commenter]: https://github.com/peaceiris/actions-label-commenter
"
labels:
- name: stable backport
labeled:
pr:
body: |
This pull request has been tagged as a stable backport. It will be cherry-picked into the next stable point release.
Please observe the following:
* Any dependent PRs that this PR requires **must** be tagged for stable backporting as well.
* Any issue(s) this PR fixes or closes **should** target the current stable release or a previous stable release to which a fix has not yet entered the current stable release.
* This PR **must** be test cherry-picked against the current release branch (`release-X.Y.z` where X and Y are numbers). It must apply cleanly, or a diff of the expected change must be provided.
To do this, run the following commands from your local copy of the Jellyfin repository:
1. `git checkout master`
1. `git merge --no-ff <myPullRequestBranch>`
1. `git log` -> `commit xxxxxxxxx`, grab hash
1. `git checkout release-X.Y.z` replacing X and Y with the *current* stable version (e.g. `release-10.7.z`)
1. `git cherry-pick -sx -m1 <hash>`
Ensure the `cherry-pick` applies cleanly. If it does not, fix any merge conflicts *preserving as much of the original code as possible*, and make note of the resulting diff.
Test your changes with a build to ensure they are successful. If not, adjust the diff accordingly.
**Do not** push your merges to either branch. Use `git reset --hard HEAD~1` to revert both branches to their original state.
Reply to this PR with a comment beginning "Cherry-pick test completed." and including the merge-conflict-fixing diff(s) if applicable.

22
.github/workflows/label-commenter.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Label Commenter
on:
issues:
types:
- labeled
- unlabeled
pull_request_target:
types:
- labeled
- unlabeled
jobs:
comment:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
ref: master
- name: Label Commenter
uses: peaceiris/actions-label-commenter@v1

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -7,7 +6,6 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Xml; using System.Xml;
using Emby.Dlna.Configuration;
using Emby.Dlna.Didl; using Emby.Dlna.Didl;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;

View File

@ -208,7 +208,8 @@ namespace Emby.Dlna.Didl
var targetWidth = streamInfo.TargetWidth; var targetWidth = streamInfo.TargetWidth;
var targetHeight = streamInfo.TargetHeight; var targetHeight = streamInfo.TargetHeight;
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader( var contentFeatureList = ContentFeatureBuilder.BuildVideoHeader(
_profile,
streamInfo.Container, streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(), streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(),
@ -599,7 +600,8 @@ namespace Emby.Dlna.Didl
? MimeTypes.GetMimeType(filename) ? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType; : mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader( var contentFeatures = ContentFeatureBuilder.BuildAudioHeader(
_profile,
streamInfo.Container, streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(),
targetAudioBitrate, targetAudioBitrate,
@ -974,15 +976,28 @@ namespace Emby.Dlna.Didl
return; return;
} }
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg"); // TODO: Remove these default values
var albumArtUrlInfo = GetImageUrl(
imageInfo,
_profile.MaxAlbumArtWidth ?? 10000,
_profile.MaxAlbumArtHeight ?? 10000,
"jpg");
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp); writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn); if (!string.IsNullOrEmpty(_profile.AlbumArtPn))
writer.WriteString(albumartUrlInfo.url); {
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
}
writer.WriteString(albumArtUrlInfo.url);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
// TOOD: Remove these default values // TODO: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg"); var iconUrlInfo = GetImageUrl(
imageInfo,
_profile.MaxIconWidth ?? 48,
_profile.MaxIconHeight ?? 48,
"jpg");
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url); writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
if (!_profile.EnableAlbumArtInDidl) if (!_profile.EnableAlbumArtInDidl)
@ -1033,8 +1048,7 @@ namespace Emby.Dlna.Didl
var width = albumartUrlInfo.width ?? maxWidth; var width = albumartUrlInfo.width ?? maxWidth;
var height = albumartUrlInfo.height ?? maxHeight; var height = albumartUrlInfo.height ?? maxHeight;
var contentFeatures = new ContentFeatureBuilder(_profile) var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn);
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
writer.WriteAttributeString( writer.WriteAttributeString(
"protocolInfo", "protocolInfo",
@ -1206,8 +1220,7 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue) if (width.HasValue && height.HasValue)
{ {
var newSize = DrawingUtils.Resize( var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
width = newSize.Width; width = newSize.Width;
height = newSize.Height; height = newSize.Height;

View File

@ -111,7 +111,7 @@ namespace Emby.Dlna
if (profile != null) if (profile != null)
{ {
_logger.LogDebug("Found matching device profile: {0}", profile.Name); _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
} }
else else
{ {
@ -138,80 +138,45 @@ namespace Emby.Dlna
_logger.LogInformation(builder.ToString()); _logger.LogInformation(builder.ToString());
} }
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) /// <summary>
/// Attempts to match a device with a profile.
/// Rules:
/// - If the profile field has no value, the field matches irregardless of its contents.
/// - the profile field can be an exact match, or a reg exp.
/// </summary>
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
/// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
/// <returns><b>True</b> if they match.</returns>
public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{ {
if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
{ && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
if (deviceInfo.FriendlyName == null || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
{ && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
return false; && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
} && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
} && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
&& IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{
if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{
if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{
if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelName))
{
if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{
if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{
if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
{
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{
if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
{
return false;
}
}
return true;
} }
private bool IsRegexOrSubstringMatch(string input, string pattern) private bool IsRegexOrSubstringMatch(string input, string pattern)
{ {
if (string.IsNullOrEmpty(pattern))
{
// In profile identification: An empty pattern matches anything.
return true;
}
if (string.IsNullOrEmpty(input))
{
// The profile contains a value, and the device doesn't.
return false;
}
try try
{ {
return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
|| Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {

View File

@ -5,7 +5,6 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.PlayTo; using Emby.Dlna.PlayTo;
using Emby.Dlna.Ssdp; using Emby.Dlna.Ssdp;

View File

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.MediaReceiverRegistrar namespace Emby.Dlna.MediaReceiverRegistrar
{ {

View File

@ -499,8 +499,8 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.MediaType == DlnaProfileType.Audio) if (streamInfo.MediaType == DlnaProfileType.Audio)
{ {
return new ContentFeatureBuilder(profile) return ContentFeatureBuilder.BuildAudioHeader(
.BuildAudioHeader( profile,
streamInfo.Container, streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetAudioBitrate, streamInfo.TargetAudioBitrate,
@ -514,8 +514,8 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.MediaType == DlnaProfileType.Video) if (streamInfo.MediaType == DlnaProfileType.Video)
{ {
var list = new ContentFeatureBuilder(profile) var list = ContentFeatureBuilder.BuildVideoHeader(
.BuildVideoHeader( profile,
streamInfo.Container, streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(), streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(), streamInfo.TargetAudioCodec.FirstOrDefault(),

View File

@ -188,7 +188,7 @@ namespace Emby.Dlna.PlayTo
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName); _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
string serverAddress = _appHost.GetSmartApiUrl(info.LocalIpAddress); string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIpAddress);
controller = new PlayToController( controller = new PlayToController(
sessionInfo, sessionInfo,

View File

@ -2,7 +2,6 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Net.Mime; using System.Net.Mime;
using System.Text; using System.Text;

View File

@ -104,7 +104,7 @@ namespace Emby.Dlna.Ssdp
{ {
Location = e.DiscoveredDevice.DescriptionLocation, Location = e.DiscoveredDevice.DescriptionLocation,
Headers = headers, Headers = headers,
LocalIpAddress = e.LocalIpAddress RemoteIpAddress = e.RemoteIpAddress
}); });
DeviceDiscoveredInternal?.Invoke(this, args); DeviceDiscoveredInternal?.Invoke(this, args);

View File

@ -68,6 +68,11 @@ namespace Emby.Naming.TV
var parsingResult = new EpisodePathParser(_options) var parsingResult = new EpisodePathParser(_options)
.Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo); .Parse(path, isDirectory, isNamed, isOptimistic, supportsAbsoluteNumbers, fillExtendedInfo);
if (!parsingResult.Success && !isStub)
{
return null;
}
return new EpisodeInfo(path) return new EpisodeInfo(path)
{ {
Container = container, Container = container,

View File

@ -3,7 +3,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase namespace Emby.Server.Implementations.AppBase

View File

@ -335,10 +335,7 @@ namespace Emby.Server.Implementations
{ {
get get
{ {
if (_deviceId == null) _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory);
{
_deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
}
return _deviceId.Value; return _deviceId.Value;
} }
@ -370,10 +367,7 @@ namespace Emby.Server.Implementations
/// <returns>System.Object.</returns> /// <returns>System.Object.</returns>
protected object CreateInstanceSafe(Type type) protected object CreateInstanceSafe(Type type)
{ {
if (_creatingInstances == null) _creatingInstances ??= new List<Type>();
{
_creatingInstances = new List<Type>();
}
if (_creatingInstances.IndexOf(type) != -1) if (_creatingInstances.IndexOf(type) != -1)
{ {
@ -607,12 +601,8 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
ServiceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
ServiceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(); ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
ServiceCollection.AddSingleton<EncodingHelper>();
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>)); ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
@ -677,8 +667,6 @@ namespace Emby.Server.Implementations
ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(); ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
ServiceCollection.AddSingleton<EncodingHelper>();
ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
ServiceCollection.AddSingleton<TranscodingJobHelper>(); ServiceCollection.AddSingleton<TranscodingJobHelper>();

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -8,11 +7,9 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -124,7 +121,7 @@ namespace Emby.Server.Implementations.Collections
private IEnumerable<BoxSet> GetCollections(User user) private IEnumerable<BoxSet> GetCollections(User user)
{ {
var folder = GetCollectionsFolder(false).Result; var folder = GetCollectionsFolder(false).GetAwaiter().GetResult();
return folder == null return folder == null
? Enumerable.Empty<BoxSet>() ? Enumerable.Empty<BoxSet>()
@ -319,11 +316,11 @@ namespace Emby.Server.Implementations.Collections
{ {
var results = new Dictionary<Guid, BaseItem>(); var results = new Dictionary<Guid, BaseItem>();
var allBoxsets = GetCollections(user).ToList(); var allBoxSets = GetCollections(user).ToList();
foreach (var item in items) foreach (var item in items)
{ {
if (!(item is ISupportsBoxSetGrouping)) if (item is not ISupportsBoxSetGrouping)
{ {
results[item.Id] = item; results[item.Id] = item;
} }
@ -331,33 +328,44 @@ namespace Emby.Server.Implementations.Collections
{ {
var itemId = item.Id; var itemId = item.Id;
var currentBoxSets = allBoxsets var itemIsInBoxSet = false;
.Where(i => i.ContainsLinkedChildByItemId(itemId)) foreach (var boxSet in allBoxSets)
.ToList();
if (currentBoxSets.Count > 0)
{ {
foreach (var boxset in currentBoxSets) if (!boxSet.ContainsLinkedChildByItemId(itemId))
{ {
results[boxset.Id] = boxset; continue;
}
itemIsInBoxSet = true;
results.TryAdd(boxSet.Id, boxSet);
}
// skip any item that is in a box set
if (itemIsInBoxSet)
{
continue;
}
var alreadyInResults = false;
// this is kind of a performance hack because only Video has alternate versions that should be in a box set?
if (item is Video video)
{
foreach (var childId in video.GetLocalAlternateVersionIds())
{
if (!results.ContainsKey(childId))
{
continue;
}
alreadyInResults = true;
break;
} }
} }
else
{
var alreadyInResults = false;
foreach (var child in item.GetMediaSources(true))
{
if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id))
{
alreadyInResults = true;
break;
}
}
if (!alreadyInResults) if (!alreadyInResults)
{ {
results[item.Id] = item; results[itemId] = item;
}
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Server.Implementations.HttpServer;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Emby.Server.Implementations namespace Emby.Server.Implementations

View File

@ -1,6 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Buffers.Text;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -502,7 +503,7 @@ namespace Emby.Server.Implementations.Data
using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
{ {
saveImagesStatement.TryBind("@Id", item.Id.ToByteArray()); saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
saveImagesStatement.TryBind("@Images", SerializeImages(item)); saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
saveImagesStatement.MoveNext(); saveImagesStatement.MoveNext();
} }
@ -897,8 +898,8 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId); saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId);
saveItemStatement.TryBind("@Tagline", item.Tagline); saveItemStatement.TryBind("@Tagline", item.Tagline);
saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item)); saveItemStatement.TryBind("@ProviderIds", SerializeProviderIds(item.ProviderIds));
saveItemStatement.TryBind("@Images", SerializeImages(item)); saveItemStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
if (item.ProductionLocations.Length > 0) if (item.ProductionLocations.Length > 0)
{ {
@ -968,10 +969,10 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.MoveNext(); saveItemStatement.MoveNext();
} }
private static string SerializeProviderIds(BaseItem item) internal static string SerializeProviderIds(Dictionary<string, string> providerIds)
{ {
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
foreach (var i in item.ProviderIds) foreach (var i in providerIds)
{ {
// Ideally we shouldn't need this IsNullOrWhiteSpace check, // Ideally we shouldn't need this IsNullOrWhiteSpace check,
// but we're seeing some cases of bad data slip through // but we're seeing some cases of bad data slip through
@ -995,35 +996,25 @@ namespace Emby.Server.Implementations.Data
return str.ToString(); return str.ToString();
} }
private static void DeserializeProviderIds(string value, BaseItem item) internal static void DeserializeProviderIds(string value, IHasProviderIds item)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
return; return;
} }
if (item.ProviderIds.Count > 0) foreach (var part in value.SpanSplit('|'))
{ {
return; var providerDelimiterIndex = part.IndexOf('=');
} if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('='))
var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
var idParts = part.Split('=');
if (idParts.Length == 2)
{ {
item.SetProviderId(idParts[0], idParts[1]); item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString());
} }
} }
} }
private string SerializeImages(BaseItem item) internal string SerializeImages(ItemImageInfo[] images)
{ {
var images = item.ImageInfos;
if (images.Length == 0) if (images.Length == 0)
{ {
return null; return null;
@ -1045,21 +1036,15 @@ namespace Emby.Server.Implementations.Data
return str.ToString(); return str.ToString();
} }
private void DeserializeImages(string value, BaseItem item) internal ItemImageInfo[] DeserializeImages(string value)
{ {
if (string.IsNullOrWhiteSpace(value)) if (string.IsNullOrWhiteSpace(value))
{ {
return; return Array.Empty<ItemImageInfo>();
} }
if (item.ImageInfos.Length > 0)
{
return;
}
var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries);
var list = new List<ItemImageInfo>(); var list = new List<ItemImageInfo>();
foreach (var part in parts) foreach (var part in value.SpanSplit('|'))
{ {
var image = ItemImageInfoFromValueString(part); var image = ItemImageInfoFromValueString(part);
@ -1069,15 +1054,14 @@ namespace Emby.Server.Implementations.Data
} }
} }
item.ImageInfos = list.ToArray(); return list.ToArray();
} }
public void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image) private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
{ {
const char Delimiter = '*'; const char Delimiter = '*';
var path = image.Path ?? string.Empty; var path = image.Path ?? string.Empty;
var hash = image.BlurHash ?? string.Empty;
bldr.Append(GetPathToSave(path)) bldr.Append(GetPathToSave(path))
.Append(Delimiter) .Append(Delimiter)
@ -1087,48 +1071,105 @@ namespace Emby.Server.Implementations.Data
.Append(Delimiter) .Append(Delimiter)
.Append(image.Width) .Append(image.Width)
.Append(Delimiter) .Append(Delimiter)
.Append(image.Height) .Append(image.Height);
.Append(Delimiter)
// Replace delimiters with other characters. var hash = image.BlurHash;
// This can be removed when we migrate to a proper DB. if (!string.IsNullOrEmpty(hash))
.Append(hash.Replace('*', '/').Replace('|', '\\')); {
bldr.Append(Delimiter)
// Replace delimiters with other characters.
// This can be removed when we migrate to a proper DB.
.Append(hash.Replace('*', '/').Replace('|', '\\'));
}
} }
public ItemImageInfo ItemImageInfoFromValueString(string value) internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value)
{ {
var parts = value.Split('*', StringSplitOptions.None); var nextSegment = value.IndexOf('*');
if (nextSegment == -1)
if (parts.Length < 3)
{ {
return null; return null;
} }
var image = new ItemImageInfo(); ReadOnlySpan<char> path = value[..nextSegment];
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf('*');
if (nextSegment == -1)
{
return null;
}
image.Path = RestorePath(parts[0]); ReadOnlySpan<char> dateModified = value[..nextSegment];
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf('*');
if (nextSegment == -1)
{
nextSegment = value.Length;
}
if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks)) ReadOnlySpan<char> imageType = value[..nextSegment];
var image = new ItemImageInfo
{
Path = RestorePath(path.ToString())
};
if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
{ {
image.DateModified = new DateTime(ticks, DateTimeKind.Utc); image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
} }
if (Enum.TryParse(parts[2], true, out ImageType type)) if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
{ {
image.Type = type; image.Type = type;
} }
if (parts.Length >= 5) // Optional parameters: width*height*blurhash
if (nextSegment + 1 < value.Length - 1)
{ {
if (int.TryParse(parts[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var width) value = value[(nextSegment + 1)..];
&& int.TryParse(parts[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var height)) nextSegment = value.IndexOf('*');
if (nextSegment == -1 || nextSegment == value.Length)
{
return image;
}
ReadOnlySpan<char> widthSpan = value[..nextSegment];
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf('*');
if (nextSegment == -1)
{
nextSegment = value.Length;
}
ReadOnlySpan<char> heightSpan = value[..nextSegment];
if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
&& int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
{ {
image.Width = width; image.Width = width;
image.Height = height; image.Height = height;
} }
if (parts.Length >= 6) if (nextSegment < value.Length - 1)
{ {
image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|'); value = value[(nextSegment + 1)..];
var length = value.Length;
Span<char> blurHashSpan = stackalloc char[length];
for (int i = 0; i < length; i++)
{
var c = value[i];
blurHashSpan[i] = c switch
{
'/' => '*',
'\\' => '|',
_ => c
};
}
image.BlurHash = new string(blurHashSpan);
} }
} }
@ -1311,7 +1352,14 @@ namespace Emby.Server.Implementations.Data
if (!reader.IsDBNull(index)) if (!reader.IsDBNull(index))
{ {
item.ChannelId = new Guid(reader.GetString(index)); if (!Utf8Parser.TryParse(reader[index].ToBlob(), out Guid value, out _, standardFormat: 'N'))
{
var str = reader.GetString(index);
Logger.LogWarning("{ChannelId} isn't in the expected format", str);
value = new Guid(str);
}
item.ChannelId = value;
} }
index++; index++;
@ -1790,7 +1838,7 @@ namespace Emby.Server.Implementations.Data
index++; index++;
} }
if (!reader.IsDBNull(index)) if (item.ProviderIds.Count == 0 && !reader.IsDBNull(index))
{ {
DeserializeProviderIds(reader.GetString(index), item); DeserializeProviderIds(reader.GetString(index), item);
} }
@ -1799,9 +1847,9 @@ namespace Emby.Server.Implementations.Data
if (query.DtoOptions.EnableImages) if (query.DtoOptions.EnableImages)
{ {
if (!reader.IsDBNull(index)) if (item.ImageInfos.Length == 0 && !reader.IsDBNull(index))
{ {
DeserializeImages(reader.GetString(index), item); item.ImageInfos = DeserializeImages(reader.GetString(index));
} }
index++; index++;
@ -2116,30 +2164,7 @@ namespace Emby.Server.Implementations.Data
|| query.IsLiked.HasValue; || query.IsLiked.HasValue;
} }
private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields)) private readonly ItemFields[] _allFields = Enum.GetValues<ItemFields>();
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
.ToArray();
private string[] GetColumnNamesFromField(ItemFields field)
{
switch (field)
{
case ItemFields.Settings:
return new[] { "IsLocked", "PreferredMetadataCountryCode", "PreferredMetadataLanguage", "LockedFields" };
case ItemFields.ServiceName:
return new[] { "ExternalServiceId" };
case ItemFields.SortName:
return new[] { "ForcedSortName" };
case ItemFields.Taglines:
return new[] { "Tagline" };
case ItemFields.Tags:
return new[] { "Tags" };
case ItemFields.IsHD:
return Array.Empty<string>();
default:
return new[] { field.ToString() };
}
}
private bool HasField(InternalItemsQuery query, ItemFields name) private bool HasField(InternalItemsQuery query, ItemFields name)
{ {
@ -2329,9 +2354,32 @@ namespace Emby.Server.Implementations.Data
{ {
if (!HasField(query, field)) if (!HasField(query, field))
{ {
foreach (var fieldToRemove in GetColumnNamesFromField(field)) switch (field)
{ {
list.Remove(fieldToRemove); case ItemFields.Settings:
list.Remove("IsLocked");
list.Remove("PreferredMetadataCountryCode");
list.Remove("PreferredMetadataLanguage");
list.Remove("LockedFields");
break;
case ItemFields.ServiceName:
list.Remove("ExternalServiceId");
break;
case ItemFields.SortName:
list.Remove("ForcedSortName");
break;
case ItemFields.Taglines:
list.Remove("Tagline");
break;
case ItemFields.Tags:
list.Remove("Tags");
break;
case ItemFields.IsHD:
// do nothing
break;
default:
list.Remove(field.ToString());
break;
} }
} }
} }
@ -2578,9 +2626,9 @@ namespace Emby.Server.Implementations.Data
} }
var commandText = "select " var commandText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query); + GetJoinUserDataText(query);
var whereClauses = GetWhereClauses(query, null); var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0) if (whereClauses.Count != 0)
@ -2721,87 +2769,22 @@ namespace Emby.Server.Implementations.Data
private string FixUnicodeChars(string buffer) private string FixUnicodeChars(string buffer)
{ {
if (buffer.IndexOf('\u2013', StringComparison.Ordinal) > -1) buffer = buffer.Replace('\u2013', '-'); // en dash
{ buffer = buffer.Replace('\u2014', '-'); // em dash
buffer = buffer.Replace('\u2013', '-'); // en dash buffer = buffer.Replace('\u2015', '-'); // horizontal bar
} buffer = buffer.Replace('\u2017', '_'); // double low line
buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
if (buffer.IndexOf('\u2014', StringComparison.Ordinal) > -1) buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
{ buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
buffer = buffer.Replace('\u2014', '-'); // em dash buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
} buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
if (buffer.IndexOf('\u2015', StringComparison.Ordinal) > -1) buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
{ buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis
buffer = buffer.Replace('\u2015', '-'); // horizontal bar buffer = buffer.Replace('\u2032', '\''); // prime
} buffer = buffer.Replace('\u2033', '\"'); // double prime
buffer = buffer.Replace('\u0060', '\''); // grave accent
if (buffer.IndexOf('\u2017', StringComparison.Ordinal) > -1) return buffer.Replace('\u00B4', '\''); // acute accent
{
buffer = buffer.Replace('\u2017', '_'); // double low line
}
if (buffer.IndexOf('\u2018', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
}
if (buffer.IndexOf('\u2019', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
}
if (buffer.IndexOf('\u201a', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
}
if (buffer.IndexOf('\u201b', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
}
if (buffer.IndexOf('\u201c', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
}
if (buffer.IndexOf('\u201d', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
}
if (buffer.IndexOf('\u201e', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
}
if (buffer.IndexOf('\u2026', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis
}
if (buffer.IndexOf('\u2032', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u2032', '\''); // prime
}
if (buffer.IndexOf('\u2033', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u2033', '\"'); // double prime
}
if (buffer.IndexOf('\u0060', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u0060', '\''); // grave accent
}
if (buffer.IndexOf('\u00B4', StringComparison.Ordinal) > -1)
{
buffer = buffer.Replace('\u00B4', '\''); // acute accent
}
return buffer;
} }
private void AddItem(List<BaseItem> items, BaseItem newItem) private void AddItem(List<BaseItem> items, BaseItem newItem)
@ -3584,11 +3567,11 @@ namespace Emby.Server.Implementations.Data
statement?.TryBind("@IsFolder", query.IsFolder); statement?.TryBind("@IsFolder", query.IsFolder);
} }
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); var includeTypes = query.IncludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray();
// Only specify excluded types if no included types are specified // Only specify excluded types if no included types are specified
if (includeTypes.Length == 0) if (includeTypes.Length == 0)
{ {
var excludeTypes = query.ExcludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray(); var excludeTypes = query.ExcludeItemTypes.Select(MapIncludeItemTypes).Where(x => x != null).ToArray();
if (excludeTypes.Length == 1) if (excludeTypes.Length == 1)
{ {
whereClauses.Add("type<>@type"); whereClauses.Add("type<>@type");
@ -4532,7 +4515,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb")); whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb"));
} }
var includedItemByNameTypes = GetItemByNameTypesInQuery(query).SelectMany(MapIncludeItemTypes).ToList(); var includedItemByNameTypes = GetItemByNameTypesInQuery(query);
var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
var queryTopParentIds = query.TopParentIds; var queryTopParentIds = query.TopParentIds;
@ -4790,27 +4773,27 @@ namespace Emby.Server.Implementations.Data
if (IsTypeInQuery(nameof(Person), query)) if (IsTypeInQuery(nameof(Person), query))
{ {
list.Add(nameof(Person)); list.Add(typeof(Person).FullName);
} }
if (IsTypeInQuery(nameof(Genre), query)) if (IsTypeInQuery(nameof(Genre), query))
{ {
list.Add(nameof(Genre)); list.Add(typeof(Genre).FullName);
} }
if (IsTypeInQuery(nameof(MusicGenre), query)) if (IsTypeInQuery(nameof(MusicGenre), query))
{ {
list.Add(nameof(MusicGenre)); list.Add(typeof(MusicGenre).FullName);
} }
if (IsTypeInQuery(nameof(MusicArtist), query)) if (IsTypeInQuery(nameof(MusicArtist), query))
{ {
list.Add(nameof(MusicArtist)); list.Add(typeof(MusicArtist).FullName);
} }
if (IsTypeInQuery(nameof(Studio), query)) if (IsTypeInQuery(nameof(Studio), query))
{ {
list.Add(nameof(Studio)); list.Add(typeof(Studio).FullName);
} }
return list; return list;
@ -4915,15 +4898,10 @@ namespace Emby.Server.Implementations.Data
typeof(AggregateFolder) typeof(AggregateFolder)
}; };
public void UpdateInheritedValues(CancellationToken cancellationToken) public void UpdateInheritedValues()
{
UpdateInheritedTags(cancellationToken);
}
private void UpdateInheritedTags(CancellationToken cancellationToken)
{ {
string sql = string.Join( string sql = string.Join(
";", ';',
new string[] new string[]
{ {
"delete from itemvalues where type = 6", "delete from itemvalues where type = 6",
@ -4946,37 +4924,38 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
} }
} }
private static Dictionary<string, string[]> GetTypeMapDictionary() private static Dictionary<string, string> GetTypeMapDictionary()
{ {
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase); var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var t in _knownTypes) foreach (var t in _knownTypes)
{ {
dict[t.Name] = new[] { t.FullName }; dict[t.Name] = t.FullName ;
} }
dict["Program"] = new[] { typeof(LiveTvProgram).FullName }; dict["Program"] = typeof(LiveTvProgram).FullName;
dict["TvChannel"] = new[] { typeof(LiveTvChannel).FullName }; dict["TvChannel"] = typeof(LiveTvChannel).FullName;
return dict; return dict;
} }
// Not crazy about having this all the way down here, but at least it's in one place // Not crazy about having this all the way down here, but at least it's in one place
private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary(); private readonly Dictionary<string, string> _types = GetTypeMapDictionary();
private string[] MapIncludeItemTypes(string value) private string MapIncludeItemTypes(string value)
{ {
if (_types.TryGetValue(value, out string[] result)) if (_types.TryGetValue(value, out string result))
{ {
return result; return result;
} }
if (IsValidType(value)) if (IsValidType(value))
{ {
return new[] { value }; return value;
} }
return Array.Empty<string>(); Logger.LogWarning("Unknown item type: {ItemType}", value);
return null;
} }
public void DeleteItem(Guid id) public void DeleteItem(Guid id)
@ -5279,31 +5258,46 @@ AND Type = @InternalPersonType)");
public List<string> GetStudioNames() public List<string> GetStudioNames()
{ {
return GetItemValueNames(new[] { 3 }, new List<string>(), new List<string>()); return GetItemValueNames(new[] { 3 }, Array.Empty<string>(), Array.Empty<string>());
} }
public List<string> GetAllArtistNames() public List<string> GetAllArtistNames()
{ {
return GetItemValueNames(new[] { 0, 1 }, new List<string>(), new List<string>()); return GetItemValueNames(new[] { 0, 1 }, Array.Empty<string>(), Array.Empty<string>());
} }
public List<string> GetMusicGenreNames() public List<string> GetMusicGenreNames()
{ {
return GetItemValueNames(new[] { 2 }, new List<string> { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }, new List<string>()); return GetItemValueNames(
new[] { 2 },
new string[]
{
typeof(Audio).FullName,
typeof(MusicVideo).FullName,
typeof(MusicAlbum).FullName,
typeof(MusicArtist).FullName
},
Array.Empty<string>());
} }
public List<string> GetGenreNames() public List<string> GetGenreNames()
{ {
return GetItemValueNames(new[] { 2 }, new List<string>(), new List<string> { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }); return GetItemValueNames(
new[] { 2 },
Array.Empty<string>(),
new string[]
{
typeof(Audio).FullName,
typeof(MusicVideo).FullName,
typeof(MusicAlbum).FullName,
typeof(MusicArtist).FullName
});
} }
private List<string> GetItemValueNames(int[] itemValueTypes, List<string> withItemTypes, List<string> excludeItemTypes) private List<string> GetItemValueNames(int[] itemValueTypes, IReadOnlyList<string> withItemTypes, IReadOnlyList<string> excludeItemTypes)
{ {
CheckDisposed(); CheckDisposed();
withItemTypes = withItemTypes.SelectMany(MapIncludeItemTypes).ToList();
excludeItemTypes = excludeItemTypes.SelectMany(MapIncludeItemTypes).ToList();
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var typeClause = itemValueTypes.Length == 1 ? var typeClause = itemValueTypes.Length == 1 ?
@ -5809,7 +5803,10 @@ AND Type = @InternalPersonType)");
var endIndex = Math.Min(people.Count, startIndex + Limit); var endIndex = Math.Min(people.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++) for (var i = startIndex; i < endIndex; i++)
{ {
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture)); insertText.AppendFormat(
CultureInfo.InvariantCulture,
"(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),",
i.ToString(CultureInfo.InvariantCulture));
} }
// Remove last comma // Remove last comma
@ -6261,7 +6258,7 @@ AND Type = @InternalPersonType)");
CheckDisposed(); CheckDisposed();
if (id == Guid.Empty) if (id == Guid.Empty)
{ {
throw new ArgumentException(nameof(id)); throw new ArgumentException("Guid can't be empty.", nameof(id));
} }
if (attachments == null) if (attachments == null)

View File

@ -665,10 +665,7 @@ namespace Emby.Server.Implementations.Dto
var tag = GetImageCacheTag(item, image); var tag = GetImageCacheTag(item, image);
if (!string.IsNullOrEmpty(image.BlurHash)) if (!string.IsNullOrEmpty(image.BlurHash))
{ {
if (dto.ImageBlurHashes == null) dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
if (!dto.ImageBlurHashes.ContainsKey(image.Type)) if (!dto.ImageBlurHashes.ContainsKey(image.Type))
{ {
@ -702,10 +699,7 @@ namespace Emby.Server.Implementations.Dto
if (hashes.Count > 0) if (hashes.Count > 0)
{ {
if (dto.ImageBlurHashes == null) dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
{
dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
}
dto.ImageBlurHashes[imageType] = hashes; dto.ImageBlurHashes[imageType] = hashes;
} }
@ -898,10 +892,7 @@ namespace Emby.Server.Implementations.Dto
dto.Taglines = new string[] { item.Tagline }; dto.Taglines = new string[] { item.Tagline };
} }
if (dto.Taglines == null) dto.Taglines ??= Array.Empty<string>();
{
dto.Taglines = Array.Empty<string>();
}
} }
dto.Type = item.GetBaseItemKind(); dto.Type = item.GetBaseItemKind();

View File

@ -29,9 +29,9 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
<PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.0.0" />
<PackageReference Include="sharpcompress" Version="0.28.1" /> <PackageReference Include="sharpcompress" Version="0.28.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.2.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" /> <PackageReference Include="DotNet.Glob" Version="3.1.2" />
</ItemGroup> </ItemGroup>

View File

@ -1,6 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;

View File

@ -2,11 +2,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Runtime.InteropServices;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
@ -24,7 +23,7 @@ namespace Emby.Server.Implementations.IO
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>(); private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private readonly string _tempPath; private readonly string _tempPath;
private readonly bool _isEnvironmentCaseInsensitive; private static readonly bool _isEnvironmentCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public ManagedFileSystem( public ManagedFileSystem(
ILogger<ManagedFileSystem> logger, ILogger<ManagedFileSystem> logger,
@ -32,8 +31,6 @@ namespace Emby.Server.Implementations.IO
{ {
Logger = logger; Logger = logger;
_tempPath = applicationPaths.TempDirectory; _tempPath = applicationPaths.TempDirectory;
_isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows;
} }
public virtual void AddShortcutHandler(IShortcutHandler handler) public virtual void AddShortcutHandler(IShortcutHandler handler)
@ -55,7 +52,7 @@ namespace Emby.Server.Implementations.IO
} }
var extension = Path.GetExtension(filename); var extension = Path.GetExtension(filename);
return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, _isEnvironmentCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)); return _shortcutHandlers.Any(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
} }
/// <summary> /// <summary>
@ -72,7 +69,7 @@ namespace Emby.Server.Implementations.IO
} }
var extension = Path.GetExtension(filename); var extension = Path.GetExtension(filename);
var handler = _shortcutHandlers.FirstOrDefault(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase)); var handler = _shortcutHandlers.Find(i => string.Equals(extension, i.Extension, StringComparison.OrdinalIgnoreCase));
return handler?.Resolve(filename); return handler?.Resolve(filename);
} }
@ -263,8 +260,6 @@ namespace Emby.Server.Implementations.IO
result.Exists = false; result.Exists = false;
} }
} }
result.DirectoryName = fileInfo.DirectoryName;
} }
result.CreationTimeUtc = GetCreationTimeUtc(info); result.CreationTimeUtc = GetCreationTimeUtc(info);
@ -303,16 +298,37 @@ namespace Emby.Server.Implementations.IO
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">The filename is null.</exception> /// <exception cref="ArgumentNullException">The filename is null.</exception>
public virtual string GetValidFilename(string filename) public string GetValidFilename(string filename)
{ {
var builder = new StringBuilder(filename); var invalid = Path.GetInvalidFileNameChars();
var first = filename.IndexOfAny(invalid);
foreach (var c in Path.GetInvalidFileNameChars()) if (first == -1)
{ {
builder = builder.Replace(c, ' '); // Fast path for clean strings
return filename;
} }
return builder.ToString(); return string.Create(
filename.Length,
(filename, invalid, first),
(chars, state) =>
{
state.filename.AsSpan().CopyTo(chars);
chars[state.first++] = ' ';
var len = chars.Length;
foreach (var c in state.invalid)
{
for (int i = state.first; i < len; i++)
{
if (chars[i] == c)
{
chars[i] = ' ';
}
}
}
});
} }
/// <summary> /// <summary>
@ -684,20 +700,5 @@ namespace Emby.Server.Implementations.IO
AttributesToSkip = 0 AttributesToSkip = 0
}; };
} }
private static void RunProcess(string path, string args, string workingDirectory)
{
using (var process = Process.Start(new ProcessStartInfo
{
Arguments = args,
FileName = path,
CreateNoWindow = true,
WorkingDirectory = workingDirectory,
WindowStyle = ProcessWindowStyle.Normal
}))
{
process.WaitForExit();
}
}
} }
} }

View File

@ -1,6 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#nullable enable #nullable enable
using System;
namespace Emby.Server.Implementations namespace Emby.Server.Implementations
{ {

View File

@ -2,20 +2,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Images namespace Emby.Server.Implementations.Images
{ {

View File

@ -4,7 +4,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View File

@ -29,9 +29,7 @@ namespace Emby.Server.Implementations.Images
{ {
var subItem = i.Item2; var subItem = i.Item2;
var episode = subItem as Episode; if (subItem is Episode episode)
if (episode != null)
{ {
var series = episode.Series; var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary)) if (series != null && series.HasImage(ImageType.Primary))

View File

@ -48,6 +48,7 @@ using MediaBrowser.Providers.MediaInfo;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
using Genre = MediaBrowser.Controller.Entities.Genre; using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person; using Person = MediaBrowser.Controller.Entities.Person;
using VideoResolver = Emby.Naming.Video.VideoResolver; using VideoResolver = Emby.Naming.Video.VideoResolver;
@ -175,10 +176,7 @@ namespace Emby.Server.Implementations.Library
{ {
lock (_rootFolderSyncLock) lock (_rootFolderSyncLock)
{ {
if (_rootFolder == null) _rootFolder ??= CreateRootFolder();
{
_rootFolder = CreateRootFolder();
}
} }
} }
@ -558,7 +556,6 @@ namespace Emby.Server.Implementations.Library
var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService) var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
{ {
Parent = parent, Parent = parent,
Path = fullPath,
FileInfo = fileInfo, FileInfo = fileInfo,
CollectionType = collectionType, CollectionType = collectionType,
LibraryOptions = libraryOptions LibraryOptions = libraryOptions
@ -684,7 +681,7 @@ namespace Emby.Server.Implementations.Library
foreach (var item in items) foreach (var item in items)
{ {
ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); ResolverHelper.SetInitialItemValues(item, parent, this, directoryService);
} }
items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions));
@ -1163,7 +1160,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100); progress.Report(percent * 100);
} }
_itemRepository.UpdateInheritedValues(cancellationToken); _itemRepository.UpdateInheritedValues();
progress.Report(100); progress.Report(100);
} }
@ -2517,7 +2514,7 @@ namespace Emby.Server.Implementations.Library
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh) public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
{ {
var series = episode.Series; var series = episode.Series;
bool? isAbsoluteNaming = series == null ? false : string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase); bool? isAbsoluteNaming = series != null && string.Equals(series.DisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase);
if (!isAbsoluteNaming.Value) if (!isAbsoluteNaming.Value)
{ {
// In other words, no filter applied // In other words, no filter applied
@ -2529,9 +2526,23 @@ namespace Emby.Server.Implementations.Library
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
// TODO nullable - what are we trying to do there with empty episodeInfo? // TODO nullable - what are we trying to do there with empty episodeInfo?
var episodeInfo = episode.IsFileProtocol EpisodeInfo episodeInfo = null;
? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo(episode.Path) if (episode.IsFileProtocol)
: new Naming.TV.EpisodeInfo(episode.Path); {
episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming);
// Resolve from parent folder if it's not the Season folder
if (episodeInfo == null && episode.Parent.GetType() == typeof(Folder))
{
episodeInfo = resolver.Resolve(episode.Parent.Path, true, null, null, isAbsoluteNaming);
if (episodeInfo != null)
{
// add the container
episodeInfo.Container = Path.GetExtension(episode.Path)?.TrimStart('.');
}
}
}
episodeInfo ??= new EpisodeInfo(episode.Path);
try try
{ {
@ -2880,6 +2891,12 @@ namespace Emby.Server.Implementations.Library
} }
public void UpdatePeople(BaseItem item, List<PersonInfo> people) public void UpdatePeople(BaseItem item, List<PersonInfo> people)
{
UpdatePeopleAsync(item, people, CancellationToken.None).GetAwaiter().GetResult();
}
/// <inheritdoc />
public async Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken)
{ {
if (!item.SupportsPeople) if (!item.SupportsPeople)
{ {
@ -2887,6 +2904,8 @@ namespace Emby.Server.Implementations.Library
} }
_itemRepository.UpdatePeople(item.Id, people); _itemRepository.UpdatePeople(item.Id, people);
await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
} }
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex) public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@ -2990,6 +3009,58 @@ namespace Emby.Server.Implementations.Library
} }
} }
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
{
var personsToSave = new List<BaseItem>();
foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
var itemUpdateType = ItemUpdateType.MetadataDownload;
var saveEntity = false;
var personEntity = GetPerson(person.Name);
// if PresentationUniqueKey is empty it's likely a new item.
if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey))
{
personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
saveEntity = true;
}
foreach (var id in person.ProviderIds)
{
if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
{
personEntity.SetProviderId(id.Key, id.Value);
saveEntity = true;
}
}
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
{
personEntity.SetImage(
new ItemImageInfo
{
Path = person.ImageUrl,
Type = ImageType.Primary
},
0);
saveEntity = true;
itemUpdateType = ItemUpdateType.ImageUpdate;
}
if (saveEntity)
{
personsToSave.Add(personEntity);
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
CreateItems(personsToSave, null, CancellationToken.None);
}
private void StartScanInBackground() private void StartScanInBackground()
{ {
Task.Run(() => Task.Run(() =>

View File

@ -199,10 +199,15 @@ namespace Emby.Server.Implementations.Library
{ {
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
} }
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
}
} }
} }
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList(); return SortMediaSources(list);
} }
public MediaProtocol GetPathProtocol(string path) public MediaProtocol GetPathProtocol(string path)
@ -436,7 +441,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources) private static List<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{ {
return sources.OrderBy(i => return sources.OrderBy(i =>
{ {
@ -451,8 +456,9 @@ namespace Emby.Server.Implementations.Library
{ {
var stream = i.VideoStream; var stream = i.VideoStream;
return stream == null || stream.Width == null ? 0 : stream.Width.Value; return stream?.Width ?? 0;
}) })
.Where(i => i.Type != MediaSourceType.Placeholder)
.ToList(); .ToList();
} }
@ -584,18 +590,9 @@ namespace Emby.Server.Implementations.Library
public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
{ {
var info = _openStreams.Values.FirstOrDefault(i => var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
{
var liveStream = i as ILiveStream;
if (liveStream != null)
{
return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
}
return false; return Task.FromResult(info.Value as IDirectStreamProvider);
});
return Task.FromResult(info as IDirectStreamProvider);
} }
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)

View File

@ -100,8 +100,7 @@ namespace Emby.Server.Implementations.Library
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions) public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{ {
var genre = item as MusicGenre; if (item is MusicGenre genre)
if (genre != null)
{ {
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
} }

View File

@ -2,8 +2,6 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.RegularExpressions;
using MediaBrowser.Common.Providers; using MediaBrowser.Common.Providers;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
@ -98,8 +96,14 @@ namespace Emby.Server.Implementations.Library
// We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results // We have to ensure that the sub path ends with a directory separator otherwise we'll get weird results
// when the sub path matches a similar but in-complete subpath // when the sub path matches a similar but in-complete subpath
var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar; var oldSubPathEndsWithSeparator = subPath[^1] == newDirectorySeparatorChar;
if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase) if (!path.StartsWith(subPath, StringComparison.OrdinalIgnoreCase))
|| (!oldSubPathEndsWithSeparator && path[subPath.Length] != newDirectorySeparatorChar)) {
return false;
}
if (path.Length > subPath.Length
&& !oldSubPathEndsWithSeparator
&& path[subPath.Length] != newDirectorySeparatorChar)
{ {
return false; return false;
} }

View File

@ -1,3 +1,5 @@
#nullable enable
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -18,11 +20,10 @@ namespace Emby.Server.Implementations.Library
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
/// <param name="directoryService">The directory service.</param> /// <param name="directoryService">The directory service.</param>
/// <exception cref="ArgumentException">Item must have a path</exception> /// <exception cref="ArgumentException">Item must have a path.</exception>
public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService) public static void SetInitialItemValues(BaseItem item, Folder? parent, ILibraryManager libraryManager, IDirectoryService directoryService)
{ {
// This version of the below method has no ItemResolveArgs, so we have to require the path already being set // This version of the below method has no ItemResolveArgs, so we have to require the path already being set
if (string.IsNullOrEmpty(item.Path)) if (string.IsNullOrEmpty(item.Path))
@ -43,9 +44,9 @@ namespace Emby.Server.Implementations.Library
// Make sure DateCreated and DateModified have values // Make sure DateCreated and DateModified have values
var fileInfo = directoryService.GetFile(item.Path); var fileInfo = directoryService.GetFile(item.Path);
SetDateCreated(item, fileSystem, fileInfo); SetDateCreated(item, fileInfo);
EnsureName(item, item.Path, fileInfo); EnsureName(item, fileInfo);
} }
/// <summary> /// <summary>
@ -72,9 +73,9 @@ namespace Emby.Server.Implementations.Library
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
// Make sure the item has a name // Make sure the item has a name
EnsureName(item, item.Path, args.FileInfo); EnsureName(item, args.FileInfo);
item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) ||
item.GetParents().Any(i => i.IsLocked); item.GetParents().Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values // Make sure DateCreated and DateModified have values
@ -84,28 +85,15 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// Ensures the name. /// Ensures the name.
/// </summary> /// </summary>
private static void EnsureName(BaseItem item, string fullPath, FileSystemMetadata fileInfo) private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo)
{ {
// If the subclass didn't supply a name, add it here // If the subclass didn't supply a name, add it here
if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(fullPath)) if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path))
{ {
var fileName = fileInfo == null ? Path.GetFileName(fullPath) : fileInfo.Name; item.Name = fileInfo.IsDirectory ? fileInfo.Name : Path.GetFileNameWithoutExtension(fileInfo.Name);
item.Name = GetDisplayName(fileName, fileInfo != null && fileInfo.IsDirectory);
} }
} }
/// <summary>
/// Gets the display name.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isDirectory">if set to <c>true</c> [is directory].</param>
/// <returns>System.String.</returns>
private static string GetDisplayName(string path, bool isDirectory)
{
return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path);
}
/// <summary> /// <summary>
/// Ensures DateCreated and DateModified have values. /// Ensures DateCreated and DateModified have values.
/// </summary> /// </summary>
@ -114,21 +102,6 @@ namespace Emby.Server.Implementations.Library
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args) private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args)
{ {
if (fileSystem == null)
{
throw new ArgumentNullException(nameof(fileSystem));
}
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
// See if a different path came out of the resolver than what went in // See if a different path came out of the resolver than what went in
if (!fileSystem.AreEqual(args.Path, item.Path)) if (!fileSystem.AreEqual(args.Path, item.Path))
{ {
@ -136,7 +109,7 @@ namespace Emby.Server.Implementations.Library
if (childData != null) if (childData != null)
{ {
SetDateCreated(item, fileSystem, childData); SetDateCreated(item, childData);
} }
else else
{ {
@ -144,17 +117,17 @@ namespace Emby.Server.Implementations.Library
if (fileData.Exists) if (fileData.Exists)
{ {
SetDateCreated(item, fileSystem, fileData); SetDateCreated(item, fileData);
} }
} }
} }
else else
{ {
SetDateCreated(item, fileSystem, args.FileInfo); SetDateCreated(item, args.FileInfo);
} }
} }
private static void SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info) private static void SetDateCreated(BaseItem item, FileSystemMetadata? info)
{ {
var config = BaseItem.ConfigurationManager.GetMetadataConfiguration(); var config = BaseItem.ConfigurationManager.GetMetadataConfiguration();
@ -163,7 +136,7 @@ namespace Emby.Server.Implementations.Library
// directoryService.getFile may return null // directoryService.getFile may return null
if (info != null) if (info != null)
{ {
var dateCreated = fileSystem.GetCreationTimeUtc(info); var dateCreated = info.CreationTimeUtc;
if (dateCreated.Equals(DateTime.MinValue)) if (dateCreated.Equals(DateTime.MinValue))
{ {

View File

@ -201,6 +201,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
continue; continue;
} }
if (resolvedItem.Files.Count == 0)
{
continue;
}
var firstMedia = resolvedItem.Files[0]; var firstMedia = resolvedItem.Files[0];
var libraryItem = new T var libraryItem = new T

View File

@ -11,6 +11,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
public abstract class ItemResolver<T> : IItemResolver public abstract class ItemResolver<T> : IItemResolver
where T : BaseItem, new() where T : BaseItem, new()
{ {
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public virtual ResolverPriority Priority => ResolverPriority.First;
/// <summary> /// <summary>
/// Resolves the specified args. /// Resolves the specified args.
/// </summary> /// </summary>
@ -21,12 +27,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null; return null;
} }
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public virtual ResolverPriority Priority => ResolverPriority.First;
/// <summary> /// <summary>
/// Sets initial values on the newly resolved item. /// Sets initial values on the newly resolved item.
/// </summary> /// </summary>

View File

@ -376,7 +376,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{ {
var multiDiscFolders = new List<FileSystemMetadata>(); var multiDiscFolders = new List<FileSystemMetadata>();
var libraryOptions = args.GetLibraryOptions(); var libraryOptions = args.LibraryOptions;
var supportPhotos = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && libraryOptions.EnablePhotos; var supportPhotos = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && libraryOptions.EnablePhotos;
var photos = new List<FileSystemMetadata>(); var photos = new List<FileSystemMetadata>();

View File

@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
public class PhotoAlbumResolver : FolderResolver<PhotoAlbum> public class PhotoAlbumResolver : FolderResolver<PhotoAlbum>
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class. /// Initializes a new instance of the <see cref="PhotoAlbumResolver"/> class.
@ -26,6 +26,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
_libraryManager = libraryManager; _libraryManager = libraryManager;
} }
/// <inheritdoc />
public override ResolverPriority Priority => ResolverPriority.Second;
/// <summary> /// <summary>
/// Resolves the specified args. /// Resolves the specified args.
/// </summary> /// </summary>
@ -39,8 +42,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Must be an image file within a photo collection // Must be an image file within a photo collection
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) || if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
(string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos))
{ {
if (HasPhotos(args)) if (HasPhotos(args))
{ {
@ -84,8 +87,5 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false; return false;
} }
/// <inheritdoc />
public override ResolverPriority Priority => ResolverPriority.Second;
} }
} }

View File

@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
var collectionType = args.CollectionType; var collectionType = args.CollectionType;
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
|| (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos)) || (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos))
{ {
if (IsImageFile(args.Path, _imageProcessor)) if (IsImageFile(args.Path, _imageProcessor))
{ {

View File

@ -35,14 +35,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null; return null;
} }
var season = parent as Season;
// Just in case the user decided to nest episodes. // Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it. // Not officially supported but in some cases we can handle it.
if (season == null)
{ var season = parent as Season ?? parent.GetParents().OfType<Season>().FirstOrDefault();
season = parent.GetParents().OfType<Season>().FirstOrDefault();
}
// If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders // Also handle flat tv folders
@ -55,11 +51,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
if (episode != null) if (episode != null)
{ {
var series = parent as Series; var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault();
if (series == null)
{
series = parent.GetParents().OfType<Series>().FirstOrDefault();
}
if (series != null) if (series != null)
{ {

View File

@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameSeasonNumber"), _localization.GetLocalizedString("NameSeasonNumber"),
seasonNumber, seasonNumber,
args.GetLibraryOptions().PreferredMetadataLanguage); args.LibraryOptions.PreferredMetadataLanguage);
} }
return season; return season;

View File

@ -19,19 +19,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// </summary> /// </summary>
public class SeriesResolver : FolderResolver<Series> public class SeriesResolver : FolderResolver<Series>
{ {
private readonly IFileSystem _fileSystem;
private readonly ILogger<SeriesResolver> _logger; private readonly ILogger<SeriesResolver> _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SeriesResolver"/> class. /// Initializes a new instance of the <see cref="SeriesResolver"/> class.
/// </summary> /// </summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param> /// <param name="libraryManager">The library manager.</param>
public SeriesResolver(IFileSystem fileSystem, ILogger<SeriesResolver> logger, ILibraryManager libraryManager) public SeriesResolver(ILogger<SeriesResolver> logger, ILibraryManager libraryManager)
{ {
_fileSystem = fileSystem;
_logger = logger; _logger = logger;
_libraryManager = libraryManager; _libraryManager = libraryManager;
} }
@ -59,15 +56,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{ {
// if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
//{
// return new Series
// {
// Path = args.Path,
// Name = Path.GetFileName(args.Path)
// };
//}
var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path); var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path);
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{ {
@ -100,7 +88,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null; return null;
} }
if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false)) if (IsSeriesFolder(args.Path, args.FileSystemChildren, _logger, _libraryManager, false))
{ {
return new Series return new Series
{ {
@ -117,8 +105,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
public static bool IsSeriesFolder( public static bool IsSeriesFolder(
string path, string path,
IEnumerable<FileSystemMetadata> fileSystemChildren, IEnumerable<FileSystemMetadata> fileSystemChildren,
IDirectoryService directoryService,
IFileSystem fileSystem,
ILogger<SeriesResolver> logger, ILogger<SeriesResolver> logger,
ILibraryManager libraryManager, ILibraryManager libraryManager,
bool isTvContentType) bool isTvContentType)

View File

@ -12,7 +12,6 @@ using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search; using MediaBrowser.Model.Search;
using Microsoft.Extensions.Logging;
using Genre = MediaBrowser.Controller.Entities.Genre; using Genre = MediaBrowser.Controller.Entities.Genre;
using Person = MediaBrowser.Controller.Entities.Person; using Person = MediaBrowser.Controller.Entities.Person;

View File

@ -13,8 +13,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using Book = MediaBrowser.Controller.Entities.Book;
using AudioBook = MediaBrowser.Controller.Entities.AudioBook; using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
using Book = MediaBrowser.Controller.Entities.Book;
namespace Emby.Server.Implementations.Library namespace Emby.Server.Implementations.Library
{ {
@ -248,15 +248,15 @@ namespace Emby.Server.Implementations.Library
} }
else if (positionTicks > 0 && hasRuntime && item is AudioBook) else if (positionTicks > 0 && hasRuntime && item is AudioBook)
{ {
var minIn = TimeSpan.FromTicks(positionTicks).TotalMinutes; var playbackPositionInMinutes = TimeSpan.FromTicks(positionTicks).TotalMinutes;
var minOut = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes; var remainingTimeInMinutes = TimeSpan.FromTicks(runtimeTicks - positionTicks).TotalMinutes;
if (minIn > _config.Configuration.MinAudiobookResume) if (playbackPositionInMinutes < _config.Configuration.MinAudiobookResume)
{ {
// ignore progress during the beginning // ignore progress during the beginning
positionTicks = 0; positionTicks = 0;
} }
else if (minOut < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks) else if (remainingTimeInMinutes < _config.Configuration.MaxAudiobookResume || positionTicks >= runtimeTicks)
{ {
// mark as completed close to the end // mark as completed close to the end
positionTicks = 0; positionTicks = 0;

View File

@ -2,7 +2,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;

View File

@ -17,7 +17,6 @@ using Jellyfin.Data.Enums;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -802,22 +801,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public ActiveRecordingInfo GetActiveRecordingInfo(string path) public ActiveRecordingInfo GetActiveRecordingInfo(string path)
{ {
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path) || _activeRecordings.IsEmpty)
{ {
return null; return null;
} }
foreach (var recording in _activeRecordings.Values) foreach (var (_, recordingInfo) in _activeRecordings)
{ {
if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested) if (string.Equals(recordingInfo.Path, path, StringComparison.Ordinal) && !recordingInfo.CancellationTokenSource.IsCancellationRequested)
{ {
var timer = recording.Timer; var timer = recordingInfo.Timer;
if (timer.Status != RecordingStatus.InProgress) if (timer.Status != RecordingStatus.InProgress)
{ {
return null; return null;
} }
return recording; return recordingInfo;
} }
} }
@ -1622,9 +1621,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
return _activeRecordings return _activeRecordings
.Values .Any(i => string.Equals(i.Value.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Value.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
.ToList()
.Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
} }
private IRecorder GetRecorder(MediaSourceInfo mediaSource) private IRecorder GetRecorder(MediaSourceInfo mediaSource)
@ -2240,14 +2237,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var enabledTimersForSeries = new List<TimerInfo>(); var enabledTimersForSeries = new List<TimerInfo>();
foreach (var timer in allTimers) foreach (var timer in allTimers)
{ {
var existingTimer = _timerProvider.GetTimer(timer.Id); var existingTimer = _timerProvider.GetTimer(timer.Id)
?? (string.IsNullOrWhiteSpace(timer.ProgramId)
if (existingTimer == null)
{
existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId)
? null ? null
: _timerProvider.GetTimerByProgramId(timer.ProgramId); : _timerProvider.GetTimerByProgramId(timer.ProgramId));
}
if (existingTimer == null) if (existingTimer == null)
{ {

View File

@ -4,9 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
using MediaBrowser.Common.Json; using MediaBrowser.Common.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -2,7 +2,6 @@
using System; using System;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV

View File

@ -787,14 +787,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{ {
var channelNumber = GetChannelNumber(channel); var channelNumber = GetChannelNumber(channel);
var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)); var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase))
if (station == null) ?? new ScheduleDirect.Station
{
station = new ScheduleDirect.Station
{ {
stationID = channel.stationID stationID = channel.stationID
}; };
}
var channelInfo = new ChannelInfo var channelInfo = new ChannelInfo
{ {

View File

@ -4,7 +4,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;

View File

@ -987,10 +987,7 @@ namespace Emby.Server.Implementations.LiveTv
var externalProgramId = programTuple.Item2; var externalProgramId = programTuple.Item2;
string externalSeriesId = programTuple.Item3; string externalSeriesId = programTuple.Item3;
if (timerList == null) timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
{
timerList = (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
}
var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase)); var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase));
var foundSeriesTimer = false; var foundSeriesTimer = false;
@ -1018,10 +1015,7 @@ namespace Emby.Server.Implementations.LiveTv
continue; continue;
} }
if (seriesTimerList == null) seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
{
seriesTimerList = (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
}
var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase)); var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase));
@ -1974,10 +1968,7 @@ namespace Emby.Server.Implementations.LiveTv
}; };
} }
if (service == null) service ??= _services[0];
{
service = _services[0];
}
var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false); var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);

View File

@ -8,10 +8,8 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json; using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -19,7 +17,6 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -424,10 +421,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
string audioCodec = channelInfo.AudioCodec; string audioCodec = channelInfo.AudioCodec;
if (!videoBitrate.HasValue) videoBitrate ??= isHd ? 15000000 : 2000000;
{
videoBitrate = isHd ? 15000000 : 2000000;
}
int? audioBitrate = isHd ? 448000 : 192000; int? audioBitrate = isHd ? 448000 : 192000;
@ -664,7 +658,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_modelCache.Clear(); _modelCache.Clear();
} }
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token; using var timedCancellationToken = new CancellationTokenSource(discoveryDurationMs);
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timedCancellationToken.Token, cancellationToken);
cancellationToken = linkedCancellationTokenSource.Token;
var list = new List<TunerHostInfo>(); var list = new List<TunerHostInfo>();
// Create udp broadcast discovery message // Create udp broadcast discovery message

View File

@ -150,7 +150,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{ {
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token; using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token);
cancellationToken = linkedCancellationTokenSource.Token;
// use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039 // use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039
var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT; var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;

View File

@ -4,7 +4,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;

View File

@ -11,7 +11,6 @@ using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Localization namespace Emby.Server.Implementations.Localization
@ -316,10 +315,9 @@ namespace Emby.Server.Implementations.Localization
} }
const string Prefix = "Core"; const string Prefix = "Core";
var key = Prefix + culture;
return _dictionaries.GetOrAdd( return _dictionaries.GetOrAdd(
key, culture,
f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
} }

View File

@ -15,7 +15,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.MediaEncoder namespace Emby.Server.Implementations.MediaEncoder

View File

@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.Playlists
// Create a list of the new linked children to add to the playlist // Create a list of the new linked children to add to the playlist
var childrenToAdd = newItems var childrenToAdd = newItems
.Select(i => LinkedChild.Create(i)) .Select(LinkedChild.Create)
.ToList(); .ToList();
// Log duplicates that have been ignored, if any // Log duplicates that have been ignored, if any

View File

@ -44,12 +44,7 @@ namespace Emby.Server.Implementations.Plugins
{ {
get get
{ {
if (_httpClientFactory == null) return _httpClientFactory ?? (_httpClientFactory = _appHost.Resolve<IHttpClientFactory>());
{
_httpClientFactory = _appHost.Resolve<IHttpClientFactory>();
}
return _httpClientFactory;
} }
} }
@ -166,9 +161,7 @@ namespace Emby.Server.Implementations.Plugins
/// </summary> /// </summary>
public void CreatePlugins() public void CreatePlugins()
{ {
_ = _appHost.GetExports<IPlugin>(CreatePluginInstance) _ = _appHost.GetExports<IPlugin>(CreatePluginInstance);
.Where(i => i != null)
.ToArray();
} }
/// <summary> /// <summary>
@ -278,11 +271,7 @@ namespace Emby.Server.Implementations.Plugins
// If no version is given, return the current instance. // If no version is given, return the current instance.
var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList();
plugin = plugins.FirstOrDefault(p => p.Instance != null); plugin = plugins.FirstOrDefault(p => p.Instance != null) ?? plugins.OrderByDescending(p => p.Version).FirstOrDefault();
if (plugin == null)
{
plugin = plugins.OrderByDescending(p => p.Version).FirstOrDefault();
}
} }
else else
{ {

View File

@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
@ -258,19 +257,17 @@ namespace Emby.Server.Implementations.QuickConnect
} }
// Expire stale connection requests // Expire stale connection requests
var values = _currentRequests.Values.ToList(); foreach (var (_, currentRequest) in _currentRequests)
for (int i = 0; i < values.Count; i++)
{ {
var added = values[i].DateAdded ?? DateTime.UnixEpoch; var added = currentRequest.DateAdded ?? DateTime.UnixEpoch;
if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll) if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout))
{ {
var code = values[i].Code; var code = currentRequest.Code;
_logger.LogDebug("Removing expired request {code}", code); _logger.LogDebug("Removing expired request {Code}", code);
if (!_currentRequests.TryRemove(code, out _)) if (!_currentRequests.TryRemove(code, out _))
{ {
_logger.LogWarning("Request {code} already expired", code); _logger.LogWarning("Request {Code} already expired", code);
} }
} }
} }

View File

@ -4,7 +4,6 @@ using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -302,12 +301,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
{ {
get get
{ {
if (_id == null) return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture);
{
_id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
return _id;
} }
} }
@ -349,9 +343,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
{ {
var trigger = (ITaskTrigger)sender; var trigger = (ITaskTrigger)sender;
var configurableTask = ScheduledTask as IConfigurableScheduledTask; if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled)
if (configurableTask != null && !configurableTask.IsEnabled)
{ {
return; return;
} }

View File

@ -12,9 +12,9 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks namespace Emby.Server.Implementations.ScheduledTasks
{ {

View File

@ -5,8 +5,8 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
namespace Emby.Server.Implementations.ScheduledTasks namespace Emby.Server.Implementations.ScheduledTasks
{ {

View File

@ -9,7 +9,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -6,8 +6,8 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Library;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
namespace Emby.Server.Implementations.ScheduledTasks namespace Emby.Server.Implementations.ScheduledTasks
{ {

View File

@ -1475,17 +1475,14 @@ namespace Emby.Server.Implementations.Session
user = _userManager.GetUserById(request.UserId); user = _userManager.GetUserById(request.UserId);
} }
if (user == null) user ??= _userManager.GetUserByName(request.Username);
{
user = _userManager.GetUserByName(request.Username);
}
if (enforcePassword) if (enforcePassword)
{ {
user = await _userManager.AuthenticateUser( user = await _userManager.AuthenticateUser(
request.Username, request.Username,
request.Password, request.Password,
request.PasswordSha1, null,
request.RemoteEndPoint, request.RemoteEndPoint,
true).ConfigureAwait(false); true).ConfigureAwait(false);
} }

View File

@ -87,7 +87,7 @@ namespace Emby.Server.Implementations.SyncPlay
_sessionManager = sessionManager; _sessionManager = sessionManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger<SyncPlayManager>(); _logger = loggerFactory.CreateLogger<SyncPlayManager>();
_sessionManager.SessionControllerConnected += OnSessionControllerConnected; _sessionManager.SessionEnded += OnSessionEnded;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -269,14 +269,17 @@ namespace Emby.Server.Implementations.SyncPlay
var user = _userManager.GetUserById(session.UserId); var user = _userManager.GetUserById(session.UserId);
List<GroupInfoDto> list = new List<GroupInfoDto>(); List<GroupInfoDto> list = new List<GroupInfoDto>();
foreach (var group in _groups.Values) lock (_groupsLock)
{ {
// Locking required as group is not thread-safe. foreach (var (_, group) in _groups)
lock (group)
{ {
if (group.HasAccessToPlayQueue(user)) // Locking required as group is not thread-safe.
lock (group)
{ {
list.Add(group.GetInfo()); if (group.HasAccessToPlayQueue(user))
{
list.Add(group.GetInfo());
}
} }
} }
} }
@ -352,18 +355,18 @@ namespace Emby.Server.Implementations.SyncPlay
return; return;
} }
_sessionManager.SessionControllerConnected -= OnSessionControllerConnected; _sessionManager.SessionEnded -= OnSessionEnded;
_disposed = true; _disposed = true;
} }
private void OnSessionControllerConnected(object sender, SessionEventArgs e) private void OnSessionEnded(object sender, SessionEventArgs e)
{ {
var session = e.SessionInfo; var session = e.SessionInfo;
if (_sessionToGroupMap.TryGetValue(session.Id, out var group)) if (_sessionToGroupMap.TryGetValue(session.Id, out var group))
{ {
var request = new JoinGroupRequest(group.GroupId); var leaveGroupRequest = new LeaveGroupRequest();
JoinGroup(session, request, CancellationToken.None); LeaveGroup(session, leaveGroupRequest, CancellationToken.None);
} }
} }

View File

@ -43,9 +43,7 @@ namespace Emby.Server.Implementations.TV
string presentationUniqueKey = null; string presentationUniqueKey = null;
if (!string.IsNullOrEmpty(request.SeriesId)) if (!string.IsNullOrEmpty(request.SeriesId))
{ {
var series = _libraryManager.GetItemById(request.SeriesId) as Series; if (_libraryManager.GetItemById(request.SeriesId) is Series series)
if (series != null)
{ {
presentationUniqueKey = GetUniqueSeriesKey(series); presentationUniqueKey = GetUniqueSeriesKey(series);
} }
@ -95,9 +93,7 @@ namespace Emby.Server.Implementations.TV
int? limit = null; int? limit = null;
if (!string.IsNullOrEmpty(request.SeriesId)) if (!string.IsNullOrEmpty(request.SeriesId))
{ {
var series = _libraryManager.GetItemById(request.SeriesId) as Series; if (_libraryManager.GetItemById(request.SeriesId) is Series series)
if (series != null)
{ {
presentationUniqueKey = GetUniqueSeriesKey(series); presentationUniqueKey = GetUniqueSeriesKey(series);
limit = 1; limit = 1;

View File

@ -52,8 +52,6 @@ namespace Jellyfin.Api.Controllers
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _configuration;
private readonly IDeviceManager _deviceManager; private readonly IDeviceManager _deviceManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly TranscodingJobHelper _transcodingJobHelper;
private readonly ILogger<DynamicHlsController> _logger; private readonly ILogger<DynamicHlsController> _logger;
@ -72,12 +70,11 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param> /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param>
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param> /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public DynamicHlsController( public DynamicHlsController(
ILibraryManager libraryManager, ILibraryManager libraryManager,
IUserManager userManager, IUserManager userManager,
@ -87,15 +84,12 @@ namespace Jellyfin.Api.Controllers
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
ISubtitleEncoder subtitleEncoder,
IConfiguration configuration,
IDeviceManager deviceManager, IDeviceManager deviceManager,
TranscodingJobHelper transcodingJobHelper, TranscodingJobHelper transcodingJobHelper,
ILogger<DynamicHlsController> logger, ILogger<DynamicHlsController> logger,
DynamicHlsHelper dynamicHlsHelper) DynamicHlsHelper dynamicHlsHelper,
EncodingHelper encodingHelper)
{ {
_encodingHelper = new EncodingHelper(mediaEncoder, fileSystem, subtitleEncoder, configuration);
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userManager = userManager; _userManager = userManager;
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
@ -104,12 +98,12 @@ namespace Jellyfin.Api.Controllers
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_subtitleEncoder = subtitleEncoder;
_configuration = configuration;
_deviceManager = deviceManager; _deviceManager = deviceManager;
_transcodingJobHelper = transcodingJobHelper; _transcodingJobHelper = transcodingJobHelper;
_logger = logger; _logger = logger;
_dynamicHlsHelper = dynamicHlsHelper; _dynamicHlsHelper = dynamicHlsHelper;
_encodingHelper = encodingHelper;
_encodingOptions = serverConfigurationManager.GetEncodingOptions(); _encodingOptions = serverConfigurationManager.GetEncodingOptions();
} }
@ -1126,9 +1120,7 @@ namespace Jellyfin.Api.Controllers
_libraryManager, _libraryManager,
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_fileSystem, _encodingHelper,
_subtitleEncoder,
_configuration,
_dlnaManager, _dlnaManager,
_deviceManager, _deviceManager,
_transcodingJobHelper, _transcodingJobHelper,
@ -1211,9 +1203,7 @@ namespace Jellyfin.Api.Controllers
_libraryManager, _libraryManager,
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_fileSystem, _encodingHelper,
_subtitleEncoder,
_configuration,
_dlnaManager, _dlnaManager,
_deviceManager, _deviceManager,
_transcodingJobHelper, _transcodingJobHelper,

View File

@ -237,48 +237,6 @@ namespace Jellyfin.Api.Controllers
return Ok(results); return Ok(results);
} }
/// <summary>
/// Gets a remote image.
/// </summary>
/// <param name="imageUrl">The image url.</param>
/// <param name="providerName">The provider name.</param>
/// <response code="200">Remote image retrieved.</response>
/// <returns>
/// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
/// The task result contains an <see cref="FileStreamResult"/> containing the images file stream.
/// </returns>
[HttpGet("Items/RemoteSearch/Image")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesImageFile]
public async Task<ActionResult> GetRemoteSearchImage(
[FromQuery, Required] string imageUrl,
[FromQuery, Required] string providerName)
{
var urlHash = imageUrl.GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
try
{
var contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
if (System.IO.File.Exists(contentPath))
{
return PhysicalFile(contentPath, MimeTypes.GetMimeType(contentPath));
}
}
catch (FileNotFoundException)
{
// Means the file isn't cached yet
}
catch (IOException)
{
// Means the file isn't cached yet
}
await DownloadImage(providerName, imageUrl, urlHash, pointerCachePath).ConfigureAwait(false);
var updatedContentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
return PhysicalFile(updatedContentPath, MimeTypes.GetMimeType(updatedContentPath));
}
/// <summary> /// <summary>
/// Applies search criteria to an item and refreshes metadata. /// Applies search criteria to an item and refreshes metadata.
/// </summary> /// </summary>
@ -320,54 +278,5 @@ namespace Jellyfin.Api.Controllers
return NoContent(); return NoContent();
} }
/// <summary>
/// Downloads the image.
/// </summary>
/// <param name="providerName">Name of the provider.</param>
/// <param name="url">The URL.</param>
/// <param name="urlHash">The URL hash.</param>
/// <param name="pointerCachePath">The pointer cache path.</param>
/// <returns>Task.</returns>
private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
{
using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
if (result.Content.Headers.ContentType?.MediaType == null)
{
throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType));
}
var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
Directory.CreateDirectory(directory);
using (var stream = result.Content)
{
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
await using var fileStream = new FileStream(
fullCachePath,
FileMode.Create,
FileAccess.Write,
FileShare.None,
IODefaults.FileStreamBufferSize,
true);
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
Directory.CreateDirectory(pointerCacheDirectory);
await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false);
}
/// <summary>
/// Gets the full cache path.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
private string GetFullCachePath(string filename)
=> Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename);
} }
} }

View File

@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers
// nameof(LiveTvProgram) // nameof(LiveTvProgram)
}, },
// IsMovie = true // IsMovie = true
OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.Random, SortOrder.Descending) },
Limit = 7, Limit = 7,
ParentId = parentIdGuid, ParentId = parentIdGuid,
Recursive = true, Recursive = true,
@ -110,7 +110,7 @@ namespace Jellyfin.Api.Controllers
{ {
IncludeItemTypes = itemTypes.ToArray(), IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true, IsMovie = true,
OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) },
Limit = 10, Limit = 10,
IsFavoriteOrLiked = true, IsFavoriteOrLiked = true,
ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(), ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(),
@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
DtoOptions = dtoOptions DtoOptions = dtoOptions
}); });
var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList(); var mostRecentMovies = recentlyPlayedMovies.GetRange(0, Math.Min(recentlyPlayedMovies.Count, 6));
// Get recently played directors // Get recently played directors
var recentDirectors = GetDirectors(mostRecentMovies) var recentDirectors = GetDirectors(mostRecentMovies)
.ToList(); .ToList();
@ -191,7 +191,8 @@ namespace Jellyfin.Api.Controllers
foreach (var name in names) foreach (var name in names)
{ {
var items = _libraryManager.GetItemList(new InternalItemsQuery(user) var items = _libraryManager.GetItemList(
new InternalItemsQuery(user)
{ {
Person = name, Person = name,
// Account for duplicates by imdb id, since the database doesn't support this yet // Account for duplicates by imdb id, since the database doesn't support this yet

View File

@ -12,7 +12,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Json; using MediaBrowser.Common.Json;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -208,12 +207,7 @@ namespace Jellyfin.Api.Controllers
var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)); var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId));
// Select the un-instanced one first. // Select the un-instanced one first.
var plugin = plugins.FirstOrDefault(p => p.Instance == null); var plugin = plugins.FirstOrDefault(p => p.Instance == null) ?? plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault();
if (plugin == null)
{
// Then by the status.
plugin = plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault();
}
if (plugin != null) if (plugin != null)
{ {

View File

@ -145,58 +145,6 @@ namespace Jellyfin.Api.Controllers
return Ok(_providerManager.GetRemoteImageProviderInfo(item)); return Ok(_providerManager.GetRemoteImageProviderInfo(item));
} }
/// <summary>
/// Gets a remote image.
/// </summary>
/// <param name="imageUrl">The image url.</param>
/// <response code="200">Remote image returned.</response>
/// <response code="404">Remote image not found.</response>
/// <returns>Image Stream.</returns>
[HttpGet("Images/Remote")]
[Produces(MediaTypeNames.Application.Octet)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesImageFile]
public async Task<ActionResult> GetRemoteImage([FromQuery, Required] Uri imageUrl)
{
var urlHash = imageUrl.ToString().GetMD5();
var pointerCachePath = GetFullCachePath(urlHash.ToString());
string? contentPath = null;
var hasFile = false;
try
{
contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
if (System.IO.File.Exists(contentPath))
{
hasFile = true;
}
}
catch (FileNotFoundException)
{
// The file isn't cached yet
}
catch (IOException)
{
// The file isn't cached yet
}
if (!hasFile)
{
await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false);
contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
}
if (string.IsNullOrEmpty(contentPath))
{
return NotFound();
}
var contentType = MimeTypes.GetMimeType(contentPath);
return PhysicalFile(contentPath, contentType);
}
/// <summary> /// <summary>
/// Downloads a remote image for an item. /// Downloads a remote image for an item.
/// </summary> /// </summary>

View File

@ -228,10 +228,7 @@ namespace Jellyfin.Api.Controllers
itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb); itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
} }
if (itemWithImage == null) itemWithImage ??= GetParentWithImage<BaseItem>(item, ImageType.Thumb);
{
itemWithImage = GetParentWithImage<BaseItem>(item, ImageType.Thumb);
}
if (itemWithImage != null) if (itemWithImage != null)
{ {

View File

@ -69,7 +69,7 @@ namespace Jellyfin.Api.Controllers
var dtoOptions = new DtoOptions().AddClientFields(Request); var dtoOptions = new DtoOptions().AddClientFields(Request);
var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user) var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{ {
OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) },
MediaTypes = mediaType, MediaTypes = mediaType,
IncludeItemTypes = type, IncludeItemTypes = type,
IsVirtualItem = false, IsVirtualItem = false,

View File

@ -155,7 +155,7 @@ namespace Jellyfin.Api.Controllers
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{ {
IncludeItemTypes = new[] { nameof(Episode) }, IncludeItemTypes = new[] { nameof(Episode) },
OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), OrderBy = new[] { (ItemSortBy.PremiereDate, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) },
MinPremiereDate = minPremiereDate, MinPremiereDate = minPremiereDate,
StartIndex = startIndex, StartIndex = startIndex,
Limit = limit, Limit = limit,

View File

@ -298,9 +298,9 @@ namespace Jellyfin.Api.Controllers
{ {
Type = DlnaProfileType.Audio, Type = DlnaProfileType.Audio,
Context = EncodingContext.Streaming, Context = EncodingContext.Streaming,
Container = transcodingContainer, Container = transcodingContainer ?? "mp3",
AudioCodec = audioCodec, AudioCodec = audioCodec ?? "mp3",
Protocol = transcodingProtocol, Protocol = transcodingProtocol ?? "http",
BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false, BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture) MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
} }

View File

@ -177,11 +177,9 @@ namespace Jellyfin.Api.Controllers
return StatusCode(StatusCodes.Status403Forbidden, "Only sha1 password is not allowed."); return StatusCode(StatusCodes.Status403Forbidden, "Only sha1 password is not allowed.");
} }
// Password should always be null
AuthenticateUserByName request = new AuthenticateUserByName AuthenticateUserByName request = new AuthenticateUserByName
{ {
Username = user.Username, Username = user.Username,
Password = null,
Pw = pw Pw = pw
}; };
return await AuthenticateUserByName(request).ConfigureAwait(false); return await AuthenticateUserByName(request).ConfigureAwait(false);
@ -208,7 +206,6 @@ namespace Jellyfin.Api.Controllers
DeviceId = auth.DeviceId, DeviceId = auth.DeviceId,
DeviceName = auth.Device, DeviceName = auth.Device,
Password = request.Pw, Password = request.Pw,
PasswordSha1 = request.Password,
RemoteEndPoint = HttpContext.GetNormalizedRemoteIp().ToString(), RemoteEndPoint = HttpContext.GetNormalizedRemoteIp().ToString(),
Username = request.Username Username = request.Username
}).ConfigureAwait(false); }).ConfigureAwait(false);

View File

@ -48,9 +48,6 @@ namespace Jellyfin.Api.Controllers
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _configuration;
private readonly IDeviceManager _deviceManager; private readonly IDeviceManager _deviceManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly TranscodingJobHelper _transcodingJobHelper;
private readonly ILogger<VideoHlsController> _logger; private readonly ILogger<VideoHlsController> _logger;
@ -60,9 +57,6 @@ namespace Jellyfin.Api.Controllers
/// Initializes a new instance of the <see cref="VideoHlsController"/> class. /// Initializes a new instance of the <see cref="VideoHlsController"/> class.
/// </summary> /// </summary>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param> /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
/// <param name="userManger">Instance of the <see cref="IUserManager"/> interface.</param> /// <param name="userManger">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
@ -72,11 +66,9 @@ namespace Jellyfin.Api.Controllers
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param> /// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
/// <param name="logger">Instance of the <see cref="ILogger{VideoHlsController}"/>.</param> /// <param name="logger">Instance of the <see cref="ILogger{VideoHlsController}"/>.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public VideoHlsController( public VideoHlsController(
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
ISubtitleEncoder subtitleEncoder,
IConfiguration configuration,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
IUserManager userManger, IUserManager userManger,
IAuthorizationContext authorizationContext, IAuthorizationContext authorizationContext,
@ -85,10 +77,9 @@ namespace Jellyfin.Api.Controllers
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IDeviceManager deviceManager, IDeviceManager deviceManager,
TranscodingJobHelper transcodingJobHelper, TranscodingJobHelper transcodingJobHelper,
ILogger<VideoHlsController> logger) ILogger<VideoHlsController> logger,
EncodingHelper encodingHelper)
{ {
_encodingHelper = new EncodingHelper(mediaEncoder, fileSystem, subtitleEncoder, configuration);
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
_authContext = authorizationContext; _authContext = authorizationContext;
_userManager = userManger; _userManager = userManger;
@ -96,12 +87,11 @@ namespace Jellyfin.Api.Controllers
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_subtitleEncoder = subtitleEncoder;
_configuration = configuration;
_deviceManager = deviceManager; _deviceManager = deviceManager;
_transcodingJobHelper = transcodingJobHelper; _transcodingJobHelper = transcodingJobHelper;
_logger = logger; _logger = logger;
_encodingHelper = encodingHelper;
_encodingOptions = serverConfigurationManager.GetEncodingOptions(); _encodingOptions = serverConfigurationManager.GetEncodingOptions();
} }
@ -285,9 +275,7 @@ namespace Jellyfin.Api.Controllers
_libraryManager, _libraryManager,
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_fileSystem, _encodingHelper,
_subtitleEncoder,
_configuration,
_dlnaManager, _dlnaManager,
_deviceManager, _deviceManager,
_transcodingJobHelper, _transcodingJobHelper,

View File

@ -49,12 +49,10 @@ namespace Jellyfin.Api.Controllers
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _configuration;
private readonly IDeviceManager _deviceManager; private readonly IDeviceManager _deviceManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly TranscodingJobHelper _transcodingJobHelper;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly EncodingHelper _encodingHelper;
private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive; private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive;
@ -69,12 +67,10 @@ namespace Jellyfin.Api.Controllers
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param> /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param> /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public VideosController( public VideosController(
ILibraryManager libraryManager, ILibraryManager libraryManager,
IUserManager userManager, IUserManager userManager,
@ -84,12 +80,10 @@ namespace Jellyfin.Api.Controllers
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
ISubtitleEncoder subtitleEncoder,
IConfiguration configuration,
IDeviceManager deviceManager, IDeviceManager deviceManager,
TranscodingJobHelper transcodingJobHelper, TranscodingJobHelper transcodingJobHelper,
IHttpClientFactory httpClientFactory) IHttpClientFactory httpClientFactory,
EncodingHelper encodingHelper)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userManager = userManager; _userManager = userManager;
@ -99,12 +93,10 @@ namespace Jellyfin.Api.Controllers
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_subtitleEncoder = subtitleEncoder;
_configuration = configuration;
_deviceManager = deviceManager; _deviceManager = deviceManager;
_transcodingJobHelper = transcodingJobHelper; _transcodingJobHelper = transcodingJobHelper;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_encodingHelper = encodingHelper;
} }
/// <summary> /// <summary>
@ -444,9 +436,7 @@ namespace Jellyfin.Api.Controllers
_libraryManager, _libraryManager,
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_fileSystem, _encodingHelper,
_subtitleEncoder,
_configuration,
_dlnaManager, _dlnaManager,
_deviceManager, _deviceManager,
_transcodingJobHelper, _transcodingJobHelper,
@ -515,8 +505,7 @@ namespace Jellyfin.Api.Controllers
// Need to start ffmpeg (because media can't be returned directly) // Need to start ffmpeg (because media can't be returned directly)
var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
var encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration); var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, "superfast");
var ffmpegCommandLineArguments = encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, "superfast");
return await FileStreamResponseHelpers.GetTranscodedFile( return await FileStreamResponseHelpers.GetTranscodedFile(
state, state,
isHeadRequest, isHeadRequest,
@ -538,7 +527,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param> /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param> /// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param> /// <param name="segmentContainer">The segment container.</param>
/// <param name="segmentLength">The segment lenght.</param> /// <param name="segmentLength">The segment length.</param>
/// <param name="minSegments">The minimum number of segments.</param> /// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param> /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param> /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
@ -567,7 +556,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param> /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param> /// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param> /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamporphic stream.</param> /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param> /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param> /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param> /// <param name="liveStreamId">The live stream id.</param>
@ -581,8 +570,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="streamOptions">Optional. The streaming options.</param> /// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response> /// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns> /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("{itemId}/{stream=stream}.{container}")] [HttpGet("{itemId}/stream.{container}")]
[HttpHead("{itemId}/{stream=stream}.{container}", Name = "HeadVideoStreamByContainer")] [HttpHead("{itemId}/stream.{container}", Name = "HeadVideoStreamByContainer")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesVideoFile] [ProducesVideoFile]
public Task<ActionResult> GetVideoStreamByContainer( public Task<ActionResult> GetVideoStreamByContainer(

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View File

@ -1,5 +1,4 @@
using System; using System.Net.Http;
using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
@ -33,13 +32,11 @@ namespace Jellyfin.Api.Helpers
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _configuration;
private readonly IDeviceManager _deviceManager; private readonly IDeviceManager _deviceManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly TranscodingJobHelper _transcodingJobHelper;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly EncodingHelper _encodingHelper;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AudioHelper"/> class. /// Initializes a new instance of the <see cref="AudioHelper"/> class.
@ -51,13 +48,11 @@ namespace Jellyfin.Api.Helpers
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param> /// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param> /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public AudioHelper( public AudioHelper(
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
IAuthorizationContext authContext, IAuthorizationContext authContext,
@ -66,13 +61,11 @@ namespace Jellyfin.Api.Helpers
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
ISubtitleEncoder subtitleEncoder,
IConfiguration configuration,
IDeviceManager deviceManager, IDeviceManager deviceManager,
TranscodingJobHelper transcodingJobHelper, TranscodingJobHelper transcodingJobHelper,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IHttpContextAccessor httpContextAccessor) IHttpContextAccessor httpContextAccessor,
EncodingHelper encodingHelper)
{ {
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
_authContext = authContext; _authContext = authContext;
@ -81,13 +74,11 @@ namespace Jellyfin.Api.Helpers
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_subtitleEncoder = subtitleEncoder;
_configuration = configuration;
_deviceManager = deviceManager; _deviceManager = deviceManager;
_transcodingJobHelper = transcodingJobHelper; _transcodingJobHelper = transcodingJobHelper;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_encodingHelper = encodingHelper;
} }
/// <summary> /// <summary>
@ -117,9 +108,7 @@ namespace Jellyfin.Api.Helpers
_libraryManager, _libraryManager,
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_fileSystem, _encodingHelper,
_subtitleEncoder,
_configuration,
_dlnaManager, _dlnaManager,
_deviceManager, _deviceManager,
_transcodingJobHelper, _transcodingJobHelper,
@ -188,8 +177,7 @@ namespace Jellyfin.Api.Helpers
// Need to start ffmpeg (because media can't be returned directly) // Need to start ffmpeg (because media can't be returned directly)
var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
var encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration); var ffmpegCommandLineArguments = _encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
var ffmpegCommandLineArguments = encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
return await FileStreamResponseHelpers.GetTranscodedFile( return await FileStreamResponseHelpers.GetTranscodedFile(
state, state,
isHeadRequest, isHeadRequest,

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Mime;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -41,14 +40,12 @@ namespace Jellyfin.Api.Helpers
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _configuration;
private readonly IDeviceManager _deviceManager; private readonly IDeviceManager _deviceManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly TranscodingJobHelper _transcodingJobHelper;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly ILogger<DynamicHlsHelper> _logger; private readonly ILogger<DynamicHlsHelper> _logger;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly EncodingHelper _encodingHelper;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DynamicHlsHelper"/> class. /// Initializes a new instance of the <see cref="DynamicHlsHelper"/> class.
@ -60,14 +57,12 @@ namespace Jellyfin.Api.Helpers
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param> /// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public DynamicHlsHelper( public DynamicHlsHelper(
ILibraryManager libraryManager, ILibraryManager libraryManager,
IUserManager userManager, IUserManager userManager,
@ -76,14 +71,12 @@ namespace Jellyfin.Api.Helpers
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem,
ISubtitleEncoder subtitleEncoder,
IConfiguration configuration,
IDeviceManager deviceManager, IDeviceManager deviceManager,
TranscodingJobHelper transcodingJobHelper, TranscodingJobHelper transcodingJobHelper,
INetworkManager networkManager, INetworkManager networkManager,
ILogger<DynamicHlsHelper> logger, ILogger<DynamicHlsHelper> logger,
IHttpContextAccessor httpContextAccessor) IHttpContextAccessor httpContextAccessor,
EncodingHelper encodingHelper)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userManager = userManager; _userManager = userManager;
@ -92,14 +85,12 @@ namespace Jellyfin.Api.Helpers
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_fileSystem = fileSystem;
_subtitleEncoder = subtitleEncoder;
_configuration = configuration;
_deviceManager = deviceManager; _deviceManager = deviceManager;
_transcodingJobHelper = transcodingJobHelper; _transcodingJobHelper = transcodingJobHelper;
_networkManager = networkManager; _networkManager = networkManager;
_logger = logger; _logger = logger;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_encodingHelper = encodingHelper;
} }
/// <summary> /// <summary>
@ -145,9 +136,7 @@ namespace Jellyfin.Api.Helpers
_libraryManager, _libraryManager,
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_fileSystem, _encodingHelper,
_subtitleEncoder,
_configuration,
_dlnaManager, _dlnaManager,
_deviceManager, _deviceManager,
_transcodingJobHelper, _transcodingJobHelper,
@ -228,9 +217,8 @@ namespace Jellyfin.Api.Helpers
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main"); var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
sdrVideoUrl += "&AllowVideoStreamCopy=false"; sdrVideoUrl += "&AllowVideoStreamCopy=false";
EncodingHelper encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration); var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec) ?? 0;
var sdrOutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec) ?? 0; var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0;
var sdrOutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0;
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);

View File

@ -309,7 +309,7 @@ namespace Jellyfin.Api.Helpers
{ {
if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
&& !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
&& !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) && user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
{ {
options.ForceDirectStream = true; options.ForceDirectStream = true;
} }

View File

@ -71,7 +71,8 @@ namespace Jellyfin.Api.Helpers
/// <returns>A <see cref="Task"/>.</returns> /// <returns>A <see cref="Task"/>.</returns>
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
{ {
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token; using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken);
cancellationToken = linkedCancellationTokenSource.Token;
try try
{ {

View File

@ -41,9 +41,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param> /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param> /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="transcodingJobHelper">Initialized <see cref="TranscodingJobHelper"/>.</param> /// <param name="transcodingJobHelper">Initialized <see cref="TranscodingJobHelper"/>.</param>
@ -59,16 +57,13 @@ namespace Jellyfin.Api.Helpers
ILibraryManager libraryManager, ILibraryManager libraryManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, EncodingHelper encodingHelper,
ISubtitleEncoder subtitleEncoder,
IConfiguration configuration,
IDlnaManager dlnaManager, IDlnaManager dlnaManager,
IDeviceManager deviceManager, IDeviceManager deviceManager,
TranscodingJobHelper transcodingJobHelper, TranscodingJobHelper transcodingJobHelper,
TranscodingJobType transcodingJobType, TranscodingJobType transcodingJobType,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
EncodingHelper encodingHelper = new EncodingHelper(mediaEncoder, fileSystem, subtitleEncoder, configuration);
// Parse the DLNA time seek header // Parse the DLNA time seek header
if (!streamingRequest.StartTimeTicks.HasValue) if (!streamingRequest.StartTimeTicks.HasValue)
{ {
@ -121,14 +116,14 @@ namespace Jellyfin.Api.Helpers
if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec)) if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec))
{ {
state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i)) state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec)
?? state.SupportedAudioCodecs.FirstOrDefault(); ?? state.SupportedAudioCodecs.FirstOrDefault();
} }
if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec)) if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec))
{ {
state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries); state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i)) state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleCodec)
?? state.SupportedSubtitleCodecs.FirstOrDefault(); ?? state.SupportedSubtitleCodecs.FirstOrDefault();
} }
@ -297,16 +292,14 @@ namespace Jellyfin.Api.Helpers
} }
} }
if (profile == null) profile ??= dlnaManager.GetDefaultProfile();
{
profile = dlnaManager.GetDefaultProfile();
}
var audioCodec = state.ActualOutputAudioCodec; var audioCodec = state.ActualOutputAudioCodec;
if (!state.IsVideoRequest) if (!state.IsVideoRequest)
{ {
responseHeaders.Add("contentFeatures.dlna.org", new ContentFeatureBuilder(profile).BuildAudioHeader( responseHeaders.Add("contentFeatures.dlna.org", ContentFeatureBuilder.BuildAudioHeader(
profile,
state.OutputContainer, state.OutputContainer,
audioCodec, audioCodec,
state.OutputAudioBitrate, state.OutputAudioBitrate,
@ -323,7 +316,7 @@ namespace Jellyfin.Api.Helpers
responseHeaders.Add( responseHeaders.Add(
"contentFeatures.dlna.org", "contentFeatures.dlna.org",
new ContentFeatureBuilder(profile).BuildVideoHeader(state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty); ContentFeatureBuilder.BuildVideoHeader(profile, state.OutputContainer, videoCodec, audioCodec, state.OutputWidth, state.OutputHeight, state.TargetVideoBitDepth, state.OutputVideoBitrate, state.TargetTimestamp, isStaticallyStreamed, state.RunTimeTicks, state.TargetVideoProfile, state.TargetVideoLevel, state.TargetFramerate, state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic, state.IsTargetInterlaced, state.TargetRefFrames, state.TargetVideoStreamCount, state.TargetAudioStreamCount, state.TargetVideoCodecTag, state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
} }
} }

View File

@ -62,8 +62,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
public TranscodingJobHelper( public TranscodingJobHelper(
ILogger<TranscodingJobHelper> logger, ILogger<TranscodingJobHelper> logger,
@ -73,8 +72,7 @@ namespace Jellyfin.Api.Helpers
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
ISessionManager sessionManager, ISessionManager sessionManager,
IAuthorizationContext authorizationContext, IAuthorizationContext authorizationContext,
ISubtitleEncoder subtitleEncoder, EncodingHelper encodingHelper,
IConfiguration configuration,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory)
{ {
_logger = logger; _logger = logger;
@ -84,8 +82,8 @@ namespace Jellyfin.Api.Helpers
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_authorizationContext = authorizationContext; _authorizationContext = authorizationContext;
_encodingHelper = encodingHelper;
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_encodingHelper = new EncodingHelper(mediaEncoder, fileSystem, subtitleEncoder, configuration);
DeleteEncodedMediaCache(); DeleteEncodedMediaCache();

View File

@ -17,8 +17,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.7" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.0.7" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,4 +1,6 @@
namespace Jellyfin.Api.Models.UserDtos using System;
namespace Jellyfin.Api.Models.UserDtos
{ {
/// <summary> /// <summary>
/// The authenticate user by name request body. /// The authenticate user by name request body.
@ -18,6 +20,7 @@
/// <summary> /// <summary>
/// Gets or sets the sha1-hashed password. /// Gets or sets the sha1-hashed password.
/// </summary> /// </summary>
[Obsolete("Send password using pw field")]
public string? Password { get; set; } public string? Password { get; set; }
} }
} }

View File

@ -26,20 +26,20 @@ namespace Jellyfin.Data.Entities
} }
/// <summary> /// <summary>
/// Gets or sets the id of this instance. /// Gets the id of this instance.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Identity, Indexed, Required. /// Identity, Indexed, Required.
/// </remarks> /// </remarks>
[XmlIgnore] [XmlIgnore]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the id of the associated user. /// Gets the id of the associated user.
/// </summary> /// </summary>
[XmlIgnore] [XmlIgnore]
public Guid UserId { get; protected set; } public Guid UserId { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the day of week. /// Gets or sets the day of week.

View File

@ -38,11 +38,10 @@ namespace Jellyfin.Data.Entities
} }
/// <summary> /// <summary>
/// Gets or sets the identity of this instance. /// Gets the identity of this instance.
/// This is the key in the backing database.
/// </summary> /// </summary>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
@ -120,7 +119,7 @@ namespace Jellyfin.Data.Entities
/// <inheritdoc /> /// <inheritdoc />
[ConcurrencyCheck] [ConcurrencyCheck]
public uint RowVersion { get; set; } public uint RowVersion { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public void OnSavingChanges() public void OnSavingChanges()

View File

@ -27,13 +27,13 @@ namespace Jellyfin.Data.Entities
} }
/// <summary> /// <summary>
/// Gets or sets the Id. /// Gets the Id.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the user Id. /// Gets or sets the user Id.

View File

@ -1,6 +1,4 @@
#pragma warning disable CA2227 using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -35,13 +33,13 @@ namespace Jellyfin.Data.Entities
} }
/// <summary> /// <summary>
/// Gets or sets the Id. /// Gets the Id.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the user Id. /// Gets or sets the user Id.
@ -145,8 +143,8 @@ namespace Jellyfin.Data.Entities
public string? TvHome { get; set; } public string? TvHome { get; set; }
/// <summary> /// <summary>
/// Gets or sets the home sections. /// Gets the home sections.
/// </summary> /// </summary>
public virtual ICollection<HomeSection> HomeSections { get; protected set; } public virtual ICollection<HomeSection> HomeSections { get; private set; }
} }
} }

View File

@ -1,5 +1,3 @@
#pragma warning disable CA2227
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
@ -33,12 +31,12 @@ namespace Jellyfin.Data.Entities
} }
/// <summary> /// <summary>
/// Gets or sets the id of this group. /// Gets the id of this group.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Identity, Indexed, Required. /// Identity, Indexed, Required.
/// </remarks> /// </remarks>
public Guid Id { get; protected set; } public Guid Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the group's name. /// Gets or sets the group's name.
@ -52,17 +50,17 @@ namespace Jellyfin.Data.Entities
/// <inheritdoc /> /// <inheritdoc />
[ConcurrencyCheck] [ConcurrencyCheck]
public uint RowVersion { get; set; } public uint RowVersion { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a collection containing the group's permissions. /// Gets a collection containing the group's permissions.
/// </summary> /// </summary>
public virtual ICollection<Permission> Permissions { get; protected set; } public virtual ICollection<Permission> Permissions { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a collection containing the group's preferences. /// Gets a collection containing the group's preferences.
/// </summary> /// </summary>
public virtual ICollection<Preference> Preferences { get; protected set; } public virtual ICollection<Preference> Preferences { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public bool HasPermission(PermissionKind kind) public bool HasPermission(PermissionKind kind)

View File

@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations.Schema;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
namespace Jellyfin.Data.Entities namespace Jellyfin.Data.Entities
@ -10,13 +9,13 @@ namespace Jellyfin.Data.Entities
public class HomeSection public class HomeSection
{ {
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets the id.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Identity. Required. /// Identity. Required.
/// </remarks> /// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the Id of the associated display preferences. /// Gets or sets the Id of the associated display preferences.

View File

@ -20,18 +20,18 @@ namespace Jellyfin.Data.Entities
} }
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets the id.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Identity, Indexed, Required. /// Identity, Indexed, Required.
/// </remarks> /// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the user id. /// Gets the user id.
/// </summary> /// </summary>
public Guid? UserId { get; protected set; } public Guid? UserId { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the path of the image. /// Gets or sets the path of the image.

View File

@ -29,13 +29,13 @@ namespace Jellyfin.Data.Entities
} }
/// <summary> /// <summary>
/// Gets or sets the Id. /// Gets the id.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Required. /// Required.
/// </remarks> /// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the user Id. /// Gets or sets the user Id.

View File

@ -1,5 +1,3 @@
#pragma warning disable CA2227
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -30,13 +28,13 @@ namespace Jellyfin.Data.Entities.Libraries
} }
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets the id.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Identity, Indexed, Required. /// Identity, Indexed, Required.
/// </remarks> /// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the path. /// Gets or sets the path.
@ -58,7 +56,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <inheritdoc /> /// <inheritdoc />
[ConcurrencyCheck] [ConcurrencyCheck]
public uint RowVersion { get; set; } public uint RowVersion { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public void OnSavingChanges() public void OnSavingChanges()

View File

@ -1,5 +1,3 @@
#pragma warning disable CA2227
using System.Collections.Generic; using System.Collections.Generic;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -21,11 +19,11 @@ namespace Jellyfin.Data.Entities.Libraries
} }
/// <summary> /// <summary>
/// Gets or sets a collection containing the metadata for this book. /// Gets a collection containing the metadata for this book.
/// </summary> /// </summary>
public virtual ICollection<BookMetadata> BookMetadata { get; protected set; } public virtual ICollection<BookMetadata> BookMetadata { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public virtual ICollection<Release> Releases { get; protected set; } public virtual ICollection<Release> Releases { get; private set; }
} }
} }

View File

@ -1,5 +1,3 @@
#pragma warning disable CA2227
using System.Collections.Generic; using System.Collections.Generic;
using Jellyfin.Data.Interfaces; using Jellyfin.Data.Interfaces;
@ -26,9 +24,9 @@ namespace Jellyfin.Data.Entities.Libraries
public long? Isbn { get; set; } public long? Isbn { get; set; }
/// <summary> /// <summary>
/// Gets or sets a collection of the publishers for this book. /// Gets a collection of the publishers for this book.
/// </summary> /// </summary>
public virtual ICollection<Company> Publishers { get; protected set; } public virtual ICollection<Company> Publishers { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public ICollection<Company> Companies => Publishers; public ICollection<Company> Companies => Publishers;

View File

@ -1,5 +1,3 @@
#pragma warning disable CA2227
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
@ -29,13 +27,13 @@ namespace Jellyfin.Data.Entities.Libraries
} }
/// <summary> /// <summary>
/// Gets or sets the id. /// Gets the id.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Identity, Indexed, Required. /// Identity, Indexed, Required.
/// </remarks> /// </remarks>
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; } public int Id { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
@ -74,7 +72,7 @@ namespace Jellyfin.Data.Entities.Libraries
/// <inheritdoc /> /// <inheritdoc />
[ConcurrencyCheck] [ConcurrencyCheck]
public uint RowVersion { get; protected set; } public uint RowVersion { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public void OnSavingChanges() public void OnSavingChanges()

Some files were not shown because too many files have changed in this diff Show More