Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd102abd81 | |||
| f8f7767cc5 | |||
| e8436814dc | |||
| 14f63e8f2f | |||
| e764de0c80 | |||
| 40147c9bb7 | |||
| 3566d21ad1 | |||
| 4c8df4c5bb | |||
| 9798bf29f3 | |||
| 93ce087fc9 | |||
| e39495354b | |||
| ee94fad8f7 | |||
| 53239b0529 | |||
| cf0da1de86 | |||
| 093510ae58 | |||
| c3fafe9289 | |||
| bd914acd16 | |||
| 81f9bec101 | |||
| 7db8601fbc | |||
| 1ec247f5d8 | |||
| 11e9173fbc | |||
| 34508286a8 | |||
| a82eded845 | |||
| fcb729ff6b | |||
| 69f30bc52c | |||
| 3b605b6280 | |||
| e8a359f97b | |||
| dbfaafc08a | |||
| de6747f6c5 | |||
| 100fe40b0a | |||
| 2197d20783 | |||
| f4f9ab777f | |||
| 9ca7d62709 | |||
| 7aad16b6ec | |||
| f77673438e | |||
| bc2eb9fa79 | |||
| df69ce55f7 | |||
| 93cca4d50e | |||
| 3c64bcffe3 | |||
| 6ece01d425 | |||
| 53f333bd64 | |||
| 9e459090ed | |||
| 95a4fc0f18 | |||
| 62bf3db885 | |||
| 42d702c091 | |||
| d07fe14814 | |||
| 970eaf8dfb | |||
| bc27c2b7da | |||
| 37b969304a | |||
| c6b5c4dda5 | |||
| 51f5da8015 | |||
| 6e89ca9a34 | |||
| de1896828f | |||
| 7d1d159b8a | |||
| c3c98331d9 | |||
| e78fa8c3ef | |||
| d63fb437c6 | |||
| 25c6388e23 | |||
| 8f16e10fc6 | |||
| 1f07586d1c | |||
| 5e0f480e48 | |||
| 210d10400a |
@@ -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
|
|
||||||
|
|||||||
@@ -104,6 +104,7 @@
|
|||||||
- [shemanaev](https://github.com/shemanaev)
|
- [shemanaev](https://github.com/shemanaev)
|
||||||
- [skaro13](https://github.com/skaro13)
|
- [skaro13](https://github.com/skaro13)
|
||||||
- [sl1288](https://github.com/sl1288)
|
- [sl1288](https://github.com/sl1288)
|
||||||
|
- [Smith00101010](https://github.com/Smith00101010)
|
||||||
- [sorinyo2004](https://github.com/sorinyo2004)
|
- [sorinyo2004](https://github.com/sorinyo2004)
|
||||||
- [sparky8251](https://github.com/sparky8251)
|
- [sparky8251](https://github.com/sparky8251)
|
||||||
- [spookbits](https://github.com/spookbits)
|
- [spookbits](https://github.com/spookbits)
|
||||||
|
|||||||
@@ -974,15 +974,28 @@ namespace Emby.Dlna.Didl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
|
// TODO: Remove these default values
|
||||||
|
var albumArtUrlInfo = GetImageUrl(
|
||||||
|
imageInfo,
|
||||||
|
_profile.MaxAlbumArtWidth ?? 10000,
|
||||||
|
_profile.MaxAlbumArtHeight ?? 10000,
|
||||||
|
"jpg");
|
||||||
|
|
||||||
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
|
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
|
||||||
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
if (!string.IsNullOrEmpty(_profile.AlbumArtPn))
|
||||||
writer.WriteString(albumartUrlInfo.url);
|
{
|
||||||
|
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString(albumArtUrlInfo.url);
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
|
|
||||||
// TOOD: Remove these default values
|
// TODO: Remove these default values
|
||||||
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
|
var iconUrlInfo = GetImageUrl(
|
||||||
|
imageInfo,
|
||||||
|
_profile.MaxIconWidth ?? 48,
|
||||||
|
_profile.MaxIconHeight ?? 48,
|
||||||
|
"jpg");
|
||||||
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
|
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
|
||||||
|
|
||||||
if (!_profile.EnableAlbumArtInDidl)
|
if (!_profile.EnableAlbumArtInDidl)
|
||||||
@@ -1206,8 +1219,7 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
if (width.HasValue && height.HasValue)
|
if (width.HasValue && height.HasValue)
|
||||||
{
|
{
|
||||||
var newSize = DrawingUtils.Resize(
|
var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
|
||||||
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
|
|
||||||
|
|
||||||
width = newSize.Width;
|
width = newSize.Width;
|
||||||
height = newSize.Height;
|
height = newSize.Height;
|
||||||
|
|||||||
+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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
|
var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -253,7 +253,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
|
var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -278,7 +278,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
|
var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -305,7 +305,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
|
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
|
var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -378,6 +378,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
public async Task SetPlay(CancellationToken cancellationToken)
|
public async Task SetPlay(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (avCommands == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
|
await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -388,7 +392,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
|
var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -406,7 +410,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
|
var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -528,7 +532,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -578,7 +582,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
|
var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -665,6 +669,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (rendererCommands == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
@@ -733,6 +741,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (rendererCommands == null)
|
||||||
|
{
|
||||||
|
return (false, null);
|
||||||
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
@@ -914,6 +927,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||||
|
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (document == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
AvCommands = TransportCommands.Create(document);
|
AvCommands = TransportCommands.Create(document);
|
||||||
return AvCommands;
|
return AvCommands;
|
||||||
@@ -942,6 +959,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||||
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (document == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
RendererCommands = TransportCommands.Create(document);
|
RendererCommands = TransportCommands.Create(document);
|
||||||
return RendererCommands;
|
return RendererCommands;
|
||||||
@@ -973,6 +994,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
|
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
|
||||||
|
|
||||||
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
||||||
|
if (document == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var friendlyNames = new List<string>();
|
var friendlyNames = new List<string>();
|
||||||
|
|
||||||
|
|||||||
@@ -943,11 +943,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
request.DeviceId = values.GetValueOrDefault("DeviceId");
|
request.DeviceId = values.GetValueOrDefault("DeviceId");
|
||||||
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
|
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
|
||||||
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
|
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
|
||||||
|
request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
|
||||||
// Be careful, IsDirectStream==true by default (Static != false or not in query).
|
|
||||||
// See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true.
|
|
||||||
request.IsDirectStream = !string.Equals("false", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
||||||
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
||||||
request.StartPositionTicks = GetLongValue(values, "StartPositionTicks");
|
request.StartPositionTicks = GetLongValue(values, "StartPositionTicks");
|
||||||
|
|||||||
@@ -178,12 +178,17 @@ namespace Emby.Dlna.PlayTo
|
|||||||
if (controller == null)
|
if (controller == null)
|
||||||
{
|
{
|
||||||
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
|
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (device == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Ignoring device as xml response is invalid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
string deviceName = device.Properties.Name;
|
string deviceName = device.Properties.Name;
|
||||||
|
|
||||||
_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,
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
return await XDocument.LoadAsync(
|
||||||
return XDocument.Parse(
|
stream,
|
||||||
await reader.ReadToEndAsync().ConfigureAwait(false),
|
LoadOptions.PreserveWhitespace,
|
||||||
LoadOptions.PreserveWhitespace);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
||||||
@@ -94,10 +94,17 @@ namespace Emby.Dlna.PlayTo
|
|||||||
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
||||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
try
|
||||||
return XDocument.Parse(
|
{
|
||||||
await reader.ReadToEndAsync().ConfigureAwait(false),
|
return await XDocument.LoadAsync(
|
||||||
LoadOptions.PreserveWhitespace);
|
stream,
|
||||||
|
LoadOptions.PreserveWhitespace,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> PostSoapDataAsync(
|
private async Task<HttpResponseMessage> PostSoapDataAsync(
|
||||||
|
|||||||
@@ -13,12 +13,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
public class TransportCommands
|
public class TransportCommands
|
||||||
{
|
{
|
||||||
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
||||||
private List<StateVariable> _stateVariables = new List<StateVariable>();
|
|
||||||
private List<ServiceAction> _serviceActions = new List<ServiceAction>();
|
|
||||||
|
|
||||||
public List<StateVariable> StateVariables => _stateVariables;
|
public List<StateVariable> StateVariables { get; } = new List<StateVariable>();
|
||||||
|
|
||||||
public List<ServiceAction> ServiceActions => _serviceActions;
|
public List<ServiceAction> ServiceActions { get; } = new List<ServiceAction>();
|
||||||
|
|
||||||
public static TransportCommands Create(XDocument document)
|
public static TransportCommands Create(XDocument document)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
@@ -171,11 +172,26 @@ namespace Emby.Drawing
|
|||||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDimensions newSize = ImageHelper.GetNewImageSize(options, null);
|
|
||||||
int quality = options.Quality;
|
int quality = options.Quality;
|
||||||
|
|
||||||
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
||||||
string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
|
string cacheFilePath = GetCacheFilePath(
|
||||||
|
originalImagePath,
|
||||||
|
options.Width,
|
||||||
|
options.Height,
|
||||||
|
options.MaxWidth,
|
||||||
|
options.MaxHeight,
|
||||||
|
options.FillWidth,
|
||||||
|
options.FillHeight,
|
||||||
|
quality,
|
||||||
|
dateModified,
|
||||||
|
outputFormat,
|
||||||
|
options.AddPlayedIndicator,
|
||||||
|
options.PercentPlayed,
|
||||||
|
options.UnplayedCount,
|
||||||
|
options.Blur,
|
||||||
|
options.BackgroundColor,
|
||||||
|
options.ForegroundLayer);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -246,48 +262,111 @@ namespace Emby.Drawing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cache file path based on a set of parameters.
|
/// Gets the cache file path based on a set of parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
|
private string GetCacheFilePath(
|
||||||
|
string originalPath,
|
||||||
|
int? width,
|
||||||
|
int? height,
|
||||||
|
int? maxWidth,
|
||||||
|
int? maxHeight,
|
||||||
|
int? fillWidth,
|
||||||
|
int? fillHeight,
|
||||||
|
int quality,
|
||||||
|
DateTime dateModified,
|
||||||
|
ImageFormat format,
|
||||||
|
bool addPlayedIndicator,
|
||||||
|
double percentPlayed,
|
||||||
|
int? unwatchedCount,
|
||||||
|
int? blur,
|
||||||
|
string backgroundColor,
|
||||||
|
string foregroundLayer)
|
||||||
{
|
{
|
||||||
var filename = originalPath
|
var filename = new StringBuilder(256);
|
||||||
+ "width=" + outputSize.Width
|
filename.Append(originalPath);
|
||||||
+ "height=" + outputSize.Height
|
|
||||||
+ "quality=" + quality
|
filename.Append(",quality=");
|
||||||
+ "datemodified=" + dateModified.Ticks
|
filename.Append(quality);
|
||||||
+ "f=" + format;
|
|
||||||
|
filename.Append(",datemodified=");
|
||||||
|
filename.Append(dateModified.Ticks);
|
||||||
|
|
||||||
|
filename.Append(",f=");
|
||||||
|
filename.Append(format);
|
||||||
|
|
||||||
|
if (width.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",width=");
|
||||||
|
filename.Append(width.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",height=");
|
||||||
|
filename.Append(height.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxWidth.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",maxwidth=");
|
||||||
|
filename.Append(maxWidth.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxHeight.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",maxheight=");
|
||||||
|
filename.Append(maxHeight.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillWidth.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",fillwidth=");
|
||||||
|
filename.Append(fillWidth.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillHeight.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",fillheight=");
|
||||||
|
filename.Append(fillHeight.Value);
|
||||||
|
}
|
||||||
|
|
||||||
if (addPlayedIndicator)
|
if (addPlayedIndicator)
|
||||||
{
|
{
|
||||||
filename += "pl=true";
|
filename.Append(",pl=true");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (percentPlayed > 0)
|
if (percentPlayed > 0)
|
||||||
{
|
{
|
||||||
filename += "p=" + percentPlayed;
|
filename.Append(",p=");
|
||||||
|
filename.Append(percentPlayed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unwatchedCount.HasValue)
|
if (unwatchedCount.HasValue)
|
||||||
{
|
{
|
||||||
filename += "p=" + unwatchedCount.Value;
|
filename.Append(",p=");
|
||||||
|
filename.Append(unwatchedCount.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blur.HasValue)
|
if (blur.HasValue)
|
||||||
{
|
{
|
||||||
filename += "blur=" + blur.Value;
|
filename.Append(",blur=");
|
||||||
|
filename.Append(blur.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(backgroundColor))
|
if (!string.IsNullOrEmpty(backgroundColor))
|
||||||
{
|
{
|
||||||
filename += "b=" + backgroundColor;
|
filename.Append(",b=");
|
||||||
|
filename.Append(backgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(foregroundLayer))
|
if (!string.IsNullOrEmpty(foregroundLayer))
|
||||||
{
|
{
|
||||||
filename += "fl=" + foregroundLayer;
|
filename.Append(",fl=");
|
||||||
|
filename.Append(foregroundLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
filename += "v=" + Version;
|
filename.Append(",v=");
|
||||||
|
filename.Append(Version);
|
||||||
|
|
||||||
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
|
return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -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.1</VersionPrefix>
|
<VersionPrefix>10.7.4</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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5415,7 +5415,6 @@ AND Type = @InternalPersonType)");
|
|||||||
ItemIds = query.ItemIds,
|
ItemIds = query.ItemIds,
|
||||||
TopParentIds = query.TopParentIds,
|
TopParentIds = query.TopParentIds,
|
||||||
ParentId = query.ParentId,
|
ParentId = query.ParentId,
|
||||||
IsPlayed = query.IsPlayed,
|
|
||||||
IsAiring = query.IsAiring,
|
IsAiring = query.IsAiring,
|
||||||
IsMovie = query.IsMovie,
|
IsMovie = query.IsMovie,
|
||||||
IsSports = query.IsSports,
|
IsSports = query.IsSports,
|
||||||
@@ -5441,6 +5440,7 @@ AND Type = @InternalPersonType)");
|
|||||||
|
|
||||||
var outerQuery = new InternalItemsQuery(query.User)
|
var outerQuery = new InternalItemsQuery(query.User)
|
||||||
{
|
{
|
||||||
|
IsPlayed = query.IsPlayed,
|
||||||
IsFavorite = query.IsFavorite,
|
IsFavorite = query.IsFavorite,
|
||||||
IsFavoriteOrLiked = query.IsFavoriteOrLiked,
|
IsFavoriteOrLiked = query.IsFavoriteOrLiked,
|
||||||
IsLiked = query.IsLiked,
|
IsLiked = query.IsLiked,
|
||||||
|
|||||||
@@ -14,15 +14,18 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
public class WebSocketManager : IWebSocketManager
|
public class WebSocketManager : IWebSocketManager
|
||||||
{
|
{
|
||||||
private readonly IWebSocketListener[] _webSocketListeners;
|
private readonly IWebSocketListener[] _webSocketListeners;
|
||||||
|
private readonly IAuthService _authService;
|
||||||
private readonly ILogger<WebSocketManager> _logger;
|
private readonly ILogger<WebSocketManager> _logger;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
public WebSocketManager(
|
public WebSocketManager(
|
||||||
|
IAuthService authService,
|
||||||
IEnumerable<IWebSocketListener> webSocketListeners,
|
IEnumerable<IWebSocketListener> webSocketListeners,
|
||||||
ILogger<WebSocketManager> logger,
|
ILogger<WebSocketManager> logger,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_webSocketListeners = webSocketListeners.ToArray();
|
_webSocketListeners = webSocketListeners.ToArray();
|
||||||
|
_authService = authService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
}
|
}
|
||||||
@@ -30,6 +33,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task WebSocketRequestHandler(HttpContext context)
|
public async Task WebSocketRequestHandler(HttpContext context)
|
||||||
{
|
{
|
||||||
|
_ = _authService.Authenticate(context.Request);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
|
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
|
||||||
|
|||||||
@@ -249,9 +249,18 @@ namespace Emby.Server.Implementations.IO
|
|||||||
// Issue #2354 get the size of files behind symbolic links
|
// Issue #2354 get the size of files behind symbolic links
|
||||||
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||||
{
|
{
|
||||||
using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
|
try
|
||||||
{
|
{
|
||||||
result.Length = thisFileStream.Length;
|
using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
|
||||||
|
{
|
||||||
|
result.Length = thisFileStream.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException ex)
|
||||||
|
{
|
||||||
|
// Dangling symlinks cannot be detected before opening the file unfortunately...
|
||||||
|
Logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
|
||||||
|
result.Exists = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1914,12 +1914,17 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Cannot get image index for {0}", img.Path);
|
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Cannot fetch image from {0}", img.Path);
|
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1932,7 +1937,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Cannot get image dimensions for {0}", image.Path);
|
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
|
||||||
image.Width = 0;
|
image.Width = 0;
|
||||||
image.Height = 0;
|
image.Height = 0;
|
||||||
continue;
|
continue;
|
||||||
@@ -1944,7 +1949,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
|
_logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path);
|
||||||
image.BlurHash = string.Empty;
|
image.BlurHash = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1954,7 +1959,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
|
_logger.LogError(ex, "Cannot update DateModified for {ImagePath}", image.Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2875,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)
|
||||||
{
|
{
|
||||||
@@ -2882,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)
|
||||||
@@ -2985,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(() =>
|
||||||
|
|||||||
@@ -200,10 +200,15 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
|
||||||
|
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList();
|
return SortMediaSources(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaProtocol GetPathProtocol(string path)
|
public MediaProtocol GetPathProtocol(string path)
|
||||||
@@ -437,7 +442,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 +457,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
{
|
{
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
Name = Path.GetFileNameWithoutExtension(args.Path),
|
Name = Path.GetFileNameWithoutExtension(args.Path),
|
||||||
IsInMixedFolder = true
|
IsInMixedFolder = true,
|
||||||
|
PlaylistMediaType = MediaType.Audio
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -82,11 +82,6 @@ namespace Emby.Server.Implementations.MediaEncoder
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video.VideoType == VideoType.Dvd)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (video.IsShortcut)
|
if (video.IsShortcut)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -368,7 +369,7 @@ namespace Emby.Server.Implementations.Plugins
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path)
|
public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status)
|
||||||
{
|
{
|
||||||
if (packageInfo == null)
|
if (packageInfo == null)
|
||||||
{
|
{
|
||||||
@@ -411,9 +412,9 @@ namespace Emby.Server.Implementations.Plugins
|
|||||||
Overview = packageInfo.Overview,
|
Overview = packageInfo.Overview,
|
||||||
Owner = packageInfo.Owner,
|
Owner = packageInfo.Owner,
|
||||||
TargetAbi = versionInfo.TargetAbi ?? string.Empty,
|
TargetAbi = versionInfo.TargetAbi ?? string.Empty,
|
||||||
Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp),
|
Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture),
|
||||||
Version = versionInfo.Version,
|
Version = versionInfo.Version,
|
||||||
Status = PluginStatus.Active,
|
Status = status == PluginStatus.Disabled ? PluginStatus.Disabled : PluginStatus.Active, // Keep disabled state.
|
||||||
AutoUpdate = true,
|
AutoUpdate = true,
|
||||||
ImagePath = imagePath
|
ImagePath = imagePath
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -131,11 +131,11 @@ namespace Emby.Server.Implementations.Sorting
|
|||||||
return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y));
|
return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetSpecialCompareValue(Episode item)
|
private static long GetSpecialCompareValue(Episode item)
|
||||||
{
|
{
|
||||||
// First sort by season number
|
// First sort by season number
|
||||||
// Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough)
|
// Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough)
|
||||||
var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000;
|
var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000L;
|
||||||
|
|
||||||
// Second sort order is if it airs after the season
|
// Second sort order is if it airs after the season
|
||||||
if (item.AirsAfterSeasonNumber.HasValue)
|
if (item.AirsAfterSeasonNumber.HasValue)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
@@ -11,7 +10,6 @@ using MediaBrowser.Controller.Dto;
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.TV;
|
using MediaBrowser.Controller.TV;
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
||||||
@@ -23,12 +21,14 @@ namespace Emby.Server.Implementations.TV
|
|||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
|
|
||||||
public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager)
|
public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
|
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
|
||||||
@@ -200,13 +200,10 @@ namespace Emby.Server.Implementations.TV
|
|||||||
ParentIndexNumberNotEquals = 0,
|
ParentIndexNumberNotEquals = 0,
|
||||||
DtoOptions = new DtoOptions
|
DtoOptions = new DtoOptions
|
||||||
{
|
{
|
||||||
Fields = new ItemFields[]
|
Fields = new[] { ItemFields.SortName },
|
||||||
{
|
|
||||||
ItemFields.SortName
|
|
||||||
},
|
|
||||||
EnableImages = false
|
EnableImages = false
|
||||||
}
|
}
|
||||||
}).FirstOrDefault();
|
}).Cast<Episode>().FirstOrDefault();
|
||||||
|
|
||||||
Func<Episode> getEpisode = () =>
|
Func<Episode> getEpisode = () =>
|
||||||
{
|
{
|
||||||
@@ -224,6 +221,43 @@ namespace Emby.Server.Implementations.TV
|
|||||||
DtoOptions = dtoOptions
|
DtoOptions = dtoOptions
|
||||||
}).Cast<Episode>().FirstOrDefault();
|
}).Cast<Episode>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
|
||||||
|
{
|
||||||
|
var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
AncestorWithPresentationUniqueKey = null,
|
||||||
|
SeriesPresentationUniqueKey = seriesKey,
|
||||||
|
ParentIndexNumber = 0,
|
||||||
|
IncludeItemTypes = new[] { nameof(Episode) },
|
||||||
|
IsPlayed = false,
|
||||||
|
IsVirtualItem = false,
|
||||||
|
DtoOptions = dtoOptions
|
||||||
|
})
|
||||||
|
.Cast<Episode>()
|
||||||
|
.Where(episode => episode.AirsBeforeSeasonNumber != null || episode.AirsAfterSeasonNumber != null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (lastWatchedEpisode != null)
|
||||||
|
{
|
||||||
|
// Last watched episode is added, because there could be specials that aired before the last watched episode
|
||||||
|
consideredEpisodes.Add(lastWatchedEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextEpisode != null)
|
||||||
|
{
|
||||||
|
consideredEpisodes.Add(nextEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending) })
|
||||||
|
.Cast<Episode>();
|
||||||
|
if (lastWatchedEpisode != null)
|
||||||
|
{
|
||||||
|
sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => episode.Id != lastWatchedEpisode.Id).Skip(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextEpisode = sortedConsideredEpisodes.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
if (nextEpisode != null)
|
if (nextEpisode != null)
|
||||||
{
|
{
|
||||||
var userData = _userDataManager.GetUserData(user, nextEpisode);
|
var userData = _userDataManager.GetUserData(user, nextEpisode);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using MediaBrowser.Controller.Configuration;
|
|||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Events.Updates;
|
using MediaBrowser.Controller.Events.Updates;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Updates;
|
using MediaBrowser.Model.Updates;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -194,7 +195,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber);
|
var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber);
|
||||||
if (plugin != null)
|
if (plugin != null)
|
||||||
{
|
{
|
||||||
await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path);
|
await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path, plugin.Manifest.Status).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove versions with a target ABI greater then the current application version.
|
// Remove versions with a target ABI greater then the current application version.
|
||||||
@@ -500,7 +501,8 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
var plugins = _pluginManager.Plugins;
|
var plugins = _pluginManager.Plugins;
|
||||||
foreach (var plugin in plugins)
|
foreach (var plugin in plugins)
|
||||||
{
|
{
|
||||||
if (plugin.Manifest?.AutoUpdate == false)
|
// Don't auto update when plugin marked not to, or when it's disabled.
|
||||||
|
if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == PluginStatus.Disabled)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -515,7 +517,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken)
|
private async Task PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(package.SourceUrl);
|
var extension = Path.GetExtension(package.SourceUrl);
|
||||||
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -567,7 +569,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
|
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
_zipClient.ExtractAllFromZip(stream, targetDir, true);
|
_zipClient.ExtractAllFromZip(stream, targetDir, true);
|
||||||
await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir);
|
await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false);
|
||||||
_pluginManager.ImportPluginFrom(targetDir);
|
_pluginManager.ImportPluginFrom(targetDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +578,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version))
|
LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version))
|
||||||
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
|
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
|
||||||
|
|
||||||
await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false);
|
await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false);
|
||||||
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
|
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
|
||||||
|
|
||||||
return plugin != null;
|
return plugin != null;
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -168,7 +168,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -177,13 +177,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -309,7 +309,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -333,7 +333,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -342,13 +342,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var streamingRequest = new HlsVideoRequestDto
|
var streamingRequest = new HlsVideoRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -250,7 +250,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -259,13 +259,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -393,7 +393,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var streamingRequest = new HlsAudioRequestDto
|
var streamingRequest = new HlsAudioRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -417,7 +417,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -426,13 +426,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -556,7 +556,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var streamingRequest = new VideoRequestDto
|
var streamingRequest = new VideoRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -580,7 +580,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -589,13 +589,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -721,7 +721,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var streamingRequest = new StreamingRequestDto
|
var streamingRequest = new StreamingRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -745,7 +745,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -754,13 +754,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -891,7 +891,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -915,7 +915,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -924,13 +924,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -1063,7 +1063,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -1087,7 +1087,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -1096,13 +1096,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
||||||
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath))
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
||||||
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath) || Path.GetExtension(file) != ".m3u8")
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.Ordinal) || Path.GetExtension(file) != ".m3u8")
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
|
file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath))
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path.StartsWith(_applicationPaths.GeneralPath))
|
if (!path.StartsWith(_applicationPaths.GeneralPath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
if (!path.StartsWith(basePath))
|
if (!path.StartsWith(basePath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
if (!path.StartsWith(basePath))
|
if (!path.StartsWith(basePath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -480,6 +480,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
||||||
@@ -509,6 +511,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] ImageFormat? format,
|
[FromQuery] ImageFormat? format,
|
||||||
@@ -539,6 +543,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -560,6 +566,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
||||||
@@ -589,6 +597,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] ImageFormat? format,
|
[FromQuery] ImageFormat? format,
|
||||||
@@ -618,6 +628,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -638,6 +650,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
|
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
|
||||||
@@ -667,6 +681,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromRoute, Required] string tag,
|
[FromRoute, Required] string tag,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromRoute, Required] ImageFormat format,
|
[FromRoute, Required] ImageFormat format,
|
||||||
@@ -697,6 +713,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -721,6 +739,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -750,6 +770,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -776,6 +798,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -800,6 +824,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -829,6 +855,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -855,6 +883,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -880,6 +910,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -909,6 +941,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -934,6 +968,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -958,6 +994,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -987,6 +1025,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1013,6 +1053,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1038,6 +1080,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1067,6 +1111,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1092,6 +1138,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1116,6 +1164,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1145,6 +1195,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1171,6 +1223,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1196,6 +1250,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1225,6 +1281,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1250,6 +1308,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1274,6 +1334,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1303,6 +1365,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1329,6 +1393,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1354,6 +1420,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1383,6 +1451,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1408,6 +1478,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1432,6 +1504,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1461,6 +1535,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1504,6 +1580,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1530,6 +1608,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1559,6 +1639,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1601,6 +1683,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1685,6 +1769,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
int? width,
|
int? width,
|
||||||
int? height,
|
int? height,
|
||||||
int? quality,
|
int? quality,
|
||||||
|
int? fillWidth,
|
||||||
|
int? fillHeight,
|
||||||
bool? cropWhitespace,
|
bool? cropWhitespace,
|
||||||
bool? addPlayedIndicator,
|
bool? addPlayedIndicator,
|
||||||
int? blur,
|
int? blur,
|
||||||
@@ -1748,11 +1834,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
item,
|
item,
|
||||||
itemId,
|
itemId,
|
||||||
imageIndex,
|
imageIndex,
|
||||||
height,
|
|
||||||
maxHeight,
|
|
||||||
maxWidth,
|
|
||||||
quality,
|
|
||||||
width,
|
width,
|
||||||
|
height,
|
||||||
|
maxWidth,
|
||||||
|
maxHeight,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
|
quality,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
percentPlayed,
|
percentPlayed,
|
||||||
unplayedCount,
|
unplayedCount,
|
||||||
@@ -1847,11 +1935,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
BaseItem? item,
|
BaseItem? item,
|
||||||
Guid itemId,
|
Guid itemId,
|
||||||
int? index,
|
int? index,
|
||||||
int? height,
|
|
||||||
int? maxHeight,
|
|
||||||
int? maxWidth,
|
|
||||||
int? quality,
|
|
||||||
int? width,
|
int? width,
|
||||||
|
int? height,
|
||||||
|
int? maxWidth,
|
||||||
|
int? maxHeight,
|
||||||
|
int? fillWidth,
|
||||||
|
int? fillHeight,
|
||||||
|
int? quality,
|
||||||
bool? addPlayedIndicator,
|
bool? addPlayedIndicator,
|
||||||
double? percentPlayed,
|
double? percentPlayed,
|
||||||
int? unplayedCount,
|
int? unplayedCount,
|
||||||
@@ -1880,6 +1970,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
ItemId = itemId,
|
ItemId = itemId,
|
||||||
MaxHeight = maxHeight,
|
MaxHeight = maxHeight,
|
||||||
MaxWidth = maxWidth,
|
MaxWidth = maxWidth,
|
||||||
|
FillHeight = fillHeight,
|
||||||
|
FillWidth = fillWidth,
|
||||||
Quality = quality ?? 100,
|
Quality = quality ?? 100,
|
||||||
Width = width,
|
Width = width,
|
||||||
AddPlayedIndicator = addPlayedIndicator ?? false,
|
AddPlayedIndicator = addPlayedIndicator ?? false,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,8 +247,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
folder = _libraryManager.GetUserRootFolder();
|
folder = _libraryManager.GetUserRootFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (folder is IHasCollectionType hasCollectionType
|
string? collectionType = null;
|
||||||
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
if (folder is IHasCollectionType hasCollectionType)
|
||||||
|
{
|
||||||
|
collectionType = hasCollectionType.CollectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(collectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
recursive = true;
|
recursive = true;
|
||||||
includeItemTypes = new[] { "Playlist" };
|
includeItemTypes = new[] { "Playlist" };
|
||||||
@@ -271,10 +276,11 @@ namespace Jellyfin.Api.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(item is UserRootFolder)
|
if (item is not UserRootFolder
|
||||||
&& !isInEnabledFolder
|
&& !isInEnabledFolder
|
||||||
&& !user.HasPermission(PermissionKind.EnableAllFolders)
|
&& !user.HasPermission(PermissionKind.EnableAllFolders)
|
||||||
&& !user.HasPermission(PermissionKind.EnableAllChannels))
|
&& !user.HasPermission(PermissionKind.EnableAllChannels)
|
||||||
|
&& !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
|
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
|
||||||
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
|
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path));
|
return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -667,7 +667,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO determine non-ASCII validity.
|
// TODO determine non-ASCII validity.
|
||||||
return PhysicalFile(path, MimeTypes.GetMimeType(path), filename);
|
return PhysicalFile(path, MimeTypes.GetMimeType(path), filename, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -153,6 +153,10 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
|
/// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
|
||||||
/// <param name="itemIds">The ids of the items to play, comma delimited.</param>
|
/// <param name="itemIds">The ids of the items to play, comma delimited.</param>
|
||||||
/// <param name="startPositionTicks">The starting position of the first item.</param>
|
/// <param name="startPositionTicks">The starting position of the first item.</param>
|
||||||
|
/// <param name="mediaSourceId">Optional. The media source id.</param>
|
||||||
|
/// <param name="audioStreamIndex">Optional. The index of the audio stream to play.</param>
|
||||||
|
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to play.</param>
|
||||||
|
/// <param name="startIndex">Optional. The start index.</param>
|
||||||
/// <response code="204">Instruction sent to session.</response>
|
/// <response code="204">Instruction sent to session.</response>
|
||||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||||
[HttpPost("Sessions/{sessionId}/Playing")]
|
[HttpPost("Sessions/{sessionId}/Playing")]
|
||||||
@@ -162,13 +166,21 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromRoute, Required] string sessionId,
|
[FromRoute, Required] string sessionId,
|
||||||
[FromQuery, Required] PlayCommand playCommand,
|
[FromQuery, Required] PlayCommand playCommand,
|
||||||
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
|
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
|
||||||
[FromQuery] long? startPositionTicks)
|
[FromQuery] long? startPositionTicks,
|
||||||
|
[FromQuery] string? mediaSourceId,
|
||||||
|
[FromQuery] int? audioStreamIndex,
|
||||||
|
[FromQuery] int? subtitleStreamIndex,
|
||||||
|
[FromQuery] int? startIndex)
|
||||||
{
|
{
|
||||||
var playRequest = new PlayRequest
|
var playRequest = new PlayRequest
|
||||||
{
|
{
|
||||||
ItemIds = itemIds,
|
ItemIds = itemIds,
|
||||||
StartPositionTicks = startPositionTicks,
|
StartPositionTicks = startPositionTicks,
|
||||||
PlayCommand = playCommand
|
PlayCommand = playCommand,
|
||||||
|
MediaSourceId = mediaSourceId,
|
||||||
|
AudioStreamIndex = audioStreamIndex,
|
||||||
|
SubtitleStreamIndex = subtitleStreamIndex,
|
||||||
|
StartIndex = startIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
_sessionManager.SendPlayCommand(
|
_sessionManager.SendPlayCommand(
|
||||||
@@ -301,9 +313,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// Issues a command to a client to display a message to the user.
|
/// Issues a command to a client to display a message to the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sessionId">The session id.</param>
|
/// <param name="sessionId">The session id.</param>
|
||||||
/// <param name="text">The message test.</param>
|
/// <param name="command">The <see cref="MessageCommand" /> object containing Header, Message Text, and TimeoutMs.</param>
|
||||||
/// <param name="header">The message header.</param>
|
|
||||||
/// <param name="timeoutMs">The message timeout. If omitted the user will have to confirm viewing the message.</param>
|
|
||||||
/// <response code="204">Message sent.</response>
|
/// <response code="204">Message sent.</response>
|
||||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||||
[HttpPost("Sessions/{sessionId}/Message")]
|
[HttpPost("Sessions/{sessionId}/Message")]
|
||||||
@@ -311,16 +321,12 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
public ActionResult SendMessageCommand(
|
public ActionResult SendMessageCommand(
|
||||||
[FromRoute, Required] string sessionId,
|
[FromRoute, Required] string sessionId,
|
||||||
[FromQuery, Required] string text,
|
[FromBody, Required] MessageCommand command)
|
||||||
[FromQuery] string? header,
|
|
||||||
[FromQuery] long? timeoutMs)
|
|
||||||
{
|
{
|
||||||
var command = new MessageCommand
|
if (string.IsNullOrWhiteSpace(command.Header))
|
||||||
{
|
{
|
||||||
Header = string.IsNullOrEmpty(header) ? "Message from Server" : header,
|
command.Header = "Message from Server";
|
||||||
TimeoutMs = timeoutMs,
|
}
|
||||||
Text = text
|
|
||||||
};
|
|
||||||
|
|
||||||
_sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None);
|
_sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None);
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,10 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets subtitles in a specified format.
|
/// Gets subtitles in a specified format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="routeItemId">The (route) item id.</param>
|
||||||
|
/// <param name="routeMediaSourceId">The (route) media source id.</param>
|
||||||
|
/// <param name="routeIndex">The (route) subtitle stream index.</param>
|
||||||
|
/// <param name="routeFormat">The (route) format of the returned subtitle.</param>
|
||||||
/// <param name="itemId">The item id.</param>
|
/// <param name="itemId">The item id.</param>
|
||||||
/// <param name="mediaSourceId">The media source id.</param>
|
/// <param name="mediaSourceId">The media source id.</param>
|
||||||
/// <param name="index">The subtitle stream index.</param>
|
/// <param name="index">The subtitle stream index.</param>
|
||||||
@@ -189,22 +193,32 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
|
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
|
||||||
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
|
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
|
||||||
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
|
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
|
||||||
/// <param name="startPositionTicks">Optional. 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/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
|
[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(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid routeItemId,
|
||||||
[FromRoute, Required] string mediaSourceId,
|
[FromRoute, Required] string routeMediaSourceId,
|
||||||
[FromRoute, Required] int index,
|
[FromRoute, Required] int routeIndex,
|
||||||
[FromRoute, Required] string format,
|
[FromRoute, Required] string routeFormat,
|
||||||
|
[FromQuery, ParameterObsolete] Guid? itemId,
|
||||||
|
[FromQuery, ParameterObsolete] string? mediaSourceId,
|
||||||
|
[FromQuery, ParameterObsolete] int? index,
|
||||||
|
[FromQuery, ParameterObsolete] string? format,
|
||||||
[FromQuery] long? endPositionTicks,
|
[FromQuery] long? endPositionTicks,
|
||||||
[FromQuery] bool copyTimestamps = false,
|
[FromQuery] bool copyTimestamps = false,
|
||||||
[FromQuery] bool addVttTimeMap = false,
|
[FromQuery] bool addVttTimeMap = false,
|
||||||
[FromQuery] long startPositionTicks = 0)
|
[FromQuery] long startPositionTicks = 0)
|
||||||
{
|
{
|
||||||
|
// Set parameters to route value if not provided via query.
|
||||||
|
itemId ??= routeItemId;
|
||||||
|
mediaSourceId ??= routeMediaSourceId;
|
||||||
|
index ??= routeIndex;
|
||||||
|
format ??= routeFormat;
|
||||||
|
|
||||||
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
format = "json";
|
format = "json";
|
||||||
@@ -212,9 +226,9 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(format))
|
if (string.IsNullOrEmpty(format))
|
||||||
{
|
{
|
||||||
var item = (Video)_libraryManager.GetItemById(itemId);
|
var item = (Video)_libraryManager.GetItemById(itemId.Value);
|
||||||
|
|
||||||
var idString = itemId.ToString("N", CultureInfo.InvariantCulture);
|
var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture);
|
||||||
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
|
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
|
||||||
.First(i => string.Equals(i.Id, mediaSourceId ?? idString, StringComparison.Ordinal));
|
.First(i => string.Equals(i.Id, mediaSourceId ?? idString, StringComparison.Ordinal));
|
||||||
|
|
||||||
@@ -226,7 +240,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (string.Equals(format, "vtt", StringComparison.OrdinalIgnoreCase) && addVttTimeMap)
|
if (string.Equals(format, "vtt", StringComparison.OrdinalIgnoreCase) && addVttTimeMap)
|
||||||
{
|
{
|
||||||
await using Stream stream = await EncodeSubtitles(itemId, mediaSourceId, index, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false);
|
await using Stream stream = await EncodeSubtitles(itemId.Value, mediaSourceId, index.Value, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false);
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
|
|
||||||
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||||
@@ -238,9 +252,9 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
return File(
|
return File(
|
||||||
await EncodeSubtitles(
|
await EncodeSubtitles(
|
||||||
itemId,
|
itemId.Value,
|
||||||
mediaSourceId,
|
mediaSourceId,
|
||||||
index,
|
index.Value,
|
||||||
format,
|
format,
|
||||||
startPositionTicks,
|
startPositionTicks,
|
||||||
endPositionTicks,
|
endPositionTicks,
|
||||||
@@ -251,30 +265,44 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets subtitles in a specified format.
|
/// Gets subtitles in a specified format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="routeItemId">The (route) item id.</param>
|
||||||
|
/// <param name="routeMediaSourceId">The (route) media source id.</param>
|
||||||
|
/// <param name="routeIndex">The (route) subtitle stream index.</param>
|
||||||
|
/// <param name="routeStartPositionTicks">The (route) start position of the subtitle in ticks.</param>
|
||||||
|
/// <param name="routeFormat">The (route) format of the returned subtitle.</param>
|
||||||
/// <param name="itemId">The item id.</param>
|
/// <param name="itemId">The item id.</param>
|
||||||
/// <param name="mediaSourceId">The media source id.</param>
|
/// <param name="mediaSourceId">The media source id.</param>
|
||||||
/// <param name="index">The subtitle stream index.</param>
|
/// <param name="index">The subtitle stream index.</param>
|
||||||
/// <param name="startPositionTicks">Optional. The start position of the subtitle in ticks.</param>
|
/// <param name="startPositionTicks">The start position of the subtitle in ticks.</param>
|
||||||
/// <param name="format">The format of the returned subtitle.</param>
|
/// <param name="format">The format of the returned subtitle.</param>
|
||||||
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
|
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
|
||||||
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
|
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
|
||||||
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
|
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</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/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks}/Stream.{format}")]
|
[HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/{routeStartPositionTicks}/Stream.{routeFormat}")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesFile("text/*")]
|
[ProducesFile("text/*")]
|
||||||
public Task<ActionResult> GetSubtitleWithTicks(
|
public Task<ActionResult> GetSubtitleWithTicks(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid routeItemId,
|
||||||
[FromRoute, Required] string mediaSourceId,
|
[FromRoute, Required] string routeMediaSourceId,
|
||||||
[FromRoute, Required] int index,
|
[FromRoute, Required] int routeIndex,
|
||||||
[FromRoute, Required] long startPositionTicks,
|
[FromRoute, Required] long routeStartPositionTicks,
|
||||||
[FromRoute, Required] string format,
|
[FromRoute, Required] string routeFormat,
|
||||||
|
[FromQuery, ParameterObsolete] Guid? itemId,
|
||||||
|
[FromQuery, ParameterObsolete] string? mediaSourceId,
|
||||||
|
[FromQuery, ParameterObsolete] int? index,
|
||||||
|
[FromQuery, ParameterObsolete] long? startPositionTicks,
|
||||||
|
[FromQuery, ParameterObsolete] string? format,
|
||||||
[FromQuery] long? endPositionTicks,
|
[FromQuery] long? endPositionTicks,
|
||||||
[FromQuery] bool copyTimestamps = false,
|
[FromQuery] bool copyTimestamps = false,
|
||||||
[FromQuery] bool addVttTimeMap = false)
|
[FromQuery] bool addVttTimeMap = false)
|
||||||
{
|
{
|
||||||
return GetSubtitle(
|
return GetSubtitle(
|
||||||
|
routeItemId,
|
||||||
|
routeMediaSourceId,
|
||||||
|
routeIndex,
|
||||||
|
routeFormat,
|
||||||
itemId,
|
itemId,
|
||||||
mediaSourceId,
|
mediaSourceId,
|
||||||
index,
|
index,
|
||||||
@@ -282,7 +310,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
endPositionTicks,
|
endPositionTicks,
|
||||||
copyTimestamps,
|
copyTimestamps,
|
||||||
addVttTimeMap,
|
addVttTimeMap,
|
||||||
startPositionTicks);
|
startPositionTicks ?? routeStartPositionTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -219,11 +219,11 @@ namespace Jellyfin.Api.Controllers
|
|||||||
AudioBitRate = audioBitRate ?? maxStreamingBitrate,
|
AudioBitRate = audioBitRate ?? maxStreamingBitrate,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
SubtitleMethod = SubtitleDeliveryMethod.Hls,
|
SubtitleMethod = SubtitleDeliveryMethod.Hls,
|
||||||
RequireAvc = true,
|
RequireAvc = false,
|
||||||
DeInterlace = true,
|
DeInterlace = false,
|
||||||
RequireNonAnamorphic = true,
|
RequireNonAnamorphic = false,
|
||||||
EnableMpegtsM2TsMode = true,
|
EnableMpegtsM2TsMode = false,
|
||||||
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
||||||
Context = EncodingContext.Static,
|
Context = EncodingContext.Static,
|
||||||
StreamOptions = new Dictionary<string, string>(),
|
StreamOptions = new Dictionary<string, string>(),
|
||||||
EnableAdaptiveBitrateStreaming = true
|
EnableAdaptiveBitrateStreaming = true
|
||||||
@@ -251,7 +251,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
AudioBitRate = isStatic ? (int?)null : (audioBitRate ?? maxStreamingBitrate),
|
AudioBitRate = isStatic ? (int?)null : (audioBitRate ?? maxStreamingBitrate),
|
||||||
MaxAudioBitDepth = maxAudioBitDepth,
|
MaxAudioBitDepth = maxAudioBitDepth,
|
||||||
AudioChannels = maxAudioChannels,
|
AudioChannels = maxAudioChannels,
|
||||||
CopyTimestamps = true,
|
CopyTimestamps = false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
SubtitleMethod = SubtitleDeliveryMethod.Embed,
|
SubtitleMethod = SubtitleDeliveryMethod.Embed,
|
||||||
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
||||||
@@ -298,9 +298,9 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Type = DlnaProfileType.Audio,
|
Type = DlnaProfileType.Audio,
|
||||||
Context = EncodingContext.Streaming,
|
Context = EncodingContext.Streaming,
|
||||||
Container = transcodingContainer,
|
Container = transcodingContainer ?? "mp3",
|
||||||
AudioCodec = audioCodec,
|
AudioCodec = audioCodec ?? "mp3",
|
||||||
Protocol = transcodingProtocol,
|
Protocol = transcodingProtocol ?? "http",
|
||||||
BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
|
BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
|
||||||
MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
|
MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -248,7 +248,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -257,13 +257,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -410,7 +410,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -419,13 +419,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ namespace Jellyfin.Api.Helpers
|
|||||||
|
|
||||||
if (isHeadRequest)
|
if (isHeadRequest)
|
||||||
{
|
{
|
||||||
return new FileContentResult(Array.Empty<byte>(), contentType);
|
httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
|
||||||
|
return new OkResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
|
return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
|
||||||
@@ -68,10 +69,10 @@ namespace Jellyfin.Api.Helpers
|
|||||||
{
|
{
|
||||||
httpContext.Response.ContentType = contentType;
|
httpContext.Response.ContentType = contentType;
|
||||||
|
|
||||||
// if the request is a head request, return a NoContent result with the same headers as it would with a GET request
|
// if the request is a head request, return an OkResult (200) with the same headers as it would with a GET request
|
||||||
if (isHeadRequest)
|
if (isHeadRequest)
|
||||||
{
|
{
|
||||||
return new NoContentResult();
|
return new OkResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
|
return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
if (streamInfo != null)
|
if (streamInfo != null)
|
||||||
{
|
{
|
||||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||||
|
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,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;
|
||||||
}
|
}
|
||||||
@@ -326,6 +327,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
if (streamInfo != null)
|
if (streamInfo != null)
|
||||||
{
|
{
|
||||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||||
|
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,6 +355,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
|
|
||||||
// Do this after the above so that StartPositionTicks is set
|
// Do this after the above so that StartPositionTicks is set
|
||||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||||
|
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -390,6 +393,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
|
|
||||||
// Do this after the above so that StartPositionTicks is set
|
// Do this after the above so that StartPositionTicks is set
|
||||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||||
|
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.1</VersionPrefix>
|
<VersionPrefix>10.7.4</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>
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
{
|
{
|
||||||
foreach (var item in source)
|
foreach (var item in source)
|
||||||
{
|
{
|
||||||
result.AddItem(item);
|
result.AddItem(item, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
if (_bindExclusions.Count > 0)
|
if (_bindExclusions.Count > 0)
|
||||||
{
|
{
|
||||||
// Return all the interfaces except the ones specifically excluded.
|
// Return all the interfaces except the ones specifically excluded.
|
||||||
return _interfaceAddresses.Exclude(_bindExclusions);
|
return _interfaceAddresses.Exclude(_bindExclusions, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (individualInterfaces)
|
if (individualInterfaces)
|
||||||
@@ -310,7 +310,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove any excluded bind interfaces.
|
// Remove any excluded bind interfaces.
|
||||||
return _bindAddresses.Exclude(_bindExclusions);
|
return _bindAddresses.Exclude(_bindExclusions, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -397,15 +397,26 @@ namespace Jellyfin.Networking.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the first LAN interface address that isn't a loopback.
|
// Get the first LAN interface address that isn't a loopback.
|
||||||
var interfaces = CreateCollection(_interfaceAddresses
|
var interfaces = CreateCollection(
|
||||||
.Exclude(_bindExclusions)
|
_interfaceAddresses
|
||||||
.Where(IsInLocalNetwork)
|
.Exclude(_bindExclusions, false)
|
||||||
.OrderBy(p => p.Tag));
|
.Where(IsInLocalNetwork)
|
||||||
|
.OrderBy(p => p.Tag));
|
||||||
|
|
||||||
if (interfaces.Count > 0)
|
if (interfaces.Count > 0)
|
||||||
{
|
{
|
||||||
if (haveSource)
|
if (haveSource)
|
||||||
{
|
{
|
||||||
|
foreach (var intf in interfaces)
|
||||||
|
{
|
||||||
|
if (intf.Address.Equals(source.Address))
|
||||||
|
{
|
||||||
|
result = FormatIP6String(intf.Address);
|
||||||
|
_logger.LogDebug("{Source}: GetBindInterface: Has found matching interface. {Result}", source, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Does the request originate in one of the interface subnets?
|
// Does the request originate in one of the interface subnets?
|
||||||
// (For systems with multiple internal network cards, and multiple subnets)
|
// (For systems with multiple internal network cards, and multiple subnets)
|
||||||
foreach (var intf in interfaces)
|
foreach (var intf in interfaces)
|
||||||
@@ -532,10 +543,10 @@ namespace Jellyfin.Networking.Manager
|
|||||||
{
|
{
|
||||||
if (filter == null)
|
if (filter == null)
|
||||||
{
|
{
|
||||||
return _lanSubnets.Exclude(_excludedSubnets).AsNetworks();
|
return _lanSubnets.Exclude(_excludedSubnets, true).AsNetworks();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _lanSubnets.Exclude(filter);
|
return _lanSubnets.Exclude(filter, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -566,7 +577,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
&& ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|
&& ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|| (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6)))
|
|| (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6)))
|
||||||
{
|
{
|
||||||
result.AddItem(iface);
|
result.AddItem(iface, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,6 +587,29 @@ namespace Jellyfin.Networking.Manager
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool HasRemoteAccess(IPAddress remoteIp)
|
||||||
|
{
|
||||||
|
var config = _configurationManager.GetNetworkConfiguration();
|
||||||
|
if (config.EnableRemoteAccess)
|
||||||
|
{
|
||||||
|
// Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
|
||||||
|
// If left blank, all remote addresses will be allowed.
|
||||||
|
if (RemoteAddressFilter.Count > 0 && !IsInLocalNetwork(remoteIp))
|
||||||
|
{
|
||||||
|
// remoteAddressFilter is a whitelist or blacklist.
|
||||||
|
return RemoteAddressFilter.ContainsAddress(remoteIp) == !config.IsRemoteIPFilterBlacklist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!IsInLocalNetwork(remoteIp))
|
||||||
|
{
|
||||||
|
// Remote not enabled. So everyone should be LAN.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reloads all settings and re-initialises the instance.
|
/// Reloads all settings and re-initialises the instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -610,8 +644,8 @@ namespace Jellyfin.Networking.Manager
|
|||||||
var address = IPNetAddress.Parse(parts[0]);
|
var address = IPNetAddress.Parse(parts[0]);
|
||||||
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
|
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
|
||||||
address.Tag = index;
|
address.Tag = index;
|
||||||
_interfaceAddresses.AddItem(address);
|
_interfaceAddresses.AddItem(address, false);
|
||||||
_interfaceNames.Add(parts[2], Math.Abs(index));
|
_interfaceNames[parts[2]] = Math.Abs(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,7 +1062,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
|
|
||||||
_logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString());
|
_logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString());
|
||||||
_logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets.AsString());
|
_logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets.AsString());
|
||||||
_logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets).AsNetworks().AsString());
|
_logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets, true).AsNetworks().AsString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1082,7 +1116,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
nw.Tag *= -1;
|
nw.Tag *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_interfaceAddresses.AddItem(nw);
|
_interfaceAddresses.AddItem(nw, false);
|
||||||
|
|
||||||
// Store interface name so we can use the name in Collections.
|
// Store interface name so we can use the name in Collections.
|
||||||
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
|
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
|
||||||
@@ -1103,7 +1137,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
nw.Tag *= -1;
|
nw.Tag *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_interfaceAddresses.AddItem(nw);
|
_interfaceAddresses.AddItem(nw, false);
|
||||||
|
|
||||||
// Store interface name so we can use the name in Collections.
|
// Store interface name so we can use the name in Collections.
|
||||||
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
|
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
|
||||||
@@ -1137,10 +1171,10 @@ namespace Jellyfin.Networking.Manager
|
|||||||
{
|
{
|
||||||
_logger.LogWarning("No interfaces information available. Using loopback.");
|
_logger.LogWarning("No interfaces information available. Using loopback.");
|
||||||
// Last ditch attempt - use loopback address.
|
// Last ditch attempt - use loopback address.
|
||||||
_interfaceAddresses.AddItem(IPNetAddress.IP4Loopback);
|
_interfaceAddresses.AddItem(IPNetAddress.IP4Loopback, false);
|
||||||
if (IsIP6Enabled)
|
if (IsIP6Enabled)
|
||||||
{
|
{
|
||||||
_interfaceAddresses.AddItem(IPNetAddress.IP6Loopback);
|
_interfaceAddresses.AddItem(IPNetAddress.IP6Loopback, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1251,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
private bool MatchesBindInterface(IPObject source, bool isInExternalSubnet, out string result)
|
private bool MatchesBindInterface(IPObject source, bool isInExternalSubnet, out string result)
|
||||||
{
|
{
|
||||||
result = string.Empty;
|
result = string.Empty;
|
||||||
var addresses = _bindAddresses.Exclude(_bindExclusions);
|
var addresses = _bindAddresses.Exclude(_bindExclusions, false);
|
||||||
|
|
||||||
int count = addresses.Count;
|
int count = addresses.Count;
|
||||||
if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any)))
|
if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any)))
|
||||||
@@ -1302,7 +1336,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
result = string.Empty;
|
result = string.Empty;
|
||||||
// Get the first WAN interface address that isn't a loopback.
|
// Get the first WAN interface address that isn't a loopback.
|
||||||
var extResult = _interfaceAddresses
|
var extResult = _interfaceAddresses
|
||||||
.Exclude(_bindExclusions)
|
.Exclude(_bindExclusions, false)
|
||||||
.Where(p => !IsInLocalNetwork(p))
|
.Where(p => !IsInLocalNetwork(p))
|
||||||
.OrderBy(p => p.Tag);
|
.OrderBy(p => p.Tag);
|
||||||
|
|
||||||
|
|||||||
Generated
+520
@@ -0,0 +1,520 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Jellyfin.Server.Implementations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(JellyfinDb))]
|
||||||
|
[Migration("20210407110544_NullableCustomPrefValue")]
|
||||||
|
partial class NullableCustomPrefValue
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasDefaultSchema("jellyfin")
|
||||||
|
.HasAnnotation("ProductVersion", "5.0.3");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DayOfWeek")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("EndHour")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("StartHour")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AccessSchedules");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ItemId")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("LogSeverity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Overview")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ShortOverview")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ActivityLogs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "ItemId", "Client", "Key")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("CustomItemDisplayPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ChromecastVersion")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DashboardTheme")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableNextVideoInfoOverlay")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("IndexBy")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ScrollDirection")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ShowBackdrop")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ShowSidebar")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SkipBackwardLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SkipForwardLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TvHome")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "ItemId", "Client")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("DisplayPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayPreferencesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Order")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayPreferencesId");
|
||||||
|
|
||||||
|
b.ToTable("HomeSection");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ImageInfos");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("IndexBy")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberIndexing")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberSorting")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SortBy")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ViewType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ItemDisplayPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("Permission_Permissions_Guid")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Value")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Permission_Permissions_Guid");
|
||||||
|
|
||||||
|
b.ToTable("Permissions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("Preference_Preferences_Guid")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Preference_Preferences_Guid");
|
||||||
|
|
||||||
|
b.ToTable("Preferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AudioLanguagePreference")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AuthenticationProviderId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("DisplayCollectionsView")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("DisplayMissingEpisodes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("EasyPassword")
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableAutoLogin")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableLocalPassword")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableNextEpisodeAutoPlay")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableUserPreferenceAccess")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("HidePlayedInLatest")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("InternalId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("InvalidLoginAttemptCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastActivityDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLoginDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxActiveSessions")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxParentalAgeRating")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("MustUpdatePassword")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordResetProviderId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PlayDefaultAudioTrack")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberAudioSelections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberSubtitleSelections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("RemoteClientBitrateLimit")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SubtitleLanguagePreference")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SubtitleMode")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SyncPlayAccess")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("AccessSchedules")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("DisplayPreferences")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
|
||||||
|
.WithMany("HomeSections")
|
||||||
|
.HasForeignKey("DisplayPreferencesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithOne("ProfileImage")
|
||||||
|
.HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("ItemDisplayPreferences")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("Permissions")
|
||||||
|
.HasForeignKey("Permission_Permissions_Guid");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("Preferences")
|
||||||
|
.HasForeignKey("Preference_Preferences_Guid");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("HomeSections");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("AccessSchedules");
|
||||||
|
|
||||||
|
b.Navigation("DisplayPreferences");
|
||||||
|
|
||||||
|
b.Navigation("ItemDisplayPreferences");
|
||||||
|
|
||||||
|
b.Navigation("Permissions");
|
||||||
|
|
||||||
|
b.Navigation("Preferences");
|
||||||
|
|
||||||
|
b.Navigation("ProfileImage");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
// <auto-generated />
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
public partial class NullableCustomPrefValue : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Value",
|
||||||
|
schema: "jellyfin",
|
||||||
|
table: "CustomItemDisplayPreferences",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "TEXT");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Value",
|
||||||
|
schema: "jellyfin",
|
||||||
|
table: "CustomItemDisplayPreferences",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "",
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "TEXT",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasDefaultSchema("jellyfin")
|
.HasDefaultSchema("jellyfin")
|
||||||
.HasAnnotation("ProductVersion", "5.0.0");
|
.HasAnnotation("ProductVersion", "5.0.3");
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
{
|
{
|
||||||
@@ -110,7 +110,6 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Value")
|
b.Property<string>("Value")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
@@ -448,8 +447,8 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
.WithOne("DisplayPreferences")
|
.WithMany("DisplayPreferences")
|
||||||
.HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@@ -502,8 +501,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("AccessSchedules");
|
b.Navigation("AccessSchedules");
|
||||||
|
|
||||||
b.Navigation("DisplayPreferences")
|
b.Navigation("DisplayPreferences");
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("ItemDisplayPreferences");
|
b.Navigation("ItemDisplayPreferences");
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IDictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client)
|
public Dictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client)
|
||||||
{
|
{
|
||||||
return _dbContext.CustomItemDisplayPreferences
|
return _dbContext.CustomItemDisplayPreferences
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ namespace Jellyfin.Server.Extensions
|
|||||||
{
|
{
|
||||||
return serviceCollection.AddSwaggerGen(c =>
|
return serviceCollection.AddSwaggerGen(c =>
|
||||||
{
|
{
|
||||||
var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString() ?? "0.0.0.1";
|
var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString(3) ?? "0.0.1";
|
||||||
c.SwaggerDoc("api-docs", new OpenApiInfo
|
c.SwaggerDoc("api-docs", new OpenApiInfo
|
||||||
{
|
{
|
||||||
Title = "Jellyfin API",
|
Title = "Jellyfin API",
|
||||||
@@ -319,7 +319,7 @@ namespace Jellyfin.Server.Extensions
|
|||||||
c.OperationFilter<FileResponseFilter>();
|
c.OperationFilter<FileResponseFilter>();
|
||||||
c.OperationFilter<FileRequestFilter>();
|
c.OperationFilter<FileRequestFilter>();
|
||||||
c.OperationFilter<ParameterObsoleteFilter>();
|
c.OperationFilter<ParameterObsoleteFilter>();
|
||||||
c.DocumentFilter<WebsocketModelFilter>();
|
c.DocumentFilter<AdditionalModelFilter>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,6 @@
|
|||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Model.ApiClient;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using MediaBrowser.Model.SyncPlay;
|
using MediaBrowser.Model.SyncPlay;
|
||||||
@@ -9,9 +10,9 @@ using Swashbuckle.AspNetCore.SwaggerGen;
|
|||||||
namespace Jellyfin.Server.Filters
|
namespace Jellyfin.Server.Filters
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add models used in websocket messaging.
|
/// Add models not directly used by the API, but used for discovery and websockets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebsocketModelFilter : IDocumentFilter
|
public class AdditionalModelFilter : IDocumentFilter
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||||
@@ -27,6 +28,7 @@ namespace Jellyfin.Server.Filters
|
|||||||
context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository);
|
context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository);
|
||||||
|
|
||||||
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
|
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
|
||||||
|
context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<AssemblyName>jellyfin</AssemblyName>
|
<AssemblyName>jellyfin</AssemblyName>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
|||||||
@@ -29,9 +29,8 @@ namespace Jellyfin.Server.Middleware
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext">The current HTTP context.</param>
|
/// <param name="httpContext">The current HTTP context.</param>
|
||||||
/// <param name="networkManager">The network manager.</param>
|
/// <param name="networkManager">The network manager.</param>
|
||||||
/// <param name="serverConfigurationManager">The server configuration manager.</param>
|
|
||||||
/// <returns>The async task.</returns>
|
/// <returns>The async task.</returns>
|
||||||
public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
|
public async Task Invoke(HttpContext httpContext, INetworkManager networkManager)
|
||||||
{
|
{
|
||||||
if (httpContext.IsLocal())
|
if (httpContext.IsLocal())
|
||||||
{
|
{
|
||||||
@@ -42,32 +41,8 @@ namespace Jellyfin.Server.Middleware
|
|||||||
|
|
||||||
var remoteIp = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback;
|
var remoteIp = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback;
|
||||||
|
|
||||||
if (serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess)
|
if (!networkManager.HasRemoteAccess(remoteIp))
|
||||||
{
|
{
|
||||||
// Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
|
|
||||||
// If left blank, all remote addresses will be allowed.
|
|
||||||
var remoteAddressFilter = networkManager.RemoteAddressFilter;
|
|
||||||
|
|
||||||
if (remoteAddressFilter.Count > 0 && !networkManager.IsInLocalNetwork(remoteIp))
|
|
||||||
{
|
|
||||||
// remoteAddressFilter is a whitelist or blacklist.
|
|
||||||
bool isListed = remoteAddressFilter.ContainsAddress(remoteIp);
|
|
||||||
if (!serverConfigurationManager.GetNetworkConfiguration().IsRemoteIPFilterBlacklist)
|
|
||||||
{
|
|
||||||
// Black list, so flip over.
|
|
||||||
isListed = !isListed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isListed)
|
|
||||||
{
|
|
||||||
// If your name isn't on the list, you arn't coming in.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!networkManager.IsInLocalNetwork(remoteIp))
|
|
||||||
{
|
|
||||||
// Remote not enabled. So everyone should be LAN.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.1</VersionPrefix>
|
<VersionPrefix>10.7.4</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>
|
||||||
|
|||||||
@@ -229,5 +229,12 @@ namespace MediaBrowser.Common.Net
|
|||||||
/// <param name="filter">Optional filter for the list.</param>
|
/// <param name="filter">Optional filter for the list.</param>
|
||||||
/// <returns>Returns a filtered list of LAN addresses.</returns>
|
/// <returns>Returns a filtered list of LAN addresses.</returns>
|
||||||
Collection<IPObject> GetFilteredLANSubnets(Collection<IPObject>? filter = null);
|
Collection<IPObject> GetFilteredLANSubnets(Collection<IPObject>? filter = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks to see if <paramref name="remoteIp"/> has access.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="remoteIp">IP Address of client.</param>
|
||||||
|
/// <returns><b>True</b> if has access, otherwise <b>false</b>.</returns>
|
||||||
|
bool HasRemoteAccess(IPAddress remoteIp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ namespace MediaBrowser.Common.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
|
/// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
|
||||||
/// <param name="item">Item to add.</param>
|
/// <param name="item">Item to add.</param>
|
||||||
public static void AddItem(this Collection<IPObject> source, IPObject item)
|
/// <param name="itemsAreNetworks">If <c>true</c> the values are treated as subnets.
|
||||||
|
/// If <b>false</b> items are addresses.</param>
|
||||||
|
public static void AddItem(this Collection<IPObject> source, IPObject item, bool itemsAreNetworks = true)
|
||||||
{
|
{
|
||||||
if (!source.ContainsAddress(item))
|
if (!source.ContainsAddress(item) || !itemsAreNetworks)
|
||||||
{
|
{
|
||||||
source.Add(item);
|
source.Add(item);
|
||||||
}
|
}
|
||||||
@@ -195,8 +197,9 @@ namespace MediaBrowser.Common.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
|
/// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
|
||||||
/// <param name="excludeList">Items to exclude.</param>
|
/// <param name="excludeList">Items to exclude.</param>
|
||||||
|
/// <param name="isNetwork">Collection is a network collection.</param>
|
||||||
/// <returns>A new collection, with the items excluded.</returns>
|
/// <returns>A new collection, with the items excluded.</returns>
|
||||||
public static Collection<IPObject> Exclude(this Collection<IPObject> source, Collection<IPObject> excludeList)
|
public static Collection<IPObject> Exclude(this Collection<IPObject> source, Collection<IPObject> excludeList, bool isNetwork)
|
||||||
{
|
{
|
||||||
if (source.Count == 0 || excludeList == null)
|
if (source.Count == 0 || excludeList == null)
|
||||||
{
|
{
|
||||||
@@ -221,7 +224,7 @@ namespace MediaBrowser.Common.Net
|
|||||||
|
|
||||||
if (!found)
|
if (!found)
|
||||||
{
|
{
|
||||||
results.AddItem(outer);
|
results.AddItem(outer, isNetwork);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Updates;
|
using MediaBrowser.Model.Updates;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -51,8 +52,9 @@ namespace MediaBrowser.Common.Plugins
|
|||||||
/// <param name="packageInfo">The <see cref="PackageInfo"/> used to generate a manifest.</param>
|
/// <param name="packageInfo">The <see cref="PackageInfo"/> used to generate a manifest.</param>
|
||||||
/// <param name="version">Version to be installed.</param>
|
/// <param name="version">Version to be installed.</param>
|
||||||
/// <param name="path">The path where to save the manifest.</param>
|
/// <param name="path">The path where to save the manifest.</param>
|
||||||
|
/// <param name="status">Initial status of the plugin.</param>
|
||||||
/// <returns>True if successful.</returns>
|
/// <returns>True if successful.</returns>
|
||||||
Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path);
|
Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Imports plugin details from a folder.
|
/// Imports plugin details from a folder.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@@ -9,67 +10,12 @@ namespace MediaBrowser.Controller.Drawing
|
|||||||
{
|
{
|
||||||
public static class ImageHelper
|
public static class ImageHelper
|
||||||
{
|
{
|
||||||
public static ImageDimensions GetNewImageSize(ImageProcessingOptions options, ImageDimensions? originalImageSize)
|
public static ImageDimensions GetNewImageSize(ImageProcessingOptions options, ImageDimensions originalImageSize)
|
||||||
{
|
{
|
||||||
if (originalImageSize.HasValue)
|
// Determine the output size based on incoming parameters
|
||||||
{
|
var newSize = DrawingUtils.Resize(originalImageSize, options.Width ?? 0, options.Height ?? 0, options.MaxWidth ?? 0, options.MaxHeight ?? 0);
|
||||||
// Determine the output size based on incoming parameters
|
newSize = DrawingUtils.ResizeFill(newSize, options.FillWidth, options.FillHeight);
|
||||||
var newSize = DrawingUtils.Resize(originalImageSize.Value, options.Width ?? 0, options.Height ?? 0, options.MaxWidth ?? 0, options.MaxHeight ?? 0);
|
return newSize;
|
||||||
|
|
||||||
return newSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetSizeEstimate(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options)
|
|
||||||
{
|
|
||||||
if (options.Width.HasValue && options.Height.HasValue)
|
|
||||||
{
|
|
||||||
return new ImageDimensions(options.Width.Value, options.Height.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
double aspect = GetEstimatedAspectRatio(options.Image.Type, options.Item);
|
|
||||||
|
|
||||||
int? width = options.Width ?? options.MaxWidth;
|
|
||||||
|
|
||||||
if (width.HasValue)
|
|
||||||
{
|
|
||||||
int heightValue = Convert.ToInt32((double)width.Value / aspect);
|
|
||||||
return new ImageDimensions(width.Value, heightValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
var height = options.Height ?? options.MaxHeight ?? 200;
|
|
||||||
int widthValue = Convert.ToInt32(aspect * height);
|
|
||||||
return new ImageDimensions(widthValue, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double GetEstimatedAspectRatio(ImageType type, BaseItem item)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ImageType.Art:
|
|
||||||
case ImageType.Backdrop:
|
|
||||||
case ImageType.Chapter:
|
|
||||||
case ImageType.Screenshot:
|
|
||||||
case ImageType.Thumb:
|
|
||||||
return 1.78;
|
|
||||||
case ImageType.Banner:
|
|
||||||
return 5.4;
|
|
||||||
case ImageType.Box:
|
|
||||||
case ImageType.BoxRear:
|
|
||||||
case ImageType.Disc:
|
|
||||||
case ImageType.Menu:
|
|
||||||
case ImageType.Profile:
|
|
||||||
return 1;
|
|
||||||
case ImageType.Logo:
|
|
||||||
return 2.58;
|
|
||||||
case ImageType.Primary:
|
|
||||||
double defaultPrimaryImageAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
|
||||||
return defaultPrimaryImageAspectRatio > 0 ? defaultPrimaryImageAspectRatio : 2.0 / 3;
|
|
||||||
default:
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ namespace MediaBrowser.Controller.Drawing
|
|||||||
|
|
||||||
public int? MaxHeight { get; set; }
|
public int? MaxHeight { get; set; }
|
||||||
|
|
||||||
|
public int? FillWidth { get; set; }
|
||||||
|
|
||||||
|
public int? FillHeight { get; set; }
|
||||||
|
|
||||||
public int Quality { get; set; }
|
public int Quality { get; set; }
|
||||||
|
|
||||||
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
|
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
|
||||||
@@ -95,6 +99,11 @@ namespace MediaBrowser.Controller.Drawing
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sizeValue.Width > FillWidth || sizeValue.Height > FillHeight)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace MediaBrowser.Controller
|
|||||||
/// <param name="itemId">The item id.</param>
|
/// <param name="itemId">The item id.</param>
|
||||||
/// <param name="client">The client string.</param>
|
/// <param name="client">The client string.</param>
|
||||||
/// <returns>The dictionary of custom item display preferences.</returns>
|
/// <returns>The dictionary of custom item display preferences.</returns>
|
||||||
IDictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client);
|
Dictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the custom item display preference for the user and client.
|
/// Sets the custom item display preference for the user and client.
|
||||||
|
|||||||
@@ -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.1</VersionPrefix>
|
<VersionPrefix>10.7.4</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>
|
||||||
|
|||||||
@@ -313,6 +313,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ISO files don't have an ffmpeg format
|
||||||
|
if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -43,7 +44,8 @@ namespace MediaBrowser.Controller.Playlists
|
|||||||
|
|
||||||
public static bool IsPlaylistFile(string path)
|
public static bool IsPlaylistFile(string path)
|
||||||
{
|
{
|
||||||
return System.IO.Path.HasExtension(path);
|
// The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
|
||||||
|
return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
{
|
{
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new ConcurrentDictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new (StringComparer.Ordinal);
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new (StringComparer.Ordinal);
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new (StringComparer.Ordinal);
|
||||||
|
|
||||||
public DirectoryService(IFileSystem fileSystem)
|
public DirectoryService(IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
|
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
|
||||||
{
|
{
|
||||||
var prefix = "file";
|
var prefix = "file";
|
||||||
if (mediaSource.VideoType == VideoType.BluRay || mediaSource.VideoType == VideoType.Iso)
|
if (mediaSource.VideoType == VideoType.BluRay)
|
||||||
{
|
{
|
||||||
prefix = "bluray";
|
prefix = "bluray";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Probing
|
namespace MediaBrowser.MediaEncoding.Probing
|
||||||
{
|
{
|
||||||
@@ -85,12 +86,14 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||||||
{
|
{
|
||||||
var val = GetDictionaryValue(tags, key);
|
var val = GetDictionaryValue(tags, key);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(val))
|
if (string.IsNullOrEmpty(val))
|
||||||
{
|
{
|
||||||
if (DateTime.TryParse(val, out var i))
|
return null;
|
||||||
{
|
}
|
||||||
return i.ToUniversalTime();
|
|
||||||
}
|
if (DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out var i))
|
||||||
|
{
|
||||||
|
return i.ToUniversalTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||||||
info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
|
info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
|
||||||
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
|
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
|
||||||
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
|
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
|
||||||
|
FFProbeHelpers.GetDictionaryDateTime(tags, "date_released") ??
|
||||||
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
|
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
|
||||||
|
|
||||||
if (isAudio)
|
if (isAudio)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#nullable disable
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
@@ -12,22 +12,12 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
[XmlAttribute("type")]
|
[XmlAttribute("type")]
|
||||||
public DlnaProfileType Type { get; set; }
|
public DlnaProfileType Type { get; set; }
|
||||||
|
|
||||||
public ProfileCondition[] Conditions { get; set; }
|
public ProfileCondition[]? Conditions { get; set; } = Array.Empty<ProfileCondition>();
|
||||||
|
|
||||||
[XmlAttribute("container")]
|
[XmlAttribute("container")]
|
||||||
public string Container { get; set; }
|
public string Container { get; set; } = string.Empty;
|
||||||
|
|
||||||
public ContainerProfile()
|
public static string[] SplitValue(string? value)
|
||||||
{
|
|
||||||
Conditions = Array.Empty<ProfileCondition>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string[] GetContainers()
|
|
||||||
{
|
|
||||||
return SplitValue(Container);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string[] SplitValue(string value)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
@@ -37,14 +27,14 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return value.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
return value.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ContainsContainer(string container)
|
public bool ContainsContainer(string? container)
|
||||||
{
|
{
|
||||||
var containers = GetContainers();
|
var containers = SplitValue(Container);
|
||||||
|
|
||||||
return ContainsContainer(containers, container);
|
return ContainsContainer(containers, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ContainsContainer(string profileContainers, string inputContainer)
|
public static bool ContainsContainer(string? profileContainers, string? inputContainer)
|
||||||
{
|
{
|
||||||
var isNegativeList = false;
|
var isNegativeList = false;
|
||||||
if (profileContainers != null && profileContainers.StartsWith('-'))
|
if (profileContainers != null && profileContainers.StartsWith('-'))
|
||||||
@@ -56,46 +46,29 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return ContainsContainer(SplitValue(profileContainers), isNegativeList, inputContainer);
|
return ContainsContainer(SplitValue(profileContainers), isNegativeList, inputContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ContainsContainer(string[] profileContainers, string inputContainer)
|
public static bool ContainsContainer(string[]? profileContainers, string? inputContainer)
|
||||||
{
|
{
|
||||||
return ContainsContainer(profileContainers, false, inputContainer);
|
return ContainsContainer(profileContainers, false, inputContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ContainsContainer(string[] profileContainers, bool isNegativeList, string inputContainer)
|
public static bool ContainsContainer(string[]? profileContainers, bool isNegativeList, string? inputContainer)
|
||||||
{
|
{
|
||||||
if (profileContainers.Length == 0)
|
if (profileContainers == null || profileContainers.Length == 0)
|
||||||
{
|
{
|
||||||
return true;
|
return isNegativeList;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNegativeList)
|
var allInputContainers = SplitValue(inputContainer);
|
||||||
{
|
|
||||||
var allInputContainers = SplitValue(inputContainer);
|
|
||||||
|
|
||||||
foreach (var container in allInputContainers)
|
foreach (var container in allInputContainers)
|
||||||
|
{
|
||||||
|
if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase))
|
return !isNegativeList;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var allInputContainers = SplitValue(inputContainer);
|
|
||||||
|
|
||||||
foreach (var container in allInputContainers)
|
return isNegativeList;
|
||||||
{
|
|
||||||
if (profileContainers.Contains(container, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#nullable disable
|
|
||||||
#pragma warning disable CA1819 // Properties should not return arrays
|
#pragma warning disable CA1819 // Properties should not return arrays
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
@@ -13,121 +13,104 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
[XmlRoot("Profile")]
|
[XmlRoot("Profile")]
|
||||||
public class DeviceProfile
|
public class DeviceProfile
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DeviceProfile"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public DeviceProfile()
|
|
||||||
{
|
|
||||||
DirectPlayProfiles = Array.Empty<DirectPlayProfile>();
|
|
||||||
TranscodingProfiles = Array.Empty<TranscodingProfile>();
|
|
||||||
ResponseProfiles = Array.Empty<ResponseProfile>();
|
|
||||||
CodecProfiles = Array.Empty<CodecProfile>();
|
|
||||||
ContainerProfiles = Array.Empty<ContainerProfile>();
|
|
||||||
SubtitleProfiles = Array.Empty<SubtitleProfile>();
|
|
||||||
|
|
||||||
XmlRootAttributes = Array.Empty<XmlAttribute>();
|
|
||||||
|
|
||||||
SupportedMediaTypes = "Audio,Photo,Video";
|
|
||||||
MaxStreamingBitrate = 8000000;
|
|
||||||
MaxStaticBitrate = 8000000;
|
|
||||||
MusicStreamingTranscodingBitrate = 128000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Name.
|
/// Gets or sets the Name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Id.
|
/// Gets or sets the Id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[XmlIgnore]
|
[XmlIgnore]
|
||||||
public string Id { get; set; }
|
public string? Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Identification.
|
/// Gets or sets the Identification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DeviceIdentification Identification { get; set; }
|
public DeviceIdentification? Identification { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the FriendlyName.
|
/// Gets or sets the FriendlyName.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FriendlyName { get; set; }
|
public string? FriendlyName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Manufacturer.
|
/// Gets or sets the Manufacturer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Manufacturer { get; set; }
|
public string? Manufacturer { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ManufacturerUrl.
|
/// Gets or sets the ManufacturerUrl.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ManufacturerUrl { get; set; }
|
public string? ManufacturerUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ModelName.
|
/// Gets or sets the ModelName.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ModelName { get; set; }
|
public string? ModelName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ModelDescription.
|
/// Gets or sets the ModelDescription.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ModelDescription { get; set; }
|
public string? ModelDescription { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ModelNumber.
|
/// Gets or sets the ModelNumber.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ModelNumber { get; set; }
|
public string? ModelNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ModelUrl.
|
/// Gets or sets the ModelUrl.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ModelUrl { get; set; }
|
public string? ModelUrl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SerialNumber.
|
/// Gets or sets the SerialNumber.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SerialNumber { get; set; }
|
public string? SerialNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether EnableAlbumArtInDidl.
|
/// Gets or sets a value indicating whether EnableAlbumArtInDidl.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[DefaultValue(false)]
|
||||||
public bool EnableAlbumArtInDidl { get; set; }
|
public bool EnableAlbumArtInDidl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether EnableSingleAlbumArtLimit.
|
/// Gets or sets a value indicating whether EnableSingleAlbumArtLimit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[DefaultValue(false)]
|
||||||
public bool EnableSingleAlbumArtLimit { get; set; }
|
public bool EnableSingleAlbumArtLimit { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether EnableSingleSubtitleLimit.
|
/// Gets or sets a value indicating whether EnableSingleSubtitleLimit.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[DefaultValue(false)]
|
||||||
public bool EnableSingleSubtitleLimit { get; set; }
|
public bool EnableSingleSubtitleLimit { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SupportedMediaTypes.
|
/// Gets or sets the SupportedMediaTypes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SupportedMediaTypes { get; set; }
|
public string SupportedMediaTypes { get; set; } = "Audio,Photo,Video";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the UserId.
|
/// Gets or sets the UserId.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UserId { get; set; }
|
public string? UserId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the AlbumArtPn.
|
/// Gets or sets the AlbumArtPn.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AlbumArtPn { get; set; }
|
public string? AlbumArtPn { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MaxAlbumArtWidth.
|
/// Gets or sets the MaxAlbumArtWidth.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxAlbumArtWidth { get; set; }
|
public int? MaxAlbumArtWidth { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MaxAlbumArtHeight.
|
/// Gets or sets the MaxAlbumArtHeight.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxAlbumArtHeight { get; set; }
|
public int? MaxAlbumArtHeight { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MaxIconWidth.
|
/// Gets or sets the MaxIconWidth.
|
||||||
@@ -142,92 +125,97 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MaxStreamingBitrate.
|
/// Gets or sets the MaxStreamingBitrate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? MaxStreamingBitrate { get; set; }
|
public int? MaxStreamingBitrate { get; set; } = 8000000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MaxStaticBitrate.
|
/// Gets or sets the MaxStaticBitrate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? MaxStaticBitrate { get; set; }
|
public int? MaxStaticBitrate { get; set; } = 8000000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MusicStreamingTranscodingBitrate.
|
/// Gets or sets the MusicStreamingTranscodingBitrate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? MusicStreamingTranscodingBitrate { get; set; }
|
public int? MusicStreamingTranscodingBitrate { get; set; } = 128000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MaxStaticMusicBitrate.
|
/// Gets or sets the MaxStaticMusicBitrate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? MaxStaticMusicBitrate { get; set; }
|
public int? MaxStaticMusicBitrate { get; set; } = 8000000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.
|
/// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SonyAggregationFlags { get; set; }
|
public string? SonyAggregationFlags { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ProtocolInfo.
|
/// Gets or sets the ProtocolInfo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ProtocolInfo { get; set; }
|
public string? ProtocolInfo { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the TimelineOffsetSeconds.
|
/// Gets or sets the TimelineOffsetSeconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[DefaultValue(0)]
|
||||||
public int TimelineOffsetSeconds { get; set; }
|
public int TimelineOffsetSeconds { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether RequiresPlainVideoItems.
|
/// Gets or sets a value indicating whether RequiresPlainVideoItems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[DefaultValue(false)]
|
||||||
public bool RequiresPlainVideoItems { get; set; }
|
public bool RequiresPlainVideoItems { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether RequiresPlainFolders.
|
/// Gets or sets a value indicating whether RequiresPlainFolders.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[DefaultValue(false)]
|
||||||
public bool RequiresPlainFolders { get; set; }
|
public bool RequiresPlainFolders { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar.
|
/// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[DefaultValue(false)]
|
||||||
public bool EnableMSMediaReceiverRegistrar { get; set; }
|
public bool EnableMSMediaReceiverRegistrar { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests.
|
/// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[DefaultValue(false)]
|
||||||
public bool IgnoreTranscodeByteRangeRequests { get; set; }
|
public bool IgnoreTranscodeByteRangeRequests { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the XmlRootAttributes.
|
/// Gets or sets the XmlRootAttributes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public XmlAttribute[] XmlRootAttributes { get; set; }
|
public XmlAttribute[] XmlRootAttributes { get; set; } = Array.Empty<XmlAttribute>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the direct play profiles.
|
/// Gets or sets the direct play profiles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DirectPlayProfile[] DirectPlayProfiles { get; set; }
|
public DirectPlayProfile[] DirectPlayProfiles { get; set; } = Array.Empty<DirectPlayProfile>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the transcoding profiles.
|
/// Gets or sets the transcoding profiles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TranscodingProfile[] TranscodingProfiles { get; set; }
|
public TranscodingProfile[] TranscodingProfiles { get; set; } = Array.Empty<TranscodingProfile>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ContainerProfiles.
|
/// Gets or sets the ContainerProfiles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ContainerProfile[] ContainerProfiles { get; set; }
|
public ContainerProfile[] ContainerProfiles { get; set; } = Array.Empty<ContainerProfile>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the CodecProfiles.
|
/// Gets or sets the CodecProfiles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CodecProfile[] CodecProfiles { get; set; }
|
public CodecProfile[] CodecProfiles { get; set; } = Array.Empty<CodecProfile>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the ResponseProfiles.
|
/// Gets or sets the ResponseProfiles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ResponseProfile[] ResponseProfiles { get; set; }
|
public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty<ResponseProfile>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SubtitleProfiles.
|
/// Gets or sets the SubtitleProfiles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SubtitleProfile[] SubtitleProfiles { get; set; }
|
public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty<SubtitleProfile>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The GetSupportedMediaTypes.
|
/// The GetSupportedMediaTypes.
|
||||||
@@ -244,13 +232,13 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
/// <param name="container">The container.</param>
|
/// <param name="container">The container.</param>
|
||||||
/// <param name="audioCodec">The audio Codec.</param>
|
/// <param name="audioCodec">The audio Codec.</param>
|
||||||
/// <returns>A <see cref="TranscodingProfile"/>.</returns>
|
/// <returns>A <see cref="TranscodingProfile"/>.</returns>
|
||||||
public TranscodingProfile GetAudioTranscodingProfile(string container, string audioCodec)
|
public TranscodingProfile? GetAudioTranscodingProfile(string? container, string? audioCodec)
|
||||||
{
|
{
|
||||||
container = (container ?? string.Empty).TrimStart('.');
|
container = (container ?? string.Empty).TrimStart('.');
|
||||||
|
|
||||||
foreach (var i in TranscodingProfiles)
|
foreach (var i in TranscodingProfiles)
|
||||||
{
|
{
|
||||||
if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Audio)
|
if (i.Type != DlnaProfileType.Audio)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -278,13 +266,13 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
/// <param name="audioCodec">The audio Codec.</param>
|
/// <param name="audioCodec">The audio Codec.</param>
|
||||||
/// <param name="videoCodec">The video Codec.</param>
|
/// <param name="videoCodec">The video Codec.</param>
|
||||||
/// <returns>The <see cref="TranscodingProfile"/>.</returns>
|
/// <returns>The <see cref="TranscodingProfile"/>.</returns>
|
||||||
public TranscodingProfile GetVideoTranscodingProfile(string container, string audioCodec, string videoCodec)
|
public TranscodingProfile? GetVideoTranscodingProfile(string? container, string? audioCodec, string? videoCodec)
|
||||||
{
|
{
|
||||||
container = (container ?? string.Empty).TrimStart('.');
|
container = (container ?? string.Empty).TrimStart('.');
|
||||||
|
|
||||||
foreach (var i in TranscodingProfiles)
|
foreach (var i in TranscodingProfiles)
|
||||||
{
|
{
|
||||||
if (i.Type != MediaBrowser.Model.Dlna.DlnaProfileType.Video)
|
if (i.Type != DlnaProfileType.Video)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -299,7 +287,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.Equals(videoCodec, i.VideoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(videoCodec, i.VideoCodec, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -320,7 +308,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
/// <param name="audioSampleRate">The audio sample rate.</param>
|
/// <param name="audioSampleRate">The audio sample rate.</param>
|
||||||
/// <param name="audioBitDepth">The audio bit depth.</param>
|
/// <param name="audioBitDepth">The audio bit depth.</param>
|
||||||
/// <returns>The <see cref="ResponseProfile"/>.</returns>
|
/// <returns>The <see cref="ResponseProfile"/>.</returns>
|
||||||
public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
|
public ResponseProfile? GetAudioMediaProfile(string container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
|
||||||
{
|
{
|
||||||
foreach (var i in ResponseProfiles)
|
foreach (var i in ResponseProfiles)
|
||||||
{
|
{
|
||||||
@@ -384,7 +372,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
/// <param name="width">The width.</param>
|
/// <param name="width">The width.</param>
|
||||||
/// <param name="height">The height.</param>
|
/// <param name="height">The height.</param>
|
||||||
/// <returns>The <see cref="ResponseProfile"/>.</returns>
|
/// <returns>The <see cref="ResponseProfile"/>.</returns>
|
||||||
public ResponseProfile GetImageMediaProfile(string container, int? width, int? height)
|
public ResponseProfile? GetImageMediaProfile(string container, int? width, int? height)
|
||||||
{
|
{
|
||||||
foreach (var i in ResponseProfiles)
|
foreach (var i in ResponseProfiles)
|
||||||
{
|
{
|
||||||
@@ -442,10 +430,10 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
/// <param name="videoCodecTag">The video Codec tag.</param>
|
/// <param name="videoCodecTag">The video Codec tag.</param>
|
||||||
/// <param name="isAvc">True if Avc.</param>
|
/// <param name="isAvc">True if Avc.</param>
|
||||||
/// <returns>The <see cref="ResponseProfile"/>.</returns>
|
/// <returns>The <see cref="ResponseProfile"/>.</returns>
|
||||||
public ResponseProfile GetVideoMediaProfile(
|
public ResponseProfile? GetVideoMediaProfile(
|
||||||
string container,
|
string container,
|
||||||
string audioCodec,
|
string? audioCodec,
|
||||||
string videoCodec,
|
string? videoCodec,
|
||||||
int? width,
|
int? width,
|
||||||
int? height,
|
int? height,
|
||||||
int? bitDepth,
|
int? bitDepth,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#nullable disable
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
@@ -8,13 +8,13 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
public class DirectPlayProfile
|
public class DirectPlayProfile
|
||||||
{
|
{
|
||||||
[XmlAttribute("container")]
|
[XmlAttribute("container")]
|
||||||
public string Container { get; set; }
|
public string? Container { get; set; }
|
||||||
|
|
||||||
[XmlAttribute("audioCodec")]
|
[XmlAttribute("audioCodec")]
|
||||||
public string AudioCodec { get; set; }
|
public string? AudioCodec { get; set; }
|
||||||
|
|
||||||
[XmlAttribute("videoCodec")]
|
[XmlAttribute("videoCodec")]
|
||||||
public string VideoCodec { get; set; }
|
public string? VideoCodec { get; set; }
|
||||||
|
|
||||||
[XmlAttribute("type")]
|
[XmlAttribute("type")]
|
||||||
public DlnaProfileType Type { get; set; }
|
public DlnaProfileType Type { get; set; }
|
||||||
|
|||||||
@@ -185,10 +185,8 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Be careful, IsDirectStream==true by default (Static != false or not in query).
|
|
||||||
// See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true.
|
|
||||||
if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) &&
|
if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) &&
|
||||||
string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase))
|
string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#nullable disable
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
@@ -8,47 +9,56 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
public class TranscodingProfile
|
public class TranscodingProfile
|
||||||
{
|
{
|
||||||
[XmlAttribute("container")]
|
[XmlAttribute("container")]
|
||||||
public string Container { get; set; }
|
public string Container { get; set; } = string.Empty;
|
||||||
|
|
||||||
[XmlAttribute("type")]
|
[XmlAttribute("type")]
|
||||||
public DlnaProfileType Type { get; set; }
|
public DlnaProfileType Type { get; set; }
|
||||||
|
|
||||||
[XmlAttribute("videoCodec")]
|
[XmlAttribute("videoCodec")]
|
||||||
public string VideoCodec { get; set; }
|
public string VideoCodec { get; set; } = string.Empty;
|
||||||
|
|
||||||
[XmlAttribute("audioCodec")]
|
[XmlAttribute("audioCodec")]
|
||||||
public string AudioCodec { get; set; }
|
public string AudioCodec { get; set; } = string.Empty;
|
||||||
|
|
||||||
[XmlAttribute("protocol")]
|
[XmlAttribute("protocol")]
|
||||||
public string Protocol { get; set; }
|
public string Protocol { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[DefaultValue(false)]
|
||||||
[XmlAttribute("estimateContentLength")]
|
[XmlAttribute("estimateContentLength")]
|
||||||
public bool EstimateContentLength { get; set; }
|
public bool EstimateContentLength { get; set; }
|
||||||
|
|
||||||
|
[DefaultValue(false)]
|
||||||
[XmlAttribute("enableMpegtsM2TsMode")]
|
[XmlAttribute("enableMpegtsM2TsMode")]
|
||||||
public bool EnableMpegtsM2TsMode { get; set; }
|
public bool EnableMpegtsM2TsMode { get; set; }
|
||||||
|
|
||||||
|
[DefaultValue(TranscodeSeekInfo.Auto)]
|
||||||
[XmlAttribute("transcodeSeekInfo")]
|
[XmlAttribute("transcodeSeekInfo")]
|
||||||
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||||
|
|
||||||
|
[DefaultValue(false)]
|
||||||
[XmlAttribute("copyTimestamps")]
|
[XmlAttribute("copyTimestamps")]
|
||||||
public bool CopyTimestamps { get; set; }
|
public bool CopyTimestamps { get; set; }
|
||||||
|
|
||||||
|
[DefaultValue(EncodingContext.Streaming)]
|
||||||
[XmlAttribute("context")]
|
[XmlAttribute("context")]
|
||||||
public EncodingContext Context { get; set; }
|
public EncodingContext Context { get; set; }
|
||||||
|
|
||||||
|
[DefaultValue(false)]
|
||||||
[XmlAttribute("enableSubtitlesInManifest")]
|
[XmlAttribute("enableSubtitlesInManifest")]
|
||||||
public bool EnableSubtitlesInManifest { get; set; }
|
public bool EnableSubtitlesInManifest { get; set; }
|
||||||
|
|
||||||
[XmlAttribute("maxAudioChannels")]
|
[XmlAttribute("maxAudioChannels")]
|
||||||
public string MaxAudioChannels { get; set; }
|
public string? MaxAudioChannels { get; set; }
|
||||||
|
|
||||||
|
[DefaultValue(0)]
|
||||||
[XmlAttribute("minSegments")]
|
[XmlAttribute("minSegments")]
|
||||||
public int MinSegments { get; set; }
|
public int MinSegments { get; set; }
|
||||||
|
|
||||||
|
[DefaultValue(0)]
|
||||||
[XmlAttribute("segmentLength")]
|
[XmlAttribute("segmentLength")]
|
||||||
public int SegmentLength { get; set; }
|
public int SegmentLength { get; set; }
|
||||||
|
|
||||||
|
[DefaultValue(false)]
|
||||||
[XmlAttribute("breakOnNonKeyFrames")]
|
[XmlAttribute("breakOnNonKeyFrames")]
|
||||||
public bool BreakOnNonKeyFrames { get; set; }
|
public bool BreakOnNonKeyFrames { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,52 @@ namespace MediaBrowser.Model.Drawing
|
|||||||
return new ImageDimensions(newWidth, newHeight);
|
return new ImageDimensions(newWidth, newHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scale down to fill box.
|
||||||
|
/// Returns original size if both width and height are null or zero.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">The original size object.</param>
|
||||||
|
/// <param name="fillWidth">A new fixed width, if desired.</param>
|
||||||
|
/// <param name="fillHeight">A new fixed height, if desired.</param>
|
||||||
|
/// <returns>A new size object or size.</returns>
|
||||||
|
public static ImageDimensions ResizeFill(
|
||||||
|
ImageDimensions size,
|
||||||
|
int? fillWidth,
|
||||||
|
int? fillHeight)
|
||||||
|
{
|
||||||
|
// Return original size if input is invalid.
|
||||||
|
if ((fillWidth == null || fillWidth == 0)
|
||||||
|
&& (fillHeight == null || fillHeight == 0))
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillWidth == null || fillWidth == 0)
|
||||||
|
{
|
||||||
|
fillWidth = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillHeight == null || fillHeight == 0)
|
||||||
|
{
|
||||||
|
fillHeight = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
double widthRatio = size.Width / (double)fillWidth;
|
||||||
|
double heightRatio = size.Height / (double)fillHeight;
|
||||||
|
double scaleRatio = Math.Min(widthRatio, heightRatio);
|
||||||
|
|
||||||
|
// Clamp to current size.
|
||||||
|
if (scaleRatio < 1)
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newWidth = Convert.ToInt32(Math.Ceiling(size.Width / scaleRatio));
|
||||||
|
int newHeight = Convert.ToInt32(Math.Ceiling(size.Height / scaleRatio));
|
||||||
|
|
||||||
|
return new ImageDimensions(newWidth, newHeight);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the new width.
|
/// Gets the new width.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -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.1</VersionPrefix>
|
<VersionPrefix>10.7.4</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)
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Session
|
namespace MediaBrowser.Model.Session
|
||||||
{
|
{
|
||||||
public class MessageCommand
|
public class MessageCommand
|
||||||
{
|
{
|
||||||
public string Header { get; set; }
|
public string Header { get; set; }
|
||||||
|
|
||||||
|
[Required(AllowEmptyStrings = false)]
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
|
|
||||||
public long? TimeoutMs { get; set; }
|
public long? TimeoutMs { get; set; }
|
||||||
|
|||||||
@@ -469,6 +469,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
|
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await _providerManager.SaveImage(
|
await _providerManager.SaveImage(
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||||||
var movie = new Movie
|
var movie = new Movie
|
||||||
{
|
{
|
||||||
Name = movieResult.Title ?? movieResult.OriginalTitle,
|
Name = movieResult.Title ?? movieResult.OriginalTitle,
|
||||||
|
OriginalTitle = movieResult.OriginalTitle,
|
||||||
Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
|
Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
|
||||||
Tagline = movieResult.Tagline,
|
Tagline = movieResult.Tagline,
|
||||||
ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray()
|
ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray()
|
||||||
|
|||||||
@@ -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.1")]
|
[assembly: AssemblyVersion("10.7.4")]
|
||||||
[assembly: AssemblyFileVersion("10.7.1")]
|
[assembly: AssemblyFileVersion("10.7.4")]
|
||||||
|
|||||||
@@ -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.1"
|
version: "10.7.4"
|
||||||
packages:
|
packages:
|
||||||
- debian.amd64
|
- debian.amd64
|
||||||
- debian.arm64
|
- debian.arm64
|
||||||
|
|||||||
Vendored
+18
@@ -1,3 +1,21 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
* New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 11 Apr 2021 14:18:22 -0400
|
||||||
|
|
||||||
jellyfin-server (10.7.1-1) unstable; urgency=medium
|
jellyfin-server (10.7.1-1) unstable; urgency=medium
|
||||||
|
|
||||||
* New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1
|
* New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1
|
||||||
|
|||||||
Vendored
+5
@@ -33,6 +33,11 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
|
|||||||
# [OPTIONAL] run Jellyfin without the web app
|
# [OPTIONAL] run Jellyfin without the web app
|
||||||
#JELLYFIN_NOWEBAPP_OPT="--nowebclient"
|
#JELLYFIN_NOWEBAPP_OPT="--nowebclient"
|
||||||
|
|
||||||
|
# [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC)
|
||||||
|
# 0 = Workstation
|
||||||
|
# 1 = Server
|
||||||
|
#COMPlus_gcServer=1
|
||||||
|
|
||||||
#
|
#
|
||||||
# SysV init/Upstart options
|
# SysV init/Upstart options
|
||||||
#
|
#
|
||||||
|
|||||||
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.1
|
Version: 10.7.4
|
||||||
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
|
||||||
|
|||||||
@@ -35,3 +35,7 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh"
|
|||||||
# [OPTIONAL] run Jellyfin without the web app
|
# [OPTIONAL] run Jellyfin without the web app
|
||||||
#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp"
|
#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp"
|
||||||
|
|
||||||
|
# [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC)
|
||||||
|
# 0 = Workstation
|
||||||
|
# 1 = Server
|
||||||
|
#COMPlus_gcServer=1
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
Name: jellyfin
|
Name: jellyfin
|
||||||
Version: 10.7.1
|
Version: 10.7.4
|
||||||
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,12 @@ fi
|
|||||||
%systemd_postun_with_restart jellyfin.service
|
%systemd_postun_with_restart jellyfin.service
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* 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>
|
||||||
|
- 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>
|
||||||
- New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1
|
- New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1
|
||||||
* Mon Mar 08 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
* Mon Mar 08 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user