Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56b05f4e2b | |||
| e8148ec0bd | |||
| 77c5c53598 | |||
| d2db73b876 | |||
| 49873c3d7f | |||
| 0b577c8a44 | |||
| 6386606a53 | |||
| cc908210d9 | |||
| dec30ade8f | |||
| 55a8d2555e | |||
| b7c3510da1 | |||
| fd102abd81 | |||
| f8f7767cc5 | |||
| e8436814dc | |||
| 14f63e8f2f | |||
| e764de0c80 | |||
| 40147c9bb7 | |||
| 3566d21ad1 | |||
| 4c8df4c5bb | |||
| 9798bf29f3 | |||
| 93ce087fc9 | |||
| e39495354b | |||
| ee94fad8f7 | |||
| 53239b0529 | |||
| cf0da1de86 | |||
| 093510ae58 | |||
| c3fafe9289 | |||
| bd914acd16 | |||
| 81f9bec101 | |||
| 7db8601fbc | |||
| 1ec247f5d8 | |||
| 11e9173fbc | |||
| 34508286a8 | |||
| a82eded845 | |||
| fcb729ff6b |
@@ -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
|
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
+33
-68
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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.6</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(
|
||||||
|
|||||||
@@ -538,7 +538,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 +567,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 +581,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(
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.6</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.6</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>
|
||||||
|
|||||||
@@ -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.6</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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.6</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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("10.7.2")]
|
[assembly: AssemblyVersion("10.7.6")]
|
||||||
[assembly: AssemblyFileVersion("10.7.2")]
|
[assembly: AssemblyFileVersion("10.7.6")]
|
||||||
|
|||||||
@@ -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
@@ -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.6"
|
||||||
packages:
|
packages:
|
||||||
- debian.amd64
|
- debian.amd64
|
||||||
- debian.arm64
|
- debian.arm64
|
||||||
|
|||||||
Vendored
+24
@@ -1,3 +1,27 @@
|
|||||||
|
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
|
||||||
|
|||||||
Vendored
+1
-1
@@ -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.6
|
||||||
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
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
Name: jellyfin
|
Name: jellyfin
|
||||||
Version: 10.7.2
|
Version: 10.7.6
|
||||||
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,14 @@ fi
|
|||||||
%systemd_postun_with_restart jellyfin.service
|
%systemd_postun_with_restart jellyfin.service
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* 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));
|
||||||
|
|||||||
Reference in New Issue
Block a user