Compare commits

..

40 Commits

Author SHA1 Message Date
Joshua M. Boniface 53186c766b Bump version to 10.7.7 2021-09-05 22:33:31 -04:00
Bond-009 26990eac71 Merge pull request #6274 from thornbill/restore-max-resolution-params
Restore max width and height params

(cherry picked from commit d8b9f4dc2d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-09-05 22:30:44 -04:00
dkanada f6be0e2d1d Merge pull request #6512 from thornbill/preferences-fix
Preferences fix
2021-09-06 11:27:32 +09:00
Bill Thornton 3d45834bd8 Fix python package name 2021-09-05 14:20:03 -04:00
Bill Thornton fddfd55018 Remove required constraint in preferences 2021-09-05 14:19:01 -04:00
Bond-009 56b05f4e2b Merge pull request #6175 from nielsvanvelzen/crobibero-is-stupid
Fix routeMediaSourceId route parameter in SubtitleController GetSubtitle

(cherry picked from commit 04daf0ff49)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-06-13 19:30:34 -04:00
Joshua M. Boniface e8148ec0bd Merge pull request #6131 from BaronGreenback/Fix_NetworkFlooding
Fix network flooding issue

(cherry picked from commit b060d9d0f1)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-06-02 22:08:16 -04:00
Claus Vium 77c5c53598 Merge pull request #6043 from peterspenler/feature/chromecast-aac-handling
Reorder requested audio channels checks

(cherry picked from commit ffe2770388)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-27 02:46:02 -04:00
Claus Vium d2db73b876 Merge pull request #6038 from crobibero/delete-existing-sessions
Don't logout if deviceId is null

(cherry picked from commit 1594385497)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-27 02:46:02 -04:00
Joshua M. Boniface 49873c3d7f Bump version to 10.7.6 2021-05-20 22:06:25 -04:00
Bond-009 0b577c8a44 Merge pull request #6053 from jellyfin/cuda-fix
Fix the 'No decoder surfaces left' error on Cuda

(cherry picked from commit 9b82b37095)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-20 22:05:00 -04:00
Bond-009 6386606a53 Merge pull request #5987 from Bond-009/ioob
PathExtensions: Fix index out of bounds in TryReplaceSubPath
(cherry picked from commit a6ee4632ce)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-20 22:05:00 -04:00
dkanada cc908210d9 Merge pull request #6022 from jellyfin/revert-remux-perm
Revert remuxing permission changes from #5859
2021-05-11 21:07:36 +09:00
Claus Vium dec30ade8f Revert remuxing permission changes from #5859
Clients cannot currently handle it properly.
2021-05-10 08:35:23 +02:00
Joshua M. Boniface 55a8d2555e Bump version to 10.7.5 2021-05-04 22:08:44 -04:00
Joshua M. Boniface b7c3510da1 Revert "Merge pull request #5943 from Maxr1998/device-profile-defaults"
This PR broke direct play in JMP and caused aspect ratio issues in web.

This reverts commit 4c8df4c5bb.
2021-05-04 22:07:32 -04:00
Joshua M. Boniface fd102abd81 Merge pull request #5973 from crobibero/legacy-ci-apiclient
Kill the CI

(cherry picked from commit d655145867)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 21:33:52 -04:00
Joshua M. Boniface f8f7767cc5 Merge pull request #5968 from crobibero/legacy-ci-apiclient
(cherry picked from commit a598a8071b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 21:21:44 -04:00
Joshua M. Boniface e8436814dc Bump version to 10.7.4 2021-05-04 21:21:44 -04:00
Joshua M. Boniface 14f63e8f2f Merge pull request #5970 from crobibero/fix-linux-test
Fix Linux Tests
2021-05-04 21:21:29 -04:00
crobibero e764de0c80 Fix linux test for backport 2021-05-04 19:19:35 -06:00
Joshua M. Boniface 40147c9bb7 Merge pull request #5969 from crobibero/required-revert
Remove Required attributes

(cherry picked from commit fe0fce26e4)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 21:14:59 -04:00
Joshua M. Boniface 3566d21ad1 Bump version to 10.7.3 2021-05-04 20:01:50 -04:00
Bond-009 4c8df4c5bb Merge pull request #5943 from Maxr1998/device-profile-defaults
(cherry picked from commit 9d3f614527)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-04 19:33:08 -04:00
Bond-009 9798bf29f3 Merge pull request #5937 from Maxr1998/videoscontroller-api-fix
Remove extraneous 'stream' parameter

(cherry picked from commit 266913c5d8)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-02 17:02:10 -04:00
Joshua M. Boniface 93ce087fc9 Merge pull request from GHSA-rgjw-4fwc-9v96
Remove /Images/Remote API endpoint

(cherry picked from commit e71cd8274a)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-05-02 17:01:33 -04:00
Bond-009 e39495354b Merge pull request #5904 from cvium/fix-updatepeople-questionmark
add UpdatePeopleAsync and add people to both tables

(cherry picked from commit 5df87b3e0d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:56:58 -04:00
Bond-009 ee94fad8f7 Merge pull request #5903 from iwalton3/auto-leave-syncplay
Leave SyncPlay group on session disconnect.

(cherry picked from commit dcc2df75ec)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:56:58 -04:00
Claus Vium 53239b0529 Merge pull request #5861 from BaronGreenback/ProfileMatch
Change profile matching to match what the web interface says.

(cherry picked from commit 12496677bd)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:56:57 -04:00
Bond-009 cf0da1de86 Merge pull request #5826 from BaronGreenback/ssdpFix
PlayTo Fix: Use external ip not internal interface

(cherry picked from commit f4a59c92e6)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-29 14:56:30 -04:00
Bond-009 093510ae58 Merge pull request #5881 from cvium/tmdb-episode-externalids
Add tvrage and imdb ids for episodes

(cherry picked from commit e19d89bb4f)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:41 -04:00
Bond-009 c3fafe9289 Merge pull request #5878 from Artiume/patch-2
(cherry picked from commit 95ab603a40)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:41 -04:00
Bond-009 bd914acd16 Merge pull request #5873 from cvium/fix-displaypref-migration
(cherry picked from commit 233900401e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:41 -04:00
Bond-009 81f9bec101 Merge pull request #5870 from cvium/fix-tmdbpersonprovider
(cherry picked from commit 6b103f7ab2)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:40 -04:00
Bond-009 7db8601fbc Merge pull request #5863 from cvium/fix-index-migration
use IF NOT EXISTS in migration

(cherry picked from commit 5a4cfe11cf)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:40 -04:00
Bond-009 1ec247f5d8 Merge pull request #5860 from cvium/fix-notification-user-list
Fix notification disabled users list

(cherry picked from commit ebe8301404)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:25:39 -04:00
Bond-009 11e9173fbc Merge pull request #5859 from cvium/fix-streambuilder-permissions
Respect user settings for transcode and remux

(cherry picked from commit 5a6e60b414)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:24:27 -04:00
Bond-009 34508286a8 Merge pull request #5852 from cvium/fix-person-creation
Add Person to TypedBaseItems if it's new

(cherry picked from commit 4eeb69233d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:24:27 -04:00
Claus Vium a82eded845 Merge pull request #5848 from sgmoore/IndexError
Fix ArgumentOutOfRangeException scanning AudioBooks

(cherry picked from commit 665220c4fb)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:24:27 -04:00
Bond-009 fcb729ff6b Merge pull request #5808 from cvium/semi-fix-collection-perf
(cherry picked from commit 48ed4b016c)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-21 21:24:27 -04:00
52 changed files with 484 additions and 534 deletions
-59
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
-3
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
+1
View File
@@ -144,6 +144,7 @@
- [nielsvanvelzen](https://github.com/nielsvanvelzen) - [nielsvanvelzen](https://github.com/nielsvanvelzen)
- [skyfrk](https://github.com/skyfrk) - [skyfrk](https://github.com/skyfrk)
- [ianjazz246](https://github.com/ianjazz246) - [ianjazz246](https://github.com/ianjazz246)
- [peterspenler](https://github.com/peterspenler)
# Emby Contributors # Emby Contributors
+1 -1
View File
@@ -2,7 +2,7 @@ ARG DOTNET_VERSION=5.0
FROM node:alpine as web-builder FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& yarn install \ && yarn install \
+33 -68
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)
{ {
+1 -1
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,
+1 -1
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);
+1 -1
View File
@@ -33,7 +33,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId> <PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.7.2</VersionPrefix> <VersionPrefix>10.7.7</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -124,7 +124,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 +319,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 +331,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;
}
} }
} }
} }
@@ -106,8 +106,6 @@ namespace Emby.Server.Implementations.EntryPoints
NatUtility.StartDiscovery(); NatUtility.StartDiscovery();
_timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); _timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
} }
private void Stop() private void Stop()
@@ -118,13 +116,6 @@ namespace Emby.Server.Implementations.EntryPoints
NatUtility.DeviceFound -= OnNatUtilityDeviceFound; NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
_timer?.Dispose(); _timer?.Dispose();
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
}
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
} }
private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e) private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
@@ -2880,6 +2880,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 +2893,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 +2998,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(() =>
@@ -203,7 +203,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList(); return SortMediaSources(list);
} }
public MediaProtocol GetPathProtocol(string path) public MediaProtocol GetPathProtocol(string path)
@@ -437,7 +437,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 =>
{ {
@@ -452,8 +452,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();
} }
@@ -90,8 +90,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;
} }
@@ -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
@@ -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;
@@ -1543,23 +1543,26 @@ namespace Emby.Server.Implementations.Session
Limit = 1 Limit = 1
}).Items.FirstOrDefault(); }).Items.FirstOrDefault();
var allExistingForDevice = _authRepo.Get( if (!string.IsNullOrEmpty(deviceId))
new AuthenticationInfoQuery
{
DeviceId = deviceId
}).Items;
foreach (var auth in allExistingForDevice)
{ {
if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) var allExistingForDevice = _authRepo.Get(
new AuthenticationInfoQuery
{
DeviceId = deviceId
}).Items;
foreach (var auth in allExistingForDevice)
{ {
try if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal))
{ {
Logout(auth); try
} {
catch (Exception ex) Logout(auth);
{ }
_logger.LogError(ex, "Error while logging out."); catch (Exception ex)
{
_logger.LogError(ex, "Error while logging out.");
}
} }
} }
} }
@@ -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 />
@@ -352,18 +352,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);
} }
} }
@@ -239,48 +239,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>
@@ -322,54 +280,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);
} }
} }
@@ -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>
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="startPositionTicks">The start position of the subtitle in ticks.</param> /// <param name="startPositionTicks">The start position of the subtitle in ticks.</param>
/// <response code="200">File returned.</response> /// <response code="200">File returned.</response>
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns> /// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
[HttpGet("Videos/{routeItemId}/routeMediaSourceId/Subtitles/{routeIndex}/Stream.{routeFormat}")] [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/Stream.{routeFormat}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile("text/*")] [ProducesFile("text/*")]
public async Task<ActionResult> GetSubtitle( public async Task<ActionResult> GetSubtitle(
+16 -4
View File
@@ -304,6 +304,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
/// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
/// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
@@ -360,6 +362,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? startTimeTicks, [FromQuery] long? startTimeTicks,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
@@ -414,6 +418,8 @@ namespace Jellyfin.Api.Controllers
StartTimeTicks = startTimeTicks, StartTimeTicks = startTimeTicks,
Width = width, Width = width,
Height = height, Height = height,
MaxWidth = maxWidth,
MaxHeight = maxHeight,
VideoBitRate = videoBitRate, VideoBitRate = videoBitRate,
SubtitleStreamIndex = subtitleStreamIndex, SubtitleStreamIndex = subtitleStreamIndex,
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode, SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
@@ -538,7 +544,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>
@@ -560,6 +566,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param> /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param> /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param> /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
/// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
/// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param> /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param> /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param> /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
@@ -567,7 +575,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 +589,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(
@@ -616,6 +624,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? startTimeTicks, [FromQuery] long? startTimeTicks,
[FromQuery] int? width, [FromQuery] int? width,
[FromQuery] int? height, [FromQuery] int? height,
[FromQuery] int? maxWidth,
[FromQuery] int? maxHeight,
[FromQuery] int? videoBitRate, [FromQuery] int? videoBitRate,
[FromQuery] int? subtitleStreamIndex, [FromQuery] int? subtitleStreamIndex,
[FromQuery] SubtitleDeliveryMethod? subtitleMethod, [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
@@ -667,6 +677,8 @@ namespace Jellyfin.Api.Controllers
startTimeTicks, startTimeTicks,
width, width,
height, height,
maxWidth,
maxHeight,
videoBitRate, videoBitRate,
subtitleStreamIndex, subtitleStreamIndex,
subtitleMethod, subtitleMethod,
+1 -1
View File
@@ -308,7 +308,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;
} }
@@ -81,10 +81,6 @@ namespace Jellyfin.Data.Entities
/// <summary> /// <summary>
/// Gets or sets the preference value. /// Gets or sets the preference value.
/// </summary> /// </summary>
/// <remarks>
/// Required.
/// </remarks>
[Required]
public string Value { get; set; } public string Value { get; set; }
} }
} }
+1 -1
View File
@@ -19,7 +19,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId> <PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.7.2</VersionPrefix> <VersionPrefix>10.7.7</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -41,9 +41,9 @@ namespace Jellyfin.Server.Migrations.Routines
var databasePath = Path.Join(_serverApplicationPaths.DataPath, DbFilename); var databasePath = Path.Join(_serverApplicationPaths.DataPath, DbFilename);
using var connection = SQLite3.Open(databasePath, ConnectionFlags.ReadWrite, null); using var connection = SQLite3.Open(databasePath, ConnectionFlags.ReadWrite, null);
_logger.LogInformation("Creating index idx_TypedBaseItemsUserDataKeyType"); _logger.LogInformation("Creating index idx_TypedBaseItemsUserDataKeyType");
connection.Execute("CREATE INDEX idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);"); connection.Execute("CREATE INDEX IF NOT EXISTS idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);");
_logger.LogInformation("Creating index idx_PeopleNameListOrder"); _logger.LogInformation("Creating index idx_PeopleNameListOrder");
connection.Execute("CREATE INDEX idx_PeopleNameListOrder ON People(Name, ListOrder);"); connection.Execute("CREATE INDEX IF NOT EXISTS idx_PeopleNameListOrder ON People(Name, ListOrder);");
} }
} }
} }
@@ -126,13 +126,13 @@ namespace Jellyfin.Server.Migrations.Routines
ShowSidebar = dto.ShowSidebar, ShowSidebar = dto.ShowSidebar,
ScrollDirection = dto.ScrollDirection, ScrollDirection = dto.ScrollDirection,
ChromecastVersion = chromecastVersion, ChromecastVersion = chromecastVersion,
SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) && int.TryParse(length, out var skipForwardLength)
? int.Parse(length, CultureInfo.InvariantCulture) ? skipForwardLength
: 30000, : 30000,
SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && !string.IsNullOrEmpty(length) && int.TryParse(length, out var skipBackwardLength)
? int.Parse(length, CultureInfo.InvariantCulture) ? skipBackwardLength
: 10000, : 10000,
EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) && !string.IsNullOrEmpty(enabled)
? bool.Parse(enabled) ? bool.Parse(enabled)
: true, : true,
DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty, DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty,
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId> <PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.7.2</VersionPrefix> <VersionPrefix>10.7.7</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
+7 -2
View File
@@ -1434,9 +1434,14 @@ namespace MediaBrowser.Controller.Entities
var linkedChildren = LinkedChildren; var linkedChildren = LinkedChildren;
foreach (var i in linkedChildren) foreach (var i in linkedChildren)
{ {
if (i.ItemId.HasValue && i.ItemId.Value == itemId) if (i.ItemId.HasValue)
{ {
return true; if (i.ItemId.Value == itemId)
{
return true;
}
continue;
} }
var child = GetLinkedChild(i); var child = GetLinkedChild(i);
@@ -466,6 +466,15 @@ namespace MediaBrowser.Controller.Library
/// <param name="people">The people.</param> /// <param name="people">The people.</param>
void UpdatePeople(BaseItem item, List<PersonInfo> people); void UpdatePeople(BaseItem item, List<PersonInfo> people);
/// <summary>
/// Asynchronously updates the people.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="people">The people.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The async task.</returns>
Task UpdatePeopleAsync(BaseItem item, List<PersonInfo> people, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the item ids. /// Gets the item ids.
/// </summary> /// </summary>
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId> <PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.7.2</VersionPrefix> <VersionPrefix>10.7.7</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -598,7 +598,8 @@ namespace MediaBrowser.Controller.MediaEncoding
&& string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
&& isNvdecDecoder) && isNvdecDecoder)
{ {
arg.Append("-hwaccel_output_format cuda "); // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
arg.Append("-hwaccel_output_format cuda -extra_hw_frames 3 -autorotate 0 ");
} }
if (state.IsVideoRequest if (state.IsVideoRequest
@@ -1072,7 +1073,6 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
{ {
// following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead.
switch (encodingOptions.EncoderPreset) switch (encodingOptions.EncoderPreset)
{ {
case "veryslow": case "veryslow":
@@ -1253,7 +1253,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
&& profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase)) && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
{ {
profile = "constrained_baseline"; profile = "constrained_baseline";
} }
@@ -273,6 +273,16 @@ namespace MediaBrowser.Controller.MediaEncoding
public int? GetRequestedAudioChannels(string codec) public int? GetRequestedAudioChannels(string codec)
{ {
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "audiochannels");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
if (BaseRequest.MaxAudioChannels.HasValue) if (BaseRequest.MaxAudioChannels.HasValue)
{ {
return BaseRequest.MaxAudioChannels; return BaseRequest.MaxAudioChannels;
@@ -288,16 +298,6 @@ namespace MediaBrowser.Controller.MediaEncoding
return BaseRequest.TranscodingMaxAudioChannels; return BaseRequest.TranscodingMaxAudioChannels;
} }
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "audiochannels");
if (!string.IsNullOrEmpty(value)
&& int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
return null; return null;
} }
@@ -16,5 +16,7 @@ namespace MediaBrowser.Model.Dlna
public IPAddress LocalIpAddress { get; set; } public IPAddress LocalIpAddress { get; set; }
public int LocalPort { get; set; } public int LocalPort { get; set; }
public IPAddress RemoteIpAddress { get; set; }
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId> <PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.7.2</VersionPrefix> <VersionPrefix>10.7.7</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -95,16 +95,17 @@ namespace MediaBrowser.Model.Notifications
{ {
NotificationOption opt = GetOptions(notificationType); NotificationOption opt = GetOptions(notificationType);
return opt == null || return opt == null
!opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase); || !opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase);
} }
public bool IsEnabledToMonitorUser(string type, Guid userId) public bool IsEnabledToMonitorUser(string type, Guid userId)
{ {
NotificationOption opt = GetOptions(type); NotificationOption opt = GetOptions(type);
return opt != null && opt.Enabled && return opt != null
!opt.DisabledMonitorUsers.Contains(userId.ToString(""), StringComparer.OrdinalIgnoreCase); && opt.Enabled
&& !opt.DisabledMonitorUsers.Contains(userId.ToString("N"), StringComparer.OrdinalIgnoreCase);
} }
public bool IsEnabledToSendToUser(string type, string userId, User user) public bool IsEnabledToSendToUser(string type, string userId, User user)
@@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Manager
} }
// Save to database // Save to database
await SaveItemAsync(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false); await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
} }
await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false); await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
@@ -216,66 +216,18 @@ namespace MediaBrowser.Providers.Manager
lookupInfo.Year = result.ProductionYear; lookupInfo.Year = result.ProductionYear;
} }
protected async Task SaveItemAsync(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken) protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
{ {
if (result.Item.SupportsPeople && result.People != null) if (result.Item.SupportsPeople && result.People != null)
{ {
var baseItem = result.Item; var baseItem = result.Item;
LibraryManager.UpdatePeople(baseItem, result.People); await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false);
await SavePeopleMetadataAsync(result.People, libraryOptions, cancellationToken).ConfigureAwait(false);
} }
await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false); await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false);
} }
private async Task SavePeopleMetadataAsync(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
{
var personsToSave = new List<BaseItem>();
foreach (var person in people)
{
cancellationToken.ThrowIfCancellationRequested();
if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl))
{
var itemUpdateType = ItemUpdateType.MetadataDownload;
var saveEntity = false;
var personEntity = LibraryManager.GetPerson(person.Name);
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 LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
}
LibraryManager.CreateItems(personsToSave, null, CancellationToken.None);
}
protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{ {
item.AfterMetadataRefresh(); item.AfterMetadataRefresh();
@@ -49,37 +49,36 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
{ {
var person = (Person)item; var person = (Person)item;
var personTmdbId = Convert.ToInt32(person.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
if (personTmdbId > 0) if (!person.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId))
{ {
var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false); return Enumerable.Empty<RemoteImageInfo>();
if (personResult?.Images?.Profiles == null)
{
return Enumerable.Empty<RemoteImageInfo>();
}
var remoteImages = new List<RemoteImageInfo>();
var language = item.GetPreferredMetadataLanguage();
for (var i = 0; i < personResult.Images.Profiles.Count; i++)
{
var image = personResult.Images.Profiles[i];
remoteImages.Add(new RemoteImageInfo
{
ProviderName = Name,
Type = ImageType.Primary,
Width = image.Width,
Height = image.Height,
Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
Url = _tmdbClientManager.GetProfileUrl(image.FilePath)
});
}
return remoteImages.OrderByLanguageDescending(language);
} }
return Enumerable.Empty<RemoteImageInfo>(); var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false);
if (personResult?.Images?.Profiles == null)
{
return Enumerable.Empty<RemoteImageInfo>();
}
var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count];
var language = item.GetPreferredMetadataLanguage();
for (var i = 0; i < personResult.Images.Profiles.Count; i++)
{
var image = personResult.Images.Profiles[i];
remoteImages[i] = new RemoteImageInfo
{
ProviderName = Name,
Type = ImageType.Primary,
Width = image.Width,
Height = image.Height,
Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language),
Url = _tmdbClientManager.GetProfileUrl(image.FilePath)
};
}
return remoteImages.OrderByLanguageDescending(language);
} }
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
@@ -30,11 +30,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
{ {
var personTmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId))
if (personTmdbId <= 0)
{ {
var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false); var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false);
if (personResult != null) if (personResult != null)
{ {
@@ -51,19 +49,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
} }
result.SetProviderId(MetadataProvider.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture)); result.SetProviderId(MetadataProvider.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture));
result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId); if (!string.IsNullOrEmpty(personResult.ExternalIds.ImdbId))
{
result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId);
}
return new[] { result }; return new[] { result };
} }
} }
// TODO why? Because of the old rate limit?
if (searchInfo.IsAutomated)
{
// Don't hammer moviedb searching by name
return Enumerable.Empty<RemoteSearchResult>();
}
var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false); var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
var remoteSearchResults = new List<RemoteSearchResult>(); var remoteSearchResults = new List<RemoteSearchResult>();
@@ -121,9 +121,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
CommunityRating = Convert.ToSingle(episodeResult.VoteAverage) CommunityRating = Convert.ToSingle(episodeResult.VoteAverage)
}; };
if (!string.IsNullOrEmpty(episodeResult.ExternalIds?.TvdbId)) var externalIds = episodeResult.ExternalIds;
if (!string.IsNullOrEmpty(externalIds?.TvdbId))
{ {
item.SetProviderId(MetadataProvider.Tvdb, episodeResult.ExternalIds.TvdbId); item.SetProviderId(MetadataProvider.Tvdb, externalIds.TvdbId);
}
if (!string.IsNullOrEmpty(externalIds?.ImdbId))
{
item.SetProviderId(MetadataProvider.Imdb, externalIds.ImdbId);
}
if (!string.IsNullOrEmpty(externalIds?.TvrageId))
{
item.SetProviderId(MetadataProvider.TvRage, externalIds.TvrageId);
} }
if (episodeResult.Videos?.Results != null) if (episodeResult.Videos?.Results != null)
+1 -1
View File
@@ -8,7 +8,7 @@ namespace Rssdp
/// </summary> /// </summary>
public sealed class DeviceAvailableEventArgs : EventArgs public sealed class DeviceAvailableEventArgs : EventArgs
{ {
public IPAddress LocalIpAddress { get; set; } public IPAddress RemoteIpAddress { get; set; }
private readonly DiscoveredSsdpDevice _DiscoveredDevice; private readonly DiscoveredSsdpDevice _DiscoveredDevice;
+13 -13
View File
@@ -208,7 +208,7 @@ namespace Rssdp.Infrastructure
/// Raises the <see cref="DeviceAvailable"/> event. /// Raises the <see cref="DeviceAvailable"/> event.
/// </summary> /// </summary>
/// <seealso cref="DeviceAvailable"/> /// <seealso cref="DeviceAvailable"/>
protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress) protected virtual void OnDeviceAvailable(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IpAddress)
{ {
if (this.IsDisposed) if (this.IsDisposed)
{ {
@@ -220,7 +220,7 @@ namespace Rssdp.Infrastructure
{ {
handlers(this, new DeviceAvailableEventArgs(device, isNewDevice) handlers(this, new DeviceAvailableEventArgs(device, isNewDevice)
{ {
LocalIpAddress = localIpAddress RemoteIpAddress = IpAddress
}); });
} }
} }
@@ -286,7 +286,7 @@ namespace Rssdp.Infrastructure
} }
} }
private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress localIpAddress) private void AddOrUpdateDiscoveredDevice(DiscoveredSsdpDevice device, IPAddress IpAddress)
{ {
bool isNewDevice = false; bool isNewDevice = false;
lock (_Devices) lock (_Devices)
@@ -304,17 +304,17 @@ namespace Rssdp.Infrastructure
} }
} }
DeviceFound(device, isNewDevice, localIpAddress); DeviceFound(device, isNewDevice, IpAddress);
} }
private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress localIpAddress) private void DeviceFound(DiscoveredSsdpDevice device, bool isNewDevice, IPAddress IpAddress)
{ {
if (!NotificationTypeMatchesFilter(device)) if (!NotificationTypeMatchesFilter(device))
{ {
return; return;
} }
OnDeviceAvailable(device, isNewDevice, localIpAddress); OnDeviceAvailable(device, isNewDevice, IpAddress);
} }
private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device) private bool NotificationTypeMatchesFilter(DiscoveredSsdpDevice device)
@@ -347,7 +347,7 @@ namespace Rssdp.Infrastructure
return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken); return _CommunicationsServer.SendMulticastMessage(message, null, cancellationToken);
} }
private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress) private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress IpAddress)
{ {
if (!message.IsSuccessStatusCode) if (!message.IsSuccessStatusCode)
{ {
@@ -367,11 +367,11 @@ namespace Rssdp.Infrastructure
ResponseHeaders = message.Headers ResponseHeaders = message.Headers
}; };
AddOrUpdateDiscoveredDevice(device, localIpAddress); AddOrUpdateDiscoveredDevice(device, IpAddress);
} }
} }
private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress localIpAddress) private void ProcessNotificationMessage(HttpRequestMessage message, IPAddress IpAddress)
{ {
if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0) if (String.Compare(message.Method.Method, "Notify", StringComparison.OrdinalIgnoreCase) != 0)
{ {
@@ -381,7 +381,7 @@ namespace Rssdp.Infrastructure
var notificationType = GetFirstHeaderStringValue("NTS", message); var notificationType = GetFirstHeaderStringValue("NTS", message);
if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0) if (String.Compare(notificationType, SsdpConstants.SsdpKeepAliveNotification, StringComparison.OrdinalIgnoreCase) == 0)
{ {
ProcessAliveNotification(message, localIpAddress); ProcessAliveNotification(message, IpAddress);
} }
else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0) else if (String.Compare(notificationType, SsdpConstants.SsdpByeByeNotification, StringComparison.OrdinalIgnoreCase) == 0)
{ {
@@ -389,7 +389,7 @@ namespace Rssdp.Infrastructure
} }
} }
private void ProcessAliveNotification(HttpRequestMessage message, IPAddress localIpAddress) private void ProcessAliveNotification(HttpRequestMessage message, IPAddress IpAddress)
{ {
var location = GetFirstHeaderUriValue("Location", message); var location = GetFirstHeaderUriValue("Location", message);
if (location != null) if (location != null)
@@ -404,7 +404,7 @@ namespace Rssdp.Infrastructure
ResponseHeaders = message.Headers ResponseHeaders = message.Headers
}; };
AddOrUpdateDiscoveredDevice(device, localIpAddress); AddOrUpdateDiscoveredDevice(device, IpAddress);
} }
} }
@@ -630,7 +630,7 @@ namespace Rssdp.Infrastructure
private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e) private void CommsServer_RequestReceived(object sender, RequestReceivedEventArgs e)
{ {
ProcessNotificationMessage(e.Message, e.LocalIpAddress); ProcessNotificationMessage(e.Message, e.ReceivedFrom.Address);
} }
} }
} }
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("10.7.2")] [assembly: AssemblyVersion("10.7.7")]
[assembly: AssemblyFileVersion("10.7.2")] [assembly: AssemblyFileVersion("10.7.7")]
-2
View File
@@ -1,2 +0,0 @@
# Prevent generator from creating these files:
git_push.sh
@@ -1,11 +0,0 @@
#!/bin/bash
artifactsDirectory="${1}"
java -jar openapi-generator-cli.jar generate \
--input-spec ${artifactsDirectory}/openapispec/openapi.json \
--generator-name typescript-axios \
--output ./apiclient/generated/typescript/axios \
--template-dir ./apiclient/templates/typescript/axios \
--ignore-file-override ./apiclient/.openapi-generator-ignore \
--additional-properties=useSingleRequestParameter="true",withSeparateModelsAndApi="true",modelPackage="models",apiPackage="api",npmName="axios"
@@ -1,30 +0,0 @@
{
"name": "@jellyfin/client-axios",
"version": "10.7.0{{snapshotVersion}}",
"description": "Jellyfin api client using axios",
"author": "Jellyfin Contributors",
"keywords": [
"axios",
"typescript",
"jellyfin"
],
"license": "GPL-3.0-only",
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"scripts": {
"build": "tsc --outDir dist/",
"prepublishOnly": "npm run build"
},
"dependencies": {
"axios": "^0.19.2"
},
"devDependencies": {
"@types/node": "^12.11.5",
"typescript": "^3.6.4"
}{{#npmRepository}},{{/npmRepository}}
{{#npmRepository}}
"publishConfig": {
"registry": "{{npmRepository}}"
}
{{/npmRepository}}
}
+1 -1
View File
@@ -1,7 +1,7 @@
--- ---
# We just wrap `build` so this is really it # We just wrap `build` so this is really it
name: "jellyfin" name: "jellyfin"
version: "10.7.2" version: "10.7.7"
packages: packages:
- debian.amd64 - debian.amd64
- debian.arm64 - debian.arm64
+30
View File
@@ -1,3 +1,33 @@
jellyfin-server (10.7.7-1) unstable; urgency=medium
* New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.7
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 05 Sep 2021 22:33:27 -0400
jellyfin-server (10.7.6-1) unstable; urgency=medium
* New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.6
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 20 May 2021 22:06:13 -0400
jellyfin-server (10.7.5-1) unstable; urgency=medium
* New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.5
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 22:08:42 -0400
jellyfin-server (10.7.4-1) unstable; urgency=medium
* New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.4
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 21:15:42 -0400
jellyfin-server (10.7.3-1) unstable; urgency=medium
* New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.3
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 20:01:46 -0400
jellyfin-server (10.7.2-1) unstable; urgency=medium jellyfin-server (10.7.2-1) unstable; urgency=medium
* New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2 * New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2
+1 -1
View File
@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
Standards-Version: 3.9.2 Standards-Version: 3.9.2
Package: jellyfin Package: jellyfin
Version: 10.7.2 Version: 10.7.7
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org> Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
Depends: jellyfin-server, jellyfin-web Depends: jellyfin-server, jellyfin-web
Description: Provides the Jellyfin Free Software Media System Description: Provides the Jellyfin Free Software Media System
+11 -1
View File
@@ -7,7 +7,7 @@
%endif %endif
Name: jellyfin Name: jellyfin
Version: 10.7.2 Version: 10.7.7
Release: 1%{?dist} Release: 1%{?dist}
Summary: The Free Software Media System Summary: The Free Software Media System
License: GPLv3 License: GPLv3
@@ -137,6 +137,16 @@ fi
%systemd_postun_with_restart jellyfin.service %systemd_postun_with_restart jellyfin.service
%changelog %changelog
* Sun Sep 05 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.7
* Thu May 20 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.6
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.5
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.4
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.3
* Sun Apr 11 2021 Jellyfin Packaging Team <packaging@jellyfin.org> * Sun Apr 11 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2 - New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2
* Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org> * Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
@@ -0,0 +1,132 @@
using Emby.Dlna;
using Emby.Dlna.PlayTo;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
namespace Jellyfin.Dlna.Tests
{
public class DlnaManagerTests
{
private DlnaManager GetManager()
{
var xmlSerializer = new Mock<IXmlSerializer>();
var fileSystem = new Mock<IFileSystem>();
var appPaths = new Mock<IApplicationPaths>();
var loggerFactory = new Mock<ILoggerFactory>();
var jsonSerializer = new Mock<IJsonSerializer>();
var appHost = new Mock<IServerApplicationHost>();
return new DlnaManager(xmlSerializer.Object, fileSystem.Object, appPaths.Object, loggerFactory.Object, jsonSerializer.Object, appHost.Object);
}
[Fact]
public void IsMatch_GivenMatchingName_ReturnsTrue()
{
var device = new DeviceInfo()
{
Name = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
};
var profile = new DeviceProfile()
{
Name = "Test Profile",
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
Identification = new ()
{
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
}
};
var profile2 = new DeviceProfile()
{
Name = "Test Profile",
FriendlyName = "My Device",
Identification = new DeviceIdentification()
{
FriendlyName = "My Device",
}
};
var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile2.Identification);
var deviceMatch2 = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
Assert.True(deviceMatch);
Assert.True(deviceMatch2);
}
[Fact]
public void IsMatch_GivenNamesAndManufacturersDoNotMatch_ReturnsFalse()
{
var device = new DeviceInfo()
{
Name = "My Device",
Manufacturer = "JVC"
};
var profile = new DeviceProfile()
{
Name = "Test Profile",
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
Identification = new ()
{
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
ManufacturerUrl = "http://www.lge.com",
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
}
};
var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
Assert.False(deviceMatch);
}
[Fact]
public void IsMatch_GivenNamesAndRegExMatch_ReturnsTrue()
{
var device = new DeviceInfo()
{
Name = "My Device"
};
var profile = new DeviceProfile()
{
Name = "Test Profile",
FriendlyName = "My .*",
Identification = new ()
};
var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
Assert.True(deviceMatch);
}
}
}
@@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="coverlet.collector" Version="1.3.0" /> <PackageReference Include="coverlet.collector" Version="1.3.0" />
@@ -33,6 +33,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
[InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff/", "/home/jeff/myfile.mkv")] [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff", "/home/jeff/", "/home/jeff/myfile.mkv")]
[InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/home/jeff/", "/home/jeff/myfile.mkv")] [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/home/jeff/", "/home/jeff/myfile.mkv")]
[InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/", "/myfile.mkv")] [InlineData("C:\\Users\\jeff\\myfile.mkv", "C:\\Users/jeff/", "/", "/myfile.mkv")]
[InlineData("/o", "/o", "/s", "/s")] // regression test for #5977
public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, string? expectedResult) public void TryReplaceSubPath_ValidArgs_Correct(string path, string subPath, string newSubPath, string? expectedResult)
{ {
Assert.True(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result)); Assert.True(PathExtensions.TryReplaceSubPath(path, subPath, newSubPath, out var result));