From 321e5cba60825d5791730da75cd12905e97b527e Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 3 Oct 2019 11:51:28 -0400 Subject: [PATCH 01/81] Add new MediaAttachment to store attachments found during media probing. --- .../Probing/ProbeResultNormalizer.cs | 38 ++++++++++++++++ MediaBrowser.Model/Dto/MediaSourceInfo.cs | 2 + .../Entities/MediaAttachment.cs | 44 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 MediaBrowser.Model/Entities/MediaAttachment.cs diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 54d02fc9f7..2c001d775a 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -49,6 +49,10 @@ namespace MediaBrowser.MediaEncoding.Probing .Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec)) .ToList(); + info.MediaAttachments = internalStreams.Select(s => GetMediaAttachment(s)) + .Where(i => i != null) + .ToList(); + if (data.format != null) { info.Container = NormalizeFormat(data.format.format_name); @@ -513,6 +517,40 @@ namespace MediaBrowser.MediaEncoding.Probing return codec; } + /// + /// Converts ffprobe stream info to our MediaAttachment class + /// + /// The stream info. + /// MediaAttachments. + 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 + }; + + // Filter out junk + if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) + { + 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; + } + /// /// Converts ffprobe stream info to our MediaStream class /// diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 5bdc4809a9..8a1aa55b69 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -57,6 +57,8 @@ namespace MediaBrowser.Model.Dto public List MediaStreams { get; set; } + public List MediaAttachments { get; set; } + public string[] Formats { get; set; } public int? Bitrate { get; set; } diff --git a/MediaBrowser.Model/Entities/MediaAttachment.cs b/MediaBrowser.Model/Entities/MediaAttachment.cs new file mode 100644 index 0000000000..daabbe91df --- /dev/null +++ b/MediaBrowser.Model/Entities/MediaAttachment.cs @@ -0,0 +1,44 @@ +namespace MediaBrowser.Model.Entities +{ + /// + /// Class MediaAttachment + /// + public class MediaAttachment + { + /// + /// Gets or sets the codec. + /// + /// The codec. + public string Codec { get; set; } + + /// + /// Gets or sets the codec tag. + /// + /// The codec tag. + public string CodecTag { get; set; } + + /// + /// Gets or sets the comment. + /// + /// The comment. + public string Comment { get; set; } + + /// + /// Gets or sets the index. + /// + /// The index. + public int Index { get; set; } + + /// + /// Gets or sets the filename. + /// + /// The filename. + public string Filename { get; set; } + + /// + /// Gets or sets the MIME type. + /// + /// The MIME type. + public string MIMEType { get; set; } + } +} From 03ecf57548b3bd6058b9427ff48f06050725ad4a Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Fri, 4 Oct 2019 13:23:08 -0400 Subject: [PATCH 02/81] Store MediaAttachments in DB. --- .../Data/SqliteItemRepository.cs | 179 ++++++++++++++++++ .../Persistence/IItemRepository.cs | 15 ++ .../Persistence/MediaAttachmentQuery.cs | 20 ++ .../MediaInfo/FFProbeVideoInfo.cs | 4 + 4 files changed, 218 insertions(+) create mode 100644 MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 31a661c5d9..58f04e4fcf 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -94,6 +94,8 @@ namespace Emby.Server.Implementations.Data { const string CreateMediaStreamsTableCommand = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; + const string CreateMediaAttachmentsTableCommand + = "create table if not exists mediaattachments (ItemId GUID, AttachmentIndex INT, Codec TEXT, CodecTag TEXT NULL, Comment TEXT NULL, Filename TEXT NULL, MIMEType TEXT NULL, PRIMARY KEY (ItemId, AttachmentIndex))"; string[] queries = { @@ -116,6 +118,7 @@ namespace Emby.Server.Implementations.Data "create table if not exists " + ChaptersTableName + " (ItemId GUID, ChapterIndex INT NOT NULL, StartPositionTicks BIGINT NOT NULL, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", CreateMediaStreamsTableCommand, + CreateMediaAttachmentsTableCommand, "pragma shrink_memory" }; @@ -423,6 +426,17 @@ namespace Emby.Server.Implementations.Data "ColorTransfer" }; + private static readonly string[] _mediaAttachmentSaveColumns = + { + "ItemId", + "AttachmentIndex", + "Codec", + "CodecTag", + "Comment", + "Filename", + "MIMEType" + }; + private static string GetSaveItemCommandText() { var saveColumns = new [] @@ -6130,5 +6144,170 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return item; } + + public List GetMediaAttachments(MediaAttachmentQuery query) + { + CheckDisposed(); + + if (query == null) + { + throw new ArgumentNullException(nameof(query)); + } + + var cmdText = "select " + + string.Join(",", _mediaAttachmentSaveColumns) + + " from mediaattachments where" + + " ItemId=@ItemId"; + + if (query.Index.HasValue) + { + cmdText += "AND AttachmentIndex=@AttachmentIndex"; + } + + cmdText += " order by AttachmentIndex ASC"; + + using (var connection = GetConnection(true)) + { + var list = new List(); + + using (var statement = PrepareStatement(connection, cmdText)) + { + statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); + + if (query.Index.HasValue) + { + statement.TryBind("@AttachmentIndex", query.Index.Value); + } + + foreach (var row in statement.ExecuteQuery()) { + list.Add(GetMediaAttachment(row)); + } + } + + return list; + } + } + + public void SaveMediaAttachments(Guid id, List attachments, CancellationToken cancellationToken) + { + CheckDisposed(); + if (id == Guid.Empty) + { + throw new ArgumentNullException(nameof(id)); + } + + if (attachments == null) + { + throw new ArgumentNullException(nameof(attachments)); + } + + using (var connection = GetConnection()) + { + connection.RunInTransaction(db => + { + var itemIdBlob = id.ToGuidBlob(); + + db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); + + InsertMediaAttachments(itemIdBlob, attachments, db); + + }, TransactionMode); + } + } + + private void InsertMediaAttachments(byte[] idBlob, List attachments, IDatabaseConnection db) + { + var startIndex = 0; + var limit = 10; + + while (startIndex < attachments.Count) + { + var insertText = new StringBuilder(string.Format("insert into mediaattachments ({0}) values ", string.Join(",", _mediaAttachmentSaveColumns))); + + var endIndex = Math.Min(attachments.Count, startIndex + limit); + + for (var i = startIndex; i < endIndex; i++) + { + if (i != startIndex) + { + insertText.Append(","); + } + + var index = i.ToString(CultureInfo.InvariantCulture); + insertText.Append("(@ItemId, "); + + foreach (var column in _mediaAttachmentSaveColumns.Skip(1)) + { + insertText.Append("@" + column + index + ","); + } + insertText.Length -= 1; + + insertText.Append(")"); + } + + using (var statement = PrepareStatement(db, insertText.ToString())) + { + statement.TryBind("@ItemId", idBlob); + + for (var i = startIndex; i < endIndex; i++) + { + var index = i.ToString(CultureInfo.InvariantCulture); + + var attachment = attachments[i]; + + statement.TryBind("@AttachmentIndex" + index, attachment.Index); + statement.TryBind("@Codec" + index, attachment.Codec); + statement.TryBind("@CodecTag" + index, attachment.CodecTag); + statement.TryBind("@Comment" + index, attachment.Comment); + statement.TryBind("@Filename" + index, attachment.Filename); + statement.TryBind("@MIMEType" + index, attachment.MIMEType); + } + + statement.Reset(); + statement.MoveNext(); + } + startIndex += limit; + } + } + + /// + /// Gets the attachment. + /// + /// The reader. + /// MediaAttachment + private MediaAttachment GetMediaAttachment(IReadOnlyList reader) + { + var item = new MediaAttachment + { + Index = reader[1].ToInt() + }; + + if (reader[2].SQLiteType != SQLiteType.Null) + { + item.Codec = reader[2].ToString(); + } + + if (reader[2].SQLiteType != SQLiteType.Null) + { + item.CodecTag = reader[3].ToString(); + } + + if (reader[4].SQLiteType != SQLiteType.Null) + { + item.Comment = reader[4].ToString(); + } + + if (reader[6].SQLiteType != SQLiteType.Null) + { + item.Filename = reader[5].ToString(); + } + + if (reader[6].SQLiteType != SQLiteType.Null) + { + item.MIMEType = reader[6].ToString(); + } + + return item; + } } } diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 47e0f34532..68df20c3af 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -78,6 +78,21 @@ namespace MediaBrowser.Controller.Persistence /// The cancellation token. void SaveMediaStreams(Guid id, List streams, CancellationToken cancellationToken); + /// + /// Gets the media attachments. + /// + /// The query. + /// IEnumerable{MediaAttachment}. + List GetMediaAttachments(MediaAttachmentQuery query); + + /// + /// Saves the media attachments. + /// + /// The identifier. + /// The attachments. + /// The cancellation token. + void SaveMediaAttachments(Guid id, List attachments, CancellationToken cancellationToken); + /// /// Gets the item ids. /// diff --git a/MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs b/MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs new file mode 100644 index 0000000000..91ab34aab6 --- /dev/null +++ b/MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs @@ -0,0 +1,20 @@ +using System; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Persistence +{ + public class MediaAttachmentQuery + { + /// + /// Gets or sets the index. + /// + /// The index. + public int? Index { get; set; } + + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public Guid ItemId { get; set; } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index d2abd2a639..ae3e584d4b 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -158,11 +158,13 @@ namespace MediaBrowser.Providers.MediaInfo MetadataRefreshOptions options) { List mediaStreams; + List mediaAttachments; List chapters; if (mediaInfo != null) { mediaStreams = mediaInfo.MediaStreams; + mediaAttachments = mediaInfo.MediaAttachments; video.TotalBitrate = mediaInfo.Bitrate; //video.FormatName = (mediaInfo.Container ?? string.Empty) @@ -198,6 +200,7 @@ namespace MediaBrowser.Providers.MediaInfo else { mediaStreams = new List(); + mediaAttachments = new List(); chapters = new List(); } @@ -223,6 +226,7 @@ namespace MediaBrowser.Providers.MediaInfo video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken); + _itemRepo.SaveMediaAttachments(video.Id, mediaAttachments, cancellationToken); if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || options.MetadataRefreshMode == MetadataRefreshMode.Default) From 1513c76a3e0b8f84def98df3fe99432370076497 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 7 Oct 2019 07:53:28 -0400 Subject: [PATCH 03/81] Support MediaAttachment retrieval in MediaSourceManager. --- .../Library/MediaSourceManager.cs | 26 +++++++++++++++++++ .../Library/IMediaSourceManager.cs | 19 ++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index d83e1fc021..e069ebd17c 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -128,6 +128,32 @@ namespace Emby.Server.Implementations.Library return streams; } + public List GetMediaAttachments(MediaAttachmentQuery query) + { + var list = _itemRepo.GetMediaAttachments(query); + return list; + } + + public List GetMediaAttachments(string mediaSourceId) + { + var list = GetMediaAttachments(new MediaAttachmentQuery + { + ItemId = new Guid(mediaSourceId) + }); + + return list; + } + + public List GetMediaAttachments(Guid itemId) + { + var list = GetMediaAttachments(new MediaAttachmentQuery + { + ItemId = itemId + }); + + return list; + } + public async Task> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) { var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index fbae4edb09..961028b046 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -38,6 +38,25 @@ namespace MediaBrowser.Controller.Library /// IEnumerable<MediaStream>. List GetMediaStreams(MediaStreamQuery query); + /// + /// Gets the media attachments. + /// + /// The item identifier. + /// IEnumerable<MediaAttachment>. + List GetMediaAttachments(Guid itemId); + /// + /// Gets the media attachments. + /// + /// The The media source identifier. + /// IEnumerable<MediaAttachment>. + List GetMediaAttachments(string mediaSourceId); + /// + /// Gets the media attachments. + /// + /// The query. + /// IEnumerable<MediaAttachment>. + List GetMediaAttachments(MediaAttachmentQuery query); + /// /// Gets the playack media sources. /// From f22ca6dd9eeac73cfc5e24dceb67a7f817f77b9a Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 7 Oct 2019 07:54:11 -0400 Subject: [PATCH 04/81] Retrieve media attachments in BaseItem. --- MediaBrowser.Controller/Entities/BaseItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 599d41bb27..2e2b465a07 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1098,6 +1098,7 @@ namespace MediaBrowser.Controller.Entities Id = item.Id.ToString("N", CultureInfo.InvariantCulture), Protocol = protocol ?? MediaProtocol.File, MediaStreams = MediaSourceManager.GetMediaStreams(item.Id), + MediaAttachments = MediaSourceManager.GetMediaAttachments(item.Id), Name = GetMediaSourceName(item), Path = enablePathSubstitution ? GetMappedPath(item, item.Path, protocol) : item.Path, RunTimeTicks = item.RunTimeTicks, From 12f752d8b1bbc9d6d480bfc34283dd15a08d1f8a Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Fri, 18 Oct 2019 07:52:32 -0400 Subject: [PATCH 05/81] FFMPEG extractor for attachments. --- .../MediaEncoding/IAttachmentExtractor.cs | 17 ++ .../Attachments/AttachmentExtractor.cs | 277 ++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs create mode 100644 MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs new file mode 100644 index 0000000000..59c0a3f214 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -0,0 +1,17 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.MediaEncoding +{ + public interface IAttachmentExtractor + { + Task<(MediaAttachment attachment, Stream stream)> GetAttachment(BaseItem item, + string mediaSourceId, + int attachmentStreamIndex, + CancellationToken cancellationToken); + } +} diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs new file mode 100644 index 0000000000..d6106df29d --- /dev/null +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Diagnostics; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using UtfUnknown; + +namespace MediaBrowser.MediaEncoding.Attachments +{ + public class AttachmentExtractor : IAttachmentExtractor + { + private readonly ILibraryManager _libraryManager; + private readonly ILogger _logger; + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + private readonly IMediaEncoder _mediaEncoder; + private readonly IMediaSourceManager _mediaSourceManager; + private readonly IProcessFactory _processFactory; + + public AttachmentExtractor( + ILibraryManager libraryManager, + ILoggerFactory loggerFactory, + IApplicationPaths appPaths, + IFileSystem fileSystem, + IMediaEncoder mediaEncoder, + IMediaSourceManager mediaSourceManager, + IProcessFactory processFactory) + { + _libraryManager = libraryManager; + _logger = loggerFactory.CreateLogger(nameof(AttachmentExtractor)); + _appPaths = appPaths; + _fileSystem = fileSystem; + _mediaEncoder = mediaEncoder; + _mediaSourceManager = mediaSourceManager; + _processFactory = processFactory; + } + + private string AttachmentCachePath => Path.Combine(_appPaths.DataPath, "attachments"); + + 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 + .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); + var mediaAttachment = mediaSource.MediaAttachments + .First(i => i.Index == attachmentStreamIndex); + var attachmentStream = await GetAttachmentStream(mediaSource, mediaAttachment, cancellationToken) + .ConfigureAwait(false); + + return (mediaAttachment, attachmentStream); + } + + private async Task GetAttachmentStream( + MediaSourceInfo mediaSource, + MediaAttachment mediaAttachment, + CancellationToken cancellationToken) + { + var inputFiles = new[] {mediaSource.Path}; + var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, mediaAttachment, cancellationToken).ConfigureAwait(false); + var stream = await GetAttachmentStream(fileInfo.Path, fileInfo.Protocol, cancellationToken).ConfigureAwait(false); + return stream; + } + + private async Task GetAttachmentStream( + string path, + MediaProtocol protocol, + CancellationToken cancellationToken) + { + return File.OpenRead(path); + } + + private async Task GetReadableFile( + string mediaPath, + string[] inputFiles, + MediaProtocol protocol, + MediaAttachment mediaAttachment, + CancellationToken cancellationToken) + { + var outputPath = GetAttachmentCachePath(mediaPath, protocol, mediaAttachment.Index); + await ExtractAttachment(inputFiles, protocol, mediaAttachment.Index, outputPath, cancellationToken) + .ConfigureAwait(false); + + return new AttachmentInfo(outputPath, MediaProtocol.File); + } + + private struct AttachmentInfo + { + public AttachmentInfo(string path, MediaProtocol protocol) + { + Path = path; + Protocol = protocol; + } + public string Path { get; set; } + public MediaProtocol Protocol { get; set; } + } + + private readonly ConcurrentDictionary _semaphoreLocks = + new ConcurrentDictionary(); + + private SemaphoreSlim GetLock(string filename) + { + return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); + } + + private async Task ExtractAttachment( + string[] inputFiles, + MediaProtocol protocol, + int attachmentStreamIndex, + string outputPath, + CancellationToken cancellationToken) + { + var semaphore = GetLock(outputPath); + + await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + if (!File.Exists(outputPath)) + { + await ExtractAttachmentInternal(_mediaEncoder.GetInputArgument(inputFiles, 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("-dump_attachment:{1} {2} -i {0} -t 0 -f null null", inputPath, attachmentStreamIndex, outputPath); + var process = _processFactory.Create(new ProcessOptions + { + CreateNoWindow = true, + UseShellExecute = false, + EnableRaisingEvents = true, + FileName = _mediaEncoder.EncoderPath, + Arguments = processArgs, + IsHidden = true, + ErrorDialog = false + }); + + _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); + + try + { + process.Start(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error starting ffmpeg"); + + throw; + } + + var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false); + + 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 == -1) + { + failed = true; + + try + { + _logger.LogWarning("Deleting extracted attachment due to failure: {Path}", outputPath); + _fileSystem.DeleteFile(outputPath); + } + catch (FileNotFoundException) + { + + } + 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 Exception(msg); + } + else + { + var msg = $"ffmpeg attachment extraction completed for {inputPath} to {outputPath}"; + + _logger.LogInformation(msg); + } + } + + private string GetAttachmentCachePath(string mediaPath, MediaProtocol protocol, int attachmentStreamIndex) + { + if (protocol == MediaProtocol.File) + { + var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); + var filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D"); + var prefix = filename.Substring(0, 1); + return Path.Combine(AttachmentCachePath, prefix, filename); + } + else + { + var filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D"); + var prefix = filename.Substring(0, 1); + return Path.Combine(AttachmentCachePath, prefix, filename); + } + } + + } +} From a9a85f251e77cf1670e69733e1d7b5ab9116aaaf Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Fri, 18 Oct 2019 07:56:53 -0400 Subject: [PATCH 06/81] Instantiate AttachmentExtractor in ApplicationHost. --- Emby.Server.Implementations/ApplicationHost.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 04904fc4a5..bcd99ffe4f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -282,6 +282,8 @@ namespace Emby.Server.Implementations private ISubtitleEncoder SubtitleEncoder { get; set; } + private IAttachmentExtractor AttachmentExtractor { get; set; } + private ISessionManager SessionManager { get; set; } private ILiveTvManager LiveTvManager { get; set; } @@ -904,6 +906,10 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); + AttachmentExtractor = new MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, MediaSourceManager, ProcessFactory); + + serviceCollection.AddSingleton(AttachmentExtractor); + _displayPreferencesRepository.Initialize(); var userDataRepo = new SqliteUserDataRepository(LoggerFactory, ApplicationPaths); From 8c89d8993269033931fb53b66212e4f87c6edbb5 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Fri, 18 Oct 2019 07:57:40 -0400 Subject: [PATCH 07/81] Attachment service. --- .../Attachments/AttachmentService.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 MediaBrowser.Api/Attachments/AttachmentService.cs diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs new file mode 100644 index 0000000000..4c7d5eccc0 --- /dev/null +++ b/MediaBrowser.Api/Attachments/AttachmentService.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Services; +using Microsoft.Extensions.Logging; +using MimeTypes = MediaBrowser.Model.Net.MimeTypes; + +namespace MediaBrowser.Api.Attachments +{ + [Route("/Videos/{Id}/{MediaSourceId}/Attachments/{Index}/Attachment", "GET", Summary = "Gets specified attachment.")] + public class GetAttachment + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public Guid Id { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string MediaSourceId { get; set; } + + [ApiMember(Name = "Index", Description = "The attachment stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] + public int Index { get; set; } + } + + public class AttachmentService : BaseApiService + { + private readonly ILibraryManager _libraryManager; + private readonly IAttachmentExtractor _attachmentExtractor; + + public AttachmentService(ILibraryManager libraryManager, IAttachmentExtractor attachmentExtractor) + { + _libraryManager = libraryManager; + _attachmentExtractor = attachmentExtractor; + } + + public async Task Get(GetAttachment request) + { + var item = (Video)_libraryManager.GetItemById(request.Id); + var (attachment, attachmentStream) = await GetAttachment(request).ConfigureAwait(false); + var mime = string.IsNullOrWhiteSpace(attachment.MIMEType) ? "application/octet-stream" : attachment.MIMEType; + + return ResultFactory.GetResult(Request, attachmentStream, mime); + } + + private Task<(MediaAttachment, Stream)> GetAttachment(GetAttachment request) + { + var item = _libraryManager.GetItemById(request.Id); + + return _attachmentExtractor.GetAttachment(item, + request.MediaSourceId, + request.Index, + CancellationToken.None); + } + + } +} From 01b1c847e95c28ec780dfe3f2c7d1b33ffd08bd0 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Fri, 18 Oct 2019 07:59:03 -0400 Subject: [PATCH 08/81] Provide delivery URLs for attachments in PlaybackInfo. --- MediaBrowser.Api/Playback/MediaInfoService.cs | 8 ++++++++ MediaBrowser.Model/Entities/MediaAttachment.cs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index da8f99a3dd..6c57e37a1a 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -524,6 +524,14 @@ namespace MediaBrowser.Api.Playback SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); } } + + foreach (var attachment in mediaSource.MediaAttachments) + { + attachment.DeliveryUrl = string.Format("/Videos/{0}/{1}/Attachments/{2}/Attachment", + item.Id, + mediaSource.Id, + attachment.Index); + } } private long? GetMaxBitrate(long? clientMaxBitrate, User user) diff --git a/MediaBrowser.Model/Entities/MediaAttachment.cs b/MediaBrowser.Model/Entities/MediaAttachment.cs index daabbe91df..26279b72b8 100644 --- a/MediaBrowser.Model/Entities/MediaAttachment.cs +++ b/MediaBrowser.Model/Entities/MediaAttachment.cs @@ -40,5 +40,11 @@ namespace MediaBrowser.Model.Entities /// /// The MIME type. public string MIMEType { get; set; } + + /// + /// Gets or sets the delivery URL. + /// + /// The delivery URL. + public string DeliveryUrl { get; set; } } } From 20727906c85c0d519eda86194b081a22f60dcf61 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 22 Oct 2019 10:57:15 -0400 Subject: [PATCH 09/81] Use attachment filename if available. --- MediaBrowser.Api/Attachments/AttachmentService.cs | 5 ++++- MediaBrowser.Api/Playback/MediaInfoService.cs | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs index 4c7d5eccc0..d8771f8c0e 100644 --- a/MediaBrowser.Api/Attachments/AttachmentService.cs +++ b/MediaBrowser.Api/Attachments/AttachmentService.cs @@ -20,7 +20,7 @@ using MimeTypes = MediaBrowser.Model.Net.MimeTypes; namespace MediaBrowser.Api.Attachments { - [Route("/Videos/{Id}/{MediaSourceId}/Attachments/{Index}/Attachment", "GET", Summary = "Gets specified attachment.")] + [Route("/Videos/{Id}/{MediaSourceId}/Attachments/{Index}/{Filename}", "GET", Summary = "Gets specified attachment.")] public class GetAttachment { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] @@ -31,6 +31,9 @@ namespace MediaBrowser.Api.Attachments [ApiMember(Name = "Index", Description = "The attachment stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] public int Index { get; set; } + + [ApiMember(Name = "Filename", Description = "The attachment filename", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Filename { get; set; } } public class AttachmentService : BaseApiService diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 6c57e37a1a..41b3249751 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -527,10 +527,12 @@ namespace MediaBrowser.Api.Playback foreach (var attachment in mediaSource.MediaAttachments) { - attachment.DeliveryUrl = string.Format("/Videos/{0}/{1}/Attachments/{2}/Attachment", + var filename = string.IsNullOrWhiteSpace(attachment.Filename) ? "Attachment" : attachment.Filename; + attachment.DeliveryUrl = string.Format("/Videos/{0}/{1}/Attachments/{2}/{3}", item.Id, mediaSource.Id, - attachment.Index); + attachment.Index, + filename); } } From 90dfe729bb9f8929bf584b93f93aabd84abbef3d Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 08:48:27 -0500 Subject: [PATCH 10/81] Add space when building query string for attachments. Co-Authored-By: Vasily --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 58f04e4fcf..d02aa9b1f2 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6161,7 +6161,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (query.Index.HasValue) { - cmdText += "AND AttachmentIndex=@AttachmentIndex"; + cmdText += " AND AttachmentIndex=@AttachmentIndex"; } cmdText += " order by AttachmentIndex ASC"; From 4573fb5301b02e8b57a15a57f909b1b63d6b3900 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 10:19:53 -0500 Subject: [PATCH 11/81] Use ToByteArray instead of ToGuidBlob. --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 08f53f0f5d..2b07844e71 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6175,7 +6175,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type using (var statement = PrepareStatement(connection, cmdText)) { - statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); + statement.TryBind("@ItemId", query.ItemId.ToByteArray()); if (query.Index.HasValue) { @@ -6208,7 +6208,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { connection.RunInTransaction(db => { - var itemIdBlob = id.ToGuidBlob(); + var itemIdBlob = id.ToByteArray(); db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); From 0dde5e46df13f63158288ce73b98b25a6440de43 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 10:28:33 -0500 Subject: [PATCH 12/81] Flatten using connection in GetMediaAttachments/SaveMediaAttachments --- .../Data/SqliteItemRepository.cs | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2b07844e71..62837933c0 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6169,26 +6169,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type cmdText += " order by AttachmentIndex ASC"; - using (var connection = GetConnection(true)) + var list = new List(); + using var connection = GetConnection(true); + using (var statement = PrepareStatement(connection, cmdText)) { - var list = new List(); + statement.TryBind("@ItemId", query.ItemId.ToByteArray()); - using (var statement = PrepareStatement(connection, cmdText)) + if (query.Index.HasValue) { - statement.TryBind("@ItemId", query.ItemId.ToByteArray()); - - if (query.Index.HasValue) - { - statement.TryBind("@AttachmentIndex", query.Index.Value); - } - - foreach (var row in statement.ExecuteQuery()) { - list.Add(GetMediaAttachment(row)); - } + statement.TryBind("@AttachmentIndex", query.Index.Value); } - return list; + foreach (var row in statement.ExecuteQuery()) { + list.Add(GetMediaAttachment(row)); + } } + + return list; } public void SaveMediaAttachments(Guid id, List attachments, CancellationToken cancellationToken) @@ -6204,18 +6201,16 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type throw new ArgumentNullException(nameof(attachments)); } - using (var connection = GetConnection()) + using var connection = GetConnection(); + connection.RunInTransaction(db => { - connection.RunInTransaction(db => - { - var itemIdBlob = id.ToByteArray(); + var itemIdBlob = id.ToByteArray(); - db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); + db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); - InsertMediaAttachments(itemIdBlob, attachments, db); + InsertMediaAttachments(itemIdBlob, attachments, db); - }, TransactionMode); - } + }, TransactionMode); } private void InsertMediaAttachments(byte[] idBlob, List attachments, IDatabaseConnection db) From c2d8f210b1c8daa86a37b530a7da05370d3b209a Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 10:31:32 -0500 Subject: [PATCH 13/81] Check for cancellation in SaveMediaAttachments. --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 62837933c0..8d39cdb8f4 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6201,6 +6201,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type throw new ArgumentNullException(nameof(attachments)); } + cancellationToken.ThrowIfCancellationRequested(); + using var connection = GetConnection(); connection.RunInTransaction(db => { From ad2101ce52999fccf51e52510729fd36e3495369 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 10:33:50 -0500 Subject: [PATCH 14/81] Rename "limit" to "insertAtOnce" in InsertMediaAttachments. --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 8d39cdb8f4..7c0a585961 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6218,13 +6218,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type private void InsertMediaAttachments(byte[] idBlob, List attachments, IDatabaseConnection db) { var startIndex = 0; - var limit = 10; + var insertAtOnce = 10; while (startIndex < attachments.Count) { var insertText = new StringBuilder(string.Format("insert into mediaattachments ({0}) values ", string.Join(",", _mediaAttachmentSaveColumns))); - var endIndex = Math.Min(attachments.Count, startIndex + limit); + var endIndex = Math.Min(attachments.Count, startIndex + insertAtOnce); for (var i = startIndex; i < endIndex; i++) { @@ -6266,7 +6266,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.Reset(); statement.MoveNext(); } - startIndex += limit; + startIndex += insertAtOnce; } } From 7110069b399496979aa8fdc701d0e1af536425f7 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 10:37:19 -0500 Subject: [PATCH 15/81] Return list result directly for MediaAttachments. --- .../Library/MediaSourceManager.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 2e24b847b6..27eb395309 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -130,28 +130,23 @@ namespace Emby.Server.Implementations.Library public List GetMediaAttachments(MediaAttachmentQuery query) { - var list = _itemRepo.GetMediaAttachments(query); - return list; + return _itemRepo.GetMediaAttachments(query); } public List GetMediaAttachments(string mediaSourceId) { - var list = GetMediaAttachments(new MediaAttachmentQuery + return GetMediaAttachments(new MediaAttachmentQuery { ItemId = new Guid(mediaSourceId) }); - - return list; } public List GetMediaAttachments(Guid itemId) { - var list = GetMediaAttachments(new MediaAttachmentQuery + return GetMediaAttachments(new MediaAttachmentQuery { ItemId = itemId }); - - return list; } public async Task> GetPlayackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) From bd545891c0327eb155198c4e835f874156f416a6 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 10:38:53 -0500 Subject: [PATCH 16/81] Indentation fix. --- MediaBrowser.Api/Attachments/AttachmentService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs index d8771f8c0e..9fceebd3b4 100644 --- a/MediaBrowser.Api/Attachments/AttachmentService.cs +++ b/MediaBrowser.Api/Attachments/AttachmentService.cs @@ -50,8 +50,8 @@ namespace MediaBrowser.Api.Attachments public async Task Get(GetAttachment request) { var item = (Video)_libraryManager.GetItemById(request.Id); - var (attachment, attachmentStream) = await GetAttachment(request).ConfigureAwait(false); - var mime = string.IsNullOrWhiteSpace(attachment.MIMEType) ? "application/octet-stream" : attachment.MIMEType; + var (attachment, attachmentStream) = await GetAttachment(request).ConfigureAwait(false); + var mime = string.IsNullOrWhiteSpace(attachment.MIMEType) ? "application/octet-stream" : attachment.MIMEType; return ResultFactory.GetResult(Request, attachmentStream, mime); } From e9c893f07eeb0ddb59f136cedfdf7e5a3f06d36d Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 10:58:56 -0500 Subject: [PATCH 17/81] Fail attachment extraction on non-zero exit code. --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index d6106df29d..246023582d 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -216,7 +216,7 @@ namespace MediaBrowser.MediaEncoding.Attachments var failed = false; - if (exitCode == -1) + if (exitCode != 0) { failed = true; From 9eef5f860d29eaa32d6b06f76d74f6c9bbe1e3cd Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 11:16:57 -0500 Subject: [PATCH 18/81] AttachmentExtractor logging cleanup. --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 246023582d..57b80d0604 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -220,9 +220,9 @@ namespace MediaBrowser.MediaEncoding.Attachments { failed = true; + _logger.LogWarning("Deleting extracted attachment {Path} due to failure: {ExitCode}", outputPath, exitCode); try { - _logger.LogWarning("Deleting extracted attachment due to failure: {Path}", outputPath); _fileSystem.DeleteFile(outputPath); } catch (FileNotFoundException) From 262a8f47afb4905be4eac815dd92849c7616ce89 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 11:31:41 -0500 Subject: [PATCH 19/81] Remove attachment filenames from attachment URLs. --- MediaBrowser.Api/Attachments/AttachmentService.cs | 5 +---- MediaBrowser.Api/Playback/MediaInfoService.cs | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs index 9fceebd3b4..c52ca24810 100644 --- a/MediaBrowser.Api/Attachments/AttachmentService.cs +++ b/MediaBrowser.Api/Attachments/AttachmentService.cs @@ -20,7 +20,7 @@ using MimeTypes = MediaBrowser.Model.Net.MimeTypes; namespace MediaBrowser.Api.Attachments { - [Route("/Videos/{Id}/{MediaSourceId}/Attachments/{Index}/{Filename}", "GET", Summary = "Gets specified attachment.")] + [Route("/Videos/{Id}/{MediaSourceId}/Attachments/{Index}", "GET", Summary = "Gets specified attachment.")] public class GetAttachment { [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] @@ -31,9 +31,6 @@ namespace MediaBrowser.Api.Attachments [ApiMember(Name = "Index", Description = "The attachment stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] public int Index { get; set; } - - [ApiMember(Name = "Filename", Description = "The attachment filename", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Filename { get; set; } } public class AttachmentService : BaseApiService diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 41b3249751..de5c4ddb72 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -527,12 +527,10 @@ namespace MediaBrowser.Api.Playback foreach (var attachment in mediaSource.MediaAttachments) { - var filename = string.IsNullOrWhiteSpace(attachment.Filename) ? "Attachment" : attachment.Filename; - attachment.DeliveryUrl = string.Format("/Videos/{0}/{1}/Attachments/{2}/{3}", + attachment.DeliveryUrl = string.Format("/Videos/{0}/{1}/Attachments/{2}", item.Id, mediaSource.Id, - attachment.Index, - filename); + attachment.Index); } } From c7d303a6ae9e0d5489273fa5a15262987c735bd0 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 11:39:40 -0500 Subject: [PATCH 20/81] MediaExtractor logging cleanup. --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 57b80d0604..b369e45153 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -250,9 +250,7 @@ namespace MediaBrowser.MediaEncoding.Attachments } else { - var msg = $"ffmpeg attachment extraction completed for {inputPath} to {outputPath}"; - - _logger.LogInformation(msg); + _logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath); } } From 154fb1fe9b45892807a9bea9abb282380bfd9383 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 11:45:31 -0500 Subject: [PATCH 21/81] AttachmentExtractor code cleanup. --- .../Attachments/AttachmentExtractor.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index b369e45153..1c214d5d3b 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -256,19 +256,18 @@ namespace MediaBrowser.MediaEncoding.Attachments private string GetAttachmentCachePath(string mediaPath, MediaProtocol protocol, int attachmentStreamIndex) { + String filename; if (protocol == MediaProtocol.File) { var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - var filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D"); - var prefix = filename.Substring(0, 1); - return Path.Combine(AttachmentCachePath, prefix, filename); + filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture) + "_" + date.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D"); } else { - var filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D"); - var prefix = filename.Substring(0, 1); - return Path.Combine(AttachmentCachePath, prefix, filename); + filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D"); } + var prefix = filename.Substring(0, 1); + return Path.Combine(AttachmentCachePath, prefix, filename); } } From 04a96788f9f3ea20b05d3c57d16d222ba7a53101 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 14:34:21 -0500 Subject: [PATCH 22/81] Convert exceptions for missing MediaSource or MediaAttachment to ResourceNotFoundException with appropriate message. --- .../Attachments/AttachmentExtractor.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 1c214d5d3b..be5908ccef 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -65,10 +65,26 @@ namespace MediaBrowser.MediaEncoding.Attachments } var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false); - var mediaSource = mediaSources - .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); - var mediaAttachment = mediaSource.MediaAttachments - .First(i => i.Index == attachmentStreamIndex); + MediaSourceInfo mediaSource; + MediaAttachment mediaAttachment; + try + { + mediaSource = mediaSources + .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); + } + catch (Exception ex) when (ex is ArgumentNullException || ex is InvalidOperationException) + { + throw new ResourceNotFoundException($"MediaSource {mediaSourceId} not found"); + } + try + { + mediaAttachment = mediaSource.MediaAttachments + .First(i => i.Index == attachmentStreamIndex); + } + catch (Exception ex) when (ex is ArgumentNullException || ex is InvalidOperationException) + { + throw new ResourceNotFoundException($"MediaSource {mediaSourceId} has no attachment with stream index {attachmentStreamIndex}"); + } var attachmentStream = await GetAttachmentStream(mediaSource, mediaAttachment, cancellationToken) .ConfigureAwait(false); From 28a6718d8e77527c3949118676dc6e165e3608f5 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Mon, 4 Nov 2019 14:48:28 -0500 Subject: [PATCH 23/81] Return path of extracted attachment, which is always a file, instead of AttachmentInfo with path and protocol. --- .../Attachments/AttachmentExtractor.cs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index be5908ccef..6bef1cd5a9 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -97,20 +97,19 @@ namespace MediaBrowser.MediaEncoding.Attachments CancellationToken cancellationToken) { var inputFiles = new[] {mediaSource.Path}; - var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, mediaAttachment, cancellationToken).ConfigureAwait(false); - var stream = await GetAttachmentStream(fileInfo.Path, fileInfo.Protocol, cancellationToken).ConfigureAwait(false); + var attachmentPath = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, mediaAttachment, cancellationToken).ConfigureAwait(false); + var stream = await GetAttachmentStream(attachmentPath, cancellationToken).ConfigureAwait(false); return stream; } private async Task GetAttachmentStream( string path, - MediaProtocol protocol, CancellationToken cancellationToken) { return File.OpenRead(path); } - private async Task GetReadableFile( + private async Task GetReadableFile( string mediaPath, string[] inputFiles, MediaProtocol protocol, @@ -121,18 +120,7 @@ namespace MediaBrowser.MediaEncoding.Attachments await ExtractAttachment(inputFiles, protocol, mediaAttachment.Index, outputPath, cancellationToken) .ConfigureAwait(false); - return new AttachmentInfo(outputPath, MediaProtocol.File); - } - - private struct AttachmentInfo - { - public AttachmentInfo(string path, MediaProtocol protocol) - { - Path = path; - Protocol = protocol; - } - public string Path { get; set; } - public MediaProtocol Protocol { get; set; } + return outputPath; } private readonly ConcurrentDictionary _semaphoreLocks = From 6ca252ba5c651e41ad8a18c5555f5b60d81f0952 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 07:09:55 -0500 Subject: [PATCH 24/81] Remove check for "[0]" in codec_tag. --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 2c001d775a..f2056c5663 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -535,8 +535,7 @@ namespace MediaBrowser.MediaEncoding.Probing Index = streamInfo.index }; - // Filter out junk - if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) + if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string)) { attachment.CodecTag = streamInfo.codec_tag_string; } From bd4da93d1e871f7d58075b6780c4c1c30fc34fd7 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 07:48:32 -0500 Subject: [PATCH 25/81] Throw ArgumentException instead of ArgumentNullException on empty Guid. --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 7c0a585961..e7e93d0db0 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6193,7 +6193,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type CheckDisposed(); if (id == Guid.Empty) { - throw new ArgumentNullException(nameof(id)); + throw new ArgumentException(nameof(id)); } if (attachments == null) From 24a460dc939b8dab2aa7e3e4db7ce47373a50d1d Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 08:13:22 -0500 Subject: [PATCH 26/81] Update Emby.Server.Implementations/Data/SqliteItemRepository.cs formatting Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 7c0a585961..27a29318bd 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6240,6 +6240,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { insertText.Append("@" + column + index + ","); } + insertText.Length -= 1; insertText.Append(")"); From 25bc7b81c37b635109e5c14baabf64954a517512 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 08:13:35 -0500 Subject: [PATCH 27/81] Update Emby.Server.Implementations/Data/SqliteItemRepository.cs formatting Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 27a29318bd..de95adfa1c 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6267,6 +6267,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.Reset(); statement.MoveNext(); } + startIndex += insertAtOnce; } } From 349310787cbf49964a0e9bd1548adb5a3a3a3c1c Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 08:16:46 -0500 Subject: [PATCH 28/81] Update MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs formatting Co-Authored-By: Bond-009 --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 6bef1cd5a9..0c8d9ae9c4 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -59,6 +59,7 @@ namespace MediaBrowser.MediaEncoding.Attachments { throw new ArgumentNullException(nameof(item)); } + if (string.IsNullOrWhiteSpace(mediaSourceId)) { throw new ArgumentNullException(nameof(mediaSourceId)); From d33e0a4e2c59783c785c992ea0c3a31596ae3058 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 08:17:34 -0500 Subject: [PATCH 29/81] Simplify AttachmentExtractor instantiation. Co-Authored-By: Bond-009 --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ac37cfe079..9330d57198 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -908,7 +908,7 @@ namespace Emby.Server.Implementations AttachmentExtractor = new MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, MediaSourceManager, ProcessFactory); - serviceCollection.AddSingleton(AttachmentExtractor); + serviceCollection.AddSingleton(); _displayPreferencesRepository.Initialize(); From e5b65ed034c6ca0fcee9f73cc991a0962a44278f Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 08:19:07 -0500 Subject: [PATCH 30/81] Update Emby.Server.Implementations/Data/SqliteItemRepository.cs formatting Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index de95adfa1c..a7e7792517 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6180,7 +6180,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@AttachmentIndex", query.Index.Value); } - foreach (var row in statement.ExecuteQuery()) { + foreach (var row in statement.ExecuteQuery()) + { list.Add(GetMediaAttachment(row)); } } From a78aec56e357742c2906edbb1fcce47cb9a3c6dd Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 08:21:06 -0500 Subject: [PATCH 31/81] Format attachment DeliveryURL with CultureInfo.InvariantCulture. Co-Authored-By: Bond-009 --- MediaBrowser.Api/Playback/MediaInfoService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index de5c4ddb72..296d565b92 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -527,7 +527,9 @@ namespace MediaBrowser.Api.Playback foreach (var attachment in mediaSource.MediaAttachments) { - attachment.DeliveryUrl = string.Format("/Videos/{0}/{1}/Attachments/{2}", + attachment.DeliveryUrl = string.Format( + CultureInfo.InvariantCulture, + "/Videos/{0}/{1}/Attachments/{2}", item.Id, mediaSource.Id, attachment.Index); From 3602251cf53c636e3006605e2b48a77d7952d029 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 08:36:17 -0500 Subject: [PATCH 32/81] Update Emby.Server.Implementations/Data/SqliteItemRepository.cs Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index a7e7792517..2152ed6055 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6231,7 +6231,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { if (i != startIndex) { - insertText.Append(","); + insertText.Append(','); } var index = i.ToString(CultureInfo.InvariantCulture); From 8505ee9d6c0b2f073fd39c538cbdb0ee9ee95354 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Tue, 5 Nov 2019 14:53:46 -0500 Subject: [PATCH 33/81] Extract the prefix for MediaAttachment insertions to a static member instead of generating it per-query. --- .../Data/SqliteItemRepository.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2b9c6a52f6..dd8620f9f4 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -51,6 +51,19 @@ namespace Emby.Server.Implementations.Data private readonly TypeMapper _typeMapper; private readonly JsonSerializerOptions _jsonOptions; + static SqliteItemRepository() { + var queryPrefixText = new StringBuilder(); + queryPrefixText.Append("insert into mediaattachments ("); + foreach (var column in _mediaAttachmentSaveColumns) + { + queryPrefixText.Append(column); + queryPrefixText.Append(','); + } + queryPrefixText.Length -= 1; + queryPrefixText.Append(") values "); + _mediaAttachmentInsertPrefix = queryPrefixText.ToString(); + } + /// /// Initializes a new instance of the class. /// @@ -436,6 +449,7 @@ namespace Emby.Server.Implementations.Data "Filename", "MIMEType" }; + private static readonly string _mediaAttachmentInsertPrefix; private static string GetSaveItemCommandText() { @@ -6223,7 +6237,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type while (startIndex < attachments.Count) { - var insertText = new StringBuilder(string.Format("insert into mediaattachments ({0}) values ", string.Join(",", _mediaAttachmentSaveColumns))); + var insertText = new StringBuilder(_mediaAttachmentInsertPrefix); var endIndex = Math.Min(attachments.Count, startIndex + insertAtOnce); From 79bbf09ecba6d3c2ddb0955bdbd51164a6c7a236 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Wed, 6 Nov 2019 08:43:49 -0500 Subject: [PATCH 34/81] Revert "Simplify AttachmentExtractor instantiation." This reverts commit d33e0a4e2c59783c785c992ea0c3a31596ae3058. --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 9330d57198..ac37cfe079 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -908,7 +908,7 @@ namespace Emby.Server.Implementations AttachmentExtractor = new MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, MediaSourceManager, ProcessFactory); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(AttachmentExtractor); _displayPreferencesRepository.Initialize(); From 4f3b8831552db1d376198384cfe42894883286dd Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Wed, 6 Nov 2019 09:46:31 -0500 Subject: [PATCH 35/81] Clean up handling of missing source/attachment in AttachmentExtractor. --- .../Attachments/AttachmentExtractor.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 0c8d9ae9c4..bda5c2f372 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -66,23 +66,15 @@ namespace MediaBrowser.MediaEncoding.Attachments } var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false); - MediaSourceInfo mediaSource; - MediaAttachment mediaAttachment; - try - { - mediaSource = mediaSources - .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); - } - catch (Exception ex) when (ex is ArgumentNullException || ex is InvalidOperationException) + var mediaSource = mediaSources + .FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); + if (mediaSource == null) { throw new ResourceNotFoundException($"MediaSource {mediaSourceId} not found"); } - try - { - mediaAttachment = mediaSource.MediaAttachments - .First(i => i.Index == attachmentStreamIndex); - } - catch (Exception ex) when (ex is ArgumentNullException || ex is InvalidOperationException) + var mediaAttachment = mediaSource.MediaAttachments + .FirstOrDefault(i => i.Index == attachmentStreamIndex); + if (mediaAttachment == null) { throw new ResourceNotFoundException($"MediaSource {mediaSourceId} has no attachment with stream index {attachmentStreamIndex}"); } From 74fb63a8989fe19c5e985a09f9cb45e42022320f Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Wed, 6 Nov 2019 10:43:14 -0500 Subject: [PATCH 36/81] Use block rather than local using statement. --- .../Data/SqliteItemRepository.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index dd8620f9f4..f7774000cd 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6184,7 +6184,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type cmdText += " order by AttachmentIndex ASC"; var list = new List(); - using var connection = GetConnection(true); + using (var connection = GetConnection(true)) using (var statement = PrepareStatement(connection, cmdText)) { statement.TryBind("@ItemId", query.ItemId.ToByteArray()); @@ -6218,16 +6218,18 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type cancellationToken.ThrowIfCancellationRequested(); - using var connection = GetConnection(); - connection.RunInTransaction(db => + using (var connection = GetConnection()) { - var itemIdBlob = id.ToByteArray(); + connection.RunInTransaction(db => + { + var itemIdBlob = id.ToByteArray(); - db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); + db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); - InsertMediaAttachments(itemIdBlob, attachments, db); + InsertMediaAttachments(itemIdBlob, attachments, db); - }, TransactionMode); + }, TransactionMode); + } } private void InsertMediaAttachments(byte[] idBlob, List attachments, IDatabaseConnection db) From 6defe80b62483350111dc6ba2f18cfd83542c4b1 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 08:38:36 -0500 Subject: [PATCH 37/81] Check for cancellation between each batch of MediaAttachment inserts. --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index f7774000cd..1956711682 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6226,13 +6226,13 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); - InsertMediaAttachments(itemIdBlob, attachments, db); + InsertMediaAttachments(itemIdBlob, attachments, db, cancellationToken); }, TransactionMode); } } - private void InsertMediaAttachments(byte[] idBlob, List attachments, IDatabaseConnection db) + private void InsertMediaAttachments(byte[] idBlob, List attachments, IDatabaseConnection db, CancellationToken cancellationToken) { var startIndex = 0; var insertAtOnce = 10; @@ -6263,6 +6263,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type insertText.Append(")"); } + cancellationToken.ThrowIfCancellationRequested(); + using (var statement = PrepareStatement(db, insertText.ToString())) { statement.TryBind("@ItemId", idBlob); From c09eb3470815c8f868fd6eda6b542169d29f2098 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 08:56:12 -0500 Subject: [PATCH 38/81] Check for attachment file before trying to remove it during cleanup. --- .../Attachments/AttachmentExtractor.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index bda5c2f372..501dd3fed9 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -220,11 +220,10 @@ namespace MediaBrowser.MediaEncoding.Attachments _logger.LogWarning("Deleting extracted attachment {Path} due to failure: {ExitCode}", outputPath, exitCode); try { - _fileSystem.DeleteFile(outputPath); - } - catch (FileNotFoundException) - { - + if (File.Exists(outputPath)) + { + _fileSystem.DeleteFile(outputPath); + } } catch (IOException ex) { From f60e9b0b62388f6e758870b53f3fb59b842fecd2 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:20:29 -0500 Subject: [PATCH 39/81] formatting Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 1956711682..ba75488fb7 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -51,7 +51,8 @@ namespace Emby.Server.Implementations.Data private readonly TypeMapper _typeMapper; private readonly JsonSerializerOptions _jsonOptions; - static SqliteItemRepository() { + static SqliteItemRepository() + { var queryPrefixText = new StringBuilder(); queryPrefixText.Append("insert into mediaattachments ("); foreach (var column in _mediaAttachmentSaveColumns) From 4b75e6518e707096e4749c82d6fb7cdd303d3a43 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:21:40 -0500 Subject: [PATCH 40/81] Update MediaBrowser.Api/Attachments/AttachmentService.cs formatting Co-Authored-By: Bond-009 --- MediaBrowser.Api/Attachments/AttachmentService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs index c52ca24810..1ebfaa14bc 100644 --- a/MediaBrowser.Api/Attachments/AttachmentService.cs +++ b/MediaBrowser.Api/Attachments/AttachmentService.cs @@ -62,6 +62,5 @@ namespace MediaBrowser.Api.Attachments request.Index, CancellationToken.None); } - } } From d6aa02ff09e262bc57a4dcf6dbbc11e6d1c963dd Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:22:07 -0500 Subject: [PATCH 41/81] Update Emby.Server.Implementations/Data/SqliteItemRepository.cs formatting Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ba75488fb7..a63052a68c 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.Data queryPrefixText.Append(column); queryPrefixText.Append(','); } + queryPrefixText.Length -= 1; queryPrefixText.Append(") values "); _mediaAttachmentInsertPrefix = queryPrefixText.ToString(); From 193a1fa474202ec0933c1a937c56ccecf58c4410 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:23:28 -0500 Subject: [PATCH 42/81] Update Emby.Server.Implementations/Library/MediaSourceManager.cs docs Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Library/MediaSourceManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 27eb395309..833a5c88fa 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -128,6 +128,7 @@ namespace Emby.Server.Implementations.Library return streams; } + /// public List GetMediaAttachments(MediaAttachmentQuery query) { return _itemRepo.GetMediaAttachments(query); From 8b2d7062c43ff6618d5bf4a5e26d0ba5bb70bd07 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:24:30 -0500 Subject: [PATCH 43/81] Update Emby.Server.Implementations/Library/MediaSourceManager.cs docs Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Library/MediaSourceManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 833a5c88fa..df6f0e77f6 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -134,6 +134,7 @@ namespace Emby.Server.Implementations.Library return _itemRepo.GetMediaAttachments(query); } + /// public List GetMediaAttachments(string mediaSourceId) { return GetMediaAttachments(new MediaAttachmentQuery From 92aae268a352c20964ca10348078fdaf4fbf8263 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:24:53 -0500 Subject: [PATCH 44/81] Update MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs formatting Co-Authored-By: Bond-009 --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 501dd3fed9..4218b180d9 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -204,7 +204,6 @@ namespace MediaBrowser.MediaEncoding.Attachments { _logger.LogError(ex, "Error killing attachment extraction process"); } - } var exitCode = ranToCompletion ? process.ExitCode : -1; From 492bbc9e13f177eea62b0b22a921106def90309c Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:25:44 -0500 Subject: [PATCH 45/81] Update MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs String -> string Co-Authored-By: Bond-009 --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 4218b180d9..7c3a0fbd3e 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -251,7 +251,7 @@ namespace MediaBrowser.MediaEncoding.Attachments private string GetAttachmentCachePath(string mediaPath, MediaProtocol protocol, int attachmentStreamIndex) { - String filename; + string filename; if (protocol == MediaProtocol.File) { var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); From d73f46dcda8db877edae4918a6053cad924808fa Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:27:55 -0500 Subject: [PATCH 46/81] Update Emby.Server.Implementations/Library/MediaSourceManager.cs docs Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Library/MediaSourceManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index df6f0e77f6..9574a28e9e 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -143,6 +143,7 @@ namespace Emby.Server.Implementations.Library }); } + /// public List GetMediaAttachments(Guid itemId) { return GetMediaAttachments(new MediaAttachmentQuery From 0fd7886a12bee56d72f00a066c3e475fa8040060 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:28:11 -0500 Subject: [PATCH 47/81] Update MediaBrowser.Controller/Library/IMediaSourceManager.cs formatting Co-Authored-By: Bond-009 --- MediaBrowser.Controller/Library/IMediaSourceManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 961028b046..8bd55b29f2 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -44,6 +44,7 @@ namespace MediaBrowser.Controller.Library /// The item identifier. /// IEnumerable<MediaAttachment>. List GetMediaAttachments(Guid itemId); + /// /// Gets the media attachments. /// From 743685110c31f5da0f3e96e537c717e251909f38 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:29:34 -0500 Subject: [PATCH 48/81] Update MediaBrowser.Controller/Library/IMediaSourceManager.cs formatting Co-Authored-By: Bond-009 --- MediaBrowser.Controller/Library/IMediaSourceManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 8bd55b29f2..dda8d397a1 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -50,6 +50,7 @@ namespace MediaBrowser.Controller.Library /// /// The The media source identifier. /// IEnumerable<MediaAttachment>. + List GetMediaAttachments(string mediaSourceId); /// /// Gets the media attachments. From c6855e6a2a347db17f2b8b2f18dd89d5b47a8816 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:53:39 -0500 Subject: [PATCH 49/81] Simplify AttachmentExtractor instantiation. --- Emby.Server.Implementations/ApplicationHost.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ac37cfe079..97da5b437f 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -282,8 +282,6 @@ namespace Emby.Server.Implementations private ISubtitleEncoder SubtitleEncoder { get; set; } - private IAttachmentExtractor AttachmentExtractor { get; set; } - private ISessionManager SessionManager { get; set; } private ILiveTvManager LiveTvManager { get; set; } @@ -906,9 +904,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); - AttachmentExtractor = new MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, MediaSourceManager, ProcessFactory); - - serviceCollection.AddSingleton(AttachmentExtractor); + serviceCollection.AddSingleton(typeof(MediaBrowser.Controller.MediaEncoding.IAttachmentExtractor),typeof(MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor)); _displayPreferencesRepository.Initialize(); From 2338a53229431070b1c128436e990a486011205b Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:55:39 -0500 Subject: [PATCH 50/81] Don't user ILoggerFactory. --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 7c3a0fbd3e..d18463cb5a 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.MediaEncoding.Attachments public AttachmentExtractor( ILibraryManager libraryManager, - ILoggerFactory loggerFactory, + ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, @@ -43,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Attachments IProcessFactory processFactory) { _libraryManager = libraryManager; - _logger = loggerFactory.CreateLogger(nameof(AttachmentExtractor)); + _logger = logger; _appPaths = appPaths; _fileSystem = fileSystem; _mediaEncoder = mediaEncoder; From 2f728fd2a1be2e0abde3b8d7f8269b849e1555c6 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:58:26 -0500 Subject: [PATCH 51/81] Update MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs formatting Co-Authored-By: Bond-009 --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 7c3a0fbd3e..2cecdf1cd0 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.MediaEncoding.Attachments MediaAttachment mediaAttachment, CancellationToken cancellationToken) { - var inputFiles = new[] {mediaSource.Path}; + var inputFiles = new[] { mediaSource.Path }; var attachmentPath = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, mediaAttachment, cancellationToken).ConfigureAwait(false); var stream = await GetAttachmentStream(attachmentPath, cancellationToken).ConfigureAwait(false); return stream; From 1eb3df1d6ce21641612ce2f7ce9f25cd757a0e0d Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 11:59:54 -0500 Subject: [PATCH 52/81] Update MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs formatting / style Co-Authored-By: Bond-009 --- .../Attachments/AttachmentExtractor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index f5f5d213f9..e5b82d075a 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -166,7 +166,12 @@ namespace MediaBrowser.MediaEncoding.Attachments Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - var processArgs = string.Format("-dump_attachment:{1} {2} -i {0} -t 0 -f null null", inputPath, attachmentStreamIndex, outputPath); + var processArgs = string.Format( + CultureInfo.InvariantCulture, + "-dump_attachment:{1} {2} -i {0} -t 0 -f null null", + inputPath, + attachmentStreamIndex, + outputPath); var process = _processFactory.Create(new ProcessOptions { CreateNoWindow = true, From 3a9bf84e3b1b4d6c3cd1eaf979a418087ce41e0d Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 12:00:47 -0500 Subject: [PATCH 53/81] Update MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs formatting Co-Authored-By: Bond-009 --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index e5b82d075a..0780df7735 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -233,7 +233,6 @@ namespace MediaBrowser.MediaEncoding.Attachments { _logger.LogError(ex, "Error deleting extracted attachment {Path}", outputPath); } - } else if (!File.Exists(outputPath)) { From 79858eb26c60aa7ad8b6e63ee90fbd2e0727d594 Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Thu, 7 Nov 2019 14:24:49 -0500 Subject: [PATCH 54/81] Remove use of ProcessFactory, as well as arbitrary timeout in AttachmentExtractor. --- .../Attachments/AttachmentExtractor.cs | 31 +++++++++++-------- MediaBrowser.MediaEncoding/packages.config | 3 -- 2 files changed, 18 insertions(+), 16 deletions(-) delete mode 100644 MediaBrowser.MediaEncoding/packages.config diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index f5f5d213f9..369e597ea2 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Collections.Concurrent; using System.Globalization; using System.IO; @@ -31,7 +32,6 @@ namespace MediaBrowser.MediaEncoding.Attachments private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; private readonly IMediaSourceManager _mediaSourceManager; - private readonly IProcessFactory _processFactory; public AttachmentExtractor( ILibraryManager libraryManager, @@ -39,8 +39,7 @@ namespace MediaBrowser.MediaEncoding.Attachments IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, - IMediaSourceManager mediaSourceManager, - IProcessFactory processFactory) + IMediaSourceManager mediaSourceManager) { _libraryManager = libraryManager; _logger = logger; @@ -48,7 +47,6 @@ namespace MediaBrowser.MediaEncoding.Attachments _fileSystem = fileSystem; _mediaEncoder = mediaEncoder; _mediaSourceManager = mediaSourceManager; - _processFactory = processFactory; } private string AttachmentCachePath => Path.Combine(_appPaths.DataPath, "attachments"); @@ -167,16 +165,19 @@ namespace MediaBrowser.MediaEncoding.Attachments Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); var processArgs = string.Format("-dump_attachment:{1} {2} -i {0} -t 0 -f null null", inputPath, attachmentStreamIndex, outputPath); - var process = _processFactory.Create(new ProcessOptions + var startInfo = new ProcessStartInfo { - CreateNoWindow = true, - UseShellExecute = false, - EnableRaisingEvents = true, - FileName = _mediaEncoder.EncoderPath, Arguments = processArgs, - IsHidden = true, + 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); @@ -191,7 +192,12 @@ namespace MediaBrowser.MediaEncoding.Attachments throw; } - var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false); + var processTcs = new TaskCompletionSource(); + 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) { @@ -205,7 +211,6 @@ namespace MediaBrowser.MediaEncoding.Attachments _logger.LogError(ex, "Error killing attachment extraction process"); } } - var exitCode = ranToCompletion ? process.ExitCode : -1; process.Dispose(); diff --git a/MediaBrowser.MediaEncoding/packages.config b/MediaBrowser.MediaEncoding/packages.config deleted file mode 100644 index bbeaf5f002..0000000000 --- a/MediaBrowser.MediaEncoding/packages.config +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file From dee247453e7b5cab1badb6a844af690cdf80aacd Mon Sep 17 00:00:00 2001 From: Andrew Mahone Date: Wed, 13 Nov 2019 08:52:37 -0500 Subject: [PATCH 55/81] Throw InvalidOperationException when attachment extraction exits abnormally or doesn't produce output. --- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 4e277826a9..cb22343c48 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -250,7 +250,7 @@ namespace MediaBrowser.MediaEncoding.Attachments _logger.LogError(msg); - throw new Exception(msg); + throw new InvalidOperationException(msg); } else { From cf2e2a3f309d59c3c31696fc7f3ef2b6668c89dd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 22 Dec 2019 21:39:26 +0100 Subject: [PATCH 56/81] Fix exceptions while scanning Fixes these exceptions: ``` [2019-12-22 20:48:14.779 +01:00] [ERR] Error in WaitForExit System.InvalidOperationException: No process is associated with this object. at System.Diagnostics.Process.EnsureState(State state) at System.Diagnostics.Process.EnsureState(State state) at System.Diagnostics.Process.GetWaitState() at System.Diagnostics.Process.WaitForExitCore(Int32 milliseconds) at System.Diagnostics.Process.WaitForExit(Int32 milliseconds) at Emby.Server.Implementations.Diagnostics.CommonProcess.WaitForExit(Int32 timeMs) in /home/pi/dev/jellyfin/Emby.Server.Implementations/Diagnostics/CommonProcess.cs:line 100 at MediaBrowser.MediaEncoding.Encoder.MediaEncoder.StopProcess(ProcessWrapper process, Int32 waitTimeMs) in /home/pi/dev/jellyfin/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs:line 785 [2019-12-22 20:48:14.790 +01:00] [INF] Killing ffmpeg process [2019-12-22 20:48:14.795 +01:00] [ERR] Error killing process System.InvalidOperationException: No process is associated with this object. at System.Diagnostics.Process.EnsureState(State state) at System.Diagnostics.Process.EnsureState(State state) at System.Diagnostics.Process.Kill() at Emby.Server.Implementations.Diagnostics.CommonProcess.Kill() in /home/pi/dev/jellyfin/Emby.Server.Implementations/Diagnostics/CommonProcess.cs:line 95 at MediaBrowser.MediaEncoding.Encoder.MediaEncoder.StopProcess(ProcessWrapper process, Int32 waitTimeMs) in /home/pi/dev/jellyfin/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs:line 799 [2019-12-22 20:48:14.808 +01:00] [ERR] Error in "ffprobe" System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: $.streams[0].start_pts | LineNumber: 32 | BytePositionInLine: 26. ---> System.InvalidOperationException: Cannot get the value of a token type 'Number' as a string. at System.Text.Json.Utf8JsonReader.GetString() at System.Text.Json.Serialization.Converters.JsonConverterString.Read(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options) at System.Text.Json.JsonPropertyInfoNotNullable`4.OnRead(JsonTokenType tokenType, ReadStack& state, Utf8JsonReader& reader) at System.Text.Json.JsonPropertyInfo.Read(JsonTokenType tokenType, ReadStack& state, Utf8JsonReader& reader) at System.Text.Json.JsonSerializer.HandleValue(JsonTokenType tokenType, JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& state) at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack) --- End of inner exception stack trace --- at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& readStack, Utf8JsonReader& reader, Exception ex) at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack) at System.Text.Json.JsonSerializer.ReadCore(JsonReaderState& readerState, Boolean isFinalBlock, ReadOnlySpan`1 buffer, JsonSerializerOptions options, ReadStack& readStack) at System.Text.Json.JsonSerializer.ReadAsync[TValue](Stream utf8Json, Type returnType, JsonSerializerOptions options, CancellationToken cancellationToken) at MediaBrowser.MediaEncoding.Encoder.MediaEncoder.GetMediaInfoInternal(String inputPath, String primaryPath, MediaProtocol protocol, Boolean extractChapters, String probeSizeArgument, Boolean isAudio, Nullable`1 videoType, Boolean forceEnableLogging, CancellationToken cancellationToken) in /home/pi/dev/jellyfin/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs:line 399 at MediaBrowser.Providers.MediaInfo.FFProbeVideoInfo.ProbeVideo[T](T item, MetadataRefreshOptions options, CancellationToken cancellationToken) in /home/pi/dev/jellyfin/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs:line 122 at MediaBrowser.Providers.Manager.MetadataService`2.RunCustomProvider(ICustomMetadataProvider`1 provider, TItemType item, String logName, MetadataRefreshOptions options, RefreshResult refreshResult, CancellationToken cancellationToken) in /home/pi/dev/jellyfin/MediaBrowser.Providers/Manager/MetadataService.cs:line 815 ``` --- ...{GuidConverter.cs => JsonGuidConverter.cs} | 2 +- .../Json/Converters/JsonInt32Converter.cs | 40 +++ MediaBrowser.Common/Json/JsonDefaults.cs | 2 +- .../Encoder/MediaEncoder.cs | 30 +- .../Probing/FFProbeHelpers.cs | 21 +- .../Probing/InternalMediaInfoResult.cs | 325 +----------------- .../Probing/MediaChapter.cs | 32 ++ .../Probing/MediaFormatInfo.cs | 81 +++++ .../Probing/MediaStreamInfo.cs | 282 +++++++++++++++ .../Probing/ProbeResultNormalizer.cs | 181 +++++----- 10 files changed, 560 insertions(+), 436 deletions(-) rename MediaBrowser.Common/Json/Converters/{GuidConverter.cs => JsonGuidConverter.cs} (91%) create mode 100644 MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaChapter.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs create mode 100644 MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs diff --git a/MediaBrowser.Common/Json/Converters/GuidConverter.cs b/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs similarity index 91% rename from MediaBrowser.Common/Json/Converters/GuidConverter.cs rename to MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs index 3081e12eee..d35a761f35 100644 --- a/MediaBrowser.Common/Json/Converters/GuidConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.Common.Json.Converters /// /// Converts a GUID object or value to/from JSON. /// - public class GuidConverter : JsonConverter + public class JsonGuidConverter : JsonConverter { /// public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs new file mode 100644 index 0000000000..0fd68babe1 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs @@ -0,0 +1,40 @@ +using System; +using System.Buffers; +using System.Buffers.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Converts a GUID object or value to/from JSON. + /// + public class JsonInt32Converter : JsonConverter + { + /// + public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + static void ThrowFormatException() => throw new FormatException("Invalid format for an integer."); + ReadOnlySpan span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (!Utf8Parser.TryParse(span, out int number, out _)) + { + ThrowFormatException(); + } + + return number; + } + + /// + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) + { + static void ThrowInvalidOperationException() => throw new InvalidOperationException(); + Span span = new byte[16]; + if (Utf8Formatter.TryFormat(value, span, out int bytesWritten)) + { + writer.WriteStringValue(span.Slice(0, bytesWritten)); + } + + ThrowInvalidOperationException(); + } + } +} diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 4ba0d5a1a5..4a6ee0a793 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Common.Json WriteIndented = false }; - options.Converters.Add(new GuidConverter()); + options.Converters.Add(new JsonGuidConverter()); options.Converters.Add(new JsonStringEnumConverter()); return options; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 6bcd6cd46a..e0f7b992ce 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -397,7 +397,8 @@ namespace MediaBrowser.MediaEncoding.Encoder try { result = await JsonSerializer.DeserializeAsync( - process.StandardOutput.BaseStream).ConfigureAwait(false); + process.StandardOutput.BaseStream, + cancellationToken: cancellationToken).ConfigureAwait(false); } catch { @@ -406,24 +407,24 @@ namespace MediaBrowser.MediaEncoding.Encoder throw; } - if (result == null || (result.streams == null && result.format == null)) + if (result == null || (result.Streams == null && result.Format == null)) { throw new Exception("ffprobe failed - streams and format are both null."); } - if (result.streams != null) + if (result.Streams != null) { // Normalize aspect ratio if invalid - foreach (var stream in result.streams) + foreach (var stream in result.Streams) { - if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(stream.DisplayAspectRatio, "0:1", StringComparison.OrdinalIgnoreCase)) { - stream.display_aspect_ratio = string.Empty; + stream.DisplayAspectRatio = string.Empty; } - if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(stream.SampleAspectRatio, "0:1", StringComparison.OrdinalIgnoreCase)) { - stream.sample_aspect_ratio = string.Empty; + stream.SampleAspectRatio = string.Empty; } } } @@ -778,6 +779,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _runningProcesses.Add(process); } } + private void StopProcess(ProcessWrapper process, int waitTimeMs) { try @@ -786,18 +788,16 @@ namespace MediaBrowser.MediaEncoding.Encoder { return; } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in WaitForExit"); - } - try - { _logger.LogInformation("Killing ffmpeg process"); process.Process.Kill(); } + catch (InvalidOperationException) + { + // The process has already exited or + // there is no process associated with this Process object. + } catch (Exception ex) { _logger.LogError(ex, "Error killing process"); diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index e4eabaf384..cd3d82e864 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -16,24 +16,19 @@ namespace MediaBrowser.MediaEncoding.Probing throw new ArgumentNullException(nameof(result)); } - if (result.format != null && result.format.tags != null) + if (result.Format != null && result.Format.Tags != null) { - result.format.tags = ConvertDictionaryToCaseInSensitive(result.format.tags); + result.Format.Tags = ConvertDictionaryToCaseInSensitive(result.Format.Tags); } - if (result.streams != null) + if (result.Streams != null) { // Convert all dictionaries to case insensitive - foreach (var stream in result.streams) + foreach (var stream in result.Streams) { - if (stream.tags != null) + if (stream.Tags != null) { - stream.tags = ConvertDictionaryToCaseInSensitive(stream.tags); - } - - if (stream.disposition != null) - { - stream.disposition = ConvertDictionaryToCaseInSensitive(stream.disposition); + stream.Tags = ConvertDictionaryToCaseInSensitive(stream.Tags); } } } @@ -45,7 +40,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// The tags. /// The key. /// System.String. - public static string GetDictionaryValue(Dictionary tags, string key) + public static string GetDictionaryValue(IReadOnlyDictionary tags, string key) { if (tags == null) { @@ -103,7 +98,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The dict. /// Dictionary{System.StringSystem.String}. - private static Dictionary ConvertDictionaryToCaseInSensitive(Dictionary dict) + private static Dictionary ConvertDictionaryToCaseInSensitive(IReadOnlyDictionary dict) { return new Dictionary(dict, StringComparer.OrdinalIgnoreCase); } diff --git a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs index cc9d27608b..0e319c1a8b 100644 --- a/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs +++ b/MediaBrowser.MediaEncoding/Probing/InternalMediaInfoResult.cs @@ -1,9 +1,10 @@ using System.Collections.Generic; +using System.Text.Json.Serialization; namespace MediaBrowser.MediaEncoding.Probing { /// - /// Class MediaInfoResult + /// Class MediaInfoResult. /// public class InternalMediaInfoResult { @@ -11,331 +12,21 @@ namespace MediaBrowser.MediaEncoding.Probing /// Gets or sets the streams. /// /// The streams. - public MediaStreamInfo[] streams { get; set; } + [JsonPropertyName("streams")] + public IReadOnlyList Streams { get; set; } /// /// Gets or sets the format. /// /// The format. - public MediaFormatInfo format { get; set; } + [JsonPropertyName("format")] + public MediaFormatInfo Format { get; set; } /// /// Gets or sets the chapters. /// /// The chapters. - public MediaChapter[] Chapters { get; set; } - } - - public class MediaChapter - { - public int id { get; set; } - public string time_base { get; set; } - public long start { get; set; } - public string start_time { get; set; } - public long end { get; set; } - public string end_time { get; set; } - public Dictionary tags { get; set; } - } - - /// - /// Represents a stream within the output - /// - public class MediaStreamInfo - { - /// - /// Gets or sets the index. - /// - /// The index. - public int index { get; set; } - - /// - /// Gets or sets the profile. - /// - /// The profile. - public string profile { get; set; } - - /// - /// Gets or sets the codec_name. - /// - /// The codec_name. - public string codec_name { get; set; } - - /// - /// Gets or sets the codec_long_name. - /// - /// The codec_long_name. - public string codec_long_name { get; set; } - - /// - /// Gets or sets the codec_type. - /// - /// The codec_type. - public string codec_type { get; set; } - - /// - /// Gets or sets the sample_rate. - /// - /// The sample_rate. - public string sample_rate { get; set; } - - /// - /// Gets or sets the channels. - /// - /// The channels. - public int channels { get; set; } - - /// - /// Gets or sets the channel_layout. - /// - /// The channel_layout. - public string channel_layout { get; set; } - - /// - /// Gets or sets the avg_frame_rate. - /// - /// The avg_frame_rate. - public string avg_frame_rate { get; set; } - - /// - /// Gets or sets the duration. - /// - /// The duration. - public string duration { get; set; } - - /// - /// Gets or sets the bit_rate. - /// - /// The bit_rate. - public string bit_rate { get; set; } - - /// - /// Gets or sets the width. - /// - /// The width. - public int width { get; set; } - - /// - /// Gets or sets the refs. - /// - /// The refs. - public int refs { get; set; } - - /// - /// Gets or sets the height. - /// - /// The height. - public int height { get; set; } - - /// - /// Gets or sets the display_aspect_ratio. - /// - /// The display_aspect_ratio. - public string display_aspect_ratio { get; set; } - - /// - /// Gets or sets the tags. - /// - /// The tags. - public Dictionary tags { get; set; } - - /// - /// Gets or sets the bits_per_sample. - /// - /// The bits_per_sample. - public int bits_per_sample { get; set; } - - /// - /// Gets or sets the bits_per_raw_sample. - /// - /// The bits_per_raw_sample. - public int bits_per_raw_sample { get; set; } - - /// - /// Gets or sets the r_frame_rate. - /// - /// The r_frame_rate. - public string r_frame_rate { get; set; } - - /// - /// Gets or sets the has_b_frames. - /// - /// The has_b_frames. - public int has_b_frames { get; set; } - - /// - /// Gets or sets the sample_aspect_ratio. - /// - /// The sample_aspect_ratio. - public string sample_aspect_ratio { get; set; } - - /// - /// Gets or sets the pix_fmt. - /// - /// The pix_fmt. - public string pix_fmt { get; set; } - - /// - /// Gets or sets the level. - /// - /// The level. - public int level { get; set; } - - /// - /// Gets or sets the time_base. - /// - /// The time_base. - public string time_base { get; set; } - - /// - /// Gets or sets the start_time. - /// - /// The start_time. - public string start_time { get; set; } - - /// - /// Gets or sets the codec_time_base. - /// - /// The codec_time_base. - public string codec_time_base { get; set; } - - /// - /// Gets or sets the codec_tag. - /// - /// The codec_tag. - public string codec_tag { get; set; } - - /// - /// Gets or sets the codec_tag_string. - /// - /// The codec_tag_string. - public string codec_tag_string { get; set; } - - /// - /// Gets or sets the sample_fmt. - /// - /// The sample_fmt. - public string sample_fmt { get; set; } - - /// - /// Gets or sets the dmix_mode. - /// - /// The dmix_mode. - public string dmix_mode { get; set; } - - /// - /// Gets or sets the start_pts. - /// - /// The start_pts. - public string start_pts { get; set; } - - /// - /// Gets or sets the is_avc. - /// - /// The is_avc. - public string is_avc { get; set; } - - /// - /// Gets or sets the nal_length_size. - /// - /// The nal_length_size. - public string nal_length_size { get; set; } - - /// - /// Gets or sets the ltrt_cmixlev. - /// - /// The ltrt_cmixlev. - public string ltrt_cmixlev { get; set; } - - /// - /// Gets or sets the ltrt_surmixlev. - /// - /// The ltrt_surmixlev. - public string ltrt_surmixlev { get; set; } - - /// - /// Gets or sets the loro_cmixlev. - /// - /// The loro_cmixlev. - public string loro_cmixlev { get; set; } - - /// - /// Gets or sets the loro_surmixlev. - /// - /// The loro_surmixlev. - public string loro_surmixlev { get; set; } - - public string field_order { get; set; } - - /// - /// Gets or sets the disposition. - /// - /// The disposition. - public Dictionary disposition { get; set; } - } - - /// - /// Class MediaFormat - /// - public class MediaFormatInfo - { - /// - /// Gets or sets the filename. - /// - /// The filename. - public string filename { get; set; } - - /// - /// Gets or sets the nb_streams. - /// - /// The nb_streams. - public int nb_streams { get; set; } - - /// - /// Gets or sets the format_name. - /// - /// The format_name. - public string format_name { get; set; } - - /// - /// Gets or sets the format_long_name. - /// - /// The format_long_name. - public string format_long_name { get; set; } - - /// - /// Gets or sets the start_time. - /// - /// The start_time. - public string start_time { get; set; } - - /// - /// Gets or sets the duration. - /// - /// The duration. - public string duration { get; set; } - - /// - /// Gets or sets the size. - /// - /// The size. - public string size { get; set; } - - /// - /// Gets or sets the bit_rate. - /// - /// The bit_rate. - public string bit_rate { get; set; } - - /// - /// Gets or sets the probe_score. - /// - /// The probe_score. - public int probe_score { get; set; } - - /// - /// Gets or sets the tags. - /// - /// The tags. - public Dictionary tags { get; set; } + [JsonPropertyName("chapters")] + public IReadOnlyList Chapters { get; set; } } } diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs new file mode 100644 index 0000000000..a3607a7600 --- /dev/null +++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace MediaBrowser.MediaEncoding.Probing +{ + /// + /// Class MediaChapter. + /// + public class MediaChapter + { + [JsonPropertyName("id")] + public int id { get; set; } + + [JsonPropertyName("time_base")] + public string TimeBase { get; set; } + + [JsonPropertyName("start")] + public long Start { get; set; } + + [JsonPropertyName("start_time")] + public string StartTime { get; set; } + + [JsonPropertyName("end")] + public long End { get; set; } + + [JsonPropertyName("end_time")] + public string EndTime { get; set; } + + [JsonPropertyName("tags")] + public IReadOnlyDictionary Tags { get; set; } + } +} diff --git a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs new file mode 100644 index 0000000000..d5529e56c8 --- /dev/null +++ b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace MediaBrowser.MediaEncoding.Probing +{ + /// + /// Class MediaFormat. + /// + public class MediaFormatInfo + { + /// + /// Gets or sets the filename. + /// + /// The filename. + [JsonPropertyName("filename")] + public string Fileame { get; set; } + + /// + /// Gets or sets the nb_streams. + /// + /// The nb_streams. + [JsonPropertyName("nb_streams")] + public int NbStreams { get; set; } + + /// + /// Gets or sets the format_name. + /// + /// The format_name. + [JsonPropertyName("format_name")] + public string FormatName { get; set; } + + /// + /// Gets or sets the format_long_name. + /// + /// The format_long_name. + [JsonPropertyName("format_long_name")] + public string FormatLongName { get; set; } + + /// + /// Gets or sets the start_time. + /// + /// The start_time. + [JsonPropertyName("start_time")] + public string StartTime { get; set; } + + /// + /// Gets or sets the duration. + /// + /// The duration. + [JsonPropertyName("duration")] + public string Duration { get; set; } + + /// + /// Gets or sets the size. + /// + /// The size. + [JsonPropertyName("size")] + public string Size { get; set; } + + /// + /// Gets or sets the bit_rate. + /// + /// The bit_rate. + [JsonPropertyName("bit_rate")] + public string BitRate { get; set; } + + /// + /// Gets or sets the probe_score. + /// + /// The probe_score. + [JsonPropertyName("probe_score")] + public int ProbeScore { get; set; } + + /// + /// Gets or sets the tags. + /// + /// The tags. + [JsonPropertyName("tags")] + public IReadOnlyDictionary Tags { get; set; } + } +} diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs new file mode 100644 index 0000000000..7fa7afa5b3 --- /dev/null +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -0,0 +1,282 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using MediaBrowser.Common.Json.Converters; + +namespace MediaBrowser.MediaEncoding.Probing +{ + /// + /// Represents a stream within the output. + /// + public class MediaStreamInfo + { + /// + /// Gets or sets the index. + /// + /// The index. + [JsonPropertyName("index")] + public int Index { get; set; } + + /// + /// Gets or sets the profile. + /// + /// The profile. + [JsonPropertyName("profile")] + public string Profile { get; set; } + + /// + /// Gets or sets the codec_name. + /// + /// The codec_name. + [JsonPropertyName("codec_name")] + public string CodecName { get; set; } + + /// + /// Gets or sets the codec_long_name. + /// + /// The codec_long_name. + [JsonPropertyName("codec_long_name")] + public string CodecLongName { get; set; } + + /// + /// Gets or sets the codec_type. + /// + /// The codec_type. + [JsonPropertyName("codec_type")] + public string CodecType { get; set; } + + /// + /// Gets or sets the sample_rate. + /// + /// The sample_rate. + [JsonPropertyName("sample_rate")] + public string SampleRate { get; set; } + + /// + /// Gets or sets the channels. + /// + /// The channels. + [JsonPropertyName("channels")] + public int Channels { get; set; } + + /// + /// Gets or sets the channel_layout. + /// + /// The channel_layout. + [JsonPropertyName("channel_layout")] + public string ChannelLayout { get; set; } + + /// + /// Gets or sets the avg_frame_rate. + /// + /// The avg_frame_rate. + [JsonPropertyName("avg_frame_rate")] + public string AverageFrameRate { get; set; } + + /// + /// Gets or sets the duration. + /// + /// The duration. + [JsonPropertyName("duration")] + public string Duration { get; set; } + + /// + /// Gets or sets the bit_rate. + /// + /// The bit_rate. + [JsonPropertyName("bit_rate")] + public string BitRate { get; set; } + + /// + /// Gets or sets the width. + /// + /// The width. + [JsonPropertyName("width")] + public int Width { get; set; } + + /// + /// Gets or sets the refs. + /// + /// The refs. + [JsonPropertyName("refs")] + public int Refs { get; set; } + + /// + /// Gets or sets the height. + /// + /// The height. + [JsonPropertyName("height")] + public int Height { get; set; } + + /// + /// Gets or sets the display_aspect_ratio. + /// + /// The display_aspect_ratio. + [JsonPropertyName("display_aspect_ratio")] + public string DisplayAspectRatio { get; set; } + + /// + /// Gets or sets the tags. + /// + /// The tags. + [JsonPropertyName("tags")] + public IReadOnlyDictionary Tags { get; set; } + + /// + /// Gets or sets the bits_per_sample. + /// + /// The bits_per_sample. + [JsonPropertyName("bits_per_sample")] + public int BitsPerSample { get; set; } + + /// + /// Gets or sets the bits_per_raw_sample. + /// + /// The bits_per_raw_sample. + [JsonPropertyName("bits_per_raw_sample")] + [JsonConverter(typeof(JsonInt32Converter))] + public int BitsPerRawSample { get; set; } + + /// + /// Gets or sets the r_frame_rate. + /// + /// The r_frame_rate. + [JsonPropertyName("r_frame_rate")] + public string RFrameRate { get; set; } + + /// + /// Gets or sets the has_b_frames. + /// + /// The has_b_frames. + [JsonPropertyName("has_b_frames")] + public int HasBFrames { get; set; } + + /// + /// Gets or sets the sample_aspect_ratio. + /// + /// The sample_aspect_ratio. + [JsonPropertyName("sample_aspect_ratio")] + public string SampleAspectRatio { get; set; } + + /// + /// Gets or sets the pix_fmt. + /// + /// The pix_fmt. + [JsonPropertyName("pix_fmt")] + public string PixelFormat { get; set; } + + /// + /// Gets or sets the level. + /// + /// The level. + [JsonPropertyName("level")] + public int Level { get; set; } + + /// + /// Gets or sets the time_base. + /// + /// The time_base. + [JsonPropertyName("time_base")] + public string TimeBase { get; set; } + + /// + /// Gets or sets the start_time. + /// + /// The start_time. + [JsonPropertyName("start_time")] + public string StartTime { get; set; } + + /// + /// Gets or sets the codec_time_base. + /// + /// The codec_time_base. + [JsonPropertyName("codec_time_base")] + public string CodecTimeBase { get; set; } + + /// + /// Gets or sets the codec_tag. + /// + /// The codec_tag. + [JsonPropertyName("codec_tag")] + public string CodecTag { get; set; } + + /// + /// Gets or sets the codec_tag_string. + /// + /// The codec_tag_string. + [JsonPropertyName("codec_tag_string")] + public string CodecTagString { get; set; } + + /// + /// Gets or sets the sample_fmt. + /// + /// The sample_fmt. + [JsonPropertyName("sample_fmt")] + public string SampleFmt { get; set; } + + /// + /// Gets or sets the dmix_mode. + /// + /// The dmix_mode. + [JsonPropertyName("dmix_mode")] + public string DmixMode { get; set; } + + /// + /// Gets or sets the start_pts. + /// + /// The start_pts. + [JsonPropertyName("start_pts")] + public int StartPts { get; set; } + + /// + /// Gets or sets the is_avc. + /// + /// The is_avc. + [JsonPropertyName("is_avc")] + public string IsAvc { get; set; } + + /// + /// Gets or sets the nal_length_size. + /// + /// The nal_length_size. + [JsonPropertyName("nal_length_size")] + public string NalLengthSize { get; set; } + + /// + /// Gets or sets the ltrt_cmixlev. + /// + /// The ltrt_cmixlev. + [JsonPropertyName("ltrt_cmixlev")] + public string LtrtCmixlev { get; set; } + + /// + /// Gets or sets the ltrt_surmixlev. + /// + /// The ltrt_surmixlev. + [JsonPropertyName("ltrt_surmixlev")] + public string LtrtSurmixlev { get; set; } + + /// + /// Gets or sets the loro_cmixlev. + /// + /// The loro_cmixlev. + [JsonPropertyName("loro_cmixlev")] + public string LoroCmixlev { get; set; } + + /// + /// Gets or sets the loro_surmixlev. + /// + /// The loro_surmixlev. + [JsonPropertyName("loro_surmixlev")] + public string LoroSurmixlev { get; set; } + + [JsonPropertyName("field_order")] + public string FieldOrder { get; set; } + + /// + /// Gets or sets the disposition. + /// + /// The disposition. + [JsonPropertyName("disposition")] + public IReadOnlyDictionary Disposition { get; set; } + } +} diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 54d02fc9f7..99f0df60fe 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -8,7 +8,6 @@ using System.Xml; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -41,21 +40,21 @@ namespace MediaBrowser.MediaEncoding.Probing FFProbeHelpers.NormalizeFFProbeResult(data); SetSize(data, info); - var internalStreams = data.streams ?? new MediaStreamInfo[] { }; + var internalStreams = data.Streams ?? new MediaStreamInfo[] { }; - info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.format)) + info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.Format)) .Where(i => i != null) // Drop subtitle streams if we don't know the codec because it will just cause failures if we don't know how to handle them .Where(i => i.Type != MediaStreamType.Subtitle || !string.IsNullOrWhiteSpace(i.Codec)) .ToList(); - if (data.format != null) + if (data.Format != null) { - info.Container = NormalizeFormat(data.format.format_name); + info.Container = NormalizeFormat(data.Format.FormatName); - if (!string.IsNullOrEmpty(data.format.bit_rate)) + if (!string.IsNullOrEmpty(data.Format.BitRate)) { - if (int.TryParse(data.format.bit_rate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(data.Format.BitRate, NumberStyles.Any, _usCulture, out var value)) { info.Bitrate = value; } @@ -65,22 +64,22 @@ namespace MediaBrowser.MediaEncoding.Probing var tags = new Dictionary(StringComparer.OrdinalIgnoreCase); var tagStreamType = isAudio ? "audio" : "video"; - if (data.streams != null) + if (data.Streams != null) { - var tagStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, tagStreamType, StringComparison.OrdinalIgnoreCase)); + var tagStream = data.Streams.FirstOrDefault(i => string.Equals(i.CodecType, tagStreamType, StringComparison.OrdinalIgnoreCase)); - if (tagStream != null && tagStream.tags != null) + if (tagStream != null && tagStream.Tags != null) { - foreach (var pair in tagStream.tags) + foreach (var pair in tagStream.Tags) { tags[pair.Key] = pair.Value; } } } - if (data.format != null && data.format.tags != null) + if (data.Format != null && data.Format.Tags != null) { - foreach (var pair in data.format.tags) + foreach (var pair in data.Format.Tags) { tags[pair.Key] = pair.Value; } @@ -153,9 +152,9 @@ namespace MediaBrowser.MediaEncoding.Probing FetchFromItunesInfo(itunesXml, info); } - if (data.format != null && !string.IsNullOrEmpty(data.format.duration)) + if (data.Format != null && !string.IsNullOrEmpty(data.Format.Duration)) { - info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks; + info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.Format.Duration, _usCulture)).Ticks; } FetchWtvInfo(info, data); @@ -523,7 +522,7 @@ namespace MediaBrowser.MediaEncoding.Probing private MediaStream GetMediaStream(bool isAudio, MediaStreamInfo streamInfo, MediaFormatInfo formatInfo) { // These are mp4 chapters - if (string.Equals(streamInfo.codec_name, "mov_text", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase)) { // Edit: but these are also sometimes subtitles? //return null; @@ -531,71 +530,71 @@ namespace MediaBrowser.MediaEncoding.Probing var stream = new MediaStream { - Codec = streamInfo.codec_name, - Profile = streamInfo.profile, - Level = streamInfo.level, - Index = streamInfo.index, - PixelFormat = streamInfo.pix_fmt, - NalLengthSize = streamInfo.nal_length_size, - TimeBase = streamInfo.time_base, - CodecTimeBase = streamInfo.codec_time_base + Codec = streamInfo.CodecName, + Profile = streamInfo.Profile, + Level = streamInfo.Level, + Index = streamInfo.Index, + PixelFormat = streamInfo.PixelFormat, + NalLengthSize = streamInfo.NalLengthSize, + TimeBase = streamInfo.TimeBase, + CodecTimeBase = streamInfo.CodecTimeBase }; - if (string.Equals(streamInfo.is_avc, "true", StringComparison.OrdinalIgnoreCase) || - string.Equals(streamInfo.is_avc, "1", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(streamInfo.IsAvc, "true", StringComparison.OrdinalIgnoreCase) || + string.Equals(streamInfo.IsAvc, "1", StringComparison.OrdinalIgnoreCase)) { stream.IsAVC = true; } - else if (string.Equals(streamInfo.is_avc, "false", StringComparison.OrdinalIgnoreCase) || - string.Equals(streamInfo.is_avc, "0", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(streamInfo.IsAvc, "false", StringComparison.OrdinalIgnoreCase) || + string.Equals(streamInfo.IsAvc, "0", StringComparison.OrdinalIgnoreCase)) { stream.IsAVC = false; } - if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(streamInfo.FieldOrder) && !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase)) { stream.IsInterlaced = true; } // Filter out junk - if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) + if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString) && streamInfo.CodecTagString.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1) { - stream.CodecTag = streamInfo.codec_tag_string; + stream.CodecTag = streamInfo.CodecTagString; } - if (streamInfo.tags != null) + if (streamInfo.Tags != null) { - stream.Language = GetDictionaryValue(streamInfo.tags, "language"); - stream.Comment = GetDictionaryValue(streamInfo.tags, "comment"); - stream.Title = GetDictionaryValue(streamInfo.tags, "title"); + stream.Language = GetDictionaryValue(streamInfo.Tags, "language"); + stream.Comment = GetDictionaryValue(streamInfo.Tags, "comment"); + stream.Title = GetDictionaryValue(streamInfo.Tags, "title"); } - if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase)) { stream.Type = MediaStreamType.Audio; - stream.Channels = streamInfo.channels; + stream.Channels = streamInfo.Channels; - if (!string.IsNullOrEmpty(streamInfo.sample_rate)) + if (!string.IsNullOrEmpty(streamInfo.SampleRate)) { - if (int.TryParse(streamInfo.sample_rate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, _usCulture, out var value)) { stream.SampleRate = value; } } - stream.ChannelLayout = ParseChannelLayout(streamInfo.channel_layout); + stream.ChannelLayout = ParseChannelLayout(streamInfo.ChannelLayout); - if (streamInfo.bits_per_sample > 0) + if (streamInfo.BitsPerSample > 0) { - stream.BitDepth = streamInfo.bits_per_sample; + stream.BitDepth = streamInfo.BitsPerSample; } - else if (streamInfo.bits_per_raw_sample > 0) + else if (streamInfo.BitsPerRawSample > 0) { - stream.BitDepth = streamInfo.bits_per_raw_sample; + stream.BitDepth = streamInfo.BitsPerRawSample; } } - else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(streamInfo.CodecType, "subtitle", StringComparison.OrdinalIgnoreCase)) { stream.Type = MediaStreamType.Subtitle; stream.Codec = NormalizeSubtitleCodec(stream.Codec); @@ -603,14 +602,14 @@ namespace MediaBrowser.MediaEncoding.Probing stream.localizedDefault = _localization.GetLocalizedString("Default"); stream.localizedForced = _localization.GetLocalizedString("Forced"); } - else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase)) { stream.Type = isAudio || string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase) ? MediaStreamType.EmbeddedImage : MediaStreamType.Video; - stream.AverageFrameRate = GetFrameRate(streamInfo.avg_frame_rate); - stream.RealFrameRate = GetFrameRate(streamInfo.r_frame_rate); + stream.AverageFrameRate = GetFrameRate(streamInfo.AverageFrameRate); + stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate); if (isAudio || string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase) || string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)) @@ -635,17 +634,17 @@ namespace MediaBrowser.MediaEncoding.Probing stream.Type = MediaStreamType.Video; } - stream.Width = streamInfo.width; - stream.Height = streamInfo.height; + stream.Width = streamInfo.Width; + stream.Height = streamInfo.Height; stream.AspectRatio = GetAspectRatio(streamInfo); - if (streamInfo.bits_per_sample > 0) + if (streamInfo.BitsPerSample > 0) { - stream.BitDepth = streamInfo.bits_per_sample; + stream.BitDepth = streamInfo.BitsPerSample; } - else if (streamInfo.bits_per_raw_sample > 0) + else if (streamInfo.BitsPerRawSample > 0) { - stream.BitDepth = streamInfo.bits_per_raw_sample; + stream.BitDepth = streamInfo.BitsPerRawSample; } //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || @@ -653,11 +652,11 @@ namespace MediaBrowser.MediaEncoding.Probing // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); // http://stackoverflow.com/questions/17353387/how-to-detect-anamorphic-video-with-ffprobe - stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase); + stream.IsAnamorphic = string.Equals(streamInfo.SampleAspectRatio, "0:1", StringComparison.OrdinalIgnoreCase); - if (streamInfo.refs > 0) + if (streamInfo.Refs > 0) { - stream.RefFrames = streamInfo.refs; + stream.RefFrames = streamInfo.Refs; } } else @@ -668,18 +667,18 @@ namespace MediaBrowser.MediaEncoding.Probing // Get stream bitrate var bitrate = 0; - if (!string.IsNullOrEmpty(streamInfo.bit_rate)) + if (!string.IsNullOrEmpty(streamInfo.BitRate)) { - if (int.TryParse(streamInfo.bit_rate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, _usCulture, out var value)) { bitrate = value; } } - if (bitrate == 0 && formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video) + if (bitrate == 0 && formatInfo != null && !string.IsNullOrEmpty(formatInfo.BitRate) && stream.Type == MediaStreamType.Video) { // If the stream info doesn't have a bitrate get the value from the media format info - if (int.TryParse(formatInfo.bit_rate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, _usCulture, out var value)) { bitrate = value; } @@ -690,14 +689,18 @@ namespace MediaBrowser.MediaEncoding.Probing stream.BitRate = bitrate; } - if (streamInfo.disposition != null) + var disposition = streamInfo.Disposition; + if (disposition != null) { - var isDefault = GetDictionaryValue(streamInfo.disposition, "default"); - var isForced = GetDictionaryValue(streamInfo.disposition, "forced"); + if (disposition.GetValueOrDefault("default") == 1) + { + stream.IsDefault = true; + } - stream.IsDefault = string.Equals(isDefault, "1", StringComparison.OrdinalIgnoreCase); - - stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase); + if (disposition.GetValueOrDefault("forced") == 1) + { + stream.IsForced = true; + } } NormalizeStreamTitle(stream); @@ -724,7 +727,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// The tags. /// The key. /// System.String. - private string GetDictionaryValue(Dictionary tags, string key) + private string GetDictionaryValue(IReadOnlyDictionary tags, string key) { if (tags == null) { @@ -747,7 +750,7 @@ namespace MediaBrowser.MediaEncoding.Probing private string GetAspectRatio(MediaStreamInfo info) { - var original = info.display_aspect_ratio; + var original = info.DisplayAspectRatio; var parts = (original ?? string.Empty).Split(':'); if (!(parts.Length == 2 && @@ -756,8 +759,8 @@ namespace MediaBrowser.MediaEncoding.Probing width > 0 && height > 0)) { - width = info.width; - height = info.height; + width = info.Width; + height = info.Height; } if (width > 0 && height > 0) @@ -850,20 +853,20 @@ namespace MediaBrowser.MediaEncoding.Probing private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data) { - if (result.streams != null) + if (result.Streams != null) { // Get the first info stream - var stream = result.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); + var stream = result.Streams.FirstOrDefault(s => string.Equals(s.CodecType, "audio", StringComparison.OrdinalIgnoreCase)); if (stream != null) { // Get duration from stream properties - var duration = stream.duration; + var duration = stream.Duration; // If it's not there go into format properties if (string.IsNullOrEmpty(duration)) { - duration = result.format.duration; + duration = result.Format.Duration; } // If we got something, parse it @@ -877,11 +880,11 @@ namespace MediaBrowser.MediaEncoding.Probing private void SetSize(InternalMediaInfoResult data, MediaInfo info) { - if (data.format != null) + if (data.Format != null) { - if (!string.IsNullOrEmpty(data.format.size)) + if (!string.IsNullOrEmpty(data.Format.Size)) { - info.Size = long.Parse(data.format.size, _usCulture); + info.Size = long.Parse(data.Format.Size, _usCulture); } else { @@ -1194,16 +1197,16 @@ namespace MediaBrowser.MediaEncoding.Probing { var info = new ChapterInfo(); - if (chapter.tags != null) + if (chapter.Tags != null) { - if (chapter.tags.TryGetValue("title", out string name)) + if (chapter.Tags.TryGetValue("title", out string name)) { info.Name = name; } } // Limit accuracy to milliseconds to match xml saving - var secondsString = chapter.start_time; + var secondsString = chapter.StartTime; if (double.TryParse(secondsString, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds)) { @@ -1218,12 +1221,12 @@ namespace MediaBrowser.MediaEncoding.Probing private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data) { - if (data.format == null || data.format.tags == null) + if (data.Format == null || data.Format.Tags == null) { return; } - var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/Genre"); + var genres = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/Genre"); if (!string.IsNullOrWhiteSpace(genres)) { @@ -1239,14 +1242,14 @@ namespace MediaBrowser.MediaEncoding.Probing } } - var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating"); + var officialRating = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/ParentalRating"); if (!string.IsNullOrWhiteSpace(officialRating)) { video.OfficialRating = officialRating; } - var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits"); + var people = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/MediaCredits"); if (!string.IsNullOrEmpty(people)) { @@ -1256,7 +1259,7 @@ namespace MediaBrowser.MediaEncoding.Probing .ToArray(); } - var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime"); + var year = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/OriginalReleaseTime"); if (!string.IsNullOrWhiteSpace(year)) { if (int.TryParse(year, NumberStyles.Integer, _usCulture, out var val)) @@ -1265,7 +1268,7 @@ namespace MediaBrowser.MediaEncoding.Probing } } - var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime"); + var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/MediaOriginalBroadcastDateTime"); if (!string.IsNullOrWhiteSpace(premiereDateString)) { // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ @@ -1276,9 +1279,9 @@ namespace MediaBrowser.MediaEncoding.Probing } } - var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription"); + var description = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/SubTitleDescription"); - var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle"); + var subTitle = FFProbeHelpers.GetDictionaryValue(data.Format.Tags, "WM/SubTitle"); // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/ From f9a454628d7204bc30d64f9c0589d766ad5f3109 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 22 Dec 2019 22:21:41 +0100 Subject: [PATCH 57/81] Preformance!!! --- .../Json/Converters/JsonInt32Converter.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs index 0fd68babe1..fe5dd6cd4d 100644 --- a/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonInt32Converter.cs @@ -15,7 +15,20 @@ namespace MediaBrowser.Common.Json.Converters public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { static void ThrowFormatException() => throw new FormatException("Invalid format for an integer."); - ReadOnlySpan span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + ReadOnlySpan span = stackalloc byte[0]; + + if (reader.HasValueSequence) + { + long sequenceLength = reader.ValueSequence.Length; + Span stackSpan = stackalloc byte[(int)sequenceLength]; + reader.ValueSequence.CopyTo(stackSpan); + span = stackSpan; + } + else + { + span = reader.ValueSpan; + } + if (!Utf8Parser.TryParse(span, out int number, out _)) { ThrowFormatException(); @@ -28,7 +41,7 @@ namespace MediaBrowser.Common.Json.Converters public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) { static void ThrowInvalidOperationException() => throw new InvalidOperationException(); - Span span = new byte[16]; + Span span = stackalloc byte[16]; if (Utf8Formatter.TryFormat(value, span, out int bytesWritten)) { writer.WriteStringValue(span.Slice(0, bytesWritten)); From a41ec5c9d4642f28cc1a277f555fc6a4f2929b85 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 23 Dec 2019 15:29:30 +0100 Subject: [PATCH 58/81] Fix typo --- MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs index d5529e56c8..6b60b66c0a 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The filename. [JsonPropertyName("filename")] - public string Fileame { get; set; } + public string Filename { get; set; } /// /// Gets or sets the nb_streams. From 59cdfdc2d9435f06dc535837ae9d1fe63543bb08 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 2 Jan 2020 14:38:13 +0100 Subject: [PATCH 59/81] Fix JSON subtitle writer --- MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs index 241ebc6df5..1b452b0cec 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(); } } } From 09d1f976d994f13dd6e69bae3d371262abb0d56d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 2 Jan 2020 20:19:01 +0100 Subject: [PATCH 60/81] Replace unicode char with its integer value --- .../Probing/ProbeResultNormalizer.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 54d02fc9f7..3067238f73 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1334,24 +1334,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: 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) @@ -1359,7 +1360,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)) { From 599432890311c9255cb376bbeb6f5f4f673fbf1b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 2 Jan 2020 21:22:10 +0100 Subject: [PATCH 61/81] Fix baseurl (again) --- .../ApplicationHost.cs | 40 +++++++++++++------ .../IServerApplicationHost.cs | 8 ++-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 67bc0cd2b1..9b853c6d13 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -103,14 +103,11 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.OpenApi.Models; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations @@ -1478,7 +1475,7 @@ namespace Emby.Server.Implementations /// /// The IPv6 address. /// The IPv6 address without the scope id. - private string RemoveScopeId(string address) + private ReadOnlySpan RemoveScopeId(ReadOnlySpan address) { var index = address.IndexOf('%'); if (index == -1) @@ -1486,33 +1483,50 @@ namespace Emby.Server.Implementations return address; } - return address.Substring(0, index); + return address.Slice(0, index); } + /// public string GetLocalApiUrl(IPAddress ipAddress) { if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { var str = RemoveScopeId(ipAddress.ToString()); + Span span = new char[str.Length + 2]; + span[0] = '['; + str.CopyTo(span.Slice(1)); + span[^1] = ']'; - return GetLocalApiUrl("[" + str + "]"); + return GetLocalApiUrl(span); } return GetLocalApiUrl(ipAddress.ToString()); } - public string GetLocalApiUrl(string host) + /// + public string GetLocalApiUrl(ReadOnlySpan host) { + var url = new StringBuilder(64); if (EnableHttps) { - return string.Format("https://{0}:{1}", - host, - HttpsPort.ToString(CultureInfo.InvariantCulture)); + url.Append("https://"); + } + else + { + url.Append("http://"); } - return string.Format("http://{0}:{1}", - host, - HttpPort.ToString(CultureInfo.InvariantCulture)); + url.Append(host) + .Append(':') + .Append(HttpPort); + + string baseUrl = ServerConfigurationManager.Configuration.BaseUrl; + if (baseUrl.Length != 0) + { + url.Append('/').Append(baseUrl); + } + + return url.ToString(); } public Task> GetLocalIpAddresses(CancellationToken cancellationToken) diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index b3c56bdd5f..25f0905eb8 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -71,13 +71,15 @@ namespace MediaBrowser.Controller /// /// Gets the local API URL. /// - /// The host. - /// System.String. - string GetLocalApiUrl(string host); + /// The hostname. + /// The local API URL. + string GetLocalApiUrl(ReadOnlySpan hostname); /// /// Gets the local API URL. /// + /// The IP address. + /// The local API URL. string GetLocalApiUrl(IPAddress address); void LaunchUrl(string url); From e5e0166951414044b4c251fc562bb8d40620ca1f Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 5 Jan 2020 15:59:31 +0900 Subject: [PATCH 62/81] update dotnet version for ci --- .ci/azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 13cc67528f..6e6cd00587 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -66,6 +66,12 @@ jobs: overWrite: true # Optional flattenFolders: false # Optional + - task: UseDotNet@2 + displayName: Use Correct DotNet Version + inputs: + packageType: sdk + version: 3.1.100 + - task: DotNetCoreCLI@2 displayName: Publish inputs: From f1008ab58d1b7dc9139f31e0a60f15e4be96aa4f Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 5 Jan 2020 16:01:42 +0900 Subject: [PATCH 63/81] update job names --- .ci/azure-pipelines.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 6e6cd00587..9927536238 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -19,9 +19,9 @@ jobs: vmImage: ubuntu-latest strategy: matrix: - release: + Release: BuildConfiguration: Release - debug: + Debug: BuildConfiguration: Debug maxParallel: 2 steps: @@ -191,12 +191,12 @@ jobs: # #publishRunAttachments: true # Optional - job: main_build_win - displayName: Main Build Windows + displayName: Publish Windows pool: vmImage: windows-latest strategy: matrix: - release: + Release: BuildConfiguration: Release maxParallel: 2 steps: @@ -373,5 +373,3 @@ jobs: script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines' workingDirectory: $(System.ArtifactsDirectory) # Optional #failOnStderr: false # Optional - - From 303385dfa2799df9162aeb64294991b3bd4108c8 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 5 Jan 2020 16:08:19 +0900 Subject: [PATCH 64/81] remove unused parameters --- .ci/azure-pipelines.yml | 61 +---------------------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 9927536238..7427190e61 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -141,54 +141,12 @@ jobs: !**\obj\** !**\xunit.runner.visualstudio.testadapter.dll !**\xunit.runner.visualstudio.dotnetcore.testadapter.dll - #testPlan: # Required when testSelector == TestPlan - #testSuite: # Required when testSelector == TestPlan - #testConfiguration: # Required when testSelector == TestPlan - #tcmTestRun: '$(test.RunId)' # Optional searchFolder: '$(System.DefaultWorkingDirectory)' - #testFiltercriteria: # Optional - #runOnlyImpactedTests: False # Optional - #runAllTestsAfterXBuilds: '50' # Optional - #uiTests: false # Optional - #vstestLocationMethod: 'version' # Optional. Options: version, location - #vsTestVersion: 'latest' # Optional. Options: latest, 16.0, 15.0, 14.0, toolsInstaller - #vstestLocation: # Optional - #runSettingsFile: # Optional - #overrideTestrunParameters: # Optional - #pathtoCustomTestAdapters: # Optional runInParallel: True # Optional runTestsInIsolation: True # Optional codeCoverageEnabled: True # Optional - #otherConsoleOptions: # Optional - #distributionBatchType: 'basedOnTestCases' # Optional. Options: basedOnTestCases, basedOnExecutionTime, basedOnAssembly - #batchingBasedOnAgentsOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customBatchSize - #customBatchSizeValue: '10' # Required when distributionBatchType == BasedOnTestCases && BatchingBasedOnAgentsOption == CustomBatchSize - #batchingBasedOnExecutionTimeOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customTimeBatchSize - #customRunTimePerBatchValue: '60' # Required when distributionBatchType == BasedOnExecutionTime && BatchingBasedOnExecutionTimeOption == CustomTimeBatchSize - #dontDistribute: False # Optional - #testRunTitle: # Optional - #platform: # Optional configuration: 'Debug' # Optional publishRunAttachments: true # Optional - #diagnosticsEnabled: false # Optional - #collectDumpOn: 'onAbortOnly' # Optional. Options: onAbortOnly, always, never - #rerunFailedTests: False # Optional - #rerunType: 'basedOnTestFailurePercentage' # Optional. Options: basedOnTestFailurePercentage, basedOnTestFailureCount - #rerunFailedThreshold: '30' # Optional - #rerunFailedTestCasesMaxLimit: '5' # Optional - #rerunMaxAttempts: '3' # Optional - - # - task: PublishTestResults@2 - # inputs: - # testResultsFormat: 'VSTest' # Options: JUnit, NUnit, VSTest, xUnit, cTest - # testResultsFiles: '**/*.trx' - # #searchFolder: '$(System.DefaultWorkingDirectory)' # Optional - # mergeTestResults: true # Optional - # #failTaskOnFailedTests: false # Optional - # #testRunTitle: # Optional - # #buildPlatform: # Optional - # #buildConfiguration: # Optional - # #publishRunAttachments: true # Optional - job: main_build_win displayName: Publish Windows @@ -252,11 +210,7 @@ jobs: targetType: 'filePath' # Optional. Options: filePath, inline filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory) - #script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue - #failOnStderr: false # Optional - #ignoreLASTEXITCODE: false # Optional - #pwsh: false # Optional workingDirectory: $(Build.SourcesDirectory) # Optional - task: CopyFiles@2 @@ -304,16 +258,9 @@ jobs: displayName: Download the New Assembly Build Artifact inputs: source: 'current' # Options: current, specific - #preferTriggeringPipeline: false # Optional - #tags: # Optional artifact: '$(NugetPackageName)' # Optional - #patterns: '**' # Optional path: '$(System.ArtifactsDirectory)/new-artifacts' - #project: # Required when source == Specific - #pipeline: # Required when source == Specific runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific - #runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch - #runId: # Required when source == Specific && runVersion == Specific - task: CopyFiles@2 displayName: Copy New Assembly to new-release folder @@ -329,16 +276,12 @@ jobs: displayName: Download the Reference Assembly Build Artifact inputs: source: 'specific' # Options: current, specific - #preferTriggeringPipeline: false # Optional - #tags: # Optional artifact: '$(NugetPackageName)' # Optional - #patterns: '**' # Optional path: '$(System.ArtifactsDirectory)/current-artifacts' project: '$(System.TeamProjectId)' # Required when source == Specific pipeline: '$(System.DefinitionId)' # Required when source == Specific runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch - #runId: # Required when source == Specific && runVersion == Specific - task: CopyFiles@2 displayName: Copy Reference Assembly to current-release folder @@ -356,7 +299,6 @@ jobs: connection: Jellyfin Release Download userRepository: EraYaN/dotnet-compatibility defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag - #version: # Required when defaultVersionType != Latest itemPattern: '**-ci.zip' # Optional downloadPath: '$(System.ArtifactsDirectory)' @@ -371,5 +313,4 @@ jobs: displayName: Execute ABI compatibility check tool inputs: script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines' - workingDirectory: $(System.ArtifactsDirectory) # Optional - #failOnStderr: false # Optional + workingDirectory: $(System.ArtifactsDirectory) From c7d5f35eb7e246191eb2d861eec8eb35be86a277 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 5 Jan 2020 16:32:24 +0900 Subject: [PATCH 65/81] update display names for some ci steps --- .ci/azure-pipelines.yml | 48 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 7427190e61..6d2a0a42be 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -31,32 +31,32 @@ jobs: persistCredentials: true - task: CmdLine@2 - displayName: "Check out web" + displayName: "Clone Web Client (Master, Release, or Tag)" condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web' - task: CmdLine@2 - displayName: "Check out web (PR)" + displayName: "Clone Web Client (PR)" condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest')) inputs: script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web' - task: NodeTool@0 - displayName: 'Install Node.js' + displayName: 'Install Node' condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: versionSpec: '10.x' - task: CmdLine@2 - displayName: "Build Web UI" + displayName: "Build Web Client" condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: script: yarn install workingDirectory: $(Agent.TempDirectory)/jellyfin-web - task: CopyFiles@2 - displayName: Copy the web UI + displayName: 'Copy Web Client' condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional @@ -67,13 +67,13 @@ jobs: flattenFolders: false # Optional - task: UseDotNet@2 - displayName: Use Correct DotNet Version + displayName: 'Update DotNet' inputs: packageType: sdk version: 3.1.100 - task: DotNetCoreCLI@2 - displayName: Publish + displayName: 'Publish Server' inputs: command: publish publishWebProjects: false @@ -164,32 +164,32 @@ jobs: persistCredentials: true - task: CmdLine@2 - displayName: "Check out web (master, release or tag)" + displayName: "Clone Web Client (Master, Release, or Tag)" condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web' - task: CmdLine@2 - displayName: "Check out web (PR)" + displayName: "Clone Web Client (PR)" condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest')) inputs: script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web' - task: NodeTool@0 - displayName: 'Install Node.js' + displayName: 'Install Node' condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: versionSpec: '10.x' - task: CmdLine@2 - displayName: "Build Web UI" + displayName: "Build Web Client" condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: script: yarn install workingDirectory: $(Agent.TempDirectory)/jellyfin-web - task: CopyFiles@2 - displayName: Copy the web UI + displayName: 'Copy Web Client' condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional @@ -200,12 +200,12 @@ jobs: flattenFolders: false # Optional - task: CmdLine@2 - displayName: Clone the UX repository + displayName: 'Clone UX Repository' inputs: script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux - task: PowerShell@2 - displayName: Build the NSIS Installer + displayName: 'Build NSIS Installer' inputs: targetType: 'filePath' # Optional. Options: filePath, inline filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath @@ -214,7 +214,7 @@ jobs: workingDirectory: $(Build.SourcesDirectory) # Optional - task: CopyFiles@2 - displayName: Copy the NSIS Installer to the artifact directory + displayName: 'Copy NSIS Installer' inputs: sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional contents: 'jellyfin*.exe' @@ -224,7 +224,7 @@ jobs: flattenFolders: true # Optional - task: PublishPipelineArtifact@0 - displayName: 'Publish Setup Artifact' + displayName: 'Publish Artifact Setup' condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded()) inputs: targetPath: '$(build.artifactstagingdirectory)/setup' @@ -255,7 +255,7 @@ jobs: - checkout: none - task: DownloadPipelineArtifact@2 - displayName: Download the New Assembly Build Artifact + displayName: 'Download New Assembly Build Artifact' inputs: source: 'current' # Options: current, specific artifact: '$(NugetPackageName)' # Optional @@ -263,7 +263,7 @@ jobs: runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific - task: CopyFiles@2 - displayName: Copy New Assembly to new-release folder + displayName: 'Copy New Assembly Build Artifact' inputs: sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional contents: '**/*.dll' @@ -273,7 +273,7 @@ jobs: flattenFolders: true # Optional - task: DownloadPipelineArtifact@2 - displayName: Download the Reference Assembly Build Artifact + displayName: 'Download Reference Assembly Build Artifact' inputs: source: 'specific' # Options: current, specific artifact: '$(NugetPackageName)' # Optional @@ -284,7 +284,7 @@ jobs: runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch - task: CopyFiles@2 - displayName: Copy Reference Assembly to current-release folder + displayName: 'Copy Reference Assembly Build Artifact' inputs: sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional contents: '**/*.dll' @@ -294,7 +294,7 @@ jobs: flattenFolders: true # Optional - task: DownloadGitHubRelease@0 - displayName: Download ABI compatibility check tool from GitHub + displayName: 'Download ABI Compatibility Check Tool' inputs: connection: Jellyfin Release Download userRepository: EraYaN/dotnet-compatibility @@ -303,14 +303,14 @@ jobs: downloadPath: '$(System.ArtifactsDirectory)' - task: ExtractFiles@1 - displayName: Extract ABI compatibility check tool + displayName: 'Extract ABI Compatibility Check Tool' inputs: archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip' destinationFolder: $(System.ArtifactsDirectory)/tools cleanDestinationFolder: true - task: CmdLine@2 - displayName: Execute ABI compatibility check tool + displayName: 'Execute ABI Compatibility Check Tool' inputs: script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines' - workingDirectory: $(System.ArtifactsDirectory) + workingDirectory: $(System.ArtifactsDirectory) # Optional From d59912450819f6b9e8cb5b2e8eeb70c4d9de1465 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 5 Jan 2020 16:39:48 +0900 Subject: [PATCH 66/81] manually disable compatibility check --- .ci/azure-pipelines.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 6d2a0a42be..fef2ecda96 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -235,7 +235,9 @@ jobs: pool: vmImage: ubuntu-latest dependsOn: main_build - condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds) + # disabled until ABI is expected to remain unchanged + # only execute for pull requests + condition: and(eq(1, 2), succeeded(), variables['System.PullRequest.PullRequestNumber']) strategy: matrix: Naming: From 2ca7200a0526e5374f0b0e10d6f5d22965bacaa5 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sun, 5 Jan 2020 19:24:24 +0100 Subject: [PATCH 67/81] Updated for v0.0.5 of the compat checker. --- .ci/azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index fef2ecda96..ba9989e70f 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -311,8 +311,9 @@ jobs: destinationFolder: $(System.ArtifactsDirectory)/tools cleanDestinationFolder: true + # The `--warnings-only` switch will swallow the return code and not emit any errors. - task: CmdLine@2 displayName: 'Execute ABI Compatibility Check Tool' inputs: - script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines' + script: 'dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only' workingDirectory: $(System.ArtifactsDirectory) # Optional From 65f65db8b9c7dcc2022ff6542a50481cb8d0cabf Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sun, 5 Jan 2020 19:25:33 +0100 Subject: [PATCH 68/81] Re-enable compat checks. --- .ci/azure-pipelines.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index ba9989e70f..e5d09b0aba 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -235,9 +235,8 @@ jobs: pool: vmImage: ubuntu-latest dependsOn: main_build - # disabled until ABI is expected to remain unchanged # only execute for pull requests - condition: and(eq(1, 2), succeeded(), variables['System.PullRequest.PullRequestNumber']) + condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) strategy: matrix: Naming: From 21b78f44c8efe7eaa01430f23075d605c3d05356 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Sun, 5 Jan 2020 19:30:32 +0100 Subject: [PATCH 69/81] Add new dotnet version to compat stage as well. --- .ci/azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index e5d09b0aba..143873266d 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -254,6 +254,12 @@ jobs: maxParallel: 2 steps: - checkout: none + + - task: UseDotNet@2 + displayName: 'Update DotNet' + inputs: + packageType: sdk + version: 3.1.100 - task: DownloadPipelineArtifact@2 displayName: 'Download New Assembly Build Artifact' From 0a483c83f94983e8e12acc8643c26452a3cb5536 Mon Sep 17 00:00:00 2001 From: Cota Fainado <20hvoby5pb@protonmail.com> Date: Sat, 4 Jan 2020 23:03:09 +0000 Subject: [PATCH 70/81] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- .../Localization/Core/pt.json | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index 0967ef424b..c8d4b4c323 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -1 +1,96 @@ -{} +{ + "HeaderLiveTV": "TV em Directo", + "Collections": "Colecções", + "Books": "Livros", + "Artists": "Artistas", + "Albums": "Álbuns", + "HeaderNextUp": "A Seguir", + "HeaderFavoriteSongs": "Músicas Favoritas", + "HeaderFavoriteArtists": "Artistas Favoritos", + "HeaderFavoriteAlbums": "Álbuns Favoritos", + "HeaderFavoriteEpisodes": "Episódios Favoritos", + "HeaderFavoriteShows": "Séries Favoritas", + "HeaderContinueWatching": "Continuar a Ver", + "HeaderAlbumArtists": "Artistas do Álbum", + "Genres": "Géneros", + "Folders": "Pastas", + "Favorites": "Favoritos", + "Channels": "Canais", + "UserDownloadingItemWithValues": "{0} está a transferir {1}", + "VersionNumber": "Versão {0}", + "ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia", + "UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}", + "UserStartedPlayingItemWithValues": "{0} está a reproduzir {1} em {2}", + "UserPolicyUpdatedWithName": "A política do utilizador {0} foi alterada", + "UserPasswordChangedWithName": "A palavra-passe do utilizador {0} foi alterada", + "UserOnlineFromDevice": "{0} ligou-se a partir de {1}", + "UserOfflineFromDevice": "{0} desligou-se a partir de {1}", + "UserLockedOutWithName": "Utilizador {0} bloqueado", + "UserDeletedWithName": "Utilizador {0} removido", + "UserCreatedWithName": "Utilizador {0} criado", + "User": "Utilizador", + "TvShows": "Programas", + "System": "Sistema", + "SubtitlesDownloadedForItem": "Legendas transferidas para {0}", + "SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas de {0} para {1}", + "StartupEmbyServerIsLoading": "O servidor Jellyfin está a iniciar. Tente novamente dentro de momentos.", + "ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciado", + "ScheduledTaskStartedWithName": "{0} iniciou", + "ScheduledTaskFailedWithName": "{0} falhou", + "ProviderValue": "Fornecedor: {0}", + "PluginUpdatedWithName": "{0} foi actualizado", + "PluginUninstalledWithName": "{0} foi desinstalado", + "PluginInstalledWithName": "{0} foi instalado", + "Plugin": "Extensão", + "NotificationOptionVideoPlaybackStopped": "Reprodução de vídeo parada", + "NotificationOptionVideoPlayback": "Reprodução de vídeo iniciada", + "NotificationOptionUserLockedOut": "Utilizador bloqueado", + "NotificationOptionTaskFailed": "Falha em tarefa agendada", + "NotificationOptionServerRestartRequired": "É necessário reiniciar o servidor", + "NotificationOptionPluginUpdateInstalled": "Extensão actualizada", + "NotificationOptionPluginUninstalled": "Extensão desinstalada", + "NotificationOptionPluginInstalled": "Extensão instalada", + "NotificationOptionPluginError": "Falha na extensão", + "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", + "NotificationOptionInstallationFailed": "Falha de instalação", + "NotificationOptionCameraImageUploaded": "Imagem da câmara enviada", + "NotificationOptionAudioPlaybackStopped": "Reprodução Parada", + "NotificationOptionAudioPlayback": "Reprodução Iniciada", + "NotificationOptionApplicationUpdateInstalled": "A actualização da aplicação foi instalada", + "NotificationOptionApplicationUpdateAvailable": "Uma actualização da aplicação está disponível", + "NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para transferência.", + "NameSeasonUnknown": "Temporada Desconhecida", + "NameSeasonNumber": "Temporada {0}", + "NameInstallFailed": "Falha na instalação de {0}", + "MusicVideos": "Videoclips", + "Music": "Música", + "MixedContent": "Conteúdo Misto", + "MessageServerConfigurationUpdated": "A configuração do servidor foi actualizada", + "MessageNamedServerConfigurationUpdatedWithValue": "Configurações do servidor na secção {0} foram atualizadas", + "MessageApplicationUpdatedTo": "O servidor Jellyfin foi actualizado para a versão {0}", + "MessageApplicationUpdated": "O servidor Jellyfin foi actualizado", + "Latest": "Mais Recente", + "LabelRunningTimeValue": "Duração: {0}", + "LabelIpAddressValue": "Endereço IP: {0}", + "ItemRemovedWithName": "{0} foi removido da biblioteca", + "ItemAddedWithName": "{0} foi adicionado à biblioteca", + "Inherit": "Herdar", + "HomeVideos": "Vídeos Caseiros", + "HeaderRecordingGroups": "Grupos de Gravação", + "ValueSpecialEpisodeName": "Especial - {0}", + "Sync": "Sincronização", + "Songs": "Músicas", + "Shows": "Séries", + "Playlists": "Listas de Reprodução", + "Photos": "Fotografias", + "Movies": "Filmes", + "HeaderCameraUploads": "Envios a partir da câmara", + "FailedLoginAttemptWithUserName": "Tentativa de ligação a partir de {0} falhou", + "DeviceOnlineWithName": "{0} ligou-se", + "DeviceOfflineWithName": "{0} desligou-se", + "ChapterNameValue": "Capítulo {0}", + "CameraImageUploadedFrom": "Uma nova imagem de câmara foi enviada a partir de {0}", + "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", + "Application": "Aplicação", + "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}" +} From a4d0e4b38ee23a7edde067f093580199a1014658 Mon Sep 17 00:00:00 2001 From: Thoscellen Date: Sat, 4 Jan 2020 23:16:08 +0000 Subject: [PATCH 71/81] Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 9805992be9..e1dce82ffe 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -19,10 +19,10 @@ "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", - "HeaderFavoriteArtists": "Artistes favoris", + "HeaderFavoriteArtists": "Artistes préférés", "HeaderFavoriteEpisodes": "Épisodes favoris", "HeaderFavoriteShows": "Séries favorites", - "HeaderFavoriteSongs": "Chansons favorites", + "HeaderFavoriteSongs": "Chansons préférées", "HeaderLiveTV": "TV en direct", "HeaderNextUp": "À suivre", "HeaderRecordingGroups": "Groupes d'enregistrements", From 80dfc78ba5ec3895fd374a5bd761d0d0e9e6a862 Mon Sep 17 00:00:00 2001 From: samucamg Date: Sun, 5 Jan 2020 21:47:39 +0000 Subject: [PATCH 72/81] Translated using Weblate (Portuguese) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt/ --- Emby.Server.Implementations/Localization/Core/pt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index c8d4b4c323..ef8d988c87 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -1,5 +1,5 @@ { - "HeaderLiveTV": "TV em Directo", + "HeaderLiveTV": "TV ao Vivo", "Collections": "Colecções", "Books": "Livros", "Artists": "Artistas", From 70210b47a4dedd3d3905fc4d234c0346f58d064f Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 6 Jan 2020 10:39:01 +0100 Subject: [PATCH 73/81] Update MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs Co-Authored-By: dkanada --- MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 3067238f73..ff3596a85e 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1345,7 +1345,7 @@ namespace MediaBrowser.MediaEncoding.Probing } } - // REVIEW: + // 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[197]; From 49fef5f09c12ff4b40b7213a9e155802acf45fff Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 7 Jan 2020 11:13:11 +0100 Subject: [PATCH 74/81] Update MediaBrowser.MediaEncoding/Probing/MediaChapter.cs Co-Authored-By: Erwin de Haan --- MediaBrowser.MediaEncoding/Probing/MediaChapter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs index a3607a7600..6a45ccf495 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaChapter.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.MediaEncoding.Probing public class MediaChapter { [JsonPropertyName("id")] - public int id { get; set; } + public int Id { get; set; } [JsonPropertyName("time_base")] public string TimeBase { get; set; } From 9dfafb9e9fcddb253b157fe03b085b7fceef4290 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 7 Jan 2020 11:13:47 +0100 Subject: [PATCH 75/81] Update MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs Co-Authored-By: Erwin de Haan --- MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs index 6b60b66c0a..8af122ef9b 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaFormatInfo.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The filename. [JsonPropertyName("filename")] - public string Filename { get; set; } + public string FileName { get; set; } /// /// Gets or sets the nb_streams. From a253fa616da3fd982ca2190b69d25853893665f1 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 26 Dec 2019 23:09:00 +0100 Subject: [PATCH 76/81] Fix build and address comments --- .../ApplicationHost.cs | 2 +- .../Data/SqliteItemRepository.cs | 34 ++++--- .../Library/MediaSourceManager.cs | 9 -- Jellyfin.Api/Controllers/StartupController.cs | 1 - .../Attachments/AttachmentService.cs | 19 ++-- .../Library/IMediaSourceManager.cs | 11 +-- .../MediaEncoding/IAttachmentExtractor.cs | 4 +- .../Attachments/AttachmentExtractor.cs | 97 ++++++++++--------- .../Encoder/EncodingUtils.cs | 2 +- 9 files changed, 82 insertions(+), 97 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7c3f59af2e..c5ac27ed48 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -878,7 +878,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager)); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(typeof(MediaBrowser.Controller.MediaEncoding.IAttachmentExtractor),typeof(MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor)); + serviceCollection.AddSingleton(typeof(IAttachmentExtractor), typeof(MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor)); _displayPreferencesRepository.Initialize(); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2206816a50..91ca8477d9 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -55,8 +55,8 @@ namespace Emby.Server.Implementations.Data queryPrefixText.Append("insert into mediaattachments ("); foreach (var column in _mediaAttachmentSaveColumns) { - queryPrefixText.Append(column); - queryPrefixText.Append(','); + queryPrefixText.Append(column) + .Append(','); } queryPrefixText.Length -= 1; @@ -449,6 +449,7 @@ namespace Emby.Server.Implementations.Data "Filename", "MIMEType" }; + private static readonly string _mediaAttachmentInsertPrefix; private static string GetSaveItemCommandText() @@ -6208,7 +6209,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return list; } - public void SaveMediaAttachments(Guid id, List attachments, CancellationToken cancellationToken) + public void SaveMediaAttachments( + Guid id, + List attachments, + CancellationToken cancellationToken) { CheckDisposed(); if (id == Guid.Empty) @@ -6237,24 +6241,22 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } - private void InsertMediaAttachments(byte[] idBlob, List attachments, IDatabaseConnection db, CancellationToken cancellationToken) + private void InsertMediaAttachments( + byte[] idBlob, + List attachments, + IDatabaseConnection db, + CancellationToken cancellationToken) { - var startIndex = 0; - var insertAtOnce = 10; + const int InsertAtOnce = 10; - while (startIndex < attachments.Count) + 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++) { - if (i != startIndex) - { - insertText.Append(','); - } - var index = i.ToString(CultureInfo.InvariantCulture); insertText.Append("(@ItemId, "); @@ -6265,9 +6267,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type insertText.Length -= 1; - insertText.Append(")"); + insertText.Append("),"); } + insertText.Length--; + cancellationToken.ThrowIfCancellationRequested(); using (var statement = PrepareStatement(db, insertText.ToString())) @@ -6291,8 +6295,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.Reset(); statement.MoveNext(); } - - startIndex += insertAtOnce; } } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 4eff2807cf..ba1564d1f8 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -136,15 +136,6 @@ namespace Emby.Server.Implementations.Library return _itemRepo.GetMediaAttachments(query); } - /// - public List GetMediaAttachments(string mediaSourceId) - { - return GetMediaAttachments(new MediaAttachmentQuery - { - ItemId = new Guid(mediaSourceId) - }); - } - /// public List GetMediaAttachments(Guid itemId) { diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 1014c8c56b..afc9b8f3da 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -96,7 +96,6 @@ namespace Jellyfin.Api.Controllers public StartupUserDto GetFirstUser() { var user = _userManager.Users.First(); - return new StartupUserDto { Name = user.Name, diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs index 1ebfaa14bc..ef09951b65 100644 --- a/MediaBrowser.Api/Attachments/AttachmentService.cs +++ b/MediaBrowser.Api/Attachments/AttachmentService.cs @@ -1,22 +1,14 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; -using MimeTypes = MediaBrowser.Model.Net.MimeTypes; namespace MediaBrowser.Api.Attachments { @@ -38,7 +30,13 @@ namespace MediaBrowser.Api.Attachments private readonly ILibraryManager _libraryManager; private readonly IAttachmentExtractor _attachmentExtractor; - public AttachmentService(ILibraryManager libraryManager, IAttachmentExtractor attachmentExtractor) + public AttachmentService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + ILibraryManager libraryManager, + IAttachmentExtractor attachmentExtractor) + : base(logger, serverConfigurationManager, httpResultFactory) { _libraryManager = libraryManager; _attachmentExtractor = attachmentExtractor; @@ -46,7 +44,6 @@ namespace MediaBrowser.Api.Attachments public async Task Get(GetAttachment request) { - var item = (Video)_libraryManager.GetItemById(request.Id); var (attachment, attachmentStream) = await GetAttachment(request).ConfigureAwait(false); var mime = string.IsNullOrWhiteSpace(attachment.MIMEType) ? "application/octet-stream" : attachment.MIMEType; diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index dda8d397a1..09e6fda885 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -41,21 +41,14 @@ namespace MediaBrowser.Controller.Library /// /// Gets the media attachments. /// - /// The item identifier. + /// The item identifier. /// IEnumerable<MediaAttachment>. List GetMediaAttachments(Guid itemId); /// /// Gets the media attachments. /// - /// The The media source identifier. - /// IEnumerable<MediaAttachment>. - - List GetMediaAttachments(string mediaSourceId); - /// - /// Gets the media attachments. - /// - /// The query. + /// The query. /// IEnumerable<MediaAttachment>. List GetMediaAttachments(MediaAttachmentQuery query); diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs index 59c0a3f214..7c7e84de64 100644 --- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs +++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs @@ -2,14 +2,14 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.MediaEncoding { public interface IAttachmentExtractor { - Task<(MediaAttachment attachment, Stream stream)> GetAttachment(BaseItem item, + Task<(MediaAttachment attachment, Stream stream)> GetAttachment( + BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken); diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index cb22343c48..c371e8b946 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -4,44 +4,41 @@ using System.Collections.Concurrent; using System.Globalization; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; -using UtfUnknown; namespace MediaBrowser.MediaEncoding.Attachments { - public class AttachmentExtractor : IAttachmentExtractor + public class AttachmentExtractor : IAttachmentExtractor, IDisposable { - private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; private readonly IMediaSourceManager _mediaSourceManager; + private readonly ConcurrentDictionary _semaphoreLocks = + new ConcurrentDictionary(); + + private bool _disposed = false; + public AttachmentExtractor( - ILibraryManager libraryManager, ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IMediaSourceManager mediaSourceManager) { - _libraryManager = libraryManager; _logger = logger; _appPaths = appPaths; _fileSystem = fileSystem; @@ -49,8 +46,7 @@ namespace MediaBrowser.MediaEncoding.Attachments _mediaSourceManager = mediaSourceManager; } - private string AttachmentCachePath => Path.Combine(_appPaths.DataPath, "attachments"); - + /// public async Task<(MediaAttachment attachment, Stream stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken) { if (item == null) @@ -70,12 +66,14 @@ namespace MediaBrowser.MediaEncoding.Attachments { 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); @@ -87,49 +85,32 @@ namespace MediaBrowser.MediaEncoding.Attachments MediaAttachment mediaAttachment, CancellationToken cancellationToken) { - var inputFiles = new[] { mediaSource.Path }; - var attachmentPath = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, mediaAttachment, cancellationToken).ConfigureAwait(false); - var stream = await GetAttachmentStream(attachmentPath, cancellationToken).ConfigureAwait(false); - return stream; + var attachmentPath = await GetReadableFile(mediaSource.Path, mediaSource.Path, mediaSource.Protocol, mediaAttachment, cancellationToken).ConfigureAwait(false); + return File.OpenRead(attachmentPath); } - private async Task GetAttachmentStream( - string path, - CancellationToken cancellationToken) - { - return File.OpenRead(path); - } - - private async Task GetReadableFile( + private async Task GetReadableFile( string mediaPath, - string[] inputFiles, + string inputFile, MediaProtocol protocol, MediaAttachment mediaAttachment, CancellationToken cancellationToken) { var outputPath = GetAttachmentCachePath(mediaPath, protocol, mediaAttachment.Index); - await ExtractAttachment(inputFiles, protocol, mediaAttachment.Index, outputPath, cancellationToken) + await ExtractAttachment(inputFile, protocol, mediaAttachment.Index, outputPath, cancellationToken) .ConfigureAwait(false); return outputPath; } - private readonly ConcurrentDictionary _semaphoreLocks = - new ConcurrentDictionary(); - - private SemaphoreSlim GetLock(string filename) - { - return _semaphoreLocks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1)); - } - private async Task ExtractAttachment( - string[] inputFiles, + string inputFile, MediaProtocol protocol, int attachmentStreamIndex, string outputPath, CancellationToken cancellationToken) { - var semaphore = GetLock(outputPath); + var semaphore = _semaphoreLocks.GetOrAdd(outputPath, key => new SemaphoreSlim(1, 1)); await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -137,7 +118,11 @@ namespace MediaBrowser.MediaEncoding.Attachments { if (!File.Exists(outputPath)) { - await ExtractAttachmentInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), attachmentStreamIndex, outputPath, cancellationToken).ConfigureAwait(false); + await ExtractAttachmentInternal( + _mediaEncoder.GetInputArgument(new[] { inputFile }, protocol), + attachmentStreamIndex, + outputPath, + cancellationToken).ConfigureAwait(false); } } finally @@ -186,16 +171,7 @@ namespace MediaBrowser.MediaEncoding.Attachments _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); - try - { - process.Start(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error starting ffmpeg"); - - throw; - } + process.Start(); var processTcs = new TaskCompletionSource(); process.EnableRaisingEvents = true; @@ -216,6 +192,7 @@ namespace MediaBrowser.MediaEncoding.Attachments _logger.LogError(ex, "Error killing attachment extraction process"); } } + var exitCode = ranToCompletion ? process.ExitCode : -1; process.Dispose(); @@ -270,9 +247,35 @@ namespace MediaBrowser.MediaEncoding.Attachments { filename = (mediaPath + attachmentStreamIndex.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("D"); } + var prefix = filename.Substring(0, 1); - return Path.Combine(AttachmentCachePath, prefix, filename); + return Path.Combine(_appPaths.DataPath, "attachments", prefix, filename); } + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + + } + + _disposed = true; + } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index d4aede572b..c5da42089a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// The path. /// System.String. - private static string GetFileInputArgument(string path) + public static string GetFileInputArgument(string path) { if (path.IndexOf("://") != -1) { From 8a0ef4103632b2888249af2f385e1e7bfc06fbfe Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 26 Dec 2019 23:20:31 +0100 Subject: [PATCH 77/81] Minor improvements --- .../Data/SqliteItemRepository.cs | 4 ++-- MediaBrowser.Controller/Persistence/IItemRepository.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs | 2 +- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 2 +- MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 91ca8477d9..2ff19a6397 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6211,7 +6211,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type public void SaveMediaAttachments( Guid id, - List attachments, + IReadOnlyList attachments, CancellationToken cancellationToken) { CheckDisposed(); @@ -6243,7 +6243,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type private void InsertMediaAttachments( byte[] idBlob, - List attachments, + IReadOnlyList attachments, IDatabaseConnection db, CancellationToken cancellationToken) { diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 68df20c3af..5a5b7f58f0 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -91,7 +91,7 @@ namespace MediaBrowser.Controller.Persistence /// The identifier. /// The attachments. /// The cancellation token. - void SaveMediaAttachments(Guid id, List attachments, CancellationToken cancellationToken); + void SaveMediaAttachments(Guid id, IReadOnlyList attachments, CancellationToken cancellationToken); /// /// Gets the item ids. diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index c5da42089a..d4aede572b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// The path. /// System.String. - public static string GetFileInputArgument(string path) + private static string GetFileInputArgument(string path) { if (path.IndexOf("://") != -1) { diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 8a1aa55b69..5cb0565666 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Model.Dto public List MediaStreams { get; set; } - public List MediaAttachments { get; set; } + public IReadOnlyList MediaAttachments { get; set; } public string[] Formats { get; set; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index ae3e584d4b..2b178d4d4e 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -158,7 +158,7 @@ namespace MediaBrowser.Providers.MediaInfo MetadataRefreshOptions options) { List mediaStreams; - List mediaAttachments; + IReadOnlyList mediaAttachments; List chapters; if (mediaInfo != null) @@ -200,7 +200,7 @@ namespace MediaBrowser.Providers.MediaInfo else { mediaStreams = new List(); - mediaAttachments = new List(); + mediaAttachments = Array.Empty(); chapters = new List(); } @@ -213,13 +213,13 @@ namespace MediaBrowser.Providers.MediaInfo FetchEmbeddedInfo(video, mediaInfo, options, libraryOptions); FetchPeople(video, mediaInfo, options); video.Timestamp = mediaInfo.Timestamp; - video.Video3DFormat = video.Video3DFormat ?? mediaInfo.Video3DFormat; + video.Video3DFormat ??= mediaInfo.Video3DFormat; } var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); - video.Height = videoStream == null ? 0 : videoStream.Height ?? 0; - video.Width = videoStream == null ? 0 : videoStream.Width ?? 0; + video.Height = videoStream?.Height ?? 0; + video.Width = videoStream?.Width ?? 0; video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index; From 73fac50e57982ac46aea2b487e9906826c3dc3b2 Mon Sep 17 00:00:00 2001 From: dkanada Date: Wed, 8 Jan 2020 10:52:48 +0900 Subject: [PATCH 78/81] rename two properties based on code suggestions --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 8 ++++---- MediaBrowser.Api/Attachments/AttachmentService.cs | 2 +- .../Probing/ProbeResultNormalizer.cs | 4 ++-- MediaBrowser.Model/Entities/MediaAttachment.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 2ff19a6397..c514846e58 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -6288,8 +6288,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@Codec" + index, attachment.Codec); statement.TryBind("@CodecTag" + index, attachment.CodecTag); statement.TryBind("@Comment" + index, attachment.Comment); - statement.TryBind("@Filename" + index, attachment.Filename); - statement.TryBind("@MIMEType" + index, attachment.MIMEType); + statement.TryBind("@FileName" + index, attachment.FileName); + statement.TryBind("@MimeType" + index, attachment.MimeType); } statement.Reset(); @@ -6327,12 +6327,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type if (reader[6].SQLiteType != SQLiteType.Null) { - item.Filename = reader[5].ToString(); + item.FileName = reader[5].ToString(); } if (reader[6].SQLiteType != SQLiteType.Null) { - item.MIMEType = reader[6].ToString(); + item.MimeType = reader[6].ToString(); } return item; diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs index ef09951b65..1632ca1b06 100644 --- a/MediaBrowser.Api/Attachments/AttachmentService.cs +++ b/MediaBrowser.Api/Attachments/AttachmentService.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Api.Attachments public async Task Get(GetAttachment request) { var (attachment, attachmentStream) = await GetAttachment(request).ConfigureAwait(false); - var mime = string.IsNullOrWhiteSpace(attachment.MIMEType) ? "application/octet-stream" : attachment.MIMEType; + var mime = string.IsNullOrWhiteSpace(attachment.MimeType) ? "application/octet-stream" : attachment.MimeType; return ResultFactory.GetResult(Request, attachmentStream, mime); } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index f2056c5663..6664b34a5c 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -542,8 +542,8 @@ namespace MediaBrowser.MediaEncoding.Probing if (streamInfo.tags != null) { - attachment.Filename = GetDictionaryValue(streamInfo.tags, "filename"); - attachment.MIMEType = GetDictionaryValue(streamInfo.tags, "mimetype"); + attachment.FileName = GetDictionaryValue(streamInfo.tags, "filename"); + attachment.MimeType = GetDictionaryValue(streamInfo.tags, "mimetype"); attachment.Comment = GetDictionaryValue(streamInfo.tags, "comment"); } diff --git a/MediaBrowser.Model/Entities/MediaAttachment.cs b/MediaBrowser.Model/Entities/MediaAttachment.cs index 26279b72b8..8f8c3efd20 100644 --- a/MediaBrowser.Model/Entities/MediaAttachment.cs +++ b/MediaBrowser.Model/Entities/MediaAttachment.cs @@ -33,13 +33,13 @@ namespace MediaBrowser.Model.Entities /// Gets or sets the filename. /// /// The filename. - public string Filename { get; set; } + public string FileName { get; set; } /// /// Gets or sets the MIME type. /// /// The MIME type. - public string MIMEType { get; set; } + public string MimeType { get; set; } /// /// Gets or sets the delivery URL. From 75f19a762cf4cf769df7545612e68e95bb6905f2 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Wed, 8 Jan 2020 04:05:07 -0500 Subject: [PATCH 79/81] Re-order the path statement to avoid file issues Fixes #31874. --- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 442fbabd17..9568f62dff 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.IO } try { - return Path.Combine(Path.GetFullPath(folderPath), filePath); + return Path.GetFullPath(Path.Combine(folderPath, filePath)); } catch (ArgumentException) { From 277e9d2b0b9859aa374ed4c4add10f3c46dbdfd2 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 8 Jan 2020 18:13:11 +0100 Subject: [PATCH 80/81] fix build --- .../Probing/ProbeResultNormalizer.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 37baef5b0c..bd89c6cae9 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.MediaEncoding.Probing .Where(i => i != null) .ToList(); - if (data.format != null) + if (data.Format != null) { info.Container = NormalizeFormat(data.Format.FormatName); @@ -523,27 +523,27 @@ namespace MediaBrowser.MediaEncoding.Probing /// MediaAttachments. private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo) { - if (!string.Equals(streamInfo.codec_type, "attachment", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase)) { return null; } var attachment = new MediaAttachment { - Codec = streamInfo.codec_name, - Index = streamInfo.index + Codec = streamInfo.CodecName, + Index = streamInfo.Index }; - if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string)) + if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString)) { - attachment.CodecTag = streamInfo.codec_tag_string; + attachment.CodecTag = streamInfo.CodecTagString; } - if (streamInfo.tags != null) + if (streamInfo.Tags != null) { - attachment.FileName = GetDictionaryValue(streamInfo.tags, "filename"); - attachment.MimeType = GetDictionaryValue(streamInfo.tags, "mimetype"); - attachment.Comment = GetDictionaryValue(streamInfo.tags, "comment"); + attachment.FileName = GetDictionaryValue(streamInfo.Tags, "filename"); + attachment.MimeType = GetDictionaryValue(streamInfo.Tags, "mimetype"); + attachment.Comment = GetDictionaryValue(streamInfo.Tags, "comment"); } return attachment; From b1af8a4178b5d993b09e5682a0149ea37766ff91 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 8 Jan 2020 18:14:01 +0100 Subject: [PATCH 81/81] Rename function --- MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index cd3d82e864..78dc7b6078 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (result.Format != null && result.Format.Tags != null) { - result.Format.Tags = ConvertDictionaryToCaseInSensitive(result.Format.Tags); + result.Format.Tags = ConvertDictionaryToCaseInsensitive(result.Format.Tags); } if (result.Streams != null) @@ -28,7 +28,7 @@ namespace MediaBrowser.MediaEncoding.Probing { if (stream.Tags != null) { - stream.Tags = ConvertDictionaryToCaseInSensitive(stream.Tags); + stream.Tags = ConvertDictionaryToCaseInsensitive(stream.Tags); } } } @@ -98,7 +98,7 @@ namespace MediaBrowser.MediaEncoding.Probing /// /// The dict. /// Dictionary{System.StringSystem.String}. - private static Dictionary ConvertDictionaryToCaseInSensitive(IReadOnlyDictionary dict) + private static Dictionary ConvertDictionaryToCaseInsensitive(IReadOnlyDictionary dict) { return new Dictionary(dict, StringComparer.OrdinalIgnoreCase); }