mirror of https://github.com/jellyfin/jellyfin.git
Merge remote-tracking branch 'upstream/master' into api-merge-again
This commit is contained in:
commit
e65ecb5687
|
@ -12,10 +12,12 @@ parameters:
|
||||||
jobs:
|
jobs:
|
||||||
- job: CompatibilityCheck
|
- job: CompatibilityCheck
|
||||||
displayName: Compatibility Check
|
displayName: Compatibility Check
|
||||||
|
dependsOn: Build
|
||||||
|
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: "${{ parameters.LinuxImage }}"
|
vmImage: "${{ parameters.LinuxImage }}"
|
||||||
# only execute for pull requests
|
|
||||||
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
${{ each Package in parameters.Packages }}:
|
${{ each Package in parameters.Packages }}:
|
||||||
|
@ -23,7 +25,7 @@ jobs:
|
||||||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||||
maxParallel: 2
|
maxParallel: 2
|
||||||
dependsOn: Build
|
|
||||||
steps:
|
steps:
|
||||||
- checkout: none
|
- checkout: none
|
||||||
|
|
||||||
|
@ -34,32 +36,33 @@ jobs:
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: 'Install ABI CompatibilityChecker tool'
|
displayName: 'Install ABI CompatibilityChecker Tool'
|
||||||
inputs:
|
inputs:
|
||||||
command: custom
|
command: custom
|
||||||
custom: tool
|
custom: tool
|
||||||
arguments: 'update compatibilitychecker -g'
|
arguments: 'update compatibilitychecker -g'
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Download New Assembly Build Artifact"
|
displayName: 'Download New Assembly Build Artifact'
|
||||||
inputs:
|
inputs:
|
||||||
source: "current"
|
source: 'current'
|
||||||
artifact: "$(NugetPackageName)"
|
artifact: "$(NugetPackageName)"
|
||||||
path: "$(System.ArtifactsDirectory)/new-artifacts"
|
path: "$(System.ArtifactsDirectory)/new-artifacts"
|
||||||
runVersion: "latest"
|
runVersion: "latest"
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
displayName: "Copy New Assembly Build Artifact"
|
displayName: 'Copy New Assembly Build Artifact'
|
||||||
inputs:
|
inputs:
|
||||||
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
|
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
|
||||||
contents: "**/*.dll"
|
contents: '**/*.dll'
|
||||||
targetFolder: $(System.ArtifactsDirectory)/new-release
|
targetFolder: $(System.ArtifactsDirectory)/new-release
|
||||||
cleanTargetFolder: true
|
cleanTargetFolder: true
|
||||||
overWrite: true
|
overWrite: true
|
||||||
flattenFolders: true
|
flattenFolders: true
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Download Reference Assembly Build Artifact"
|
displayName: 'Download Reference Assembly Build Artifact'
|
||||||
|
enabled: false
|
||||||
inputs:
|
inputs:
|
||||||
source: "specific"
|
source: "specific"
|
||||||
artifact: "$(NugetPackageName)"
|
artifact: "$(NugetPackageName)"
|
||||||
|
@ -70,18 +73,19 @@ jobs:
|
||||||
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
|
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
displayName: "Copy Reference Assembly Build Artifact"
|
displayName: 'Copy Reference Assembly Build Artifact'
|
||||||
|
enabled: false
|
||||||
inputs:
|
inputs:
|
||||||
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
|
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
|
||||||
contents: "**/*.dll"
|
contents: '**/*.dll'
|
||||||
targetFolder: $(System.ArtifactsDirectory)/current-release
|
targetFolder: $(System.ArtifactsDirectory)/current-release
|
||||||
cleanTargetFolder: true
|
cleanTargetFolder: true
|
||||||
overWrite: true
|
overWrite: true
|
||||||
flattenFolders: true
|
flattenFolders: true
|
||||||
|
|
||||||
# The `--warnings-only` switch will swallow the return code and not emit any errors.
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: 'Execute ABI Compatibility Check Tool'
|
displayName: 'Execute ABI Compatibility Check Tool'
|
||||||
|
enabled: false
|
||||||
inputs:
|
inputs:
|
||||||
command: custom
|
command: custom
|
||||||
custom: compat
|
custom: compat
|
||||||
|
|
|
@ -116,6 +116,7 @@ jobs:
|
||||||
$(JellyfinVersion)-$(BuildConfiguration)
|
$(JellyfinVersion)-$(BuildConfiguration)
|
||||||
|
|
||||||
- job: CollectArtifacts
|
- job: CollectArtifacts
|
||||||
|
timeoutInMinutes: 10
|
||||||
displayName: 'Collect Artifacts'
|
displayName: 'Collect Artifacts'
|
||||||
dependsOn:
|
dependsOn:
|
||||||
- BuildPackage
|
- BuildPackage
|
||||||
|
@ -131,22 +132,16 @@ jobs:
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
inputs:
|
inputs:
|
||||||
sshEndpoint: repository
|
sshEndpoint: repository
|
||||||
runOptions: 'inline'
|
runOptions: 'commands'
|
||||||
inline: |
|
commands: sudo -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable
|
||||||
sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable
|
|
||||||
rm $0
|
|
||||||
exit
|
|
||||||
|
|
||||||
- task: SSH@0
|
- task: SSH@0
|
||||||
displayName: 'Update Stable Repository'
|
displayName: 'Update Stable Repository'
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
|
||||||
inputs:
|
inputs:
|
||||||
sshEndpoint: repository
|
sshEndpoint: repository
|
||||||
runOptions: 'inline'
|
runOptions: 'commands'
|
||||||
inline: |
|
commands: sudo -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
|
||||||
sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
|
|
||||||
rm $0
|
|
||||||
exit
|
|
||||||
|
|
||||||
- job: PublishNuget
|
- job: PublishNuget
|
||||||
displayName: 'Publish NuGet packages'
|
displayName: 'Publish NuGet packages'
|
||||||
|
|
|
@ -978,7 +978,10 @@ namespace Emby.Server.Implementations.Data
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
str.Append($"{i.Key}={i.Value}|");
|
str.Append(i.Key)
|
||||||
|
.Append('=')
|
||||||
|
.Append(i.Value)
|
||||||
|
.Append('|');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str.Length == 0)
|
if (str.Length == 0)
|
||||||
|
@ -1032,8 +1035,8 @@ namespace Emby.Server.Implementations.Data
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
str.Append(ToValueString(i))
|
AppendItemImageInfo(str, i);
|
||||||
.Append('|');
|
str.Append('|');
|
||||||
}
|
}
|
||||||
|
|
||||||
str.Length -= 1; // Remove last |
|
str.Length -= 1; // Remove last |
|
||||||
|
@ -1067,26 +1070,26 @@ namespace Emby.Server.Implementations.Data
|
||||||
item.ImageInfos = list.ToArray();
|
item.ImageInfos = list.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToValueString(ItemImageInfo image)
|
public void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
|
||||||
{
|
{
|
||||||
const string Delimeter = "*";
|
const char Delimiter = '*';
|
||||||
|
|
||||||
var path = image.Path ?? string.Empty;
|
var path = image.Path ?? string.Empty;
|
||||||
var hash = image.BlurHash ?? string.Empty;
|
var hash = image.BlurHash ?? string.Empty;
|
||||||
|
|
||||||
return GetPathToSave(path) +
|
bldr.Append(GetPathToSave(path))
|
||||||
Delimeter +
|
.Append(Delimiter)
|
||||||
image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
|
.Append(image.DateModified.Ticks)
|
||||||
Delimeter +
|
.Append(Delimiter)
|
||||||
image.Type +
|
.Append(image.Type)
|
||||||
Delimeter +
|
.Append(Delimiter)
|
||||||
image.Width.ToString(CultureInfo.InvariantCulture) +
|
.Append(image.Width)
|
||||||
Delimeter +
|
.Append(Delimiter)
|
||||||
image.Height.ToString(CultureInfo.InvariantCulture) +
|
.Append(image.Height)
|
||||||
Delimeter +
|
.Append(Delimiter)
|
||||||
// Replace delimiters with other characters.
|
// Replace delimiters with other characters.
|
||||||
// This can be removed when we migrate to a proper DB.
|
// This can be removed when we migrate to a proper DB.
|
||||||
hash.Replace('*', '/').Replace('|', '\\');
|
.Append(hash.Replace('*', '/').Replace('|', '\\'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
public ItemImageInfo ItemImageInfoFromValueString(string value)
|
||||||
|
@ -5659,10 +5662,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
const int Limit = 100;
|
const int Limit = 100;
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
|
|
||||||
|
const string StartInsertText = "insert into ItemValues (ItemId, Type, Value, CleanValue) values ";
|
||||||
|
var insertText = new StringBuilder(StartInsertText);
|
||||||
while (startIndex < values.Count)
|
while (startIndex < values.Count)
|
||||||
{
|
{
|
||||||
var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
|
|
||||||
|
|
||||||
var endIndex = Math.Min(values.Count, startIndex + Limit);
|
var endIndex = Math.Min(values.Count, startIndex + Limit);
|
||||||
|
|
||||||
for (var i = startIndex; i < endIndex; i++)
|
for (var i = startIndex; i < endIndex; i++)
|
||||||
|
@ -5704,6 +5707,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += Limit;
|
startIndex += Limit;
|
||||||
|
insertText.Length = StartInsertText.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5741,10 +5745,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
var listIndex = 0;
|
var listIndex = 0;
|
||||||
|
|
||||||
|
const string StartInsertText = "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ";
|
||||||
|
var insertText = new StringBuilder(StartInsertText);
|
||||||
while (startIndex < people.Count)
|
while (startIndex < people.Count)
|
||||||
{
|
{
|
||||||
var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
|
|
||||||
|
|
||||||
var endIndex = Math.Min(people.Count, startIndex + Limit);
|
var endIndex = Math.Min(people.Count, startIndex + Limit);
|
||||||
for (var i = startIndex; i < endIndex; i++)
|
for (var i = startIndex; i < endIndex; i++)
|
||||||
{
|
{
|
||||||
|
@ -5778,6 +5782,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += Limit;
|
startIndex += Limit;
|
||||||
|
insertText.Length = StartInsertText.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5893,10 +5898,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
const int Limit = 10;
|
const int Limit = 10;
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
|
|
||||||
|
var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery);
|
||||||
while (startIndex < streams.Count)
|
while (startIndex < streams.Count)
|
||||||
{
|
{
|
||||||
var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery);
|
|
||||||
|
|
||||||
var endIndex = Math.Min(streams.Count, startIndex + Limit);
|
var endIndex = Math.Min(streams.Count, startIndex + Limit);
|
||||||
|
|
||||||
for (var i = startIndex; i < endIndex; i++)
|
for (var i = startIndex; i < endIndex; i++)
|
||||||
|
@ -5979,6 +5983,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
startIndex += Limit;
|
startIndex += Limit;
|
||||||
|
insertText.Length = _mediaStreamSaveColumnsInsertQuery.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6230,10 +6235,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
{
|
{
|
||||||
const int InsertAtOnce = 10;
|
const int InsertAtOnce = 10;
|
||||||
|
|
||||||
|
var insertText = new StringBuilder(_mediaAttachmentInsertPrefix);
|
||||||
for (var startIndex = 0; startIndex < attachments.Count; startIndex += InsertAtOnce)
|
for (var startIndex = 0; startIndex < attachments.Count; startIndex += InsertAtOnce)
|
||||||
{
|
{
|
||||||
var insertText = new StringBuilder(_mediaAttachmentInsertPrefix);
|
|
||||||
|
|
||||||
var endIndex = Math.Min(attachments.Count, startIndex + InsertAtOnce);
|
var endIndex = Math.Min(attachments.Count, startIndex + InsertAtOnce);
|
||||||
|
|
||||||
for (var i = startIndex; i < endIndex; i++)
|
for (var i = startIndex; i < endIndex; i++)
|
||||||
|
@ -6279,6 +6283,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||||
statement.Reset();
|
statement.Reset();
|
||||||
statement.MoveNext();
|
statement.MoveNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
insertText.Length = _mediaAttachmentInsertPrefix.Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -567,7 +567,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
|
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var connection = new WebSocketConnection(
|
using var connection = new WebSocketConnection(
|
||||||
_loggerFactory.CreateLogger<WebSocketConnection>(),
|
_loggerFactory.CreateLogger<WebSocketConnection>(),
|
||||||
webSocket,
|
webSocket,
|
||||||
context.Connection.RemoteIpAddress,
|
context.Connection.RemoteIpAddress,
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class WebSocketConnection.
|
/// Class WebSocketConnection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebSocketConnection : IWebSocketConnection
|
public class WebSocketConnection : IWebSocketConnection, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger.
|
/// The logger.
|
||||||
|
@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
Memory<byte> memory = writer.GetMemory(512);
|
Memory<byte> memory = writer.GetMemory(512);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
receiveresult = await _socket.ReceiveAsync(memory, cancellationToken);
|
receiveresult = await _socket.ReceiveAsync(memory, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (WebSocketException ex)
|
catch (WebSocketException ex)
|
||||||
{
|
{
|
||||||
|
@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
writer.Advance(bytesRead);
|
writer.Advance(bytesRead);
|
||||||
|
|
||||||
// Make the data available to the PipeReader
|
// Make the data available to the PipeReader
|
||||||
FlushResult flushResult = await writer.FlushAsync();
|
FlushResult flushResult = await writer.FlushAsync().ConfigureAwait(false);
|
||||||
if (flushResult.IsCompleted)
|
if (flushResult.IsCompleted)
|
||||||
{
|
{
|
||||||
// The PipeReader stopped reading
|
// The PipeReader stopped reading
|
||||||
|
@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
|
if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
await SendKeepAliveResponse();
|
await SendKeepAliveResponse().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -40,8 +40,9 @@ namespace Jellyfin.Server.Implementations.Activity
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task CreateAsync(ActivityLog entry)
|
public async Task CreateAsync(ActivityLog entry)
|
||||||
{
|
{
|
||||||
using var dbContext = _provider.CreateContext();
|
await using var dbContext = _provider.CreateContext();
|
||||||
await dbContext.ActivityLogs.AddAsync(entry);
|
|
||||||
|
dbContext.ActivityLogs.Add(entry);
|
||||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
|
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BDInfo.IO;
|
using BDInfo.IO;
|
||||||
|
@ -5,7 +7,7 @@ using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.BdInfo
|
namespace MediaBrowser.MediaEncoding.BdInfo
|
||||||
{
|
{
|
||||||
class BdInfoDirectoryInfo : IDirectoryInfo
|
public class BdInfoDirectoryInfo : IDirectoryInfo
|
||||||
{
|
{
|
||||||
private readonly IFileSystem _fileSystem = null;
|
private readonly IFileSystem _fileSystem = null;
|
||||||
|
|
||||||
|
@ -43,25 +45,32 @@ namespace MediaBrowser.MediaEncoding.BdInfo
|
||||||
|
|
||||||
public IDirectoryInfo[] GetDirectories()
|
public IDirectoryInfo[] GetDirectories()
|
||||||
{
|
{
|
||||||
return Array.ConvertAll(_fileSystem.GetDirectories(_impl.FullName).ToArray(),
|
return Array.ConvertAll(
|
||||||
|
_fileSystem.GetDirectories(_impl.FullName).ToArray(),
|
||||||
x => new BdInfoDirectoryInfo(_fileSystem, x));
|
x => new BdInfoDirectoryInfo(_fileSystem, x));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFileInfo[] GetFiles()
|
public IFileInfo[] GetFiles()
|
||||||
{
|
{
|
||||||
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName).ToArray(),
|
return Array.ConvertAll(
|
||||||
|
_fileSystem.GetFiles(_impl.FullName).ToArray(),
|
||||||
x => new BdInfoFileInfo(x));
|
x => new BdInfoFileInfo(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFileInfo[] GetFiles(string searchPattern)
|
public IFileInfo[] GetFiles(string searchPattern)
|
||||||
{
|
{
|
||||||
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(),
|
return Array.ConvertAll(
|
||||||
|
_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(),
|
||||||
x => new BdInfoFileInfo(x));
|
x => new BdInfoFileInfo(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption)
|
public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption)
|
||||||
{
|
{
|
||||||
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false,
|
return Array.ConvertAll(
|
||||||
|
_fileSystem.GetFiles(
|
||||||
|
_impl.FullName,
|
||||||
|
new[] { searchPattern },
|
||||||
|
false,
|
||||||
searchOption.HasFlag(System.IO.SearchOption.AllDirectories)).ToArray(),
|
searchOption.HasFlag(System.IO.SearchOption.AllDirectories)).ToArray(),
|
||||||
x => new BdInfoFileInfo(x));
|
x => new BdInfoFileInfo(x));
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@ namespace MediaBrowser.MediaEncoding.BdInfo
|
||||||
{
|
{
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BdInfoExaminer" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
public BdInfoExaminer(IFileSystem fileSystem)
|
public BdInfoExaminer(IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.BdInfo
|
namespace MediaBrowser.MediaEncoding.BdInfo
|
||||||
{
|
{
|
||||||
class BdInfoFileInfo : BDInfo.IO.IFileInfo
|
public class BdInfoFileInfo : BDInfo.IO.IFileInfo
|
||||||
{
|
{
|
||||||
FileSystemMetadata _impl = null;
|
private FileSystemMetadata _impl = null;
|
||||||
|
|
||||||
|
public BdInfoFileInfo(FileSystemMetadata impl)
|
||||||
|
{
|
||||||
|
_impl = impl;
|
||||||
|
}
|
||||||
|
|
||||||
public string Name => _impl.Name;
|
public string Name => _impl.Name;
|
||||||
|
|
||||||
|
@ -17,14 +24,10 @@ namespace MediaBrowser.MediaEncoding.BdInfo
|
||||||
|
|
||||||
public bool IsDir => _impl.IsDirectory;
|
public bool IsDir => _impl.IsDirectory;
|
||||||
|
|
||||||
public BdInfoFileInfo(FileSystemMetadata impl)
|
|
||||||
{
|
|
||||||
_impl = impl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public System.IO.Stream OpenRead()
|
public System.IO.Stream OpenRead()
|
||||||
{
|
{
|
||||||
return new FileStream(FullName,
|
return new FileStream(
|
||||||
|
FullName,
|
||||||
FileMode.Open,
|
FileMode.Open,
|
||||||
FileAccess.Read,
|
FileAccess.Read,
|
||||||
FileShare.Read);
|
FileShare.Read);
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
using System;
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Model.Configuration;
|
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Configuration
|
namespace MediaBrowser.MediaEncoding.Configuration
|
||||||
{
|
{
|
||||||
|
@ -17,32 +15,4 @@ namespace MediaBrowser.MediaEncoding.Configuration
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration
|
|
||||||
{
|
|
||||||
public EncodingConfigurationStore()
|
|
||||||
{
|
|
||||||
ConfigurationType = typeof(EncodingOptions);
|
|
||||||
Key = "encoding";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Validate(object oldConfig, object newConfig)
|
|
||||||
{
|
|
||||||
var newPath = ((EncodingOptions)newConfig).TranscodingTempPath;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(newPath)
|
|
||||||
&& !string.Equals(((EncodingOptions)oldConfig).TranscodingTempPath, newPath, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
// Validate
|
|
||||||
if (!Directory.Exists(newPath))
|
|
||||||
{
|
|
||||||
throw new DirectoryNotFoundException(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"{0} does not exist.",
|
|
||||||
newPath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Configuration
|
||||||
|
{
|
||||||
|
public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration
|
||||||
|
{
|
||||||
|
public EncodingConfigurationStore()
|
||||||
|
{
|
||||||
|
ConfigurationType = typeof(EncodingOptions);
|
||||||
|
Key = "encoding";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Validate(object oldConfig, object newConfig)
|
||||||
|
{
|
||||||
|
var newPath = ((EncodingOptions)newConfig).TranscodingTempPath;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(newPath)
|
||||||
|
&& !string.Equals(((EncodingOptions)oldConfig).TranscodingTempPath, newPath, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
// Validate
|
||||||
|
if (!Directory.Exists(newPath))
|
||||||
|
{
|
||||||
|
throw new DirectoryNotFoundException(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0} does not exist.",
|
||||||
|
newPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
@ -12,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
{
|
{
|
||||||
private const string DefaultEncoderPath = "ffmpeg";
|
private const string DefaultEncoderPath = "ffmpeg";
|
||||||
|
|
||||||
private static readonly string[] requiredDecoders = new[]
|
private static readonly string[] _requiredDecoders = new[]
|
||||||
{
|
{
|
||||||
"h264",
|
"h264",
|
||||||
"hevc",
|
"hevc",
|
||||||
|
@ -55,7 +57,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
"vc1_opencl"
|
"vc1_opencl"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly string[] requiredEncoders = new[]
|
private static readonly string[] _requiredEncoders = new[]
|
||||||
{
|
{
|
||||||
"libx264",
|
"libx264",
|
||||||
"libx265",
|
"libx265",
|
||||||
|
@ -110,6 +112,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
_encoderPath = encoderPath;
|
_encoderPath = encoderPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum Codec
|
||||||
|
{
|
||||||
|
Encoder,
|
||||||
|
Decoder
|
||||||
|
}
|
||||||
|
|
||||||
public static Version MinVersion { get; } = new Version(4, 0);
|
public static Version MinVersion { get; } = new Version(4, 0);
|
||||||
|
|
||||||
public static Version MaxVersion { get; } = null;
|
public static Version MaxVersion { get; } = null;
|
||||||
|
@ -193,8 +201,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// If that fails then we use one of the main libraries to determine if it's new/older than the latest
|
/// If that fails then we use one of the main libraries to determine if it's new/older than the latest
|
||||||
/// we have stored.
|
/// we have stored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="output"></param>
|
/// <param name="output">The output from "ffmpeg -version".</param>
|
||||||
/// <returns></returns>
|
/// <returns>The FFmpeg version.</returns>
|
||||||
internal static Version GetFFmpegVersion(string output)
|
internal static Version GetFFmpegVersion(string output)
|
||||||
{
|
{
|
||||||
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
||||||
|
@ -216,10 +224,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
|
/// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
|
||||||
/// and condenses them on to one line. Output format is "name1=major.minor,name2=major.minor,etc."
|
/// and condenses them on to one line. Output format is "name1=major.minor,name2=major.minor,etc.".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="output"></param>
|
/// <param name="output">The 'ffmpeg -version' output.</param>
|
||||||
/// <returns></returns>
|
/// <returns>The library names and major.minor version numbers.</returns>
|
||||||
private static string GetLibrariesVersionString(string output)
|
private static string GetLibrariesVersionString(string output)
|
||||||
{
|
{
|
||||||
var rc = new StringBuilder(144);
|
var rc = new StringBuilder(144);
|
||||||
|
@ -239,12 +247,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return rc.Length == 0 ? null : rc.ToString();
|
return rc.Length == 0 ? null : rc.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Codec
|
|
||||||
{
|
|
||||||
Encoder,
|
|
||||||
Decoder
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<string> GetHwaccelTypes()
|
private IEnumerable<string> GetHwaccelTypes()
|
||||||
{
|
{
|
||||||
string output = null;
|
string output = null;
|
||||||
|
@ -262,7 +264,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return Enumerable.Empty<string>();
|
return Enumerable.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
|
var found = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
|
||||||
_logger.LogInformation("Available hwaccel types: {Types}", found);
|
_logger.LogInformation("Available hwaccel types: {Types}", found);
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
|
@ -286,7 +288,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return Enumerable.Empty<string>();
|
return Enumerable.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var required = codec == Codec.Encoder ? requiredEncoders : requiredDecoders;
|
var required = codec == Codec.Encoder ? _requiredEncoders : _requiredDecoders;
|
||||||
|
|
||||||
var found = Regex
|
var found = Regex
|
||||||
.Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
|
.Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -19,9 +22,8 @@ using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Encoder
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
{
|
{
|
||||||
|
@ -35,6 +37,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal const int DefaultImageExtractionTimeout = 5000;
|
internal const int DefaultImageExtractionTimeout = 5000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The us culture.
|
||||||
|
/// </summary>
|
||||||
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private readonly ILogger<MediaEncoder> _logger;
|
private readonly ILogger<MediaEncoder> _logger;
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
@ -47,6 +54,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
private readonly object _runningProcessesLock = new object();
|
private readonly object _runningProcessesLock = new object();
|
||||||
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
||||||
|
|
||||||
|
private List<string> _encoders = new List<string>();
|
||||||
|
private List<string> _decoders = new List<string>();
|
||||||
|
private List<string> _hwaccels = new List<string>();
|
||||||
|
|
||||||
private string _ffmpegPath = string.Empty;
|
private string _ffmpegPath = string.Empty;
|
||||||
private string _ffprobePath;
|
private string _ffprobePath;
|
||||||
|
|
||||||
|
@ -77,7 +88,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Run at startup or if the user removes a Custom path from transcode page.
|
/// Run at startup or if the user removes a Custom path from transcode page.
|
||||||
/// Sets global variables FFmpegPath.
|
/// Sets global variables FFmpegPath.
|
||||||
/// Precedence is: Config > CLI > $PATH
|
/// Precedence is: Config > CLI > $PATH.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetFFmpegPath()
|
public void SetFFmpegPath()
|
||||||
{
|
{
|
||||||
|
@ -122,8 +133,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// Triggered from the Settings > Transcoding UI page when users submits Custom FFmpeg path to use.
|
/// Triggered from the Settings > Transcoding UI page when users submits Custom FFmpeg path to use.
|
||||||
/// Only write the new path to xml if it exists. Do not perform validation checks on ffmpeg here.
|
/// Only write the new path to xml if it exists. Do not perform validation checks on ffmpeg here.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path"></param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="pathType"></param>
|
/// <param name="pathType">The path type.</param>
|
||||||
public void UpdateEncoderPath(string path, string pathType)
|
public void UpdateEncoderPath(string path, string pathType)
|
||||||
{
|
{
|
||||||
string newPath;
|
string newPath;
|
||||||
|
@ -168,8 +179,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
|
/// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">FQPN to test.</param>
|
/// <param name="path">FQPN to test.</param>
|
||||||
/// <param name="location">Location (External, Custom, System) of tool</param>
|
/// <param name="location">Location (External, Custom, System) of tool.</param>
|
||||||
/// <returns></returns>
|
/// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
|
||||||
private bool ValidatePath(string path, FFmpegLocation location)
|
private bool ValidatePath(string path, FFmpegLocation location)
|
||||||
{
|
{
|
||||||
bool rc = false;
|
bool rc = false;
|
||||||
|
@ -221,8 +232,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Search the system $PATH environment variable looking for given filename.
|
/// Search the system $PATH environment variable looking for given filename.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fileName"></param>
|
/// <param name="fileName">The filename.</param>
|
||||||
/// <returns></returns>
|
/// <returns>The full path to the file.</returns>
|
||||||
private string ExistsOnSystemPath(string fileName)
|
private string ExistsOnSystemPath(string fileName)
|
||||||
{
|
{
|
||||||
var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
|
var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
|
||||||
|
@ -246,25 +257,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> _encoders = new List<string>();
|
|
||||||
public void SetAvailableEncoders(IEnumerable<string> list)
|
public void SetAvailableEncoders(IEnumerable<string> list)
|
||||||
{
|
{
|
||||||
_encoders = list.ToList();
|
_encoders = list.ToList();
|
||||||
// _logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> _decoders = new List<string>();
|
|
||||||
public void SetAvailableDecoders(IEnumerable<string> list)
|
public void SetAvailableDecoders(IEnumerable<string> list)
|
||||||
{
|
{
|
||||||
_decoders = list.ToList();
|
_decoders = list.ToList();
|
||||||
// _logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> _hwaccels = new List<string>();
|
|
||||||
public void SetAvailableHwaccels(IEnumerable<string> list)
|
public void SetAvailableHwaccels(IEnumerable<string> list)
|
||||||
{
|
{
|
||||||
_hwaccels = list.ToList();
|
_hwaccels = list.ToList();
|
||||||
//_logger.Info("Supported hwaccels: {0}", string.Join(",", list.ToArray()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SupportsEncoder(string encoder)
|
public bool SupportsEncoder(string encoder)
|
||||||
|
@ -332,8 +337,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
|
var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
|
||||||
|
|
||||||
return GetMediaInfoInternal(GetInputArgument(inputFiles, request.MediaSource.Protocol), request.MediaSource.Path, request.MediaSource.Protocol, extractChapters,
|
return GetMediaInfoInternal(
|
||||||
probeSize, request.MediaType == DlnaProfileType.Audio, request.MediaSource.VideoType, forceEnableLogging, cancellationToken);
|
GetInputArgument(inputFiles, request.MediaSource.Protocol),
|
||||||
|
request.MediaSource.Path,
|
||||||
|
request.MediaSource.Protocol,
|
||||||
|
extractChapters,
|
||||||
|
probeSize,
|
||||||
|
request.MediaType == DlnaProfileType.Audio,
|
||||||
|
request.MediaSource.VideoType,
|
||||||
|
forceEnableLogging,
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -342,7 +355,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// <param name="inputFiles">The input files.</param>
|
/// <param name="inputFiles">The input files.</param>
|
||||||
/// <param name="protocol">The protocol.</param>
|
/// <param name="protocol">The protocol.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
/// <exception cref="ArgumentException">Unrecognized InputType</exception>
|
/// <exception cref="ArgumentException">Unrecognized InputType.</exception>
|
||||||
public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol)
|
public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol)
|
||||||
=> EncodingUtils.GetInputArgument(inputFiles, protocol);
|
=> EncodingUtils.GetInputArgument(inputFiles, protocol);
|
||||||
|
|
||||||
|
@ -350,7 +363,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// Gets the media info internal.
|
/// Gets the media info internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Task{MediaInfoResult}.</returns>
|
/// <returns>Task{MediaInfoResult}.</returns>
|
||||||
private async Task<MediaInfo> GetMediaInfoInternal(string inputPath,
|
private async Task<MediaInfo> GetMediaInfoInternal(
|
||||||
|
string inputPath,
|
||||||
string primaryPath,
|
string primaryPath,
|
||||||
MediaProtocol protocol,
|
MediaProtocol protocol,
|
||||||
bool extractChapters,
|
bool extractChapters,
|
||||||
|
@ -378,7 +392,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
FileName = _ffprobePath,
|
FileName = _ffprobePath,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
|
|
||||||
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false,
|
||||||
},
|
},
|
||||||
|
@ -439,11 +452,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The us culture.
|
|
||||||
/// </summary>
|
|
||||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
|
||||||
|
|
||||||
public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
|
public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return ExtractImage(new[] { path }, null, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
|
return ExtractImage(new[] { path }, null, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
|
||||||
|
@ -459,8 +467,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return ExtractImage(inputFiles, container, imageStream, imageStreamIndex, protocol, false, null, null, cancellationToken);
|
return ExtractImage(inputFiles, container, imageStream, imageStreamIndex, protocol, false, null, null, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> ExtractImage(string[] inputFiles, string container, MediaStream videoStream, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
|
private async Task<string> ExtractImage(
|
||||||
Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
|
string[] inputFiles,
|
||||||
|
string container,
|
||||||
|
MediaStream videoStream,
|
||||||
|
int? imageStreamIndex,
|
||||||
|
MediaProtocol protocol,
|
||||||
|
bool isAudio,
|
||||||
|
Video3DFormat? threedFormat,
|
||||||
|
TimeSpan? offset,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var inputArgument = GetInputArgument(inputFiles, protocol);
|
var inputArgument = GetInputArgument(inputFiles, protocol);
|
||||||
|
|
||||||
|
@ -645,7 +661,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
public string GetTimeParameter(TimeSpan time)
|
public string GetTimeParameter(TimeSpan time)
|
||||||
{
|
{
|
||||||
return time.ToString(@"hh\:mm\:ss\.fff", UsCulture);
|
return time.ToString(@"hh\:mm\:ss\.fff", _usCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExtractVideoImagesOnInterval(
|
public async Task ExtractVideoImagesOnInterval(
|
||||||
|
@ -662,11 +678,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
{
|
{
|
||||||
var inputArgument = GetInputArgument(inputFiles, protocol);
|
var inputArgument = GetInputArgument(inputFiles, protocol);
|
||||||
|
|
||||||
var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture);
|
var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(_usCulture);
|
||||||
|
|
||||||
if (maxWidth.HasValue)
|
if (maxWidth.HasValue)
|
||||||
{
|
{
|
||||||
var maxWidthParam = maxWidth.Value.ToString(UsCulture);
|
var maxWidthParam = maxWidth.Value.ToString(_usCulture);
|
||||||
|
|
||||||
vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
|
vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
|
||||||
}
|
}
|
||||||
|
@ -859,6 +875,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
if (dispose)
|
if (dispose)
|
||||||
{
|
{
|
||||||
StopProcesses();
|
StopProcesses();
|
||||||
|
_thumbnailResourcePool.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -27,4 +28,16 @@
|
||||||
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
|
<PackageReference Include="UTF.Unknown" Version="2.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Code Analyzers-->
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<!-- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
|
||||||
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -3,6 +3,9 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Probing
|
namespace MediaBrowser.MediaEncoding.Probing
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class containing helper methods for working with FFprobe output.
|
||||||
|
/// </summary>
|
||||||
public static class FFProbeHelpers
|
public static class FFProbeHelpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
|
|
@ -269,6 +269,10 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
[JsonPropertyName("loro_surmixlev")]
|
[JsonPropertyName("loro_surmixlev")]
|
||||||
public string LoroSurmixlev { get; set; }
|
public string LoroSurmixlev { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the field_order.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The field_order.</value>
|
||||||
[JsonPropertyName("field_order")]
|
[JsonPropertyName("field_order")]
|
||||||
public string FieldOrder { get; set; }
|
public string FieldOrder { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
@ -16,10 +18,19 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
{
|
{
|
||||||
public class ProbeResultNormalizer
|
public class ProbeResultNormalizer
|
||||||
{
|
{
|
||||||
|
// When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
|
||||||
|
private const int MaxSubtitleDescriptionExtractionLength = 100;
|
||||||
|
|
||||||
|
private const string ArtistReplaceValue = " | ";
|
||||||
|
|
||||||
|
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
|
||||||
|
private List<string> _splitWhiteList = null;
|
||||||
|
|
||||||
public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
|
public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -368,7 +379,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
|
|
||||||
private List<NameValuePair> ReadValueArray(XmlReader reader)
|
private List<NameValuePair> ReadValueArray(XmlReader reader)
|
||||||
{
|
{
|
||||||
|
|
||||||
var pairs = new List<NameValuePair>();
|
var pairs = new List<NameValuePair>();
|
||||||
|
|
||||||
reader.MoveToContent();
|
reader.MoveToContent();
|
||||||
|
@ -949,50 +959,46 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
|
|
||||||
private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
|
private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
|
||||||
{
|
{
|
||||||
|
var peoples = new List<BaseItemPerson>();
|
||||||
var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
|
var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
|
||||||
if (!string.IsNullOrWhiteSpace(composer))
|
if (!string.IsNullOrWhiteSpace(composer))
|
||||||
{
|
{
|
||||||
var peoples = new List<BaseItemPerson>();
|
|
||||||
foreach (var person in Split(composer, false))
|
foreach (var person in Split(composer, false))
|
||||||
{
|
{
|
||||||
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
|
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
|
||||||
}
|
}
|
||||||
|
|
||||||
audio.People = peoples.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
|
var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
|
||||||
// if (!string.IsNullOrWhiteSpace(conductor))
|
if (!string.IsNullOrWhiteSpace(conductor))
|
||||||
//{
|
{
|
||||||
// foreach (var person in Split(conductor, false))
|
foreach (var person in Split(conductor, false))
|
||||||
// {
|
{
|
||||||
// audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
|
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
|
|
||||||
// var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
|
var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
|
||||||
// if (!string.IsNullOrWhiteSpace(lyricist))
|
if (!string.IsNullOrWhiteSpace(lyricist))
|
||||||
//{
|
{
|
||||||
// foreach (var person in Split(lyricist, false))
|
foreach (var person in Split(lyricist, false))
|
||||||
// {
|
{
|
||||||
// audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
|
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
|
||||||
// }
|
}
|
||||||
//}
|
}
|
||||||
|
|
||||||
// Check for writer some music is tagged that way as alternative to composer/lyricist
|
// Check for writer some music is tagged that way as alternative to composer/lyricist
|
||||||
var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer");
|
var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer");
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(writer))
|
if (!string.IsNullOrWhiteSpace(writer))
|
||||||
{
|
{
|
||||||
var peoples = new List<BaseItemPerson>();
|
|
||||||
foreach (var person in Split(writer, false))
|
foreach (var person in Split(writer, false))
|
||||||
{
|
{
|
||||||
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
|
peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
|
||||||
}
|
}
|
||||||
|
|
||||||
audio.People = peoples.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audio.People = peoples.ToArray();
|
||||||
audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
|
audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
|
||||||
|
|
||||||
var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
|
var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
|
||||||
|
@ -1117,8 +1123,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
|
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Splits the specified val.
|
/// Splits the specified val.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1138,8 +1142,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
.Select(i => i.Trim());
|
.Select(i => i.Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string ArtistReplaceValue = " | ";
|
|
||||||
|
|
||||||
private IEnumerable<string> SplitArtists(string val, char[] delimiters, bool splitFeaturing)
|
private IEnumerable<string> SplitArtists(string val, char[] delimiters, bool splitFeaturing)
|
||||||
{
|
{
|
||||||
if (splitFeaturing)
|
if (splitFeaturing)
|
||||||
|
@ -1169,9 +1171,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
return artistsFound;
|
return artistsFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<string> _splitWhiteList = null;
|
|
||||||
|
|
||||||
private IEnumerable<string> GetSplitWhitelist()
|
private IEnumerable<string> GetSplitWhitelist()
|
||||||
{
|
{
|
||||||
if (_splitWhiteList == null)
|
if (_splitWhiteList == null)
|
||||||
|
@ -1248,7 +1247,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
|
/// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tags">The tags.</param>
|
/// <param name="tags">The tags.</param>
|
||||||
/// <param name="tagName">Name of the tag.</param>
|
/// <param name="tagName">Name of the tag.</param>
|
||||||
|
@ -1294,8 +1293,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
|
|
||||||
|
|
||||||
private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
|
private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
|
||||||
{
|
{
|
||||||
if (data.Format == null || data.Format.Tags == null)
|
if (data.Format == null || data.Format.Tags == null)
|
||||||
|
@ -1380,8 +1377,8 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||||
if (subtitle.Contains('/', StringComparison.Ordinal)) // It contains a episode number and season number
|
if (subtitle.Contains('/', StringComparison.Ordinal)) // It contains a episode number and season number
|
||||||
{
|
{
|
||||||
string[] numbers = subtitle.Split(' ');
|
string[] numbers = subtitle.Split(' ');
|
||||||
video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
|
video.IndexNumber = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[0]);
|
||||||
int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]);
|
int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[1]);
|
||||||
|
|
||||||
description = string.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
|
description = string.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
@ -13,6 +15,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
|
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var trackInfo = new SubtitleTrackInfo();
|
var trackInfo = new SubtitleTrackInfo();
|
||||||
|
@ -22,7 +25,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
string line;
|
string line;
|
||||||
while (reader.ReadLine() != "[Events]")
|
while (reader.ReadLine() != "[Events]")
|
||||||
{ }
|
{
|
||||||
|
}
|
||||||
|
|
||||||
var headers = ParseFieldHeaders(reader.ReadLine());
|
var headers = ParseFieldHeaders(reader.ReadLine());
|
||||||
|
|
||||||
|
@ -72,17 +76,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
var fields = line.Substring(8).Split(',').Select(x => x.Trim()).ToList();
|
var fields = line.Substring(8).Split(',').Select(x => x.Trim()).ToList();
|
||||||
|
|
||||||
var result = new Dictionary<string, int> {
|
return new Dictionary<string, int>
|
||||||
{"Start", fields.IndexOf("Start")},
|
{
|
||||||
{"End", fields.IndexOf("End")},
|
{ "Start", fields.IndexOf("Start") },
|
||||||
{"Text", fields.IndexOf("Text")}
|
{ "End", fields.IndexOf("End") },
|
||||||
};
|
{ "Text", fields.IndexOf("Text") }
|
||||||
return result;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Credit: https://github.com/SubtitleEdit/subtitleedit/blob/master/src/Logic/SubtitleFormats/AdvancedSubStationAlpha.cs
|
|
||||||
/// </summary>
|
|
||||||
private void RemoteNativeFormatting(SubtitleTrackEvent p)
|
private void RemoteNativeFormatting(SubtitleTrackEvent p)
|
||||||
{
|
{
|
||||||
int indexOfBegin = p.Text.IndexOf('{');
|
int indexOfBegin = p.Text.IndexOf('{');
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
|
|
@ -8,7 +8,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs
|
/// <see href="https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs">Credit</see>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SsaParser : ISubtitleParser
|
public class SsaParser : ISubtitleParser
|
||||||
{
|
{
|
||||||
|
@ -179,10 +179,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
// h:mm:ss.cc
|
// h:mm:ss.cc
|
||||||
string[] timeCode = time.Split(':', '.');
|
string[] timeCode = time.Split(':', '.');
|
||||||
return new TimeSpan(0, int.Parse(timeCode[0]),
|
return new TimeSpan(
|
||||||
int.Parse(timeCode[1]),
|
0,
|
||||||
int.Parse(timeCode[2]),
|
int.Parse(timeCode[0]),
|
||||||
int.Parse(timeCode[3]) * 10).Ticks;
|
int.Parse(timeCode[1]),
|
||||||
|
int.Parse(timeCode[2]),
|
||||||
|
int.Parse(timeCode[3]) * 10).Ticks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetFormattedText(string text)
|
private static string GetFormattedText(string text)
|
||||||
|
@ -282,6 +284,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
|
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
|
||||||
}
|
}
|
||||||
|
|
||||||
int indexOfEndTag = text.IndexOf("{\\c}", start);
|
int indexOfEndTag = text.IndexOf("{\\c}", start);
|
||||||
if (indexOfEndTag > 0)
|
if (indexOfEndTag > 0)
|
||||||
{
|
{
|
||||||
|
@ -320,6 +323,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
|
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
|
||||||
}
|
}
|
||||||
|
|
||||||
text += "</font>";
|
text += "</font>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _semaphoreLocks.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
|
||||||
|
new ConcurrentDictionary<string, SemaphoreSlim>();
|
||||||
|
|
||||||
public SubtitleEncoder(
|
public SubtitleEncoder(
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILogger<SubtitleEncoder> logger,
|
ILogger<SubtitleEncoder> logger,
|
||||||
|
@ -269,25 +275,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
return new SubtitleInfo(subtitleStream.Path, protocol, currentFormat, true);
|
return new SubtitleInfo(subtitleStream.Path, protocol, currentFormat, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SubtitleInfo
|
|
||||||
{
|
|
||||||
public SubtitleInfo(string path, MediaProtocol protocol, string format, bool isExternal)
|
|
||||||
{
|
|
||||||
Path = path;
|
|
||||||
Protocol = protocol;
|
|
||||||
Format = format;
|
|
||||||
IsExternal = isExternal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Path { get; set; }
|
|
||||||
|
|
||||||
public MediaProtocol Protocol { get; set; }
|
|
||||||
|
|
||||||
public string Format { get; set; }
|
|
||||||
|
|
||||||
public bool IsExternal { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private ISubtitleParser GetReader(string format, bool throwIfMissing)
|
private ISubtitleParser GetReader(string format, bool throwIfMissing)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(format))
|
if (string.IsNullOrEmpty(format))
|
||||||
|
@ -360,12 +347,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
throw new ArgumentException("Unsupported format: " + format);
|
throw new ArgumentException("Unsupported format: " + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _semaphoreLocks.
|
|
||||||
/// </summary>
|
|
||||||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
|
|
||||||
new ConcurrentDictionary<string, SemaphoreSlim>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the lock.
|
/// Gets the lock.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -380,6 +361,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
/// Converts the text subtitle to SRT.
|
/// Converts the text subtitle to SRT.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="inputPath">The input path.</param>
|
/// <param name="inputPath">The input path.</param>
|
||||||
|
/// <param name="language">The language.</param>
|
||||||
/// <param name="inputProtocol">The input protocol.</param>
|
/// <param name="inputProtocol">The input protocol.</param>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
@ -407,14 +389,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
/// Converts the text subtitle to SRT internal.
|
/// Converts the text subtitle to SRT internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="inputPath">The input path.</param>
|
/// <param name="inputPath">The input path.</param>
|
||||||
|
/// <param name="language">The language.</param>
|
||||||
/// <param name="inputProtocol">The input protocol.</param>
|
/// <param name="inputProtocol">The input protocol.</param>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
/// <exception cref="ArgumentNullException">
|
/// <exception cref="ArgumentNullException">
|
||||||
/// inputPath
|
/// The <c>inputPath</c> or <c>outputPath</c> is <c>null</c>.
|
||||||
/// or
|
|
||||||
/// outputPath
|
|
||||||
/// </exception>
|
/// </exception>
|
||||||
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
|
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
@ -438,7 +419,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
(encodingParam.Equals("UTF-16BE", StringComparison.OrdinalIgnoreCase) ||
|
(encodingParam.Equals("UTF-16BE", StringComparison.OrdinalIgnoreCase) ||
|
||||||
encodingParam.Equals("UTF-16LE", StringComparison.OrdinalIgnoreCase)))
|
encodingParam.Equals("UTF-16LE", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
encodingParam = "";
|
encodingParam = string.Empty;
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(encodingParam))
|
else if (!string.IsNullOrEmpty(encodingParam))
|
||||||
{
|
{
|
||||||
|
@ -540,7 +521,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
/// <exception cref="ArgumentException">Must use inputPath list overload</exception>
|
/// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
|
||||||
private async Task ExtractTextSubtitle(
|
private async Task ExtractTextSubtitle(
|
||||||
string[] inputFiles,
|
string[] inputFiles,
|
||||||
MediaProtocol protocol,
|
MediaProtocol protocol,
|
||||||
|
@ -759,7 +740,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
&& (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
|
&& (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
|
|| string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
charset = "";
|
charset = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
|
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
|
||||||
|
@ -790,5 +771,24 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
throw new ArgumentOutOfRangeException(nameof(protocol));
|
throw new ArgumentOutOfRangeException(nameof(protocol));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct SubtitleInfo
|
||||||
|
{
|
||||||
|
public SubtitleInfo(string path, MediaProtocol protocol, string format, bool isExternal)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
Protocol = protocol;
|
||||||
|
Format = format;
|
||||||
|
IsExternal = isExternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
|
public MediaProtocol Protocol { get; set; }
|
||||||
|
|
||||||
|
public string Format { get; set; }
|
||||||
|
|
||||||
|
public bool IsExternal { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,7 +233,7 @@ namespace MediaBrowser.Model.Entities
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Title))
|
if (!string.IsNullOrEmpty(Title))
|
||||||
{
|
{
|
||||||
var result = new StringBuilder(Title);
|
var result = new StringBuilder(Title);
|
||||||
foreach (var tag in attributes)
|
foreach (var tag in attributes)
|
||||||
{
|
{
|
||||||
// Keep Tags that are not already in Title.
|
// Keep Tags that are not already in Title.
|
||||||
|
@ -246,7 +246,7 @@ namespace MediaBrowser.Model.Entities
|
||||||
return result.ToString();
|
return result.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Join(" - ", attributes.ToArray());
|
return string.Join(" - ", attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
|
|
||||||
// If there are more than one output paths, the stream will need to be seekable
|
// If there are more than one output paths, the stream will need to be seekable
|
||||||
var memoryStream = new MemoryStream();
|
var memoryStream = new MemoryStream();
|
||||||
using (source)
|
await using (source.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await source.CopyToAsync(memoryStream).ConfigureAwait(false);
|
await source.CopyToAsync(memoryStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
|
|
||||||
var savedPaths = new List<string>();
|
var savedPaths = new List<string>();
|
||||||
|
|
||||||
await using (source)
|
await using (source.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var currentPathIndex = 0;
|
var currentPathIndex = 0;
|
||||||
|
|
||||||
|
|
|
@ -966,7 +966,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void OnRefreshStart(BaseItem item)
|
public void OnRefreshStart(BaseItem item)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
_logger.LogDebug("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
_activeRefreshes[item.Id] = 0;
|
_activeRefreshes[item.Id] = 0;
|
||||||
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
||||||
}
|
}
|
||||||
|
@ -974,7 +974,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void OnRefreshComplete(BaseItem item)
|
public void OnRefreshComplete(BaseItem item)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
_logger.LogDebug("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
_activeRefreshes.Remove(item.Id, out _);
|
_activeRefreshes.Remove(item.Id, out _);
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ Note that it is also possible to [host the web client separately](#hosting-the-w
|
||||||
|
|
||||||
There are three options to get the files for the web client.
|
There are three options to get the files for the web client.
|
||||||
|
|
||||||
1. Download one of the finished builds from the [Azure DevOps pipeline](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11). You can download the build for a specific release by looking at the [branches tab](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11&_a=summary&repositoryFilter=6&view=branches) of the pipelines page.
|
1. Download one of the finished builds from the [Azure DevOps pipeline](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=27). You can download the build for a specific release by looking at the [branches tab](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=27&_a=summary&repositoryFilter=6&view=branches) of the pipelines page.
|
||||||
2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web)
|
2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web)
|
||||||
3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web`
|
3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web`
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
|
||||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
Loading…
Reference in New Issue