aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding
diff options
context:
space:
mode:
authorBond-009 <bond.009@outlook.com>2020-01-08 18:10:17 +0100
committerBond-009 <bond.009@outlook.com>2020-01-08 18:10:17 +0100
commitc3752b1a3080f6f54c77a436c7d0b309792b8872 (patch)
treec762d033f487407f483b03e2f16ac7bec44ad22c /MediaBrowser.MediaEncoding
parent9dfafb9e9fcddb253b157fe03b085b7fceef4290 (diff)
parent124a852787ff94201de452a952eb31376beafe12 (diff)
Merge branch 'master' into scanerrors
Diffstat (limited to 'MediaBrowser.MediaEncoding')
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs281
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs74
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs4
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs40
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs52
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs4
-rw-r--r--MediaBrowser.MediaEncoding/packages.config3
8 files changed, 447 insertions, 13 deletions
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
new file mode 100644
index 000000000..c371e8b94
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Diagnostics;
+using System.Collections.Concurrent;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.MediaEncoding.Attachments
+{
+ public class AttachmentExtractor : IAttachmentExtractor, IDisposable
+ {
+ private readonly ILogger _logger;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IFileSystem _fileSystem;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IMediaSourceManager _mediaSourceManager;
+
+ private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
+ new ConcurrentDictionary<string, SemaphoreSlim>();
+
+ private bool _disposed = false;
+
+ public AttachmentExtractor(
+ ILogger<AttachmentExtractor> logger,
+ IApplicationPaths appPaths,
+ IFileSystem fileSystem,
+ IMediaEncoder mediaEncoder,
+ IMediaSourceManager mediaSourceManager)
+ {
+ _logger = logger;
+ _appPaths = appPaths;
+ _fileSystem = fileSystem;
+ _mediaEncoder = mediaEncoder;
+ _mediaSourceManager = mediaSourceManager;
+ }
+
+ /// <inheritdoc />
+ public async Task<(MediaAttachment attachment, Stream stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ if (string.IsNullOrWhiteSpace(mediaSourceId))
+ {
+ throw new ArgumentNullException(nameof(mediaSourceId));
+ }
+
+ var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);
+ var mediaSource = mediaSources
+ .FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
+ if (mediaSource == null)
+ {
+ throw new ResourceNotFoundException($"MediaSource {mediaSourceId} not found");
+ }
+
+ var mediaAttachment = mediaSource.MediaAttachments
+ .FirstOrDefault(i => i.Index == attachmentStreamIndex);
+ if (mediaAttachment == null)
+ {
+ throw new ResourceNotFoundException($"MediaSource {mediaSourceId} has no attachment with stream index {attachmentStreamIndex}");
+ }
+
+ var attachmentStream = await GetAttachmentStream(mediaSource, mediaAttachment, cancellationToken)
+ .ConfigureAwait(false);
+
+ return (mediaAttachment, attachmentStream);
+ }
+
+ private async Task<Stream> GetAttachmentStream(
+ MediaSourceInfo mediaSource,
+ MediaAttachment mediaAttachment,
+ CancellationToken cancellationToken)
+ {
+ var attachmentPath = await GetReadableFile(mediaSource.Path, mediaSource.Path, mediaSource.Protocol, mediaAttachment, cancellationToken).ConfigureAwait(false);
+ return File.OpenRead(attachmentPath);
+ }
+
+ private async Task<string> GetReadableFile(
+ string mediaPath,
+ string inputFile,
+ MediaProtocol protocol,
+ MediaAttachment mediaAttachment,
+ CancellationToken cancellationToken)
+ {
+ var outputPath = GetAttachmentCachePath(mediaPath, protocol, mediaAttachment.Index);
+ await ExtractAttachment(inputFile, protocol, mediaAttachment.Index, outputPath, cancellationToken)
+ .ConfigureAwait(false);
+
+ return outputPath;
+ }
+
+ private async Task ExtractAttachment(
+ string inputFile,
+ MediaProtocol protocol,
+ int attachmentStreamIndex,
+ string outputPath,
+ CancellationToken cancellationToken)
+ {
+ var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1));
+
+ await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ if (!File.Exists(outputPath))
+ {
+ await ExtractAttachmentInternal(
+ _mediaEncoder.GetInputArgument(new[] { inputFile }, protocol),
+ attachmentStreamIndex,
+ outputPath,
+ cancellationToken).ConfigureAwait(false);
+ }
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+
+ private async Task ExtractAttachmentInternal(
+ string inputPath,
+ int attachmentStreamIndex,
+ string outputPath,
+ CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(inputPath))
+ {
+ throw new ArgumentNullException(nameof(inputPath));
+ }
+
+ if (string.IsNullOrEmpty(outputPath))
+ {
+ throw new ArgumentNullException(nameof(outputPath));
+ }
+
+ Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
+
+ var processArgs = string.Format(
+ CultureInfo.InvariantCulture,
+ "-dump_attachment:{1} {2} -i {0} -t 0 -f null null",
+ inputPath,
+ attachmentStreamIndex,
+ outputPath);
+ var startInfo = new ProcessStartInfo
+ {
+ Arguments = processArgs,
+ FileName = _mediaEncoder.EncoderPath,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ };
+ var process = new Process
+ {
+ StartInfo = startInfo
+ };
+
+ _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
+
+ process.Start();
+
+ var processTcs = new TaskCompletionSource<bool>();
+ process.EnableRaisingEvents = true;
+ process.Exited += (sender, args) => processTcs.TrySetResult(true);
+ var unregister = cancellationToken.Register(() => processTcs.TrySetResult(process.HasExited));
+ var ranToCompletion = await processTcs.Task.ConfigureAwait(false);
+ unregister.Dispose();
+
+ if (!ranToCompletion)
+ {
+ try
+ {
+ _logger.LogWarning("Killing ffmpeg attachment extraction process");
+ process.Kill();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error killing attachment extraction process");
+ }
+ }
+
+ var exitCode = ranToCompletion ? process.ExitCode : -1;
+
+ process.Dispose();
+
+ var failed = false;
+
+ if (exitCode != 0)
+ {
+ failed = true;
+
+ _logger.LogWarning("Deleting extracted attachment {Path} due to failure: {ExitCode}", outputPath, exitCode);
+ try
+ {
+ if (File.Exists(outputPath))
+ {
+ _fileSystem.DeleteFile(outputPath);
+ }
+ }
+ catch (IOException ex)
+ {
+ _logger.LogError(ex, "Error deleting extracted attachment {Path}", outputPath);
+ }
+ }
+ else if (!File.Exists(outputPath))
+ {
+ failed = true;
+ }
+
+ if (failed)
+ {
+ var msg = $"ffmpeg attachment extraction failed for {inputPath} to {outputPath}";
+
+ _logger.LogError(msg);
+
+ throw new InvalidOperationException(msg);
+ }
+ else
+ {
+ _logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath);
+ }
+ }
+
+ private string GetAttachmentCachePath(string mediaPath, MediaProtocol protocol, int attachmentStreamIndex)
+ {
+ string filename;
+ if (protocol == MediaProtocol.File)
+ {
+ var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
+ filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D");
+ }
+ else
+ {
+ filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D");
+ }
+
+ var prefix = filename.Substring(0, 1);
+ return Path.Combine(_appPaths.DataPath, "attachments", prefix, filename);
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+
+ }
+
+ _disposed = true;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
new file mode 100644
index 000000000..91c8b2792
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using BDInfo.IO;
+using MediaBrowser.Model.IO;
+
+namespace MediaBrowser.MediaEncoding.BdInfo
+{
+ class BdInfoDirectoryInfo : BDInfo.IO.IDirectoryInfo
+ {
+ IFileSystem _fileSystem = null;
+
+ FileSystemMetadata _impl = null;
+
+ public string Name => _impl.Name;
+
+ public string FullName => _impl.FullName;
+
+ public IDirectoryInfo Parent
+ {
+ get
+ {
+ var parentFolder = System.IO.Path.GetDirectoryName(_impl.FullName);
+ if (parentFolder != null)
+ {
+ return new BdInfoDirectoryInfo(_fileSystem, parentFolder);
+ }
+ return null;
+ }
+ }
+
+ public BdInfoDirectoryInfo(IFileSystem fileSystem, string path)
+ {
+ _fileSystem = fileSystem;
+ _impl = _fileSystem.GetDirectoryInfo(path);
+ }
+
+ private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl)
+ {
+ _fileSystem = fileSystem;
+ _impl = impl;
+ }
+
+ public IDirectoryInfo[] GetDirectories()
+ {
+ return Array.ConvertAll(_fileSystem.GetDirectories(_impl.FullName).ToArray(),
+ x => new BdInfoDirectoryInfo(_fileSystem, x));
+ }
+
+ public IFileInfo[] GetFiles()
+ {
+ return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName).ToArray(),
+ x => new BdInfoFileInfo(_fileSystem, x));
+ }
+
+ public IFileInfo[] GetFiles(string searchPattern)
+ {
+ return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(),
+ x => new BdInfoFileInfo(_fileSystem, x));
+ }
+
+ public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption)
+ {
+ return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false,
+ searchOption.HasFlag(System.IO.SearchOption.AllDirectories)).ToArray(),
+ x => new BdInfoFileInfo(_fileSystem, x));
+ }
+
+ public static IDirectoryInfo FromFileSystemPath(Model.IO.IFileSystem fs, string path)
+ {
+ return new BdInfoDirectoryInfo(fs, path);
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
index 3b6b91684..3260f3051 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using BDInfo;
@@ -32,7 +32,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo
throw new ArgumentNullException(nameof(path));
}
- var bdrom = new BDROM(path, _fileSystem);
+ var bdrom = new BDROM(BdInfoDirectoryInfo.FromFileSystemPath(_fileSystem, path));
bdrom.Scan();
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
new file mode 100644
index 000000000..de9d7cb78
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
@@ -0,0 +1,40 @@
+using MediaBrowser.Model.IO;
+
+namespace MediaBrowser.MediaEncoding.BdInfo
+{
+ class BdInfoFileInfo : BDInfo.IO.IFileInfo
+ {
+ IFileSystem _fileSystem = null;
+
+ FileSystemMetadata _impl = null;
+
+ public string Name => _impl.Name;
+
+ public string FullName => _impl.FullName;
+
+ public string Extension => _impl.Extension;
+
+ public long Length => _impl.Length;
+
+ public bool IsDir => _impl.IsDirectory;
+
+ public BdInfoFileInfo(IFileSystem fileSystem, FileSystemMetadata impl)
+ {
+ _fileSystem = fileSystem;
+ _impl = impl;
+ }
+
+ public System.IO.Stream OpenRead()
+ {
+ return _fileSystem.GetFileStream(FullName,
+ FileOpenMode.Open,
+ FileAccessMode.Read,
+ FileShareMode.Read);
+ }
+
+ public System.IO.StreamReader OpenText()
+ {
+ return new System.IO.StreamReader(OpenRead());
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index e977bd8fe..783457bda 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -11,13 +11,13 @@
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\BDInfo\BDInfo.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="BDInfo" Version="0.7.6.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.6.0" />
<PackageReference Include="UTF.Unknown" Version="2.2.0" />
</ItemGroup>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 99f0df60f..37baef5b0 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -48,7 +48,11 @@ namespace MediaBrowser.MediaEncoding.Probing
.Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec))
.ToList();
- if (data.Format != null)
+ info.MediaAttachments = internalStreams.Select(s => GetMediaAttachment(s))
+ .Where(i => i != null)
+ .ToList();
+
+ if (data.format != null)
{
info.Container = NormalizeFormat(data.Format.FormatName);
@@ -513,6 +517,39 @@ namespace MediaBrowser.MediaEncoding.Probing
}
/// <summary>
+ /// Converts ffprobe stream info to our MediaAttachment class
+ /// </summary>
+ /// <param name="streamInfo">The stream info.</param>
+ /// <returns>MediaAttachments.</returns>
+ private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo)
+ {
+ if (!string.Equals(streamInfo.codec_type, "attachment", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ var attachment = new MediaAttachment
+ {
+ Codec = streamInfo.codec_name,
+ Index = streamInfo.index
+ };
+
+ if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string))
+ {
+ attachment.CodecTag = streamInfo.codec_tag_string;
+ }
+
+ if (streamInfo.tags != null)
+ {
+ attachment.FileName = GetDictionaryValue(streamInfo.tags, "filename");
+ attachment.MimeType = GetDictionaryValue(streamInfo.tags, "mimetype");
+ attachment.Comment = GetDictionaryValue(streamInfo.tags, "comment");
+ }
+
+ return attachment;
+ }
+
+ /// <summary>
/// Converts ffprobe stream info to our MediaStream class
/// </summary>
/// <param name="isAudio">if set to <c>true</c> [is info].</param>
@@ -1337,24 +1374,25 @@ namespace MediaBrowser.MediaEncoding.Probing
{
video.Timestamp = GetMpegTimestamp(video.Path);
- _logger.LogDebug("Video has {timestamp} timestamp", video.Timestamp);
+ _logger.LogDebug("Video has {Timestamp} timestamp", video.Timestamp);
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error extracting timestamp info from {path}", video.Path);
+ _logger.LogError(ex, "Error extracting timestamp info from {Path}", video.Path);
video.Timestamp = null;
}
}
}
}
+ // REVIEW: find out why the byte array needs to be 197 bytes long and comment the reason
private TransportStreamTimestamp GetMpegTimestamp(string path)
{
- var packetBuffer = new byte['Å'];
+ var packetBuffer = new byte[197];
- using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
- fs.Read(packetBuffer, 0, packetBuffer.Length);
+ fs.Read(packetBuffer);
}
if (packetBuffer[0] == 71)
@@ -1362,7 +1400,7 @@ namespace MediaBrowser.MediaEncoding.Probing
return TransportStreamTimestamp.None;
}
- if ((packetBuffer[4] == 71) && (packetBuffer['Ä'] == 71))
+ if ((packetBuffer[4] == 71) && (packetBuffer[196] == 71))
{
if ((packetBuffer[0] == 0) && (packetBuffer[1] == 0) && (packetBuffer[2] == 0) && (packetBuffer[3] == 0))
{
diff --git a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs
index 241ebc6df..1b452b0ce 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs
@@ -16,6 +16,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
using (var writer = new Utf8JsonWriter(stream))
{
var trackevents = info.TrackEvents;
+ writer.WriteStartObject();
writer.WriteStartArray("TrackEvents");
for (int i = 0; i < trackevents.Count; i++)
@@ -33,7 +34,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles
writer.WriteEndObject();
}
+ writer.WriteEndArray();
writer.WriteEndObject();
+
+ writer.Flush();
}
}
}
diff --git a/MediaBrowser.MediaEncoding/packages.config b/MediaBrowser.MediaEncoding/packages.config
deleted file mode 100644
index bbeaf5f00..000000000
--- a/MediaBrowser.MediaEncoding/packages.config
+++ /dev/null
@@ -1,3 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
-</packages> \ No newline at end of file