Merge remote-tracking branch 'upstream/master' into api-merge-again

This commit is contained in:
crobibero 2020-08-04 20:29:06 -06:00
commit e65ecb5687
29 changed files with 314 additions and 237 deletions

View File

@ -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

View File

@ -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'

View File

@ -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;
} }
} }

View File

@ -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,

View File

@ -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
{ {

View File

@ -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)));

View File

@ -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;

View File

@ -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));
} }

View File

@ -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;

View File

@ -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);

View File

@ -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));
}
}
}
}
} }

View File

@ -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));
}
}
}
}
}

View File

@ -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)

View File

@ -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;

View File

@ -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();
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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; }

View File

@ -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
} }

View File

@ -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('{');

View File

@ -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;

View File

@ -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>";
} }
} }

View File

@ -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; }
}
} }
} }

View File

@ -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:

View File

@ -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;

View File

@ -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 _);

View File

@ -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`

View File

@ -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>