From b91f1e4d16299e5f861c037bf449e5718140d82c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 10 Feb 2017 15:06:52 -0500 Subject: support dummied up image a/r's --- MediaBrowser.Controller/Entities/Audio/Audio.cs | 5 +++++ MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs | 5 +++++ MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs | 5 +++++ MediaBrowser.Controller/Entities/Audio/MusicArtist.cs | 5 +++++ MediaBrowser.Controller/Entities/Audio/MusicGenre.cs | 5 +++++ MediaBrowser.Controller/Entities/AudioBook.cs | 5 +++++ MediaBrowser.Controller/Entities/BaseItem.cs | 5 +++++ MediaBrowser.Controller/Entities/IHasImages.cs | 2 ++ MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 8 ++++++++ MediaBrowser.Controller/Entities/Movies/Movie.cs | 8 ++++++++ MediaBrowser.Controller/Entities/Person.cs | 8 ++++++++ MediaBrowser.Controller/Entities/Studio.cs | 8 ++++++++ MediaBrowser.Controller/Entities/TV/Episode.cs | 8 ++++++++ MediaBrowser.Controller/Entities/TV/Season.cs | 8 ++++++++ MediaBrowser.Controller/Entities/TV/Series.cs | 8 ++++++++ MediaBrowser.Controller/Entities/Trailer.cs | 8 ++++++++ MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs | 5 +++++ MediaBrowser.Controller/Playlists/Playlist.cs | 5 +++++ 18 files changed, 111 insertions(+) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 4cc6a7c7e..59b5a3869 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -46,6 +46,11 @@ namespace MediaBrowser.Controller.Entities.Audio AlbumArtists = new List(); } + public override double? GetDefaultPrimaryImageAspectRatio() + { + return 1; + } + [IgnoreDataMember] public override bool SupportsPlayedStatus { diff --git a/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs b/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs index 8c820d367..cdb6f3f61 100644 --- a/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs +++ b/MediaBrowser.Controller/Entities/Audio/AudioPodcast.cs @@ -12,5 +12,10 @@ namespace MediaBrowser.Controller.Entities.Audio return true; } } + + public override double? GetDefaultPrimaryImageAspectRatio() + { + return null; + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index ffdbba6f2..6ad38033a 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -119,6 +119,11 @@ namespace MediaBrowser.Controller.Entities.Audio return Tracks; } + public override double? GetDefaultPrimaryImageAspectRatio() + { + return 1; + } + public override List GetUserDataKeys() { var list = base.GetUserDataKeys(); diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 37631bbe8..20b2529c0 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -68,6 +68,11 @@ namespace MediaBrowser.Controller.Entities.Audio } } + public override double? GetDefaultPrimaryImageAspectRatio() + { + return 1; + } + public override bool CanDelete() { return !IsAccessedByName; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index bbe1a54a4..74679b474 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -63,6 +63,11 @@ namespace MediaBrowser.Controller.Entities.Audio } } + public override double? GetDefaultPrimaryImageAspectRatio() + { + return 1; + } + public override bool CanDelete() { return false; diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index c15cae8b1..8b1c338f1 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -47,6 +47,11 @@ namespace MediaBrowser.Controller.Entities return SeriesPresentationUniqueKey; } + public override double? GetDefaultPrimaryImageAspectRatio() + { + return null; + } + [IgnoreDataMember] public override bool EnableRefreshOnDateModifiedChange { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cab7588f0..0efb7ade8 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1260,6 +1260,11 @@ namespace MediaBrowser.Controller.Entities get { return null; } } + public virtual double? GetDefaultPrimaryImageAspectRatio() + { + return null; + } + public virtual string CreatePresentationUniqueKey() { return Id.ToString("N"); diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index 888e2080d..4c033dc00 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -204,6 +204,8 @@ namespace MediaBrowser.Controller.Entities /// The image. /// The index. void SetImage(ItemImageInfo image, int index); + + double? GetDefaultPrimaryImageAspectRatio(); } public static class HasImagesExtensions diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 3e6c88a85..030831717 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -57,6 +57,14 @@ namespace MediaBrowser.Controller.Entities.Movies return config.BlockUnratedItems.Contains(UnratedItem.Movie); } + public override double? GetDefaultPrimaryImageAspectRatio() + { + double value = 2; + value /= 3; + + return value; + } + public override UnratedItem GetBlockUnratedType() { return UnratedItem.Movie; diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index ec04879b5..de465b2f5 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -70,6 +70,14 @@ namespace MediaBrowser.Controller.Entities.Movies set { TmdbCollectionName = value; } } + public override double? GetDefaultPrimaryImageAspectRatio() + { + double value = 2; + value /= 3; + + return value; + } + [IgnoreDataMember] protected override bool SupportsIsInMixedFolderDetection { diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 0c36442af..ee1aea938 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -38,6 +38,14 @@ namespace MediaBrowser.Controller.Entities return GetItemLookupInfo(); } + public override double? GetDefaultPrimaryImageAspectRatio() + { + double value = 2; + value /= 3; + + return value; + } + public IEnumerable GetTaggedItems(InternalItemsQuery query) { query.PersonIds = new[] { Id.ToString("N") }; diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index dbd9d1cef..b8ad691a9 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -57,6 +57,14 @@ namespace MediaBrowser.Controller.Entities } } + public override double? GetDefaultPrimaryImageAspectRatio() + { + double value = 16; + value /= 9; + + return value; + } + public override bool CanDelete() { return false; diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index e6ebcb7fd..d2ea8d315 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -114,6 +114,14 @@ namespace MediaBrowser.Controller.Entities.TV { return false; } + } + + public override double? GetDefaultPrimaryImageAspectRatio() + { + double value = 16; + value /= 9; + + return value; } public override List GetUserDataKeys() diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index f2a6586e2..2ff7e4ce5 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -54,6 +54,14 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public string SeriesSortName { get; set; } + public override double? GetDefaultPrimaryImageAspectRatio() + { + double value = 2; + value /= 3; + + return value; + } + public string FindSeriesSortName() { var series = Series; diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index eea11b167..9388160bd 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -93,6 +93,14 @@ namespace MediaBrowser.Controller.Entities.TV } } + public override double? GetDefaultPrimaryImageAspectRatio() + { + double value = 2; + value /= 3; + + return value; + } + public override string CreatePresentationUniqueKey() { if (LibraryManager.GetLibraryOptions(this).EnableAutomaticSeriesGrouping) diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index dd6d8a999..b4a142a8e 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -29,6 +29,14 @@ namespace MediaBrowser.Controller.Entities get { return TrailerTypes.Contains(TrailerType.LocalTrailer); } } + public override double? GetDefaultPrimaryImageAspectRatio() + { + double value = 2; + value /= 3; + + return value; + } + public override UnratedItem GetBlockUnratedType() { return UnratedItem.Trailer; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs index e67fc5759..8501f1580 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs @@ -91,6 +91,11 @@ namespace MediaBrowser.Controller.LiveTv } } + public override double? GetDefaultPrimaryImageAspectRatio() + { + return null; + } + public override string GetClientTypeName() { return "Recording"; diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 9087a6e1d..0eb435375 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -58,6 +58,11 @@ namespace MediaBrowser.Controller.Playlists } } + public override double? GetDefaultPrimaryImageAspectRatio() + { + return 1; + } + public override bool IsAuthorizedToDelete(User user) { return true; -- cgit v1.2.3 From f447098e53896f55a8fb99b405f502896e97f827 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 11 Feb 2017 16:16:22 -0500 Subject: update xmltv xml parsing --- .../Emby.Server.Implementations.csproj | 4 +-- .../LiveTv/Listings/XmlTvListingsProvider.cs | 34 ++++++++++++++++++++-- Emby.Server.Implementations/packages.config | 2 +- MediaBrowser.Controller/LiveTv/LiveTvProgram.cs | 13 +++++++++ 4 files changed, 47 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index a1bd67f1f..b4bb92cfa 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -309,8 +309,8 @@ {4f26d5d8-a7b0-42b3-ba42-7cb7d245934e} SocketHttpListener.Portable - - ..\packages\Emby.XmlTv.1.0.6\lib\portable-net45+win8\Emby.XmlTv.dll + + ..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll True diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index d7803f9e3..a89acf647 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.IO.Compression; using System.Linq; using System.Net; using System.Text; @@ -26,13 +27,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings private readonly IHttpClient _httpClient; private readonly ILogger _logger; private readonly IFileSystem _fileSystem; + private readonly IZipClient _zipClient; - public XmlTvListingsProvider(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IFileSystem fileSystem) + public XmlTvListingsProvider(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IFileSystem fileSystem, IZipClient zipClient) { _config = config; _httpClient = httpClient; _logger = logger; _fileSystem = fileSystem; + _zipClient = zipClient; } public string Name @@ -63,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings var cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename); if (_fileSystem.FileExists(cacheFile)) { - return cacheFile; + return UnzipIfNeeded(path, cacheFile); } _logger.Info("Downloading xmltv listings from {0}", path); @@ -103,7 +106,30 @@ namespace Emby.Server.Implementations.LiveTv.Listings } _logger.Debug("Returning xmltv path {0}", cacheFile); - return cacheFile; + return UnzipIfNeeded(path, cacheFile); + } + + private string UnzipIfNeeded(string originalUrl, string file) + { + //var ext = Path.GetExtension(originalUrl); + + //if (string.Equals(ext, ".gz", StringComparison.OrdinalIgnoreCase)) + //{ + // using (var stream = _fileSystem.OpenRead(file)) + // { + // var tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString()); + // _fileSystem.CreateDirectory(tempFolder); + + // _zipClient.ExtractAllFromZip(stream, tempFolder, true); + + // return _fileSystem.GetFiles(tempFolder, true) + // .Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase)) + // .Select(i => i.FullName) + // .FirstOrDefault(); + // } + //} + + return file; } public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) @@ -122,6 +148,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings } } + _logger.Debug("Getting xmltv programs for channel {0}", channelId); + var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false); var reader = new XmlTvReader(path, GetLanguage()); diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 3c82e979b..88627957b 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -1,6 +1,6 @@  - + diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 5a139e09d..5919d87fd 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -48,6 +48,19 @@ namespace MediaBrowser.Controller.LiveTv return list; } + public override double? GetDefaultPrimaryImageAspectRatio() + { + if (IsMovie) + { + double value = 2; + value /= 3; + + return value; + } + + return null; + } + [IgnoreDataMember] public override SourceType SourceType { -- cgit v1.2.3 From 36f8eb1149e821de46b1ee15dcd1990c6a378ca9 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 17 Feb 2017 16:11:13 -0500 Subject: add db startup error handling --- Emby.Server.Core/ApplicationHost.cs | 2 +- .../Library/LibraryManager.cs | 24 -- .../Library/UserViewManager.cs | 9 +- .../LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 +- .../LiveTv/LiveStreamHelper.cs | 13 +- .../LiveTv/LiveTvMediaSourceProvider.cs | 4 +- .../Notifications/SqliteNotificationsRepository.cs | 22 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + MediaBrowser.Api/Playback/BaseStreamingService.cs | 20 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 3 - MediaBrowser.Api/Playback/MediaInfoService.cs | 11 +- .../Playback/Progressive/AudioService.cs | 2 - MediaBrowser.Api/Playback/StreamRequest.cs | 3 + MediaBrowser.Api/Playback/UniversalAudioService.cs | 248 +++++++++++++++++++++ MediaBrowser.Controller/Library/ILibraryManager.cs | 2 - 16 files changed, 307 insertions(+), 61 deletions(-) create mode 100644 MediaBrowser.Api/Playback/UniversalAudioService.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index a3c228a58..2163c4e47 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -863,7 +863,7 @@ namespace Emby.Server.Core /// private void ConfigureNotificationsRepository() { - var repo = new SqliteNotificationsRepository(LogManager.GetLogger("SqliteNotificationsRepository"), ServerConfigurationManager.ApplicationPaths); + var repo = new SqliteNotificationsRepository(LogManager.GetLogger("SqliteNotificationsRepository"), ServerConfigurationManager.ApplicationPaths, FileSystemManager); repo.Initialize(); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 616c6c1a2..879735ccb 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -818,30 +818,6 @@ namespace Emby.Server.Implementations.Library return _userRootFolder; } - public Guid? FindIdByPath(string path, bool? isFolder) - { - // If this returns multiple items it could be tricky figuring out which one is correct. - // In most cases, the newest one will be and the others obsolete but not yet cleaned up - - var query = new InternalItemsQuery - { - Path = path, - IsFolder = isFolder, - SortBy = new[] { ItemSortBy.DateCreated }, - SortOrder = SortOrder.Descending, - Limit = 1 - }; - - var id = GetItemIds(query); - - if (id.Count == 0) - { - return null; - } - - return id[0]; - } - public BaseItem FindByPath(string path, bool? isFolder) { // If this returns multiple items it could be tricky figuring out which one is correct. diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index f11cbd498..9e1291847 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -248,6 +248,13 @@ namespace Emby.Server.Implementations.Library } } + var isPlayed = request.IsPlayed; + + if (parents.OfType().Any(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))) + { + isPlayed = null; + } + if (parents.Count == 0) { parents = user.RootFolder.GetChildren(user, true) @@ -282,7 +289,7 @@ namespace Emby.Server.Implementations.Library IsVirtualItem = false, Limit = limit * 5, SourceTypes = parents.Count == 0 ? new[] { SourceType.Library } : new SourceType[] { }, - IsPlayed = request.IsPlayed + IsPlayed = isPlayed }, parents); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index bbb060203..7aae0d68a 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1172,7 +1172,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV }; var isAudio = false; - await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, false, cancellationToken).ConfigureAwait(false); + await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false); return new List { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 68126f926..5adb0b3c6 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -260,7 +260,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { _logger.Info("Calling recording process.WaitForExit for {0}", _targetPath); - if (_process.WaitForExit(5000)) + if (_process.WaitForExit(10000)) { return; } diff --git a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs b/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs index 2ee6869f6..e2f973699 100644 --- a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv _logger = logger; } - public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, bool assumeInterlaced, CancellationToken cancellationToken) + public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) { var originalRuntime = mediaSource.RunTimeTicks; @@ -96,17 +96,6 @@ namespace Emby.Server.Implementations.LiveTv videoStream.IsAVC = null; } - if (assumeInterlaced) - { - foreach (var mediaStream in mediaSource.MediaStreams) - { - if (mediaStream.Type == MediaStreamType.Video) - { - mediaStream.IsInterlaced = true; - } - } - } - // Try to estimate this mediaSource.InferTotalBitrate(true); } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index e25e28484..747e0fdd3 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -126,14 +126,12 @@ namespace Emby.Server.Implementations.LiveTv var keys = openToken.Split(new[] { StreamIdDelimeter }, 3); var mediaSourceId = keys.Length >= 3 ? keys[2] : null; IDirectStreamProvider directStreamProvider = null; - var assumeInterlaced = false; if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase)) { var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false); stream = info.Item1; directStreamProvider = info.Item2; - assumeInterlaced = info.Item3; } else { @@ -148,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv } else { - await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, assumeInterlaced, cancellationToken).ConfigureAwait(false); + await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false); } } catch (Exception ex) diff --git a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs index f18278cb2..76c7a7d77 100644 --- a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs +++ b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Data; using MediaBrowser.Controller; using MediaBrowser.Controller.Notifications; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Notifications; using SQLitePCL.pretty; @@ -16,8 +17,11 @@ namespace Emby.Server.Implementations.Notifications { public class SqliteNotificationsRepository : BaseSqliteRepository, INotificationsRepository { - public SqliteNotificationsRepository(ILogger logger, IServerApplicationPaths appPaths) : base(logger) + protected IFileSystem FileSystem { get; private set; } + + public SqliteNotificationsRepository(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem) : base(logger) { + FileSystem = fileSystem; DbFilePath = Path.Combine(appPaths.DataPath, "notifications.db"); } @@ -26,6 +30,22 @@ namespace Emby.Server.Implementations.Notifications ////public event EventHandler NotificationUpdated; public void Initialize() + { + try + { + InitializeInternal(); + } + catch (Exception ex) + { + Logger.ErrorException("Error loading notifications database file. Will reset and retry.", ex); + + FileSystem.DeleteFile(DbFilePath); + + InitializeInternal(); + } + } + + private void InitializeInternal() { using (var connection = CreateConnection()) { diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 7be04d892..db5914f81 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -59,6 +59,7 @@ + diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index d1c3de427..e561b3f46 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -126,14 +126,10 @@ namespace MediaBrowser.Api.Playback /// /// Gets the output file path. /// - /// The state. - /// System.String. - private string GetOutputFilePath(StreamState state) + private string GetOutputFilePath(StreamState state, string outputFileExtension) { var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath; - var outputFileExtension = GetOutputFileExtension(state); - var data = GetCommandLineArguments("dummy\\dummy", state, false); data += "-" + (state.Request.DeviceId ?? string.Empty); @@ -802,7 +798,7 @@ namespace MediaBrowser.Api.Playback { state.User = UserManager.GetUserById(auth.UserId); } - + //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || // (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || // (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) @@ -876,11 +872,16 @@ namespace MediaBrowser.Api.Playback var container = Path.GetExtension(state.RequestedUrl); + if (string.IsNullOrEmpty(container)) + { + container = request.Container; + } + if (string.IsNullOrEmpty(container)) { container = request.Static ? state.InputContainer : - (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.'); + GetOutputFileExtension(state); } state.OutputContainer = (container ?? string.Empty).TrimStart('.'); @@ -923,7 +924,10 @@ namespace MediaBrowser.Api.Playback ApplyDeviceProfileSettings(state); } - state.OutputFilePath = GetOutputFilePath(state); + var ext = string.IsNullOrWhiteSpace(state.OutputContainer) + ? GetOutputFileExtension(state) + : ("." + state.OutputContainer); + state.OutputFilePath = GetOutputFilePath(state, ext); return state; } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index cfd7471c4..1074a8bc1 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -18,9 +18,6 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using MimeTypes = MediaBrowser.Model.Net.MimeTypes; diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index fcb8c34f3..80885271c 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -146,7 +146,7 @@ namespace MediaBrowser.Api.Playback Task.WaitAll(task); } - public async Task Post(GetPostedPlaybackInfo request) + public async Task GetPlaybackInfo(GetPostedPlaybackInfo request) { var authInfo = _authContext.GetAuthorizationInfo(Request); @@ -172,7 +172,14 @@ namespace MediaBrowser.Api.Playback SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId); } - return ToOptimizedResult(info); + return info; + } + + public async Task Post(GetPostedPlaybackInfo request) + { + var result = await GetPlaybackInfo(request).ConfigureAwait(false); + + return ToOptimizedResult(result); } private T Clone(T obj) diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index a0ab90664..04825c7a5 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -26,8 +26,6 @@ namespace MediaBrowser.Api.Playback.Progressive [Route("/Audio/{Id}/stream", "HEAD", Summary = "Gets an audio stream")] public class GetAudioStream : StreamRequest { - [ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Container { get; set; } } /// diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 6bdb30890..f223c99ef 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -22,6 +22,9 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string DeviceId { get; set; } + [ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Container { get; set; } + /// /// Gets or sets the audio codec. /// diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs new file mode 100644 index 000000000..a52ae1df4 --- /dev/null +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using MediaBrowser.Api.Playback.Hls; +using MediaBrowser.Api.Playback.Progressive; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Services; + +namespace MediaBrowser.Api.Playback +{ + public class BaseUniversalRequest + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string MediaSourceId { get; set; } + + [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DeviceId { get; set; } + + public string Token { get; set; } + + public string UserId { get; set; } + public string AudioCodec { get; set; } + public string Container { get; set; } + + public int? MaxAudioChannels { get; set; } + + public long? MaxStreamingBitrate { get; set; } + + [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public long? StartTimeTicks { get; set; } + } + + [Route("/Audio/{Id}/universal.{Container}", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/universal", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/universal.{Container}", "HEAD", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/universal", "HEAD", Summary = "Gets an audio stream")] + public class GetUniversalAudioStream : BaseUniversalRequest + { + } + + //[Authenticated] + public class UniversalAudioService : BaseApiService + { + public UniversalAudioService(IServerConfigurationManager serverConfigurationManager, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, IDeviceManager deviceManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, INetworkManager networkManager) + { + ServerConfigurationManager = serverConfigurationManager; + UserManager = userManager; + LibraryManager = libraryManager; + IsoManager = isoManager; + MediaEncoder = mediaEncoder; + FileSystem = fileSystem; + DlnaManager = dlnaManager; + DeviceManager = deviceManager; + SubtitleEncoder = subtitleEncoder; + MediaSourceManager = mediaSourceManager; + ZipClient = zipClient; + JsonSerializer = jsonSerializer; + AuthorizationContext = authorizationContext; + ImageProcessor = imageProcessor; + NetworkManager = networkManager; + } + + protected IServerConfigurationManager ServerConfigurationManager { get; private set; } + protected IUserManager UserManager { get; private set; } + protected ILibraryManager LibraryManager { get; private set; } + protected IIsoManager IsoManager { get; private set; } + protected IMediaEncoder MediaEncoder { get; private set; } + protected IFileSystem FileSystem { get; private set; } + protected IDlnaManager DlnaManager { get; private set; } + protected IDeviceManager DeviceManager { get; private set; } + protected ISubtitleEncoder SubtitleEncoder { get; private set; } + protected IMediaSourceManager MediaSourceManager { get; private set; } + protected IZipClient ZipClient { get; private set; } + protected IJsonSerializer JsonSerializer { get; private set; } + protected IAuthorizationContext AuthorizationContext { get; private set; } + protected IImageProcessor ImageProcessor { get; private set; } + protected INetworkManager NetworkManager { get; private set; } + + public Task Get(GetUniversalAudioStream request) + { + return GetUniversalStream(request, false); + } + + public Task Head(GetUniversalAudioStream request) + { + return GetUniversalStream(request, true); + } + + private DeviceProfile GetDeviceProfile(GetUniversalAudioStream request) + { + var deviceProfile = new DeviceProfile(); + + var directPlayProfiles = new List(); + + directPlayProfiles.Add(new DirectPlayProfile + { + Type = DlnaProfileType.Audio, + Container = request.Container + }); + + deviceProfile.DirectPlayProfiles = directPlayProfiles.ToArray(); + + deviceProfile.TranscodingProfiles = new[] + { + new TranscodingProfile + { + Type = DlnaProfileType.Audio, + Context = EncodingContext.Streaming, + Container = "ts", + AudioCodec = "aac", + Protocol = "hls" + } + }; + + return deviceProfile; + } + + private async Task GetUniversalStream(GetUniversalAudioStream request, bool isHeadRequest) + { + var deviceProfile = GetDeviceProfile(request); + + AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId; + + var mediaInfoService = new MediaInfoService(MediaSourceManager, DeviceManager, LibraryManager, ServerConfigurationManager, NetworkManager, MediaEncoder, UserManager, JsonSerializer, AuthorizationContext) + { + Request = Request + }; + + var playbackInfoResult = await mediaInfoService.GetPlaybackInfo(new GetPostedPlaybackInfo + { + Id = request.Id, + MaxAudioChannels = request.MaxAudioChannels, + MaxStreamingBitrate = request.MaxStreamingBitrate, + StartTimeTicks = request.StartTimeTicks, + UserId = request.UserId, + DeviceProfile = deviceProfile, + MediaSourceId = request.MediaSourceId + + }).ConfigureAwait(false); + + var mediaSource = playbackInfoResult.MediaSources[0]; + + var isStatic = mediaSource.SupportsDirectStream; + + if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + var service = new DynamicHlsService(ServerConfigurationManager, + UserManager, + LibraryManager, + IsoManager, + MediaEncoder, + FileSystem, + DlnaManager, + SubtitleEncoder, + DeviceManager, + MediaSourceManager, + ZipClient, + JsonSerializer, + AuthorizationContext, + NetworkManager) + { + Request = Request + }; + + var transcodingProfile = deviceProfile.TranscodingProfiles[0]; + + var newRequest = new GetMasterHlsAudioPlaylist + { + AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)), + AudioCodec = transcodingProfile.AudioCodec, + Container = ".m3u8", + DeviceId = request.DeviceId, + Id = request.Id, + MaxAudioChannels = request.MaxAudioChannels, + MediaSourceId = mediaSource.Id, + PlaySessionId = playbackInfoResult.PlaySessionId, + StartTimeTicks = request.StartTimeTicks, + Static = isStatic + }; + + if (isHeadRequest) + { + return service.Head(newRequest); + } + return service.Get(newRequest); + } + else + { + var service = new AudioService(ServerConfigurationManager, + UserManager, + LibraryManager, + IsoManager, + MediaEncoder, + FileSystem, + DlnaManager, + SubtitleEncoder, + DeviceManager, + MediaSourceManager, + ZipClient, + JsonSerializer, + AuthorizationContext, + ImageProcessor) + { + Request = Request + }; + + var newRequest = new GetAudioStream + { + AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)), + //AudioCodec = request.AudioCodec, + Container = isStatic ? null : ("." + mediaSource.TranscodingContainer), + DeviceId = request.DeviceId, + Id = request.Id, + MaxAudioChannels = request.MaxAudioChannels, + MediaSourceId = mediaSource.Id, + PlaySessionId = playbackInfoResult.PlaySessionId, + StartTimeTicks = request.StartTimeTicks, + Static = isStatic + }; + + if (isHeadRequest) + { + return service.Head(newRequest); + } + return service.Get(newRequest); + } + } + } +} diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 33cd4f3d1..ebebe71a3 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -62,8 +62,6 @@ namespace MediaBrowser.Controller.Library /// BaseItem. BaseItem FindByPath(string path, bool? isFolder); - Guid? FindIdByPath(string path, bool? isFolder); - /// /// Gets the artist. /// -- cgit v1.2.3 From 6091e00e180b9b859de99c72b23dc155f284f516 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 18 Feb 2017 03:32:17 -0500 Subject: clean related files when deleting items --- .../Data/SqliteItemRepository.cs | 43 ++++++-------- Emby.Server.Implementations/Dto/DtoService.cs | 26 ++++++--- .../Library/LibraryManager.cs | 14 ++--- .../LiveTv/LiveTvManager.cs | 4 ++ MediaBrowser.Api/Reports/ReportsService.cs | 6 -- .../UserLibrary/BaseItemsByNameService.cs | 68 +--------------------- MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs | 2 + MediaBrowser.Api/UserLibrary/ItemsService.cs | 20 ++++++- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 3 +- MediaBrowser.Controller/Entities/BaseItem.cs | 28 ++++++++- MediaBrowser.Controller/Entities/Game.cs | 11 +++- .../Entities/InternalItemsQuery.cs | 4 +- MediaBrowser.Controller/Entities/TV/Episode.cs | 16 +++-- MediaBrowser.Controller/Entities/TV/Season.cs | 1 - MediaBrowser.Controller/Entities/TV/Series.cs | 4 -- .../Entities/UserViewBuilder.cs | 47 --------------- MediaBrowser.Controller/Entities/Video.cs | 10 +++- MediaBrowser.Controller/Providers/EpisodeInfo.cs | 1 - MediaBrowser.Controller/Providers/SeasonInfo.cs | 1 - MediaBrowser.Controller/Providers/SeriesInfo.cs | 1 - .../Parsers/SeriesXmlParser.cs | 15 ----- MediaBrowser.Model/Dlna/StreamBuilder.cs | 13 ++++- MediaBrowser.Model/Dto/BaseItemDto.cs | 6 +- .../TV/FanArt/FanArtSeasonProvider.cs | 12 +--- .../TV/MissingEpisodeProvider.cs | 42 ++++++------- .../TV/TheTVDB/TvdbEpisodeImageProvider.cs | 1 - .../TV/TheTVDB/TvdbSeasonImageProvider.cs | 9 --- .../TV/TheTVDB/TvdbSeriesImageProvider.cs | 4 -- .../TV/TheTVDB/TvdbSeriesProvider.cs | 20 ------- .../Parsers/SeriesNfoParser.cs | 16 ----- MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs | 8 +-- 31 files changed, 161 insertions(+), 295 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 4a4a1a6bf..30fa68d95 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3874,6 +3874,25 @@ namespace Emby.Server.Implementations.Data whereClauses.Add(clause); } + if (query.AlbumIds.Length > 0) + { + var clauses = new List(); + var index = 0; + foreach (var albumId in query.AlbumIds) + { + var paramName = "@AlbumIds" + index; + + clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")"); + if (statement != null) + { + statement.TryBind(paramName, albumId.ToGuidParamValue()); + } + index++; + } + var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")"; + whereClauses.Add(clause); + } + if (query.ExcludeArtistIds.Length > 0) { var clauses = new List(); @@ -4227,30 +4246,6 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("ProviderIds like '%tvdb=%'"); } - - if (query.AlbumNames.Length > 0) - { - var clause = "("; - - var index = 0; - foreach (var name in query.AlbumNames) - { - if (index > 0) - { - clause += " OR "; - } - clause += "Album=@AlbumName" + index; - - if (statement != null) - { - statement.TryBind("@AlbumName" + index, name); - } - index++; - } - - clause += ")"; - whereClauses.Add(clause); - } if (query.HasThemeSong.HasValue) { if (query.HasThemeSong.Value) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index c2cefe754..5b0bd8bbc 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -492,7 +492,10 @@ namespace Emby.Server.Implementations.Dto } } - dto.PlayAccess = item.GetPlayAccess(user); + //if (!(item is LiveTvProgram)) + { + dto.PlayAccess = item.GetPlayAccess(user); + } if (fields.Contains(ItemFields.BasicSyncInfo) || fields.Contains(ItemFields.SyncInfo)) { @@ -994,7 +997,12 @@ namespace Emby.Server.Implementations.Dto } dto.MediaType = item.MediaType; - dto.LocationType = item.LocationType; + + if (!(item is LiveTvProgram)) + { + dto.LocationType = item.LocationType; + } + if (item.IsHD.HasValue && item.IsHD.Value) { dto.IsHD = item.IsHD; @@ -1102,7 +1110,10 @@ namespace Emby.Server.Implementations.Dto } dto.Type = item.GetClientTypeName(); - dto.CommunityRating = item.CommunityRating; + if ((item.CommunityRating ?? 0) > 0) + { + dto.CommunityRating = item.CommunityRating; + } if (fields.Contains(ItemFields.VoteCount)) { @@ -1410,8 +1421,6 @@ namespace Emby.Server.Implementations.Dto dto.AirDays = series.AirDays; dto.AirTime = series.AirTime; dto.SeriesStatus = series.Status; - - dto.AnimeSeriesIndex = series.AnimeSeriesIndex; } // Add SeasonInfo @@ -1473,9 +1482,12 @@ namespace Emby.Server.Implementations.Dto SetBookProperties(dto, book); } - if (item.ProductionLocations.Count > 0 || item is Movie) + if (fields.Contains(ItemFields.ProductionLocations)) { - dto.ProductionLocations = item.ProductionLocations.ToArray(); + if (item.ProductionLocations.Count > 0 || item is Movie) + { + dto.ProductionLocations = item.ProductionLocations.ToArray(); + } } var photo = item as Photo; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 879735ccb..56bffc233 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -409,17 +409,17 @@ namespace Emby.Server.Implementations.Library if (options.DeleteFileLocation && locationType != LocationType.Remote && locationType != LocationType.Virtual) { - foreach (var path in item.GetDeletePaths().ToList()) + foreach (var fileSystemInfo in item.GetDeletePaths().ToList()) { - if (_fileSystem.DirectoryExists(path)) + if (fileSystemInfo.IsDirectory) { - _logger.Debug("Deleting path {0}", path); - _fileSystem.DeleteDirectory(path, true); + _logger.Debug("Deleting path {0}", fileSystemInfo.FullName); + _fileSystem.DeleteDirectory(fileSystemInfo.FullName, true); } - else if (_fileSystem.FileExists(path)) + else { - _logger.Debug("Deleting path {0}", path); - _fileSystem.DeleteFile(path); + _logger.Debug("Deleting path {0}", fileSystemInfo.FullName); + _fileSystem.DeleteFile(fileSystemInfo.FullName); } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index b139c68f4..d5ea0d493 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -607,6 +607,10 @@ namespace Emby.Server.Implementations.LiveTv item.Audio = info.Audio; item.ChannelId = channel.Id.ToString("N"); item.CommunityRating = item.CommunityRating ?? info.CommunityRating; + if ((item.CommunityRating ?? 0).Equals(0)) + { + item.CommunityRating = null; + } item.EpisodeTitle = info.EpisodeTitle; item.ExternalId = info.Id; diff --git a/MediaBrowser.Api/Reports/ReportsService.cs b/MediaBrowser.Api/Reports/ReportsService.cs index cb275fa29..5e13c1653 100644 --- a/MediaBrowser.Api/Reports/ReportsService.cs +++ b/MediaBrowser.Api/Reports/ReportsService.cs @@ -317,12 +317,6 @@ namespace MediaBrowser.Api.Reports query.MaxParentalRating = _localization.GetRatingLevel(request.MaxOfficialRating); } - // Albums - if (!string.IsNullOrEmpty(request.Albums)) - { - query.AlbumNames = request.Albums.Split('|'); - } - return query; } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 5b939244e..c1cc1555d 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -245,25 +245,15 @@ namespace MediaBrowser.Api.UserLibrary User user = null; BaseItem parentItem; - List libraryItems = null; if (!string.IsNullOrWhiteSpace(request.UserId)) { user = UserManager.GetUserById(request.UserId); parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId); - - if (RequiresLibraryItems(request, dtoOptions)) - { - libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); - } } else { parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId); - if (RequiresLibraryItems(request, dtoOptions)) - { - libraryItems = LibraryManager.RootFolder.GetRecursiveChildren().ToList(); - } } IEnumerable items; @@ -307,8 +297,6 @@ namespace MediaBrowser.Api.UserLibrary var filteredItems = FilterItems(request, extractedItems, user); - filteredItems = FilterByLibraryItems(request, filteredItems.Cast(), user, libraryItems).Cast(); - filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending); var ibnItemsArray = filteredItems.ToList(); @@ -334,15 +322,7 @@ namespace MediaBrowser.Api.UserLibrary } - IEnumerable>> tuples; - if (dtoOptions.Fields.Contains(ItemFields.ItemCounts)) - { - tuples = ibnItems.Select(i => new Tuple>(i, ((IItemByName)i).GetTaggedItems(libraryItems).ToList())); - } - else - { - tuples = ibnItems.Select(i => new Tuple>(i, new List())); - } + var tuples = ibnItems.Select(i => new Tuple>(i, new List())); var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions); var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, syncProgess, user)); @@ -352,52 +332,6 @@ namespace MediaBrowser.Api.UserLibrary return result; } - private bool RequiresLibraryItems(GetItemsByName request, DtoOptions options) - { - var filters = request.GetFilters().ToList(); - - if (filters.Contains(ItemFilter.IsPlayed)) - { - return true; - } - - if (filters.Contains(ItemFilter.IsUnplayed)) - { - return true; - } - - if (request.IsPlayed.HasValue) - { - return true; - } - - return options.Fields.Contains(ItemFields.ItemCounts); - } - - private IEnumerable FilterByLibraryItems(GetItemsByName request, IEnumerable items, User user, IEnumerable libraryItems) - { - var filters = request.GetFilters().ToList(); - - if (filters.Contains(ItemFilter.IsPlayed)) - { - items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsPlayed(user))); - } - - if (filters.Contains(ItemFilter.IsUnplayed)) - { - items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsUnplayed(user))); - } - - if (request.IsPlayed.HasValue) - { - var val = request.IsPlayed.Value; - - items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsPlayed(user)) == val); - } - - return items; - } - /// /// Filters the items. /// diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 0594691a2..1acbce6db 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -277,6 +277,8 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Albums { get; set; } + public string AlbumIds { get; set; } + /// /// Gets or sets the item ids. /// diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 8300beac3..5d267d059 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Services; @@ -359,15 +360,30 @@ namespace MediaBrowser.Api.UserLibrary } // ExcludeArtistIds - if (!string.IsNullOrEmpty(request.ExcludeArtistIds)) + if (!string.IsNullOrWhiteSpace(request.ExcludeArtistIds)) { query.ExcludeArtistIds = request.ExcludeArtistIds.Split('|'); } + if (!string.IsNullOrWhiteSpace(request.AlbumIds)) + { + query.AlbumIds = request.AlbumIds.Split('|'); + } + // Albums if (!string.IsNullOrEmpty(request.Albums)) { - query.AlbumNames = request.Albums.Split('|'); + query.AlbumIds = request.Albums.Split('|').Select(i => + { + return _libraryManager.GetItemList(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + Name = i, + Limit = 1 + + }).Select(album => album.Id.ToString("N")).FirstOrDefault(); + + }).ToArray(); } // Studios diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 08aba90d2..32f3a1f00 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -360,7 +360,8 @@ namespace MediaBrowser.Api.UserLibrary var currentUser = user; var dtos = series - .GetRecursiveChildren(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0) + .GetEpisodes(user) + .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0) .OrderBy(i => { if (i.PremiereDate.HasValue) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 0efb7ade8..52e150aa4 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -156,7 +156,7 @@ namespace MediaBrowser.Controller.Entities { if (SupportsIsInMixedFolderDetection) { - + } return IsInMixedFolder; @@ -2078,9 +2078,31 @@ namespace MediaBrowser.Controller.Entities /// Gets the file system path to delete when the item is to be deleted /// /// - public virtual IEnumerable GetDeletePaths() + public virtual IEnumerable GetDeletePaths() + { + return new[] { + new FileSystemMetadata + { + FullName = Path, + IsDirectory = IsFolder + } + }.Concat(GetLocalMetadataFilesToDelete()); + } + + protected List GetLocalMetadataFilesToDelete() { - return new[] { Path }; + if (IsFolder || !IsInMixedFolder) + { + return new List(); + } + + var filename = System.IO.Path.GetFileNameWithoutExtension(Path); + var extensions = new[] { ".nfo", ".xml", ".srt" }.ToList(); + extensions.AddRange(SupportedImageExtensionsList); + + return FileSystem.GetFiles(System.IO.Path.GetDirectoryName(Path)) + .Where(i => extensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase) && System.IO.Path.GetFileNameWithoutExtension(i.FullName).StartsWith(filename, StringComparison.OrdinalIgnoreCase)) + .ToList(); } public bool AllowsMultipleImages(ImageType type) diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index 8bfb8be99..d19552c07 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -4,6 +4,7 @@ using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities @@ -97,11 +98,17 @@ namespace MediaBrowser.Controller.Entities return list; } - public override IEnumerable GetDeletePaths() + public override IEnumerable GetDeletePaths() { if (!DetectIsInMixedFolder()) { - return new[] { System.IO.Path.GetDirectoryName(Path) }; + return new[] { + new FileSystemMetadata + { + FullName = System.IO.Path.GetDirectoryName(Path), + IsDirectory = true + } + }; } return base.GetDeletePaths(); diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index f03531270..ea4d60a44 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -141,7 +141,7 @@ namespace MediaBrowser.Controller.Entities public string ExternalSeriesId { get; set; } public string ExternalId { get; set; } - public string[] AlbumNames { get; set; } + public string[] AlbumIds { get; set; } public string[] ArtistIds { get; set; } public string[] ExcludeArtistIds { get; set; } public string AncestorWithPresentationUniqueKey { get; set; } @@ -202,7 +202,7 @@ namespace MediaBrowser.Controller.Entities EnableTotalRecordCount = true; DtoOptions = new DtoOptions(); - AlbumNames = new string[] { }; + AlbumIds = new string[] { }; ArtistIds = new string[] { }; ExcludeArtistIds = new string[] { }; ExcludeProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index d2ea8d315..31bf8d28b 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities.TV @@ -319,10 +320,16 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - public override IEnumerable GetDeletePaths() - { - return new[] { Path }; - } + public override IEnumerable GetDeletePaths() + { + return new[] { + new FileSystemMetadata + { + FullName = Path, + IsDirectory = IsFolder + } + }.Concat(GetLocalMetadataFilesToDelete()); + } public override UnratedItem GetBlockUnratedType() { @@ -338,7 +345,6 @@ namespace MediaBrowser.Controller.Entities.TV if (series != null) { id.SeriesProviderIds = series.ProviderIds; - id.AnimeSeriesIndex = series.AnimeSeriesIndex; } id.IsMissingEpisode = IsMissingEpisode; diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 2ff7e4ce5..be268782d 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -251,7 +251,6 @@ namespace MediaBrowser.Controller.Entities.TV if (series != null) { id.SeriesProviderIds = series.ProviderIds; - id.AnimeSeriesIndex = series.AnimeSeriesIndex; } return id; diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 9388160bd..7641c9523 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -19,8 +19,6 @@ namespace MediaBrowser.Controller.Entities.TV /// public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo, IMetadataContainer { - public int? AnimeSeriesIndex { get; set; } - public Series() { AirDays = new List(); @@ -554,8 +552,6 @@ namespace MediaBrowser.Controller.Entities.TV { var info = GetItemLookupInfo(); - info.AnimeSeriesIndex = AnimeSeriesIndex; - return info; } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 6e0f4ada9..f879d0fd8 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -1719,53 +1719,6 @@ namespace MediaBrowser.Controller.Entities } } - // Artists - if (query.ArtistIds.Length > 0) - { - var audio = item as IHasArtist; - - //if (!(audio != null && query.ArtistNames.Any(audio.HasAnyArtist))) - //{ - // return false; - //} - } - - // Albums - if (query.AlbumNames.Length > 0) - { - var audio = item as Audio.Audio; - - if (audio != null) - { - if (!query.AlbumNames.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - - var album = item as MusicAlbum; - - if (album != null) - { - if (!query.AlbumNames.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - - var musicVideo = item as MusicVideo; - - if (musicVideo != null) - { - if (!query.AlbumNames.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - - return false; - } - return true; } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index fb9c3d213..78f907d61 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -477,11 +477,17 @@ namespace MediaBrowser.Controller.Entities } } - public override IEnumerable GetDeletePaths() + public override IEnumerable GetDeletePaths() { if (!DetectIsInMixedFolder()) { - return new[] { ContainingFolderPath }; + return new[] { + new FileSystemMetadata + { + FullName = System.IO.Path.GetDirectoryName(Path), + IsDirectory = true + } + }; } return base.GetDeletePaths(); diff --git a/MediaBrowser.Controller/Providers/EpisodeInfo.cs b/MediaBrowser.Controller/Providers/EpisodeInfo.cs index b879040f8..b8e88ea53 100644 --- a/MediaBrowser.Controller/Providers/EpisodeInfo.cs +++ b/MediaBrowser.Controller/Providers/EpisodeInfo.cs @@ -8,7 +8,6 @@ namespace MediaBrowser.Controller.Providers public Dictionary SeriesProviderIds { get; set; } public int? IndexNumberEnd { get; set; } - public int? AnimeSeriesIndex { get; set; } public bool IsMissingEpisode { get; set; } public bool IsVirtualUnaired { get; set; } diff --git a/MediaBrowser.Controller/Providers/SeasonInfo.cs b/MediaBrowser.Controller/Providers/SeasonInfo.cs index 2c785d7d7..31af268b8 100644 --- a/MediaBrowser.Controller/Providers/SeasonInfo.cs +++ b/MediaBrowser.Controller/Providers/SeasonInfo.cs @@ -6,7 +6,6 @@ namespace MediaBrowser.Controller.Providers public class SeasonInfo : ItemLookupInfo { public Dictionary SeriesProviderIds { get; set; } - public int? AnimeSeriesIndex { get; set; } public SeasonInfo() { diff --git a/MediaBrowser.Controller/Providers/SeriesInfo.cs b/MediaBrowser.Controller/Providers/SeriesInfo.cs index 387865de2..0b1361757 100644 --- a/MediaBrowser.Controller/Providers/SeriesInfo.cs +++ b/MediaBrowser.Controller/Providers/SeriesInfo.cs @@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers { public class SeriesInfo : ItemLookupInfo { - public int? AnimeSeriesIndex { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs index 2ddd84378..f5352add7 100644 --- a/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs @@ -69,21 +69,6 @@ namespace MediaBrowser.LocalMetadata.Parsers break; } - case "AnimeSeriesIndex": - { - var number = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(number)) - { - int num; - - if (int.TryParse(number, out num)) - { - item.AnimeSeriesIndex = num; - } - } - break; - } case "Status": { var status = reader.ReadElementContentAsString(); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 129b49cf6..480eb23a5 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -606,9 +606,20 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } + private int GetDefaultAudioBitrateIfUnknown(MediaStream audioStream) + { + if ((audioStream.Channels ?? 0) >= 6) + { + return 384000; + } + + return 192000; + } + private int GetAudioBitrate(string subProtocol, long? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream) { - int defaultBitrate = audioStream == null ? 192000 : audioStream.BitRate ?? 192000; + int defaultBitrate = audioStream == null ? 192000 : audioStream.BitRate ?? GetDefaultAudioBitrateIfUnknown(audioStream); + // Reduce the bitrate if we're downmixing if (targetAudioChannels.HasValue && audioStream != null && audioStream.Channels.HasValue && targetAudioChannels.Value < audioStream.Channels.Value) { diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index c69bbb581..ff3f0be59 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -85,8 +85,6 @@ namespace MediaBrowser.Model.Dto public float? Metascore { get; set; } public bool? HasDynamicCategories { get; set; } - public int? AnimeSeriesIndex { get; set; } - /// /// Gets or sets a value indicating whether [supports synchronize]. /// @@ -263,7 +261,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the play access. /// /// The play access. - public PlayAccess PlayAccess { get; set; } + public PlayAccess? PlayAccess { get; set; } /// /// Gets or sets the aspect ratio. @@ -759,7 +757,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the type of the location. /// /// The type of the location. - public LocationType LocationType { get; set; } + public LocationType? LocationType { get; set; } /// /// Gets or sets the type of the iso. diff --git a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs index 884fa297f..f1fc2c9f3 100644 --- a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs @@ -96,8 +96,7 @@ namespace MediaBrowser.Providers.TV try { - int seasonNumber = AdjustForSeriesOffset(series, season.IndexNumber.Value); - AddImages(list, seasonNumber, path, cancellationToken); + AddImages(list, season.IndexNumber.Value, path, cancellationToken); } catch (FileNotFoundException) { @@ -139,15 +138,6 @@ namespace MediaBrowser.Providers.TV .ThenByDescending(i => i.VoteCount ?? 0); } - private int AdjustForSeriesOffset(Series series, int seasonNumber) - { - var offset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds); - if (offset != null) - return (int)(seasonNumber + offset); - - return seasonNumber; - } - private void AddImages(List list, int seasonNumber, string path, CancellationToken cancellationToken) { var root = _json.DeserializeFromFile(path); diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 1fd04070b..d5154b1d3 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -203,9 +203,8 @@ namespace MediaBrowser.Providers.TV CancellationToken cancellationToken) { var existingEpisodes = (from s in series - let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) from c in s.GetRecursiveChildren().OfType() - select new Tuple((c.ParentIndexNumber ?? 0) + seasonOffset, c)) + select new Tuple((c.ParentIndexNumber ?? 0) , c)) .ToList(); var lookup = episodeLookup as IList> ?? episodeLookup.ToList(); @@ -248,7 +247,6 @@ namespace MediaBrowser.Providers.TV var now = DateTime.UtcNow; var targetSeries = DetermineAppropriateSeries(series, tuple.Item1); - var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(targetSeries.ProviderIds) ?? ((targetSeries.AnimeSeriesIndex ?? 1) - 1); var unairedThresholdDays = 2; now = now.AddDays(0 - unairedThresholdDays); @@ -259,7 +257,7 @@ namespace MediaBrowser.Providers.TV { // tvdb has a lot of nearly blank episodes _logger.Info("Creating virtual missing episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2); - await AddEpisode(targetSeries, tuple.Item1 - seasonOffset, tuple.Item2, cancellationToken).ConfigureAwait(false); + await AddEpisode(targetSeries, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); hasChanges = true; } @@ -268,7 +266,7 @@ namespace MediaBrowser.Providers.TV { // tvdb has a lot of nearly blank episodes _logger.Info("Creating virtual unaired episode {0} {1}x{2}", targetSeries.Name, tuple.Item1, tuple.Item2); - await AddEpisode(targetSeries, tuple.Item1 - seasonOffset, tuple.Item2, cancellationToken).ConfigureAwait(false); + await AddEpisode(targetSeries, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false); hasChanges = true; } @@ -279,13 +277,11 @@ namespace MediaBrowser.Providers.TV private Series DetermineAppropriateSeries(IEnumerable series, int seasonNumber) { - var seriesAndOffsets = series.Select(s => new { Series = s, SeasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) }).ToList(); + var seriesAndOffsets = series.ToList(); - var bestMatch = seriesAndOffsets.FirstOrDefault(s => s.Series.GetRecursiveChildren().OfType().Any(season => (season.IndexNumber + s.SeasonOffset) == seasonNumber)) ?? - seriesAndOffsets.FirstOrDefault(s => s.Series.GetRecursiveChildren().OfType().Any(season => (season.IndexNumber + s.SeasonOffset) == 1)) ?? - seriesAndOffsets.OrderBy(s => s.Series.GetRecursiveChildren().OfType().Select(season => season.IndexNumber + s.SeasonOffset).Min()).First(); - - return bestMatch.Series; + return seriesAndOffsets.FirstOrDefault(s => s.GetRecursiveChildren().OfType().Any(season => (season.IndexNumber) == seasonNumber)) ?? + seriesAndOffsets.FirstOrDefault(s => s.GetRecursiveChildren().OfType().Any(season => (season.IndexNumber) == 1)) ?? + seriesAndOffsets.OrderBy(s => s.GetRecursiveChildren().OfType().Select(season => season.IndexNumber).Min()).First(); } /// @@ -296,9 +292,8 @@ namespace MediaBrowser.Providers.TV bool allowMissingEpisodes) { var existingEpisodes = (from s in series - let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) from c in s.GetRecursiveChildren().OfType() - select new { SeasonOffset = seasonOffset, Episode = c }) + select new { Episode = c }) .ToList(); var physicalEpisodes = existingEpisodes @@ -314,12 +309,12 @@ namespace MediaBrowser.Providers.TV { if (i.Episode.IndexNumber.HasValue && i.Episode.ParentIndexNumber.HasValue) { - var seasonNumber = i.Episode.ParentIndexNumber.Value + i.SeasonOffset; + var seasonNumber = i.Episode.ParentIndexNumber.Value; var episodeNumber = i.Episode.IndexNumber.Value; // If there's a physical episode with the same season and episode number, delete it if (physicalEpisodes.Any(p => - p.Episode.ParentIndexNumber.HasValue && (p.Episode.ParentIndexNumber.Value + p.SeasonOffset) == seasonNumber && + p.Episode.ParentIndexNumber.HasValue && (p.Episode.ParentIndexNumber.Value) == seasonNumber && p.Episode.ContainsEpisodeNumber(episodeNumber))) { return true; @@ -371,28 +366,27 @@ namespace MediaBrowser.Providers.TV IEnumerable> episodeLookup) { var existingSeasons = (from s in series - let seasonOffset = TvdbSeriesProvider.GetSeriesOffset(s.ProviderIds) ?? ((s.AnimeSeriesIndex ?? 1) - 1) from c in s.Children.OfType() - select new { SeasonOffset = seasonOffset, Season = c }) + select c) .ToList(); var physicalSeasons = existingSeasons - .Where(i => i.Season.LocationType != LocationType.Virtual) + .Where(i => i.LocationType != LocationType.Virtual) .ToList(); var virtualSeasons = existingSeasons - .Where(i => i.Season.LocationType == LocationType.Virtual) + .Where(i => i.LocationType == LocationType.Virtual) .ToList(); var seasonsToRemove = virtualSeasons .Where(i => { - if (i.Season.IndexNumber.HasValue) + if (i.IndexNumber.HasValue) { - var seasonNumber = i.Season.IndexNumber.Value + i.SeasonOffset; + var seasonNumber = i.IndexNumber.Value; // If there's a physical season with the same number, delete it - if (physicalSeasons.Any(p => p.Season.IndexNumber.HasValue && (p.Season.IndexNumber.Value + p.SeasonOffset) == seasonNumber && string.Equals(p.Season.Series.PresentationUniqueKey, i.Season.Series.PresentationUniqueKey, StringComparison.Ordinal))) + if (physicalSeasons.Any(p => p.IndexNumber.HasValue && (p.IndexNumber.Value) == seasonNumber && string.Equals(p.Series.PresentationUniqueKey, i.Series.PresentationUniqueKey, StringComparison.Ordinal))) { return true; } @@ -408,13 +402,13 @@ namespace MediaBrowser.Providers.TV // Season does not have a number // Remove if there are no episodes directly in series without a season number - return i.Season.Series.GetRecursiveChildren().OfType().All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder); + return i.Series.GetRecursiveChildren().OfType().All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder); }) .ToList(); var hasChanges = false; - foreach (var seasonToRemove in seasonsToRemove.Select(s => s.Season)) + foreach (var seasonToRemove in seasonsToRemove) { _logger.Info("Removing virtual season {0} {1}", seasonToRemove.Series.Name, seasonToRemove.IndexNumber); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs index 791f56beb..989748846 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs @@ -59,7 +59,6 @@ namespace MediaBrowser.Providers.TV { // Process images var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds); - var indexOffset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds) ?? 0; var nodes = TvdbEpisodeProvider.Current.GetEpisodeXmlNodes(seriesDataPath, episode.GetLookupInfo()); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs index e189c292c..2c4ccddd7 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs @@ -99,15 +99,6 @@ namespace MediaBrowser.Providers.TV return new RemoteImageInfo[] { }; } - private int AdjustForSeriesOffset(Series series, int seasonNumber) - { - var offset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds); - if (offset != null) - return (seasonNumber + offset.Value); - - return seasonNumber; - } - internal static IEnumerable GetImages(string xmlPath, string preferredLanguage, int seasonNumber, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem, CancellationToken cancellationToken) { var settings = xmlReaderSettingsFactory.Create(false); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs index 2595ad585..97eedfa92 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs @@ -76,10 +76,6 @@ namespace MediaBrowser.Providers.TV try { - var seriesOffset = TvdbSeriesProvider.GetSeriesOffset(item.ProviderIds); - if (seriesOffset != null && seriesOffset.Value != 0) - return TvdbSeasonImageProvider.GetImages(path, language, seriesOffset.Value + 1, _xmlReaderSettingsFactory, _fileSystem, cancellationToken); - return GetImages(path, language, cancellationToken); } catch (FileNotFoundException) diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 1798299e8..8769dc801 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -27,9 +27,6 @@ namespace MediaBrowser.Providers.TV { public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { - private const string TvdbSeriesOffset = "TvdbSeriesOffset"; - private const string TvdbSeriesOffsetFormat = "{0}-{1}"; - internal readonly SemaphoreSlim TvDbResourcePool = new SemaphoreSlim(2, 2); internal static TvdbSeriesProvider Current { get; private set; } private readonly IZipClient _zipClient; @@ -123,23 +120,6 @@ namespace MediaBrowser.Providers.TV return result; } - internal static int? GetSeriesOffset(Dictionary seriesProviderIds) - { - string idString; - if (!seriesProviderIds.TryGetValue(TvdbSeriesOffset, out idString)) - return null; - - var parts = idString.Split('-'); - if (parts.Length < 2) - return null; - - int offset; - if (int.TryParse(parts[1], out offset)) - return offset; - - return null; - } - /// /// Fetches the series data. /// diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index 57238ef87..98016f4f7 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -65,22 +65,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } - case "animeseriesindex": - { - var number = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(number)) - { - int num; - - if (int.TryParse(number, out num)) - { - item.AnimeSeriesIndex = num; - } - } - break; - } - case "status": { var status = reader.ReadElementContentAsString(); diff --git a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs index 9e48b0c8b..e9a5d4d60 100644 --- a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs @@ -83,11 +83,6 @@ namespace MediaBrowser.XbmcMetadata.Savers { writer.WriteElementString("airs_dayofweek", series.AirDays[0].ToString()); } - - if (series.AnimeSeriesIndex.HasValue) - { - writer.WriteElementString("animeseriesindex", series.AnimeSeriesIndex.Value.ToString(CultureInfo.InvariantCulture)); - } } protected override List GetTagsUsed(IHasMetadata item) @@ -101,8 +96,7 @@ namespace MediaBrowser.XbmcMetadata.Savers "episode", "status", "airs_time", - "airs_dayofweek", - "animeseriesindex" + "airs_dayofweek" }); return list; } -- cgit v1.2.3 From 0ee1a0d7bd827e53351ee5e4ad21c4dda258362d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 18 Feb 2017 22:46:09 -0500 Subject: fix mapping multiple tuner channels to same epg channel --- Emby.Common.Implementations/BaseApplicationHost.cs | 8 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 2 +- Emby.Server.Core/ApplicationHost.cs | 117 +- .../Data/SqliteItemRepository.cs | 3 +- Emby.Server.Implementations/Dto/DtoService.cs | 4 +- .../LiveTv/EmbyTV/EmbyTV.cs | 5 + .../LiveTv/Listings/SchedulesDirect.cs | 8 +- .../LiveTv/Listings/XmlTvListingsProvider.cs | 4 +- .../LiveTv/LiveTvManager.cs | 11 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 16 +- .../LiveTv/TunerHosts/MulticastStream.cs | 4 +- MediaBrowser.Api/ItemUpdateService.cs | 13 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 4 - MediaBrowser.Api/Playback/BaseStreamingService.cs | 1 - MediaBrowser.Api/Playback/StreamState.cs | 2 +- MediaBrowser.Api/Playback/UniversalAudioService.cs | 8 +- MediaBrowser.Api/Sync/SyncHelper.cs | 4 +- .../MediaBrowser.Controller.csproj | 2 + .../MediaEncoding/EncodingHelper.cs | 1675 +++++++++++++++++++ .../MediaEncoding/EncodingJobInfo.cs | 114 ++ .../Encoder/EncodingHelper.cs | 1681 -------------------- .../Encoder/EncodingJobInfo.cs | 118 -- .../MediaBrowser.MediaEncoding.csproj | 2 - MediaBrowser.Model/Dto/BaseItemDto.cs | 172 -- MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 10 +- MediaBrowser.Server.Mono/MonoAppHost.cs | 92 -- MediaBrowser.ServerApplication/MainStartup.cs | 2 +- MediaBrowser.ServerApplication/WindowsAppHost.cs | 106 +- src/Emby.Server/Program.cs | 2 +- 29 files changed, 1957 insertions(+), 2233 deletions(-) create mode 100644 MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs create mode 100644 MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs delete mode 100644 MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs delete mode 100644 MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Common.Implementations/BaseApplicationHost.cs b/Emby.Common.Implementations/BaseApplicationHost.cs index 147a43fa1..f53433511 100644 --- a/Emby.Common.Implementations/BaseApplicationHost.cs +++ b/Emby.Common.Implementations/BaseApplicationHost.cs @@ -873,7 +873,13 @@ return null; /// Gets or sets a value indicating whether this instance can self update. /// /// true if this instance can self update; otherwise, false. - public abstract bool CanSelfUpdate { get; } + public virtual bool CanSelfUpdate + { + get + { + return false; + } + } /// /// Checks for update. diff --git a/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index de528a94f..f0518f69e 100644 --- a/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -379,7 +379,7 @@ namespace Emby.Common.Implementations.ScheduledTasks /// Cannot execute a Task that is already running public async Task Execute(TaskExecutionOptions options) { - var task = ExecuteInternal(options); + var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false)); _currentTask = task; diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 2163c4e47..c3d88eeab 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -312,7 +312,13 @@ namespace Emby.Server.Core } } - public abstract bool SupportsRunningAsService { get; } + public virtual bool SupportsRunningAsService + { + get + { + return false; + } + } /// /// Gets the name. @@ -326,14 +332,26 @@ namespace Emby.Server.Core } } - public abstract bool IsRunningAsService { get; } + public virtual bool IsRunningAsService + { + get + { + return false; + } + } private Assembly GetAssembly(Type type) { return type.GetTypeInfo().Assembly; } - public abstract bool SupportsAutoRunAtStartup { get; } + public virtual bool SupportsAutoRunAtStartup + { + get + { + return EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; + } + } private void SetBaseExceptionMessage() { @@ -716,7 +734,13 @@ namespace Emby.Server.Core await ((UserManager)UserManager).Initialize().ConfigureAwait(false); } - protected abstract bool SupportsDualModeSockets { get; } + protected virtual bool SupportsDualModeSockets + { + get + { + return true; + } + } private ICertificate GetCertificate(string certificateLocation) { @@ -761,7 +785,75 @@ namespace Emby.Server.Core return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, ImageEncoder, maxConcurrentImageProcesses, () => LibraryManager, TimerFactory); } - protected abstract FFMpegInstallInfo GetFfmpegInstallInfo(); + protected virtual FFMpegInstallInfo GetFfmpegInstallInfo() + { + var info = new FFMpegInstallInfo(); + + // Windows builds: http://ffmpeg.zeranoe.com/builds/ + // Linux builds: http://johnvansickle.com/ffmpeg/ + // OS X builds: http://ffmpegmac.net/ + // OS X x64: http://www.evermeet.cx/ffmpeg/ + + if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux) + { + info.FFMpegFilename = "ffmpeg"; + info.FFProbeFilename = "ffprobe"; + info.ArchiveType = "7z"; + info.Version = "20160215"; + info.DownloadUrls = GetLinuxDownloadUrls(); + } + else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) + { + info.FFMpegFilename = "ffmpeg.exe"; + info.FFProbeFilename = "ffprobe.exe"; + info.Version = "20160410"; + info.ArchiveType = "7z"; + info.DownloadUrls = GetWindowsDownloadUrls(); + } + + // No version available - user requirement + info.DownloadUrls = new string[] { }; + + return info; + } + + private string[] GetWindowsDownloadUrls() + { + switch (EnvironmentInfo.SystemArchitecture) + { + case Architecture.X64: + return new[] + { + "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win64.7z" + }; + case Architecture.X86: + return new[] + { + "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win32.7z" + }; + } + + return new string[] { }; + } + + private string[] GetLinuxDownloadUrls() + { + switch (EnvironmentInfo.SystemArchitecture) + { + case Architecture.X64: + return new[] + { + "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z" + }; + case Architecture.X86: + return new[] + { + "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z" + }; + } + + return new string[] { }; + } /// /// Registers the media encoder. @@ -1489,7 +1581,10 @@ namespace Emby.Server.Core } } - protected abstract void AuthorizeServer(); + protected virtual void AuthorizeServer() + { + throw new NotImplementedException(); + } public event EventHandler HasUpdateAvailableChanged; @@ -1565,7 +1660,10 @@ namespace Emby.Server.Core } } - protected abstract void ConfigureAutoRunInternal(bool autorun); + protected virtual void ConfigureAutoRunInternal(bool autorun) + { + throw new NotImplementedException(); + } /// /// This returns localhost in the case of no external dns, and the hostname if the @@ -1631,7 +1729,10 @@ namespace Emby.Server.Core EnableLoopbackInternal(appName); } - protected abstract void EnableLoopbackInternal(string appName); + protected virtual void EnableLoopbackInternal(string appName) + { + + } private void RegisterModules() { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 30fa68d95..1f72ebd54 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2872,7 +2872,8 @@ namespace Emby.Server.Implementations.Data } if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase)) { - return new Tuple("IsFavorite", true); + // (Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then 2 Else 0 End ) + return new Tuple("(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", true); } if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 5b0bd8bbc..824812494 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -492,7 +492,7 @@ namespace Emby.Server.Implementations.Dto } } - //if (!(item is LiveTvProgram)) + if (!(item is LiveTvProgram)) { dto.PlayAccess = item.GetPlayAccess(user); } @@ -1420,7 +1420,7 @@ namespace Emby.Server.Implementations.Dto { dto.AirDays = series.AirDays; dto.AirTime = series.AirTime; - dto.SeriesStatus = series.Status; + dto.Status = series.Status.HasValue ? series.Status.Value.ToString() : null; } // Add SeasonInfo diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7aae0d68a..1fc3dcd72 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -986,6 +986,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV foreach (var program in programs) { program.ChannelId = channelId; + + if (provider.Item2.EnableNewProgramIds) + { + program.Id += "_" + channelId; + } } if (programs.Count > 0) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 0d7a26553..f76735030 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -81,12 +81,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings return programsInfo; } - if (string.IsNullOrWhiteSpace(info.ListingsId)) - { - _logger.Warn("ListingsId is null, returning empty program list"); - return programsInfo; - } - var dates = GetScheduleRequestDates(startDateUtc, endDateUtc); string stationID = channelId; @@ -156,7 +150,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID) .ToList(); - var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken); + var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); var schedules = dailySchedules.SelectMany(d => d.programs); foreach (ScheduleDirect.Program schedule in schedules) diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index a89acf647..c22bb1171 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -168,7 +168,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings EpisodeNumber = p.Episode == null ? null : p.Episode.Episode, EpisodeTitle = episodeTitle, Genres = p.Categories, - Id = String.Format("{0}_{1:O}", p.ChannelId, p.StartDate), // Construct an id from the channel and start date, StartDate = GetDate(p.StartDate), Name = p.Title, Overview = p.Description, @@ -208,6 +207,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings programInfo.ShowId = uniqueString.GetMD5().ToString("N"); } + // Construct an id from the channel and start date + programInfo.Id = String.Format("{0}_{1:O}", p.ChannelId, p.StartDate); + if (programInfo.IsMovie) { programInfo.IsSeries = false; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index d5ea0d493..887784213 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -598,10 +598,6 @@ namespace Emby.Server.Implementations.LiveTv item.ParentId = channel.Id; //item.ChannelType = channelType; - if (!string.Equals(item.ServiceName, serviceName, StringComparison.Ordinal)) - { - forceUpdate = true; - } item.ServiceName = serviceName; item.Audio = info.Audio; @@ -1311,7 +1307,7 @@ namespace Emby.Server.Implementations.LiveTv var isKids = false; var iSSeries = false; - var channelPrograms = await service.GetProgramsAsync(GetItemExternalId(currentChannel), start, end, cancellationToken).ConfigureAwait(false); + var channelPrograms = (await service.GetProgramsAsync(GetItemExternalId(currentChannel), start, end, cancellationToken).ConfigureAwait(false)).ToList(); var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery { @@ -1409,7 +1405,7 @@ namespace Emby.Server.Implementations.LiveTv double percent = numComplete; percent /= allChannelsList.Count; - progress.Report(80 * percent + 10); + progress.Report(85 * percent + 15); } progress.Report(100); @@ -1884,7 +1880,7 @@ namespace Emby.Server.Implementations.LiveTv : _tvDtoService.GetInternalTimerId(service.Name, info.TimerId).ToString("N"); dto.StartDate = info.StartDate; - dto.RecordingStatus = info.Status; + dto.Status = info.Status.ToString(); dto.IsRepeat = info.IsRepeat; dto.EpisodeTitle = info.EpisodeTitle; dto.IsMovie = info.IsMovie; @@ -2865,6 +2861,7 @@ namespace Emby.Server.Implementations.LiveTv { info.Id = Guid.NewGuid().ToString("N"); config.ListingProviders.Add(info); + info.EnableNewProgramIds = true; } else { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 1c7c0828c..bc9d01254 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -9,17 +9,14 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Net; @@ -66,7 +63,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return id; } - private async Task> GetLineup(TunerHostInfo info, CancellationToken cancellationToken) + private async Task> GetLineup(TunerHostInfo info, CancellationToken cancellationToken) { var options = new HttpRequestOptions { @@ -74,7 +71,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun CancellationToken = cancellationToken, BufferContent = false }; - using (var stream = await _httpClient.Get(options)) + using (var stream = await _httpClient.Get(options).ConfigureAwait(false)) { var lineup = JsonSerializer.DeserializeFromStream>(stream) ?? new List(); @@ -127,7 +124,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun CacheMode = CacheMode.Unconditional, TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), BufferContent = false - })) + + }).ConfigureAwait(false)) { var response = JsonSerializer.DeserializeFromStream(stream); @@ -169,7 +167,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun CancellationToken = cancellationToken, TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), BufferContent = false - })) + + }).ConfigureAwait(false)) { var tuners = new List(); using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8)) @@ -536,7 +535,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), CancellationToken = CancellationToken.None, BufferContent = false - })) + + }).ConfigureAwait(false)) { var response = JsonSerializer.DeserializeFromStream(stream); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs index a7e1b3cf3..df83d4341 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs @@ -74,10 +74,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts OnFinished = OnFinished }; - var initial = _sharedBuffer.ToList(); var list = new List(); - - foreach (var bytes in initial) + foreach (var bytes in _sharedBuffer) { list.AddRange(bytes); } diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index a3d43a3f9..dcaf7c676 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -401,10 +401,21 @@ namespace MediaBrowser.Api var series = item as Series; if (series != null) { - series.Status = request.SeriesStatus; + series.Status = GetSeriesStatus(request); series.AirDays = request.AirDays; series.AirTime = request.AirTime; } } + + private SeriesStatus? GetSeriesStatus(BaseItemDto item) + { + if (string.IsNullOrEmpty(item.Status)) + { + return null; + } + + return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true); + + } } } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index db5914f81..6c8c6b2ab 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -173,10 +173,6 @@ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2} MediaBrowser.Controller - - {0bd82fa6-eb8a-4452-8af5-74f9c3849451} - MediaBrowser.MediaEncoding - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B} MediaBrowser.Model diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index e561b3f46..e1559cabf 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -22,7 +22,6 @@ using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Net; -using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.Model.Diagnostics; namespace MediaBrowser.Api.Playback diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 4fb936340..912d60889 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -13,7 +13,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading; -using MediaBrowser.MediaEncoding.Encoder; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Api.Playback { diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index a52ae1df4..e50d0f2c5 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -199,9 +199,9 @@ namespace MediaBrowser.Api.Playback if (isHeadRequest) { - return service.Head(newRequest); + return await service.Head(newRequest).ConfigureAwait(false); } - return service.Get(newRequest); + return await service.Get(newRequest).ConfigureAwait(false); } else { @@ -239,9 +239,9 @@ namespace MediaBrowser.Api.Playback if (isHeadRequest) { - return service.Head(newRequest); + return await service.Head(newRequest).ConfigureAwait(false); } - return service.Get(newRequest); + return await service.Get(newRequest).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Api/Sync/SyncHelper.cs b/MediaBrowser.Api/Sync/SyncHelper.cs index 60df2bb1e..116cd8060 100644 --- a/MediaBrowser.Api/Sync/SyncHelper.cs +++ b/MediaBrowser.Api/Sync/SyncHelper.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Api.Sync options.Add(SyncJobOption.ItemLimit); break; } - if (item.IsFolderItem && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre) + if ((item.IsFolder ?? false) && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre) { options.Add(SyncJobOption.Quality); options.Add(SyncJobOption.Profile); @@ -57,7 +57,7 @@ namespace MediaBrowser.Api.Sync { if (item.SupportsSync ?? false) { - if (item.IsFolderItem || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson) + if ((item.IsFolder ?? false) || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson) { options.Add(SyncJobOption.SyncNewContent); options.Add(SyncJobOption.ItemLimit); diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 28229f8a7..db5a5009c 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -184,6 +184,8 @@ + + diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs new file mode 100644 index 000000000..c6cbc8986 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -0,0 +1,1675 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.Controller.MediaEncoding +{ + public class EncodingHelper + { + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + private readonly IMediaEncoder _mediaEncoder; + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly ISubtitleEncoder _subtitleEncoder; + + public EncodingHelper(IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder) + { + _mediaEncoder = mediaEncoder; + _config = config; + _fileSystem = fileSystem; + _subtitleEncoder = subtitleEncoder; + } + + public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) + { + var defaultEncoder = "libx264"; + + // Only use alternative encoders for video files. + // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully + // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. + if (state.VideoType == VideoType.VideoFile) + { + var hwType = encodingOptions.HardwareAccelerationType; + + if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) || + string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + { + return GetAvailableEncoder("h264_qsv", defaultEncoder); + } + + if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + return GetAvailableEncoder("h264_nvenc", defaultEncoder); + } + if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase)) + { + return GetAvailableEncoder("h264_omx", defaultEncoder); + } + if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice)) + { + if (IsVaapiSupported(state)) + { + return GetAvailableEncoder("h264_vaapi", defaultEncoder); + } + } + } + + return defaultEncoder; + } + + private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder) + { + if (_mediaEncoder.SupportsEncoder(preferredEncoder)) + { + return preferredEncoder; + } + return defaultEncoder; + } + + private bool IsVaapiSupported(EncodingJobInfo state) + { + var videoStream = state.VideoStream; + + if (videoStream != null) + { + // vaapi will throw an error with this input + // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. + if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) + { + if (videoStream.Level == -99 || videoStream.Level == 15) + { + return false; + } + } + } + return true; + } + + /// + /// Gets the name of the output video codec + /// + public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions) + { + var codec = state.OutputVideoCodec; + + if (!string.IsNullOrEmpty(codec)) + { + if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) + { + return GetH264Encoder(state, encodingOptions); + } + if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) + { + return "libvpx"; + } + if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase)) + { + return "wmv2"; + } + if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase)) + { + return "libtheora"; + } + + return codec.ToLower(); + } + + return "copy"; + } + + /// + /// Gets the user agent param. + /// + /// The state. + /// System.String. + public string GetUserAgentParam(EncodingJobInfo state) + { + string useragent = null; + + state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); + + if (!string.IsNullOrWhiteSpace(useragent)) + { + return "-user-agent \"" + useragent + "\""; + } + + return string.Empty; + } + + public string GetInputFormat(string container) + { + if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase)) + { + return "matroska"; + } + if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase)) + { + return "mpegts"; + } + + return container; + } + + public string GetDecoderFromCodec(string codec) + { + if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + return codec; + } + + /// + /// Infers the audio codec based on the url + /// + /// The URL. + /// System.Nullable{AudioCodecs}. + public string InferAudioCodec(string url) + { + var ext = Path.GetExtension(url); + + if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase)) + { + return "mp3"; + } + if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) + { + return "aac"; + } + if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) + { + return "wma"; + } + if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase)) + { + return "vorbis"; + } + if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase)) + { + return "vorbis"; + } + if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) + { + return "vorbis"; + } + if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) + { + return "vorbis"; + } + if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) + { + return "vorbis"; + } + + return "copy"; + } + + /// + /// Infers the video codec. + /// + /// The URL. + /// System.Nullable{VideoCodecs}. + public string InferVideoCodec(string url) + { + var ext = Path.GetExtension(url); + + if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase)) + { + return "wmv"; + } + if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) + { + return "vpx"; + } + if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) + { + return "theora"; + } + if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) + { + return "h264"; + } + + return "copy"; + } + + public int GetVideoProfileScore(string profile) + { + var list = new List + { + "Constrained Baseline", + "Baseline", + "Extended", + "Main", + "High", + "Progressive High", + "Constrained High" + }; + + return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase)); + } + + public string GetInputPathArgument(EncodingJobInfo state) + { + var protocol = state.InputProtocol; + var mediaPath = state.MediaPath ?? string.Empty; + + var inputPath = new[] { mediaPath }; + + if (state.IsInputVideo) + { + if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) + { + inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames); + } + } + + return _mediaEncoder.GetInputArgument(inputPath, protocol); + } + + /// + /// Gets the audio encoder. + /// + /// The state. + /// System.String. + public string GetAudioEncoder(EncodingJobInfo state) + { + var codec = state.OutputAudioCodec; + + if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)) + { + return "aac -strict experimental"; + } + if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) + { + return "libmp3lame"; + } + if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase)) + { + return "libvorbis"; + } + if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase)) + { + return "wmav2"; + } + + return codec.ToLower(); + } + + /// + /// Gets the input argument. + /// + public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) + { + var request = state.BaseRequest; + + var arg = string.Format("-i {0}", GetInputPathArgument(state)); + + if (state.SubtitleStream != null && request.SubtitleMethod == SubtitleDeliveryMethod.Encode) + { + if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + { + if (state.VideoStream != null && state.VideoStream.Width.HasValue) + { + // This is hacky but not sure how to get the exact subtitle resolution + double height = state.VideoStream.Width.Value; + height /= 16; + height *= 9; + + arg += string.Format(" -canvas_size {0}:{1}", state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture), Convert.ToInt32(height).ToString(CultureInfo.InvariantCulture)); + } + + var subtitlePath = state.SubtitleStream.Path; + + if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase)) + { + var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); + if (_fileSystem.FileExists(idxFile)) + { + subtitlePath = idxFile; + } + } + + arg += " -i \"" + subtitlePath + "\""; + } + } + + if (state.IsVideoRequest) + { + if (GetVideoEncoder(state, encodingOptions).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) + { + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && request.SubtitleMethod == SubtitleDeliveryMethod.Encode; + var hwOutputFormat = "vaapi"; + + if (hasGraphicalSubs) + { + hwOutputFormat = "yuv420p"; + } + + arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg; + } + } + + return arg.Trim(); + } + + /// + /// Determines whether the specified stream is H264. + /// + /// The stream. + /// true if the specified stream is H264; otherwise, false. + public bool IsH264(MediaStream stream) + { + var codec = stream.Codec ?? string.Empty; + + return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || + codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; + } + + public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec) + { + var bitrate = state.OutputVideoBitrate; + + if (bitrate.HasValue) + { + if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) + { + // With vpx when crf is used, b:v becomes a max rate + // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up. + return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(_usCulture)); + } + + if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) + { + return string.Format(" -b:v {0}", bitrate.Value.ToString(_usCulture)); + } + + if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + { + // h264 + return string.Format(" -maxrate {0} -bufsize {1}", + bitrate.Value.ToString(_usCulture), + (bitrate.Value * 2).ToString(_usCulture)); + } + + // h264 + return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}", + bitrate.Value.ToString(_usCulture), + (bitrate.Value * 2).ToString(_usCulture)); + } + + return string.Empty; + } + + public string NormalizeTranscodingLevel(string videoCodec, string level) + { + double requestLevel; + + // Clients may direct play higher than level 41, but there's no reason to transcode higher + if (double.TryParse(level, NumberStyles.Any, _usCulture, out requestLevel)) + { + if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) + { + if (requestLevel > 41) + { + return "41"; + } + } + } + + return level; + } + + /// + /// Gets the probe size argument. + /// + /// The state. + /// System.String. + public string GetProbeSizeArgument(EncodingJobInfo state) + { + if (state.PlayableStreamFileNames.Count > 0) + { + return _mediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol); + } + + return _mediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(new[] { state.MediaPath }, state.InputProtocol); + } + + /// + /// Gets the text subtitle param. + /// + /// The state. + /// System.String. + public string GetTextSubtitleParam(EncodingJobInfo state) + { + var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds); + + var setPtsParam = state.CopyTimestamps + ? string.Empty + : string.Format(",setpts=PTS -{0}/TB", seconds.ToString(_usCulture)); + + if (state.SubtitleStream.IsExternal) + { + var subtitlePath = state.SubtitleStream.Path; + + var charsetParam = string.Empty; + + if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) + { + var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result; + + if (!string.IsNullOrEmpty(charenc)) + { + charsetParam = ":charenc=" + charenc; + } + } + + // TODO: Perhaps also use original_size=1920x800 ?? + return string.Format("subtitles=filename='{0}'{1}{2}", + _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath), + charsetParam, + setPtsParam); + } + + var mediaPath = state.MediaPath ?? string.Empty; + + return string.Format("subtitles='{0}:si={1}'{2}", + _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), + state.InternalSubtitleStreamOffset.ToString(_usCulture), + setPtsParam); + } + + public double? GetFramerateParam(EncodingJobInfo state) + { + var request = state.BaseRequest; + + if (request.Framerate.HasValue) + { + return request.Framerate.Value; + } + + var maxrate = request.MaxFramerate; + + if (maxrate.HasValue && state.VideoStream != null) + { + var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate; + + if (contentRate.HasValue && contentRate.Value > maxrate.Value) + { + return maxrate; + } + } + + return null; + } + + /// + /// Gets the video bitrate to specify on the command line + /// + public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultH264Preset) + { + var param = string.Empty; + + var isVc1 = state.VideoStream != null && + string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); + + if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) + { + if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset)) + { + param += "-preset " + encodingOptions.H264Preset; + } + else + { + param += "-preset " + defaultH264Preset; + } + + if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51) + { + param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture); + } + else + { + param += " -crf 23"; + } + } + + else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) + { + param += "-preset fast"; + + param += " -crf 28"; + } + + // h264 (h264_qsv) + else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + { + param += "-preset 7 -look_ahead 0"; + + } + + // h264 (h264_nvenc) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + { + param += "-preset default"; + } + + // webm + else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) + { + // Values 0-3, 0 being highest quality but slower + var profileScore = 0; + + string crf; + var qmin = "0"; + var qmax = "50"; + + crf = "10"; + + if (isVc1) + { + profileScore++; + } + + // Max of 2 + profileScore = Math.Min(profileScore, 2); + + // http://www.webmproject.org/docs/encoder-parameters/ + param += string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}", + profileScore.ToString(_usCulture), + crf, + qmin, + qmax); + } + + else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase)) + { + param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; + } + + // asf/wmv + else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) + { + param += "-qmin 2"; + } + + else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase)) + { + param += "-mbd 2"; + } + + param += GetVideoBitrateParam(state, videoEncoder); + + var framerate = GetFramerateParam(state); + if (framerate.HasValue) + { + param += string.Format(" -r {0}", framerate.Value.ToString(_usCulture)); + } + + if (!string.IsNullOrEmpty(state.OutputVideoSync)) + { + param += " -vsync " + state.OutputVideoSync; + } + + var request = state.BaseRequest; + + if (!string.IsNullOrEmpty(request.Profile)) + { + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + // not supported by h264_omx + param += " -profile:v " + request.Profile; + } + } + + if (!string.IsNullOrEmpty(request.Level)) + { + var level = NormalizeTranscodingLevel(state.OutputVideoCodec, request.Level); + + // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format + // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 + if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) + { + switch (level) + { + case "30": + param += " -level 3.0"; + break; + case "31": + param += " -level 3.1"; + break; + case "32": + param += " -level 3.2"; + break; + case "40": + param += " -level 4.0"; + break; + case "41": + param += " -level 4.1"; + break; + case "42": + param += " -level 4.2"; + break; + case "50": + param += " -level 5.0"; + break; + case "51": + param += " -level 5.1"; + break; + case "52": + param += " -level 5.2"; + break; + default: + param += " -level " + level; + break; + } + } + else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) + { + param += " -level " + level; + } + } + + if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) + { + param += " -x264opts:0 subme=0:rc_lookahead=10:me_range=4:me=dia:no_chroma_me:8x8dct=0:partitions=none"; + } + + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && + !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + param = "-pix_fmt yuv420p " + param; + } + + return param; + } + + public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream) + { + var request = state.BaseRequest; + + if (videoStream.IsInterlaced) + { + return false; + } + + if (videoStream.IsAnamorphic ?? false) + { + return false; + } + + // Can't stream copy if we're burning in subtitles + if (request.SubtitleStreamIndex.HasValue) + { + if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode) + { + return false; + } + } + + if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value && request.RequireAvc) + { + return false; + } + } + + // Source and target codecs must match + if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + // If client is requesting a specific video profile, it must match the source + if (!string.IsNullOrEmpty(request.Profile)) + { + if (string.IsNullOrEmpty(videoStream.Profile)) + { + //return false; + } + + if (!string.IsNullOrEmpty(videoStream.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase)) + { + var currentScore = GetVideoProfileScore(videoStream.Profile); + var requestedScore = GetVideoProfileScore(request.Profile); + + if (currentScore == -1 || currentScore > requestedScore) + { + return false; + } + } + } + + // Video width must fall within requested value + if (request.MaxWidth.HasValue) + { + if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value) + { + return false; + } + } + + // Video height must fall within requested value + if (request.MaxHeight.HasValue) + { + if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value) + { + return false; + } + } + + // Video framerate must fall within requested value + var requestedFramerate = request.MaxFramerate ?? request.Framerate; + if (requestedFramerate.HasValue) + { + var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate; + + if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value) + { + return false; + } + } + + // Video bitrate must fall within requested value + if (request.VideoBitRate.HasValue) + { + if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value) + { + return false; + } + } + + if (request.MaxVideoBitDepth.HasValue) + { + if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > request.MaxVideoBitDepth.Value) + { + return false; + } + } + + if (request.MaxRefFrames.HasValue) + { + if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > request.MaxRefFrames.Value) + { + return false; + } + } + + // If a specific level was requested, the source must match or be less than + if (!string.IsNullOrEmpty(request.Level)) + { + double requestLevel; + + if (double.TryParse(request.Level, NumberStyles.Any, _usCulture, out requestLevel)) + { + if (!videoStream.Level.HasValue) + { + //return false; + } + + if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel) + { + return false; + } + } + } + + return request.EnableAutoStreamCopy; + } + + public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, List supportedAudioCodecs) + { + var request = state.BaseRequest; + + // Source and target codecs must match + if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + // Video bitrate must fall within requested value + if (request.AudioBitRate.HasValue) + { + if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value <= 0) + { + return false; + } + if (audioStream.BitRate.Value > request.AudioBitRate.Value) + { + return false; + } + } + + // Channels must fall within requested value + var channels = request.AudioChannels ?? request.MaxAudioChannels; + if (channels.HasValue) + { + if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0) + { + return false; + } + if (audioStream.Channels.Value > channels.Value) + { + return false; + } + } + + // Sample rate must fall within requested value + if (request.AudioSampleRate.HasValue) + { + if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0) + { + return false; + } + if (audioStream.SampleRate.Value > request.AudioSampleRate.Value) + { + return false; + } + } + + return request.EnableAutoStreamCopy; + } + + public int? GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec) + { + var bitrate = request.VideoBitRate; + + if (videoStream != null) + { + var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue && + request.Height.Value > videoStream.Height.Value; + + if (request.Width.HasValue && videoStream.Width.HasValue && + request.Width.Value > videoStream.Width.Value) + { + isUpscaling = true; + } + + // Don't allow bitrate increases unless upscaling + if (!isUpscaling) + { + if (bitrate.HasValue && videoStream.BitRate.HasValue) + { + bitrate = Math.Min(bitrate.Value, videoStream.BitRate.Value); + } + } + } + + if (bitrate.HasValue) + { + var inputVideoCodec = videoStream == null ? null : videoStream.Codec; + bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); + + // If a max bitrate was requested, don't let the scaled bitrate exceed it + if (request.VideoBitRate.HasValue) + { + bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); + } + } + + return bitrate; + } + + public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) + { + if (request.AudioBitRate.HasValue) + { + // Make sure we don't request a bitrate higher than the source + var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value; + + // Don't encode any higher than this + return Math.Min(384000, request.AudioBitRate.Value); + //return Math.Min(currentBitrate, request.AudioBitRate.Value); + } + + return null; + } + + public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions, bool isHls) + { + var volParam = string.Empty; + var audioSampleRate = string.Empty; + + var channels = state.OutputAudioChannels; + + // Boost volume to 200% when downsampling from 6ch to 2ch + if (channels.HasValue && channels.Value <= 2) + { + if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !encodingOptions.DownMixAudioBoost.Equals(1)) + { + volParam = ",volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture); + } + } + + if (state.OutputAudioSampleRate.HasValue) + { + audioSampleRate = state.OutputAudioSampleRate.Value + ":"; + } + + var adelay = isHls ? "adelay=1," : string.Empty; + + var pts = string.Empty; + + if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.BaseRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode && !state.CopyTimestamps) + { + var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds; + + pts = string.Format(",asetpts=PTS-{0}/TB", Math.Round(seconds).ToString(_usCulture)); + } + + return string.Format("-af \"{0}aresample={1}async={4}{2}{3}\"", + + adelay, + audioSampleRate, + volParam, + pts, + state.OutputAudioSync); + } + + /// + /// Gets the number of audio channels to specify on the command line + /// + /// The request. + /// The audio stream. + /// The output audio codec. + /// System.Nullable{System.Int32}. + public int? GetNumAudioChannelsParam(BaseEncodingJobOptions request, MediaStream audioStream, string outputAudioCodec) + { + var inputChannels = audioStream == null + ? null + : audioStream.Channels; + + if (inputChannels <= 0) + { + inputChannels = null; + } + + int? transcoderChannelLimit = null; + var codec = outputAudioCodec ?? string.Empty; + + if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1) + { + // wmav2 currently only supports two channel output + transcoderChannelLimit = 2; + } + + else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1) + { + // libmp3lame currently only supports two channel output + transcoderChannelLimit = 2; + } + else + { + // If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels + transcoderChannelLimit = 6; + } + + var isTranscodingAudio = !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); + + int? resultChannels = null; + if (isTranscodingAudio) + { + resultChannels = request.TranscodingMaxAudioChannels; + } + resultChannels = resultChannels ?? request.MaxAudioChannels ?? request.AudioChannels; + + if (inputChannels.HasValue) + { + resultChannels = resultChannels.HasValue + ? Math.Min(resultChannels.Value, inputChannels.Value) + : inputChannels.Value; + } + + if (isTranscodingAudio && transcoderChannelLimit.HasValue) + { + resultChannels = resultChannels.HasValue + ? Math.Min(resultChannels.Value, transcoderChannelLimit.Value) + : transcoderChannelLimit.Value; + } + + return resultChannels ?? request.AudioChannels; + } + + /// + /// Enforces the resolution limit. + /// + /// The state. + public void EnforceResolutionLimit(EncodingJobInfo state) + { + var videoRequest = state.BaseRequest; + + // Switch the incoming params to be ceilings rather than fixed values + videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width; + videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height; + + videoRequest.Width = null; + videoRequest.Height = null; + } + + /// + /// Gets the fast seek command line parameter. + /// + /// The request. + /// System.String. + /// The fast seek command line parameter. + public string GetFastSeekCommandLineParameter(BaseEncodingJobOptions request) + { + var time = request.StartTimeTicks ?? 0; + + if (time > 0) + { + return string.Format("-ss {0}", _mediaEncoder.GetTimeParameter(time)); + } + + return string.Empty; + } + + /// + /// Gets the map args. + /// + /// The state. + /// System.String. + public string GetMapArgs(EncodingJobInfo state) + { + // If we don't have known media info + // If input is video, use -sn to drop subtitles + // Otherwise just return empty + if (state.VideoStream == null && state.AudioStream == null) + { + return state.IsInputVideo ? "-sn" : string.Empty; + } + + // We have media info, but we don't know the stream indexes + if (state.VideoStream != null && state.VideoStream.Index == -1) + { + return "-sn"; + } + + // We have media info, but we don't know the stream indexes + if (state.AudioStream != null && state.AudioStream.Index == -1) + { + return state.IsInputVideo ? "-sn" : string.Empty; + } + + var args = string.Empty; + + if (state.VideoStream != null) + { + args += string.Format("-map 0:{0}", state.VideoStream.Index); + } + else + { + // No known video stream + args += "-vn"; + } + + if (state.AudioStream != null) + { + args += string.Format(" -map 0:{0}", state.AudioStream.Index); + } + + else + { + args += " -map -0:a"; + } + + var subtitleMethod = state.BaseRequest.SubtitleMethod; + if (state.SubtitleStream == null || subtitleMethod == SubtitleDeliveryMethod.Hls) + { + args += " -map -0:s"; + } + else if (subtitleMethod == SubtitleDeliveryMethod.Embed) + { + args += string.Format(" -map 0:{0}", state.SubtitleStream.Index); + } + else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + { + args += " -map 1:0 -sn"; + } + + return args; + } + + /// + /// Determines which stream will be used for playback + /// + /// All stream. + /// Index of the desired. + /// The type. + /// if set to true [return first if no index]. + /// MediaStream. + public MediaStream GetMediaStream(IEnumerable allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true) + { + var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList(); + + if (desiredIndex.HasValue) + { + var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value); + + if (stream != null) + { + return stream; + } + } + + if (type == MediaStreamType.Video) + { + streams = streams.Where(i => !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)).ToList(); + } + + if (returnFirstIfNoIndex && type == MediaStreamType.Audio) + { + return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ?? + streams.FirstOrDefault(); + } + + // Just return the first one + return returnFirstIfNoIndex ? streams.FirstOrDefault() : null; + } + + /// + /// Gets the internal graphical subtitle param. + /// + /// The state. + /// The output video codec. + /// System.String. + public string GetGraphicalSubtitleParam(EncodingJobInfo state, string outputVideoCodec) + { + var outputSizeParam = string.Empty; + + var request = state.BaseRequest; + + // Add resolution params, if specified + if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) + { + outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"'); + + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase)); + } + else + { + outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase)); + } + } + + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0) + { + outputSizeParam = ",format=nv12|vaapi,hwupload"; + } + + var videoSizeParam = string.Empty; + + if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) + { + videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(_usCulture), state.VideoStream.Height.Value.ToString(_usCulture)); + } + + var mapPrefix = state.SubtitleStream.IsExternal ? + 1 : + 0; + + var subtitleStreamIndex = state.SubtitleStream.IsExternal + ? 0 + : state.SubtitleStream.Index; + + return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub] ; [0:{2}] [sub] overlay{3}\"", + mapPrefix.ToString(_usCulture), + subtitleStreamIndex.ToString(_usCulture), + state.VideoStream.Index.ToString(_usCulture), + outputSizeParam, + videoSizeParam); + } + + /// + /// If we're going to put a fixed size on the command line, this will calculate it + /// + /// The state. + /// The output video codec. + /// if set to true [allow time stamp copy]. + /// System.String. + public string GetOutputSizeParam(EncodingJobInfo state, + string outputVideoCodec, + bool allowTimeStampCopy = true) + { + // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ + + var request = state.BaseRequest; + + var filters = new List(); + + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + filters.Add("format=nv12|vaapi"); + filters.Add("hwupload"); + } + else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + filters.Add("yadif=0:-1:0"); + } + + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + { + // Work around vaapi's reduced scaling features + var scaler = "scale_vaapi"; + + // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions + // (outputWidth, outputHeight). The user may request precise output dimensions or maximum + // output dimensions. Output dimensions are guaranteed to be even. + decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width); + decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height); + decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth; + decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight; + decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth; + decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight; + + if (outputWidth > maximumWidth || outputHeight > maximumHeight) + { + var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight); + outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale)); + outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale)); + } + + outputWidth = 2 * Math.Truncate(outputWidth / 2); + outputHeight = 2 * Math.Truncate(outputHeight / 2); + + if (outputWidth != inputWidth || outputHeight != inputHeight) + { + filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(_usCulture), outputHeight.ToString(_usCulture))); + } + } + else + { + // If fixed dimensions were supplied + if (request.Width.HasValue && request.Height.HasValue) + { + var widthParam = request.Width.Value.ToString(_usCulture); + var heightParam = request.Height.Value.ToString(_usCulture); + + filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam)); + } + + // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size + else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue) + { + var maxWidthParam = request.MaxWidth.Value.ToString(_usCulture); + var maxHeightParam = request.MaxHeight.Value.ToString(_usCulture); + + filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); + } + + // If a fixed width was requested + else if (request.Width.HasValue) + { + var widthParam = request.Width.Value.ToString(_usCulture); + + filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam)); + } + + // If a fixed height was requested + else if (request.Height.HasValue) + { + var heightParam = request.Height.Value.ToString(_usCulture); + + filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam)); + } + + // If a max width was requested + else if (request.MaxWidth.HasValue) + { + var maxWidthParam = request.MaxWidth.Value.ToString(_usCulture); + + filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam)); + } + + // If a max height was requested + else if (request.MaxHeight.HasValue) + { + var maxHeightParam = request.MaxHeight.Value.ToString(_usCulture); + + filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); + } + } + + var output = string.Empty; + + if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && request.SubtitleMethod == SubtitleDeliveryMethod.Encode) + { + var subParam = GetTextSubtitleParam(state); + + filters.Add(subParam); + + if (allowTimeStampCopy) + { + output += " -copyts"; + } + } + + if (filters.Count > 0) + { + output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray())); + } + + return output; + } + + + /// + /// Gets the number of threads. + /// + /// System.Int32. + public int GetNumberOfThreads(EncodingJobInfo state, EncodingOptions encodingOptions, bool isWebm) + { + var threads = GetNumberOfThreadsInternal(state, encodingOptions, isWebm); + + if (state.BaseRequest.CpuCoreLimit.HasValue && state.BaseRequest.CpuCoreLimit.Value > 0) + { + threads = Math.Min(threads, state.BaseRequest.CpuCoreLimit.Value); + } + + return threads; + } + + public void TryStreamCopy(EncodingJobInfo state) + { + if (state.VideoStream != null && CanStreamCopyVideo(state, state.VideoStream)) + { + state.OutputVideoCodec = "copy"; + } + else + { + var user = state.User; + + // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not + if (user != null && !user.Policy.EnableVideoPlaybackTranscoding) + { + state.OutputVideoCodec = "copy"; + } + } + + if (state.AudioStream != null && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)) + { + state.OutputAudioCodec = "copy"; + } + else + { + var user = state.User; + + // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not + if (user != null && !user.Policy.EnableAudioPlaybackTranscoding) + { + state.OutputAudioCodec = "copy"; + } + } + } + + public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) + { + var inputModifier = string.Empty; + + var probeSize = GetProbeSizeArgument(state); + inputModifier += " " + probeSize; + inputModifier = inputModifier.Trim(); + + var userAgentParam = GetUserAgentParam(state); + + if (!string.IsNullOrWhiteSpace(userAgentParam)) + { + inputModifier += " " + userAgentParam; + } + + inputModifier = inputModifier.Trim(); + + inputModifier += " " + GetFastSeekCommandLineParameter(state.BaseRequest); + inputModifier = inputModifier.Trim(); + + //inputModifier += " -fflags +genpts+ignidx+igndts"; + //if (state.IsVideoRequest && genPts) + //{ + // inputModifier += " -fflags +genpts"; + //} + + if (!string.IsNullOrEmpty(state.InputAudioSync)) + { + inputModifier += " -async " + state.InputAudioSync; + } + + if (!string.IsNullOrEmpty(state.InputVideoSync)) + { + inputModifier += " -vsync " + state.InputVideoSync; + } + + if (state.ReadInputAtNativeFramerate) + { + inputModifier += " -re"; + } + + var videoDecoder = GetVideoDecoder(state, encodingOptions); + if (!string.IsNullOrWhiteSpace(videoDecoder)) + { + inputModifier += " " + videoDecoder; + } + + if (state.IsVideoRequest) + { + // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking + if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.CopyTimestamps) + { + //inputModifier += " -noaccurate_seek"; + } + + if (!string.IsNullOrWhiteSpace(state.InputContainer)) + { + var inputFormat = GetInputFormat(state.InputContainer); + if (!string.IsNullOrWhiteSpace(inputFormat)) + { + inputModifier += " -f " + inputFormat; + } + } + + if (state.RunTimeTicks.HasValue) + { + foreach (var stream in state.MediaSource.MediaStreams) + { + if (!stream.IsExternal && stream.Type != MediaStreamType.Subtitle) + { + if (!string.IsNullOrWhiteSpace(stream.Codec) && stream.Index != -1) + { + var decoder = GetDecoderFromCodec(stream.Codec); + + if (!string.IsNullOrWhiteSpace(decoder)) + { + inputModifier += " -codec:" + stream.Index.ToString(_usCulture) + " " + decoder; + } + } + } + } + } + } + + return inputModifier; + } + + + public void AttachMediaSourceInfo(EncodingJobInfo state, + MediaSourceInfo mediaSource, + string requestedUrl) + { + state.MediaPath = mediaSource.Path; + state.InputProtocol = mediaSource.Protocol; + state.InputContainer = mediaSource.Container; + state.RunTimeTicks = mediaSource.RunTimeTicks; + state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; + + if (mediaSource.VideoType.HasValue) + { + state.VideoType = mediaSource.VideoType.Value; + } + + state.IsoType = mediaSource.IsoType; + + state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList(); + + if (mediaSource.Timestamp.HasValue) + { + state.InputTimestamp = mediaSource.Timestamp.Value; + } + + state.InputProtocol = mediaSource.Protocol; + state.MediaPath = mediaSource.Path; + state.RunTimeTicks = mediaSource.RunTimeTicks; + state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; + state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; + + if (state.ReadInputAtNativeFramerate || + mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) + { + state.OutputAudioSync = "1000"; + state.InputVideoSync = "-1"; + state.InputAudioSync = "1"; + } + + if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)) + { + // Seeing some stuttering when transcoding wma to audio-only HLS + state.InputAudioSync = "1"; + } + + var mediaStreams = mediaSource.MediaStreams; + + if (state.IsVideoRequest) + { + var videoRequest = state.BaseRequest; + + if (string.IsNullOrEmpty(videoRequest.VideoCodec)) + { + if (string.IsNullOrWhiteSpace(requestedUrl)) + { + requestedUrl = "test." + videoRequest.OutputContainer; + } + + videoRequest.VideoCodec = InferVideoCodec(requestedUrl); + } + + state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video); + state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false); + state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod; + state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio); + + if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal) + { + state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream); + } + + if (state.VideoStream != null && state.VideoStream.IsInterlaced) + { + state.DeInterlace = true; + } + + EnforceResolutionLimit(state); + } + else + { + state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true); + } + + state.MediaSource = mediaSource; + } + + /// + /// Gets the name of the output video codec + /// + protected string GetVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) + { + if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Only use alternative encoders for video files. + // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully + // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. + if (state.VideoType != VideoType.VideoFile) + { + return null; + } + + if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) + { + if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + switch (state.MediaSource.VideoStream.Codec.ToLower()) + { + case "avc": + case "h264": + if (_mediaEncoder.SupportsDecoder("h264_qsv")) + { + return "-c:v h264_qsv "; + } + break; + case "mpeg2video": + if (_mediaEncoder.SupportsDecoder("mpeg2_qsv")) + { + return "-c:v mpeg2_qsv "; + } + break; + case "vc1": + if (_mediaEncoder.SupportsDecoder("vc1_qsv")) + { + return "-c:v vc1_qsv "; + } + break; + } + } + } + + // leave blank so ffmpeg will decide + return null; + } + + /// + /// Gets the number of threads. + /// + /// System.Int32. + private int GetNumberOfThreadsInternal(EncodingJobInfo state, EncodingOptions encodingOptions, bool isWebm) + { + var threads = encodingOptions.EncodingThreadCount; + + if (isWebm) + { + // Recommended per docs + return Math.Max(Environment.ProcessorCount - 1, 2); + } + + // Automatic + if (threads == -1) + { + return 0; + } + + return threads; + } + } +} diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs new file mode 100644 index 000000000..a18b86432 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.MediaInfo; + +namespace MediaBrowser.Controller.MediaEncoding +{ + // For now, a common base class until the API and MediaEncoding classes are unified + public class EncodingJobInfo + { + private readonly ILogger _logger; + + public MediaStream VideoStream { get; set; } + public VideoType VideoType { get; set; } + public Dictionary RemoteHttpHeaders { get; set; } + public string OutputVideoCodec { get; set; } + public MediaProtocol InputProtocol { get; set; } + public string MediaPath { get; set; } + public bool IsInputVideo { get; set; } + public IIsoMount IsoMount { get; set; } + public List PlayableStreamFileNames { get; set; } + public string OutputAudioCodec { get; set; } + public int? OutputVideoBitrate { get; set; } + public MediaStream SubtitleStream { get; set; } + public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } + + public int InternalSubtitleStreamOffset { get; set; } + public MediaSourceInfo MediaSource { get; set; } + public User User { get; set; } + + public long? RunTimeTicks { get; set; } + + public bool ReadInputAtNativeFramerate { get; set; } + + public string OutputContainer { get; set; } + + public string OutputVideoSync = "-1"; + public string OutputAudioSync = "1"; + public string InputAudioSync { get; set; } + public string InputVideoSync { get; set; } + public TransportStreamTimestamp InputTimestamp { get; set; } + + public MediaStream AudioStream { get; set; } + public List SupportedAudioCodecs { get; set; } + public List SupportedVideoCodecs { get; set; } + public string InputContainer { get; set; } + public IsoType? IsoType { get; set; } + + public BaseEncodingJobOptions BaseRequest { get; set; } + + public long? StartTimeTicks + { + get { return BaseRequest.StartTimeTicks; } + } + + public bool CopyTimestamps + { + get { return BaseRequest.CopyTimestamps; } + } + + public int? OutputAudioChannels; + public int? OutputAudioSampleRate; + public bool DeInterlace { get; set; } + public bool IsVideoRequest { get; set; } + + public EncodingJobInfo(ILogger logger) + { + _logger = logger; + RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + PlayableStreamFileNames = new List(); + SupportedVideoCodecs = new List(); + SupportedVideoCodecs = new List(); + } + + /// + /// Predicts the audio sample rate that will be in the output stream + /// + public double? TargetVideoLevel + { + get + { + var stream = VideoStream; + var request = BaseRequest; + + return !string.IsNullOrEmpty(request.Level) && !request.Static + ? double.Parse(request.Level, CultureInfo.InvariantCulture) + : stream == null ? null : stream.Level; + } + } + + protected void DisposeIsoMount() + { + if (IsoMount != null) + { + try + { + IsoMount.Dispose(); + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing iso mount", ex); + } + + IsoMount = null; + } + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs deleted file mode 100644 index ed9552964..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs +++ /dev/null @@ -1,1681 +0,0 @@ -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.Common.Configuration; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncodingHelper - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - private readonly IMediaEncoder _mediaEncoder; - private readonly IServerConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly ISubtitleEncoder _subtitleEncoder; - - public EncodingHelper(IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder) - { - _mediaEncoder = mediaEncoder; - _config = config; - _fileSystem = fileSystem; - _subtitleEncoder = subtitleEncoder; - } - - public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) - { - var defaultEncoder = "libx264"; - - // Only use alternative encoders for video files. - // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully - // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. - if (state.VideoType == VideoType.VideoFile) - { - var hwType = encodingOptions.HardwareAccelerationType; - - if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_qsv", defaultEncoder); - } - - if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_nvenc", defaultEncoder); - } - if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase)) - { - return GetAvailableEncoder("h264_omx", defaultEncoder); - } - if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice)) - { - if (IsVaapiSupported(state)) - { - return GetAvailableEncoder("h264_vaapi", defaultEncoder); - } - } - } - - return defaultEncoder; - } - - private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder) - { - if (_mediaEncoder.SupportsEncoder(preferredEncoder)) - { - return preferredEncoder; - } - return defaultEncoder; - } - - private bool IsVaapiSupported(EncodingJobInfo state) - { - var videoStream = state.VideoStream; - - if (videoStream != null) - { - // vaapi will throw an error with this input - // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. - if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) - { - if (videoStream.Level == -99 || videoStream.Level == 15) - { - return false; - } - } - } - return true; - } - - /// - /// Gets the name of the output video codec - /// - public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions) - { - var codec = state.OutputVideoCodec; - - if (!string.IsNullOrEmpty(codec)) - { - if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) - { - return GetH264Encoder(state, encodingOptions); - } - if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return "libvpx"; - } - if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return "wmv2"; - } - if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return "libtheora"; - } - - return codec.ToLower(); - } - - return "copy"; - } - - /// - /// Gets the user agent param. - /// - /// The state. - /// System.String. - public string GetUserAgentParam(EncodingJobInfo state) - { - string useragent = null; - - state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); - - if (!string.IsNullOrWhiteSpace(useragent)) - { - return "-user-agent \"" + useragent + "\""; - } - - return string.Empty; - } - - public string GetInputFormat(string container) - { - if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase)) - { - return "matroska"; - } - if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase)) - { - return "mpegts"; - } - - return container; - } - - public string GetDecoderFromCodec(string codec) - { - if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - return codec; - } - - /// - /// Infers the audio codec based on the url - /// - /// The URL. - /// System.Nullable{AudioCodecs}. - public string InferAudioCodec(string url) - { - var ext = Path.GetExtension(url); - - if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase)) - { - return "mp3"; - } - if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) - { - return "aac"; - } - if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) - { - return "wma"; - } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - - return "copy"; - } - - /// - /// Infers the video codec. - /// - /// The URL. - /// System.Nullable{VideoCodecs}. - public string InferVideoCodec(string url) - { - var ext = Path.GetExtension(url); - - if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase)) - { - return "wmv"; - } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) - { - return "vpx"; - } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) - { - return "theora"; - } - if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) - { - return "h264"; - } - - return "copy"; - } - - public int GetVideoProfileScore(string profile) - { - var list = new List - { - "Constrained Baseline", - "Baseline", - "Extended", - "Main", - "High", - "Progressive High", - "Constrained High" - }; - - return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase)); - } - - public string GetInputPathArgument(EncodingJobInfo state) - { - var protocol = state.InputProtocol; - var mediaPath = state.MediaPath ?? string.Empty; - - var inputPath = new[] { mediaPath }; - - if (state.IsInputVideo) - { - if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) - { - inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames); - } - } - - return _mediaEncoder.GetInputArgument(inputPath, protocol); - } - - /// - /// Gets the audio encoder. - /// - /// The state. - /// System.String. - public string GetAudioEncoder(EncodingJobInfo state) - { - var codec = state.OutputAudioCodec; - - if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)) - { - return "aac -strict experimental"; - } - if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) - { - return "libmp3lame"; - } - if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase)) - { - return "libvorbis"; - } - if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase)) - { - return "wmav2"; - } - - return codec.ToLower(); - } - - /// - /// Gets the input argument. - /// - public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) - { - var request = state.BaseRequest; - - var arg = string.Format("-i {0}", GetInputPathArgument(state)); - - if (state.SubtitleStream != null && request.SubtitleMethod == SubtitleDeliveryMethod.Encode) - { - if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) - { - if (state.VideoStream != null && state.VideoStream.Width.HasValue) - { - // This is hacky but not sure how to get the exact subtitle resolution - double height = state.VideoStream.Width.Value; - height /= 16; - height *= 9; - - arg += string.Format(" -canvas_size {0}:{1}", state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture), Convert.ToInt32(height).ToString(CultureInfo.InvariantCulture)); - } - - var subtitlePath = state.SubtitleStream.Path; - - if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase)) - { - var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); - if (_fileSystem.FileExists(idxFile)) - { - subtitlePath = idxFile; - } - } - - arg += " -i \"" + subtitlePath + "\""; - } - } - - if (state.IsVideoRequest) - { - if (GetVideoEncoder(state, encodingOptions).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) - { - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && request.SubtitleMethod == SubtitleDeliveryMethod.Encode; - var hwOutputFormat = "vaapi"; - - if (hasGraphicalSubs) - { - hwOutputFormat = "yuv420p"; - } - - arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg; - } - } - - return arg.Trim(); - } - - /// - /// Determines whether the specified stream is H264. - /// - /// The stream. - /// true if the specified stream is H264; otherwise, false. - public bool IsH264(MediaStream stream) - { - var codec = stream.Codec ?? string.Empty; - - return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || - codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; - } - - public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec) - { - var bitrate = state.OutputVideoBitrate; - - if (bitrate.HasValue) - { - if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) - { - // With vpx when crf is used, b:v becomes a max rate - // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up. - return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(_usCulture)); - } - - if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) - { - return string.Format(" -b:v {0}", bitrate.Value.ToString(_usCulture)); - } - - if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) - { - // h264 - return string.Format(" -maxrate {0} -bufsize {1}", - bitrate.Value.ToString(_usCulture), - (bitrate.Value * 2).ToString(_usCulture)); - } - - // h264 - return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}", - bitrate.Value.ToString(_usCulture), - (bitrate.Value * 2).ToString(_usCulture)); - } - - return string.Empty; - } - - public string NormalizeTranscodingLevel(string videoCodec, string level) - { - double requestLevel; - - // Clients may direct play higher than level 41, but there's no reason to transcode higher - if (double.TryParse(level, NumberStyles.Any, _usCulture, out requestLevel)) - { - if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) - { - if (requestLevel > 41) - { - return "41"; - } - } - } - - return level; - } - - /// - /// Gets the probe size argument. - /// - /// The state. - /// System.String. - public string GetProbeSizeArgument(EncodingJobInfo state) - { - if (state.PlayableStreamFileNames.Count > 0) - { - return _mediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol); - } - - return _mediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(new[] { state.MediaPath }, state.InputProtocol); - } - - /// - /// Gets the text subtitle param. - /// - /// The state. - /// System.String. - public string GetTextSubtitleParam(EncodingJobInfo state) - { - var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds); - - var setPtsParam = state.CopyTimestamps - ? string.Empty - : string.Format(",setpts=PTS -{0}/TB", seconds.ToString(_usCulture)); - - if (state.SubtitleStream.IsExternal) - { - var subtitlePath = state.SubtitleStream.Path; - - var charsetParam = string.Empty; - - if (!string.IsNullOrEmpty(state.SubtitleStream.Language)) - { - var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result; - - if (!string.IsNullOrEmpty(charenc)) - { - charsetParam = ":charenc=" + charenc; - } - } - - // TODO: Perhaps also use original_size=1920x800 ?? - return string.Format("subtitles=filename='{0}'{1}{2}", - _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath), - charsetParam, - setPtsParam); - } - - var mediaPath = state.MediaPath ?? string.Empty; - - return string.Format("subtitles='{0}:si={1}'{2}", - _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), - state.InternalSubtitleStreamOffset.ToString(_usCulture), - setPtsParam); - } - - public double? GetFramerateParam(EncodingJobInfo state) - { - var request = state.BaseRequest; - - if (request.Framerate.HasValue) - { - return request.Framerate.Value; - } - - var maxrate = request.MaxFramerate; - - if (maxrate.HasValue && state.VideoStream != null) - { - var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate; - - if (contentRate.HasValue && contentRate.Value > maxrate.Value) - { - return maxrate; - } - } - - return null; - } - - /// - /// Gets the video bitrate to specify on the command line - /// - public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultH264Preset) - { - var param = string.Empty; - - var isVc1 = state.VideoStream != null && - string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); - - if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) - { - if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset)) - { - param += "-preset " + encodingOptions.H264Preset; - } - else - { - param += "-preset " + defaultH264Preset; - } - - if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51) - { - param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture); - } - else - { - param += " -crf 23"; - } - } - - else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) - { - param += "-preset fast"; - - param += " -crf 28"; - } - - // h264 (h264_qsv) - else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) - { - param += "-preset 7 -look_ahead 0"; - - } - - // h264 (h264_nvenc) - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) - { - param += "-preset default"; - } - - // webm - else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) - { - // Values 0-3, 0 being highest quality but slower - var profileScore = 0; - - string crf; - var qmin = "0"; - var qmax = "50"; - - crf = "10"; - - if (isVc1) - { - profileScore++; - } - - // Max of 2 - profileScore = Math.Min(profileScore, 2); - - // http://www.webmproject.org/docs/encoder-parameters/ - param += string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}", - profileScore.ToString(_usCulture), - crf, - qmin, - qmax); - } - - else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase)) - { - param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; - } - - // asf/wmv - else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) - { - param += "-qmin 2"; - } - - else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase)) - { - param += "-mbd 2"; - } - - param += GetVideoBitrateParam(state, videoEncoder); - - var framerate = GetFramerateParam(state); - if (framerate.HasValue) - { - param += string.Format(" -r {0}", framerate.Value.ToString(_usCulture)); - } - - if (!string.IsNullOrEmpty(state.OutputVideoSync)) - { - param += " -vsync " + state.OutputVideoSync; - } - - var request = state.BaseRequest; - - if (!string.IsNullOrEmpty(request.Profile)) - { - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) - { - // not supported by h264_omx - param += " -profile:v " + request.Profile; - } - } - - if (!string.IsNullOrEmpty(request.Level)) - { - var level = NormalizeTranscodingLevel(state.OutputVideoCodec, request.Level); - - // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format - // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 - if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) - { - switch (level) - { - case "30": - param += " -level 3.0"; - break; - case "31": - param += " -level 3.1"; - break; - case "32": - param += " -level 3.2"; - break; - case "40": - param += " -level 4.0"; - break; - case "41": - param += " -level 4.1"; - break; - case "42": - param += " -level 4.2"; - break; - case "50": - param += " -level 5.0"; - break; - case "51": - param += " -level 5.1"; - break; - case "52": - param += " -level 5.2"; - break; - default: - param += " -level " + level; - break; - } - } - else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) - { - param += " -level " + level; - } - } - - if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) - { - param += " -x264opts:0 subme=0:rc_lookahead=10:me_range=4:me=dia:no_chroma_me:8x8dct=0:partitions=none"; - } - - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) - { - param = "-pix_fmt yuv420p " + param; - } - - return param; - } - - public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream) - { - var request = state.BaseRequest; - - if (videoStream.IsInterlaced) - { - return false; - } - - if (videoStream.IsAnamorphic ?? false) - { - return false; - } - - // Can't stream copy if we're burning in subtitles - if (request.SubtitleStreamIndex.HasValue) - { - if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode) - { - return false; - } - } - - if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) - { - if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value && request.RequireAvc) - { - return false; - } - } - - // Source and target codecs must match - if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - // If client is requesting a specific video profile, it must match the source - if (!string.IsNullOrEmpty(request.Profile)) - { - if (string.IsNullOrEmpty(videoStream.Profile)) - { - //return false; - } - - if (!string.IsNullOrEmpty(videoStream.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase)) - { - var currentScore = GetVideoProfileScore(videoStream.Profile); - var requestedScore = GetVideoProfileScore(request.Profile); - - if (currentScore == -1 || currentScore > requestedScore) - { - return false; - } - } - } - - // Video width must fall within requested value - if (request.MaxWidth.HasValue) - { - if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value) - { - return false; - } - } - - // Video height must fall within requested value - if (request.MaxHeight.HasValue) - { - if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value) - { - return false; - } - } - - // Video framerate must fall within requested value - var requestedFramerate = request.MaxFramerate ?? request.Framerate; - if (requestedFramerate.HasValue) - { - var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate; - - if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value) - { - return false; - } - } - - // Video bitrate must fall within requested value - if (request.VideoBitRate.HasValue) - { - if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value) - { - return false; - } - } - - if (request.MaxVideoBitDepth.HasValue) - { - if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > request.MaxVideoBitDepth.Value) - { - return false; - } - } - - if (request.MaxRefFrames.HasValue) - { - if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > request.MaxRefFrames.Value) - { - return false; - } - } - - // If a specific level was requested, the source must match or be less than - if (!string.IsNullOrEmpty(request.Level)) - { - double requestLevel; - - if (double.TryParse(request.Level, NumberStyles.Any, _usCulture, out requestLevel)) - { - if (!videoStream.Level.HasValue) - { - //return false; - } - - if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel) - { - return false; - } - } - } - - return request.EnableAutoStreamCopy; - } - - public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, List supportedAudioCodecs) - { - var request = state.BaseRequest; - - // Source and target codecs must match - if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - // Video bitrate must fall within requested value - if (request.AudioBitRate.HasValue) - { - if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value <= 0) - { - return false; - } - if (audioStream.BitRate.Value > request.AudioBitRate.Value) - { - return false; - } - } - - // Channels must fall within requested value - var channels = request.AudioChannels ?? request.MaxAudioChannels; - if (channels.HasValue) - { - if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0) - { - return false; - } - if (audioStream.Channels.Value > channels.Value) - { - return false; - } - } - - // Sample rate must fall within requested value - if (request.AudioSampleRate.HasValue) - { - if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0) - { - return false; - } - if (audioStream.SampleRate.Value > request.AudioSampleRate.Value) - { - return false; - } - } - - return request.EnableAutoStreamCopy; - } - - public int? GetVideoBitrateParamValue(BaseEncodingJobOptions request, MediaStream videoStream, string outputVideoCodec) - { - var bitrate = request.VideoBitRate; - - if (videoStream != null) - { - var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue && - request.Height.Value > videoStream.Height.Value; - - if (request.Width.HasValue && videoStream.Width.HasValue && - request.Width.Value > videoStream.Width.Value) - { - isUpscaling = true; - } - - // Don't allow bitrate increases unless upscaling - if (!isUpscaling) - { - if (bitrate.HasValue && videoStream.BitRate.HasValue) - { - bitrate = Math.Min(bitrate.Value, videoStream.BitRate.Value); - } - } - } - - if (bitrate.HasValue) - { - var inputVideoCodec = videoStream == null ? null : videoStream.Codec; - bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); - - // If a max bitrate was requested, don't let the scaled bitrate exceed it - if (request.VideoBitRate.HasValue) - { - bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); - } - } - - return bitrate; - } - - public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) - { - if (request.AudioBitRate.HasValue) - { - // Make sure we don't request a bitrate higher than the source - var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value; - - // Don't encode any higher than this - return Math.Min(384000, request.AudioBitRate.Value); - //return Math.Min(currentBitrate, request.AudioBitRate.Value); - } - - return null; - } - - public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions, bool isHls) - { - var volParam = string.Empty; - var audioSampleRate = string.Empty; - - var channels = state.OutputAudioChannels; - - // Boost volume to 200% when downsampling from 6ch to 2ch - if (channels.HasValue && channels.Value <= 2) - { - if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !encodingOptions.DownMixAudioBoost.Equals(1)) - { - volParam = ",volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture); - } - } - - if (state.OutputAudioSampleRate.HasValue) - { - audioSampleRate = state.OutputAudioSampleRate.Value + ":"; - } - - var adelay = isHls ? "adelay=1," : string.Empty; - - var pts = string.Empty; - - if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.BaseRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode && !state.CopyTimestamps) - { - var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds; - - pts = string.Format(",asetpts=PTS-{0}/TB", Math.Round(seconds).ToString(_usCulture)); - } - - return string.Format("-af \"{0}aresample={1}async={4}{2}{3}\"", - - adelay, - audioSampleRate, - volParam, - pts, - state.OutputAudioSync); - } - - /// - /// Gets the number of audio channels to specify on the command line - /// - /// The request. - /// The audio stream. - /// The output audio codec. - /// System.Nullable{System.Int32}. - public int? GetNumAudioChannelsParam(BaseEncodingJobOptions request, MediaStream audioStream, string outputAudioCodec) - { - var inputChannels = audioStream == null - ? null - : audioStream.Channels; - - if (inputChannels <= 0) - { - inputChannels = null; - } - - int? transcoderChannelLimit = null; - var codec = outputAudioCodec ?? string.Empty; - - if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1) - { - // wmav2 currently only supports two channel output - transcoderChannelLimit = 2; - } - - else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1) - { - // libmp3lame currently only supports two channel output - transcoderChannelLimit = 2; - } - else - { - // If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels - transcoderChannelLimit = 6; - } - - var isTranscodingAudio = !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); - - int? resultChannels = null; - if (isTranscodingAudio) - { - resultChannels = request.TranscodingMaxAudioChannels; - } - resultChannels = resultChannels ?? request.MaxAudioChannels ?? request.AudioChannels; - - if (inputChannels.HasValue) - { - resultChannels = resultChannels.HasValue - ? Math.Min(resultChannels.Value, inputChannels.Value) - : inputChannels.Value; - } - - if (isTranscodingAudio && transcoderChannelLimit.HasValue) - { - resultChannels = resultChannels.HasValue - ? Math.Min(resultChannels.Value, transcoderChannelLimit.Value) - : transcoderChannelLimit.Value; - } - - return resultChannels ?? request.AudioChannels; - } - - /// - /// Enforces the resolution limit. - /// - /// The state. - public void EnforceResolutionLimit(EncodingJobInfo state) - { - var videoRequest = state.BaseRequest; - - // Switch the incoming params to be ceilings rather than fixed values - videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width; - videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height; - - videoRequest.Width = null; - videoRequest.Height = null; - } - - /// - /// Gets the fast seek command line parameter. - /// - /// The request. - /// System.String. - /// The fast seek command line parameter. - public string GetFastSeekCommandLineParameter(BaseEncodingJobOptions request) - { - var time = request.StartTimeTicks ?? 0; - - if (time > 0) - { - return string.Format("-ss {0}", _mediaEncoder.GetTimeParameter(time)); - } - - return string.Empty; - } - - /// - /// Gets the map args. - /// - /// The state. - /// System.String. - public string GetMapArgs(EncodingJobInfo state) - { - // If we don't have known media info - // If input is video, use -sn to drop subtitles - // Otherwise just return empty - if (state.VideoStream == null && state.AudioStream == null) - { - return state.IsInputVideo ? "-sn" : string.Empty; - } - - // We have media info, but we don't know the stream indexes - if (state.VideoStream != null && state.VideoStream.Index == -1) - { - return "-sn"; - } - - // We have media info, but we don't know the stream indexes - if (state.AudioStream != null && state.AudioStream.Index == -1) - { - return state.IsInputVideo ? "-sn" : string.Empty; - } - - var args = string.Empty; - - if (state.VideoStream != null) - { - args += string.Format("-map 0:{0}", state.VideoStream.Index); - } - else - { - // No known video stream - args += "-vn"; - } - - if (state.AudioStream != null) - { - args += string.Format(" -map 0:{0}", state.AudioStream.Index); - } - - else - { - args += " -map -0:a"; - } - - var subtitleMethod = state.BaseRequest.SubtitleMethod; - if (state.SubtitleStream == null || subtitleMethod == SubtitleDeliveryMethod.Hls) - { - args += " -map -0:s"; - } - else if (subtitleMethod == SubtitleDeliveryMethod.Embed) - { - args += string.Format(" -map 0:{0}", state.SubtitleStream.Index); - } - else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) - { - args += " -map 1:0 -sn"; - } - - return args; - } - - /// - /// Determines which stream will be used for playback - /// - /// All stream. - /// Index of the desired. - /// The type. - /// if set to true [return first if no index]. - /// MediaStream. - public MediaStream GetMediaStream(IEnumerable allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true) - { - var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList(); - - if (desiredIndex.HasValue) - { - var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value); - - if (stream != null) - { - return stream; - } - } - - if (type == MediaStreamType.Video) - { - streams = streams.Where(i => !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)).ToList(); - } - - if (returnFirstIfNoIndex && type == MediaStreamType.Audio) - { - return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ?? - streams.FirstOrDefault(); - } - - // Just return the first one - return returnFirstIfNoIndex ? streams.FirstOrDefault() : null; - } - - /// - /// Gets the internal graphical subtitle param. - /// - /// The state. - /// The output video codec. - /// System.String. - public string GetGraphicalSubtitleParam(EncodingJobInfo state, string outputVideoCodec) - { - var outputSizeParam = string.Empty; - - var request = state.BaseRequest; - - // Add resolution params, if specified - if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) - { - outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"'); - - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) - { - outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase)); - } - else - { - outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase)); - } - } - - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0) - { - outputSizeParam = ",format=nv12|vaapi,hwupload"; - } - - var videoSizeParam = string.Empty; - - if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) - { - videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(_usCulture), state.VideoStream.Height.Value.ToString(_usCulture)); - } - - var mapPrefix = state.SubtitleStream.IsExternal ? - 1 : - 0; - - var subtitleStreamIndex = state.SubtitleStream.IsExternal - ? 0 - : state.SubtitleStream.Index; - - return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub] ; [0:{2}] [sub] overlay{3}\"", - mapPrefix.ToString(_usCulture), - subtitleStreamIndex.ToString(_usCulture), - state.VideoStream.Index.ToString(_usCulture), - outputSizeParam, - videoSizeParam); - } - - /// - /// If we're going to put a fixed size on the command line, this will calculate it - /// - /// The state. - /// The output video codec. - /// if set to true [allow time stamp copy]. - /// System.String. - public string GetOutputSizeParam(EncodingJobInfo state, - string outputVideoCodec, - bool allowTimeStampCopy = true) - { - // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/ - - var request = state.BaseRequest; - - var filters = new List(); - - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) - { - filters.Add("format=nv12|vaapi"); - filters.Add("hwupload"); - } - else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) - { - filters.Add("yadif=0:-1:0"); - } - - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) - { - // Work around vaapi's reduced scaling features - var scaler = "scale_vaapi"; - - // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions - // (outputWidth, outputHeight). The user may request precise output dimensions or maximum - // output dimensions. Output dimensions are guaranteed to be even. - decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width); - decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height); - decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth; - decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight; - decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth; - decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight; - - if (outputWidth > maximumWidth || outputHeight > maximumHeight) - { - var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight); - outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale)); - outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale)); - } - - outputWidth = 2 * Math.Truncate(outputWidth / 2); - outputHeight = 2 * Math.Truncate(outputHeight / 2); - - if (outputWidth != inputWidth || outputHeight != inputHeight) - { - filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(_usCulture), outputHeight.ToString(_usCulture))); - } - } - else - { - // If fixed dimensions were supplied - if (request.Width.HasValue && request.Height.HasValue) - { - var widthParam = request.Width.Value.ToString(_usCulture); - var heightParam = request.Height.Value.ToString(_usCulture); - - filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam)); - } - - // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size - else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue) - { - var maxWidthParam = request.MaxWidth.Value.ToString(_usCulture); - var maxHeightParam = request.MaxHeight.Value.ToString(_usCulture); - - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); - } - - // If a fixed width was requested - else if (request.Width.HasValue) - { - var widthParam = request.Width.Value.ToString(_usCulture); - - filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam)); - } - - // If a fixed height was requested - else if (request.Height.HasValue) - { - var heightParam = request.Height.Value.ToString(_usCulture); - - filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam)); - } - - // If a max width was requested - else if (request.MaxWidth.HasValue) - { - var maxWidthParam = request.MaxWidth.Value.ToString(_usCulture); - - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam)); - } - - // If a max height was requested - else if (request.MaxHeight.HasValue) - { - var maxHeightParam = request.MaxHeight.Value.ToString(_usCulture); - - filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); - } - } - - var output = string.Empty; - - if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && request.SubtitleMethod == SubtitleDeliveryMethod.Encode) - { - var subParam = GetTextSubtitleParam(state); - - filters.Add(subParam); - - if (allowTimeStampCopy) - { - output += " -copyts"; - } - } - - if (filters.Count > 0) - { - output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray())); - } - - return output; - } - - - /// - /// Gets the number of threads. - /// - /// System.Int32. - public int GetNumberOfThreads(EncodingJobInfo state, EncodingOptions encodingOptions, bool isWebm) - { - var threads = GetNumberOfThreadsInternal(state, encodingOptions, isWebm); - - if (state.BaseRequest.CpuCoreLimit.HasValue && state.BaseRequest.CpuCoreLimit.Value > 0) - { - threads = Math.Min(threads, state.BaseRequest.CpuCoreLimit.Value); - } - - return threads; - } - - public void TryStreamCopy(EncodingJobInfo state) - { - if (state.VideoStream != null && CanStreamCopyVideo(state, state.VideoStream)) - { - state.OutputVideoCodec = "copy"; - } - else - { - var user = state.User; - - // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not - if (user != null && !user.Policy.EnableVideoPlaybackTranscoding) - { - state.OutputVideoCodec = "copy"; - } - } - - if (state.AudioStream != null && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)) - { - state.OutputAudioCodec = "copy"; - } - else - { - var user = state.User; - - // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not - if (user != null && !user.Policy.EnableAudioPlaybackTranscoding) - { - state.OutputAudioCodec = "copy"; - } - } - } - - public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) - { - var inputModifier = string.Empty; - - var probeSize = GetProbeSizeArgument(state); - inputModifier += " " + probeSize; - inputModifier = inputModifier.Trim(); - - var userAgentParam = GetUserAgentParam(state); - - if (!string.IsNullOrWhiteSpace(userAgentParam)) - { - inputModifier += " " + userAgentParam; - } - - inputModifier = inputModifier.Trim(); - - inputModifier += " " + GetFastSeekCommandLineParameter(state.BaseRequest); - inputModifier = inputModifier.Trim(); - - //inputModifier += " -fflags +genpts+ignidx+igndts"; - //if (state.IsVideoRequest && genPts) - //{ - // inputModifier += " -fflags +genpts"; - //} - - if (!string.IsNullOrEmpty(state.InputAudioSync)) - { - inputModifier += " -async " + state.InputAudioSync; - } - - if (!string.IsNullOrEmpty(state.InputVideoSync)) - { - inputModifier += " -vsync " + state.InputVideoSync; - } - - if (state.ReadInputAtNativeFramerate) - { - inputModifier += " -re"; - } - - var videoDecoder = GetVideoDecoder(state, encodingOptions); - if (!string.IsNullOrWhiteSpace(videoDecoder)) - { - inputModifier += " " + videoDecoder; - } - - if (state.IsVideoRequest) - { - // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking - if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.CopyTimestamps) - { - //inputModifier += " -noaccurate_seek"; - } - - if (!string.IsNullOrWhiteSpace(state.InputContainer)) - { - var inputFormat = GetInputFormat(state.InputContainer); - if (!string.IsNullOrWhiteSpace(inputFormat)) - { - inputModifier += " -f " + inputFormat; - } - } - - if (state.RunTimeTicks.HasValue) - { - foreach (var stream in state.MediaSource.MediaStreams) - { - if (!stream.IsExternal && stream.Type != MediaStreamType.Subtitle) - { - if (!string.IsNullOrWhiteSpace(stream.Codec) && stream.Index != -1) - { - var decoder = GetDecoderFromCodec(stream.Codec); - - if (!string.IsNullOrWhiteSpace(decoder)) - { - inputModifier += " -codec:" + stream.Index.ToString(_usCulture) + " " + decoder; - } - } - } - } - } - } - - return inputModifier; - } - - - public void AttachMediaSourceInfo(EncodingJobInfo state, - MediaSourceInfo mediaSource, - string requestedUrl) - { - state.MediaPath = mediaSource.Path; - state.InputProtocol = mediaSource.Protocol; - state.InputContainer = mediaSource.Container; - state.RunTimeTicks = mediaSource.RunTimeTicks; - state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; - - if (mediaSource.VideoType.HasValue) - { - state.VideoType = mediaSource.VideoType.Value; - } - - state.IsoType = mediaSource.IsoType; - - state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList(); - - if (mediaSource.Timestamp.HasValue) - { - state.InputTimestamp = mediaSource.Timestamp.Value; - } - - state.InputProtocol = mediaSource.Protocol; - state.MediaPath = mediaSource.Path; - state.RunTimeTicks = mediaSource.RunTimeTicks; - state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; - state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; - - if (state.ReadInputAtNativeFramerate || - mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) - { - state.OutputAudioSync = "1000"; - state.InputVideoSync = "-1"; - state.InputAudioSync = "1"; - } - - if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase)) - { - // Seeing some stuttering when transcoding wma to audio-only HLS - state.InputAudioSync = "1"; - } - - var mediaStreams = mediaSource.MediaStreams; - - if (state.IsVideoRequest) - { - var videoRequest = state.BaseRequest; - - if (string.IsNullOrEmpty(videoRequest.VideoCodec)) - { - if (string.IsNullOrWhiteSpace(requestedUrl)) - { - requestedUrl = "test." + videoRequest.OutputContainer; - } - - videoRequest.VideoCodec = InferVideoCodec(requestedUrl); - } - - state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video); - state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false); - state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod; - state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio); - - if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal) - { - state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream); - } - - if (state.VideoStream != null && state.VideoStream.IsInterlaced) - { - state.DeInterlace = true; - } - - EnforceResolutionLimit(state); - } - else - { - state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true); - } - - state.MediaSource = mediaSource; - } - - /// - /// Gets the name of the output video codec - /// - protected string GetVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) - { - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - // Only use alternative encoders for video files. - // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully - // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. - if (state.VideoType != VideoType.VideoFile) - { - return null; - } - - if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) - { - if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - switch (state.MediaSource.VideoStream.Codec.ToLower()) - { - case "avc": - case "h264": - if (_mediaEncoder.SupportsDecoder("h264_qsv")) - { - return "-c:v h264_qsv "; - } - break; - case "mpeg2video": - if (_mediaEncoder.SupportsDecoder("mpeg2_qsv")) - { - return "-c:v mpeg2_qsv "; - } - break; - case "vc1": - if (_mediaEncoder.SupportsDecoder("vc1_qsv")) - { - return "-c:v vc1_qsv "; - } - break; - } - } - } - - // leave blank so ffmpeg will decide - return null; - } - - /// - /// Gets the number of threads. - /// - /// System.Int32. - private int GetNumberOfThreadsInternal(EncodingJobInfo state, EncodingOptions encodingOptions, bool isWebm) - { - var threads = encodingOptions.EncodingThreadCount; - - if (isWebm) - { - // Recommended per docs - return Math.Max(Environment.ProcessorCount - 1, 2); - } - - // Automatic - if (threads == -1) - { - return 0; - } - - return threads; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs deleted file mode 100644 index 20a9817a3..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - // For now, a common base class until the API and MediaEncoding classes are unified - public class EncodingJobInfo - { - private readonly ILogger _logger; - - public MediaStream VideoStream { get; set; } - public VideoType VideoType { get; set; } - public Dictionary RemoteHttpHeaders { get; set; } - public string OutputVideoCodec { get; set; } - public MediaProtocol InputProtocol { get; set; } - public string MediaPath { get; set; } - public bool IsInputVideo { get; set; } - public IIsoMount IsoMount { get; set; } - public List PlayableStreamFileNames { get; set; } - public string OutputAudioCodec { get; set; } - public int? OutputVideoBitrate { get; set; } - public MediaStream SubtitleStream { get; set; } - public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } - - public int InternalSubtitleStreamOffset { get; set; } - public MediaSourceInfo MediaSource { get; set; } - public User User { get; set; } - - public long? RunTimeTicks { get; set; } - - public bool ReadInputAtNativeFramerate { get; set; } - - public string OutputContainer { get; set; } - - public string OutputVideoSync = "-1"; - public string OutputAudioSync = "1"; - public string InputAudioSync { get; set; } - public string InputVideoSync { get; set; } - public TransportStreamTimestamp InputTimestamp { get; set; } - - public MediaStream AudioStream { get; set; } - public List SupportedAudioCodecs { get; set; } - public List SupportedVideoCodecs { get; set; } - public string InputContainer { get; set; } - public IsoType? IsoType { get; set; } - - public BaseEncodingJobOptions BaseRequest { get; set; } - - public long? StartTimeTicks - { - get { return BaseRequest.StartTimeTicks; } - } - - public bool CopyTimestamps - { - get { return BaseRequest.CopyTimestamps; } - } - - public int? OutputAudioChannels; - public int? OutputAudioSampleRate; - public bool DeInterlace { get; set; } - public bool IsVideoRequest { get; set; } - - public EncodingJobInfo(ILogger logger) - { - _logger = logger; - RemoteHttpHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - PlayableStreamFileNames = new List(); - SupportedVideoCodecs = new List(); - SupportedVideoCodecs = new List(); - } - - /// - /// Predicts the audio sample rate that will be in the output stream - /// - public double? TargetVideoLevel - { - get - { - var stream = VideoStream; - var request = BaseRequest; - - return !string.IsNullOrEmpty(request.Level) && !request.Static - ? double.Parse(request.Level, CultureInfo.InvariantCulture) - : stream == null ? null : stream.Level; - } - } - - protected void DisposeIsoMount() - { - if (IsoMount != null) - { - try - { - IsoMount.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing iso mount", ex); - } - - IsoMount = null; - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 5eac1a16d..63e789a59 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -50,10 +50,8 @@ - - diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index ff3f0be59..f8e1ca1b4 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -342,15 +342,6 @@ namespace MediaBrowser.Model.Dto /// true if this instance is folder; otherwise, false. public bool? IsFolder { get; set; } - [IgnoreDataMember] - public bool IsFolderItem - { - get - { - return IsFolder ?? false; - } - } - /// /// Gets or sets the parent id. /// @@ -458,56 +449,6 @@ namespace MediaBrowser.Model.Dto /// The status. public string Status { get; set; } - [IgnoreDataMember] - public SeriesStatus? SeriesStatus - { - get - { - if (string.IsNullOrEmpty(Status)) - { - return null; - } - - return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), Status, true); - } - set - { - if (value == null) - { - Status = null; - } - else - { - Status = value.Value.ToString(); - } - } - } - - [IgnoreDataMember] - public RecordingStatus? RecordingStatus - { - get - { - if (string.IsNullOrEmpty(Status)) - { - return null; - } - - return (RecordingStatus)Enum.Parse(typeof(RecordingStatus), Status, true); - } - set - { - if (value == null) - { - Status = null; - } - else - { - Status = value.Value.ToString(); - } - } - } - /// /// Gets or sets the air time. /// @@ -650,19 +591,6 @@ namespace MediaBrowser.Model.Dto return IsType(type.Name); } - /// - /// Gets or sets a value indicating whether [supports playlists]. - /// - /// true if [supports playlists]; otherwise, false. - [IgnoreDataMember] - public bool SupportsPlaylists - { - get - { - return RunTimeTicks.HasValue || IsFolderItem || IsGenre || IsMusicGenre || IsArtist; - } - } - /// /// Determines whether the specified type is type. /// @@ -876,56 +804,6 @@ namespace MediaBrowser.Model.Dto /// The series timer identifier. public string SeriesTimerId { get; set; } - /// - /// Gets a value indicating whether this instance can resume. - /// - /// true if this instance can resume; otherwise, false. - [IgnoreDataMember] - public bool CanResume - { - get { return UserData != null && UserData.PlaybackPositionTicks > 0; } - } - - /// - /// Gets the resume position ticks. - /// - /// The resume position ticks. - [IgnoreDataMember] - public long ResumePositionTicks - { - get { return UserData == null ? 0 : UserData.PlaybackPositionTicks; } - } - - /// - /// Gets the backdrop count. - /// - /// The backdrop count. - [IgnoreDataMember] - public int BackdropCount - { - get { return BackdropImageTags == null ? 0 : BackdropImageTags.Count; } - } - - /// - /// Gets the screenshot count. - /// - /// The screenshot count. - [IgnoreDataMember] - public int ScreenshotCount - { - get { return ScreenshotImageTags == null ? 0 : ScreenshotImageTags.Count; } - } - - /// - /// Gets a value indicating whether this instance has banner. - /// - /// true if this instance has banner; otherwise, false. - [IgnoreDataMember] - public bool HasBanner - { - get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Banner); } - } - /// /// Gets a value indicating whether this instance has art. /// @@ -976,46 +854,6 @@ namespace MediaBrowser.Model.Dto get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Primary); } } - /// - /// Gets a value indicating whether this instance has disc image. - /// - /// true if this instance has disc image; otherwise, false. - [IgnoreDataMember] - public bool HasDiscImage - { - get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Disc); } - } - - /// - /// Gets a value indicating whether this instance has box image. - /// - /// true if this instance has box image; otherwise, false. - [IgnoreDataMember] - public bool HasBoxImage - { - get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Box); } - } - - /// - /// Gets a value indicating whether this instance has box image. - /// - /// true if this instance has box image; otherwise, false. - [IgnoreDataMember] - public bool HasBoxRearImage - { - get { return ImageTags != null && ImageTags.ContainsKey(ImageType.BoxRear); } - } - - /// - /// Gets a value indicating whether this instance has menu image. - /// - /// true if this instance has menu image; otherwise, false. - [IgnoreDataMember] - public bool HasMenuImage - { - get { return ImageTags != null && ImageTags.ContainsKey(ImageType.Menu); } - } - /// /// Gets a value indicating whether this instance is video. /// @@ -1056,16 +894,6 @@ namespace MediaBrowser.Model.Dto get { return StringHelper.EqualsIgnoreCase(Type, "Person"); } } - /// - /// Gets a value indicating whether this instance is root. - /// - /// true if this instance is root; otherwise, false. - [IgnoreDataMember] - public bool IsRoot - { - get { return StringHelper.EqualsIgnoreCase(Type, "AggregateFolder"); } - } - [IgnoreDataMember] public bool IsMusicGenre { diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 5cf52e0ef..e2c6b0503 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -48,15 +48,6 @@ namespace MediaBrowser.Model.LiveTv public bool ImportFavoritesOnly { get; set; } public bool AllowHWTranscoding { get; set; } public bool IsEnabled { get; set; } - public string M3UUrl { get; set; } - public string InfoUrl { get; set; } - public string FriendlyName { get; set; } - public int Tuners { get; set; } - public string DiseqC { get; set; } - public string SourceA { get; set; } - public string SourceB { get; set; } - public string SourceC { get; set; } - public string SourceD { get; set; } public bool EnableTvgId { get; set; } public TunerHostInfo() @@ -85,6 +76,7 @@ namespace MediaBrowser.Model.LiveTv public string[] MovieCategories { get; set; } public NameValuePair[] ChannelMappings { get; set; } public string MoviePrefix { get; set; } + public bool EnableNewProgramIds { get; set; } public ListingsProviderInfo() { diff --git a/MediaBrowser.Server.Mono/MonoAppHost.cs b/MediaBrowser.Server.Mono/MonoAppHost.cs index 932e2d6cd..93ced1186 100644 --- a/MediaBrowser.Server.Mono/MonoAppHost.cs +++ b/MediaBrowser.Server.Mono/MonoAppHost.cs @@ -26,63 +26,6 @@ namespace MediaBrowser.Server.Mono } } - public override bool CanSelfUpdate - { - get - { - return false; - } - } - - protected override FFMpegInstallInfo GetFfmpegInstallInfo() - { - var info = new FFMpegInstallInfo(); - - // Windows builds: http://ffmpeg.zeranoe.com/builds/ - // Linux builds: http://johnvansickle.com/ffmpeg/ - // OS X builds: http://ffmpegmac.net/ - // OS X x64: http://www.evermeet.cx/ffmpeg/ - - var environment = (MonoEnvironmentInfo) EnvironmentInfo; - - if (environment.IsBsd) - { - - } - else if (environment.OperatingSystem == Model.System.OperatingSystem.Linux) - { - info.FFMpegFilename = "ffmpeg"; - info.FFProbeFilename = "ffprobe"; - info.ArchiveType = "7z"; - info.Version = "20160215"; - info.DownloadUrls = GetDownloadUrls(); - } - - // No version available - user requirement - info.DownloadUrls = new string[] { }; - - return info; - } - - private string[] GetDownloadUrls() - { - switch (EnvironmentInfo.SystemArchitecture) - { - case Architecture.X64: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z" - }; - case Architecture.X86: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z" - }; - } - - return new string[] { }; - } - protected override void RestartInternal() { MainClass.Restart(StartupOptions); @@ -137,40 +80,5 @@ namespace MediaBrowser.Server.Mono return new Version(1, 0); } - - protected override void AuthorizeServer() - { - throw new NotImplementedException(); - } - - protected override void ConfigureAutoRunInternal(bool autorun) - { - throw new NotImplementedException(); - } - - protected override void EnableLoopbackInternal(string appName) - { - } - - public override bool SupportsRunningAsService - { - get - { - return false; - } - } - - public override bool SupportsAutoRunAtStartup - { - get { return false; } - } - - public override bool IsRunningAsService - { - get - { - return false; - } - } } } diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 0e3f684b5..c42cd0396 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -324,7 +324,7 @@ namespace MediaBrowser.ServerApplication /// The options. private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService, StartupOptions options) { - var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), true, true, true, appPaths.TempDirectory); + var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), true, true, false, appPaths.TempDirectory); fileSystem.AddShortcutHandler(new LnkShortcutHandler()); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index ec66923aa..9d19525b4 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -27,38 +27,6 @@ namespace MediaBrowser.ServerApplication get { return MainStartup.IsRunningAsService; } } - protected override FFMpegInstallInfo GetFfmpegInstallInfo() - { - var info = new FFMpegInstallInfo(); - - info.FFMpegFilename = "ffmpeg.exe"; - info.FFProbeFilename = "ffprobe.exe"; - info.Version = "20160410"; - info.ArchiveType = "7z"; - info.DownloadUrls = GetDownloadUrls(); - - return info; - } - - private string[] GetDownloadUrls() - { - switch (EnvironmentInfo.SystemArchitecture) - { - case Architecture.X64: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win64.7z" - }; - case Architecture.X86: - return new[] - { - "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win32.7z" - }; - } - - return new string[] { }; - } - protected override void RestartInternal() { MainStartup.Restart(); @@ -94,7 +62,7 @@ namespace MediaBrowser.ServerApplication protected override void ConfigureAutoRunInternal(bool autorun) { - var startupPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Startup); + var startupPath = Environment.GetFolderPath(System.Environment.SpecialFolder.Startup); if (autorun && !MainStartup.IsRunningAsService) { @@ -121,14 +89,6 @@ namespace MediaBrowser.ServerApplication } } - protected override bool SupportsDualModeSockets - { - get - { - return true; - } - } - protected override void EnableLoopbackInternal(string appName) { LoopUtil.Run(appName); @@ -150,14 +110,6 @@ namespace MediaBrowser.ServerApplication } } - public override bool SupportsAutoRunAtStartup - { - get - { - return true; - } - } - public override bool CanSelfUpdate { get @@ -165,61 +117,5 @@ namespace MediaBrowser.ServerApplication return MainStartup.CanSelfUpdate; } } - - public bool PortsRequireAuthorization(string applicationPath) - { - var appNameSrch = Path.GetFileName(applicationPath); - - var startInfo = new ProcessStartInfo - { - FileName = "netsh", - - Arguments = "advfirewall firewall show rule \"" + appNameSrch + "\"", - - CreateNoWindow = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false, - RedirectStandardOutput = true - }; - - using (var process = Process.Start(startInfo)) - { - process.Start(); - - try - { - var data = process.StandardOutput.ReadToEnd() ?? string.Empty; - - if (data.IndexOf("Block", StringComparison.OrdinalIgnoreCase) != -1) - { - Logger.Info("Found potential windows firewall rule blocking Emby Server: " + data); - } - - //var parts = data.Split('\n'); - - //return parts.Length > 4; - //return Confirm(); - return false; - } - catch (Exception ex) - { - Logger.ErrorException("Error querying windows firewall", ex); - - // Hate having to do this - try - { - process.Kill(); - } - catch (Exception ex1) - { - Logger.ErrorException("Error killing process", ex1); - } - - throw; - } - } - } - } } diff --git a/src/Emby.Server/Program.cs b/src/Emby.Server/Program.cs index fde2ab7b2..5e55cfa29 100644 --- a/src/Emby.Server/Program.cs +++ b/src/Emby.Server/Program.cs @@ -193,7 +193,7 @@ namespace Emby.Server /// The options. private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, StartupOptions options, EnvironmentInfo environmentInfo) { - var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), true, true, true, appPaths.TempDirectory); + var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), true, true, false, appPaths.TempDirectory); fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); -- cgit v1.2.3 From d8f48e8cdfbee12507f3b1e1235309ef2507416c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 18 Feb 2017 23:13:54 -0500 Subject: update components --- Emby.Server.Implementations/Dto/DtoService.cs | 44 +------------------------ MediaBrowser.Api/ItemUpdateService.cs | 7 ---- MediaBrowser.Controller/Entities/UserView.cs | 5 --- MediaBrowser.Model/Dto/BaseItemDto.cs | 46 --------------------------- 4 files changed, 1 insertion(+), 101 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 824812494..696be80ed 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -360,7 +360,6 @@ namespace Emby.Server.Implementations.Dto var collectionFolder = item as ICollectionFolder; if (collectionFolder != null) { - dto.OriginalCollectionType = collectionFolder.CollectionType; dto.CollectionType = collectionFolder.CollectionType; } @@ -492,7 +491,7 @@ namespace Emby.Server.Implementations.Dto } } - if (!(item is LiveTvProgram)) + //if (!(item is LiveTvProgram)) { dto.PlayAccess = item.GetPlayAccess(user); } @@ -505,33 +504,6 @@ namespace Emby.Server.Implementations.Dto dto.SupportsSync = true; } } - - if (fields.Contains(ItemFields.SeasonUserData)) - { - var episode = item as Episode; - - if (episode != null) - { - var season = episode.Season; - - if (season != null) - { - dto.SeasonUserData = await _userDataRepository.GetUserDataDto(season, user).ConfigureAwait(false); - } - } - } - - var userView = item as UserView; - if (userView != null) - { - dto.HasDynamicCategories = userView.ContainsDynamicCategories(user); - } - - var collectionFolder = item as ICollectionFolder; - if (collectionFolder != null) - { - dto.HasDynamicCategories = false; - } } private int GetChildCount(Folder folder, User user) @@ -882,20 +854,6 @@ namespace Emby.Server.Implementations.Dto } dto.Container = item.Container; - var hasBudget = item as IHasBudget; - if (hasBudget != null) - { - if (fields.Contains(ItemFields.Budget)) - { - dto.Budget = hasBudget.Budget; - } - - if (fields.Contains(ItemFields.Revenue)) - { - dto.Revenue = hasBudget.Revenue; - } - } - dto.EndDate = item.EndDate; if (fields.Contains(ItemFields.HomePageUrl)) diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index dcaf7c676..cadf52be1 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -237,13 +237,6 @@ namespace MediaBrowser.Api item.Name = request.Name; item.ForcedSortName = request.ForcedSortName; - var hasBudget = item as IHasBudget; - if (hasBudget != null) - { - hasBudget.Budget = request.Budget; - hasBudget.Revenue = request.Revenue; - } - item.OriginalTitle = string.IsNullOrWhiteSpace(request.OriginalTitle) ? null : request.OriginalTitle; item.CriticRating = request.CriticRating; diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 5ac5843d7..fb00937fb 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -20,11 +20,6 @@ namespace MediaBrowser.Controller.Entities public static ITVSeriesManager TVSeriesManager; public static IPlaylistManager PlaylistManager; - public bool ContainsDynamicCategories(User user) - { - return true; - } - public override IEnumerable GetIdsForAncestorQuery() { var list = new List(); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index f8e1ca1b4..c78c92967 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -80,10 +80,8 @@ namespace MediaBrowser.Model.Dto public string PreferredMetadataCountryCode { get; set; } public string AwardSummary { get; set; } - public string ShareUrl { get; set; } public float? Metascore { get; set; } - public bool? HasDynamicCategories { get; set; } /// /// Gets or sets a value indicating whether [supports synchronize]. @@ -318,12 +316,6 @@ namespace MediaBrowser.Model.Dto /// The trailer urls. public List RemoteTrailers { get; set; } - /// - /// Gets or sets the soundtrack ids. - /// - /// The soundtrack ids. - public string[] SoundtrackIds { get; set; } - /// /// Gets or sets the provider ids. /// @@ -395,11 +387,6 @@ namespace MediaBrowser.Model.Dto /// /// The user data. public UserItemDataDto UserData { get; set; } - /// - /// Gets or sets the season user data. - /// - /// The season user data. - public UserItemDataDto SeasonUserData { get; set; } /// /// Gets or sets the recursive item count. @@ -461,12 +448,6 @@ namespace MediaBrowser.Model.Dto /// The air days. public List AirDays { get; set; } - /// - /// Gets or sets the index options. - /// - /// The index options. - public string[] IndexOptions { get; set; } - /// /// Gets or sets the tags. /// @@ -509,12 +490,6 @@ namespace MediaBrowser.Model.Dto /// The type of the collection. public string CollectionType { get; set; } - /// - /// Gets or sets the type of the original collection. - /// - /// The type of the original collection. - public string OriginalCollectionType { get; set; } - /// /// Gets or sets the display order. /// @@ -711,18 +686,6 @@ namespace MediaBrowser.Model.Dto /// The home page URL. public string HomePageUrl { get; set; } - /// - /// Gets or sets the budget. - /// - /// The budget. - public double? Budget { get; set; } - - /// - /// Gets or sets the revenue. - /// - /// The revenue. - public double? Revenue { get; set; } - /// /// Gets or sets the locked fields. /// @@ -930,15 +893,6 @@ namespace MediaBrowser.Model.Dto get { return StringHelper.EqualsIgnoreCase(Type, "Studio"); } } - [IgnoreDataMember] - public bool SupportsSimilarItems - { - get - { - return IsType("Movie") || IsType("Series") || IsType("MusicAlbum") || IsType("MusicArtist") || IsType("Program") || IsType("Recording") || IsType("ChannelVideoItem") || IsType("Game"); - } - } - /// /// Gets or sets the program identifier. /// -- cgit v1.2.3 From 149d16a314b2c3549afcb741ee04631d56e02ec7 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 20 Feb 2017 02:04:03 -0500 Subject: preserve manual recording overrides --- .../LiveTv/EmbyTV/EmbyTV.cs | 9 +++++++ .../Session/SessionManager.cs | 29 ++++++++++++++-------- MediaBrowser.Api/Playback/MediaInfoService.cs | 29 ++++++++++++++++++---- MediaBrowser.Controller/LiveTv/TimerInfo.cs | 2 ++ .../MediaInfo/PlaybackInfoRequest.cs | 11 ++++++++ 5 files changed, 65 insertions(+), 15 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 1fc3dcd72..82f32efa2 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -635,6 +635,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV existingTimer.Status == RecordingStatus.Completed) { existingTimer.Status = RecordingStatus.New; + existingTimer.IsManual = true; _timerProvider.Update(existingTimer); return Task.FromResult(existingTimer.Id); } @@ -663,6 +664,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); } + timer.IsManual = true; _timerProvider.Add(timer); return Task.FromResult(timer.Id); } @@ -758,6 +760,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV existingTimer.PostPaddingSeconds = updatedTimer.PostPaddingSeconds; existingTimer.IsPostPaddingRequired = updatedTimer.IsPostPaddingRequired; existingTimer.IsPrePaddingRequired = updatedTimer.IsPrePaddingRequired; + + _timerProvider.Update(existingTimer); } return Task.FromResult(true); @@ -2203,6 +2207,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer) { + if (timer.IsManual) + { + return false; + } + if (!seriesTimer.RecordAnyTime) { if (Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(5).Ticks) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index a20fb67b2..8051b7848 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -954,8 +954,11 @@ namespace Emby.Server.Implementations.Session { var session = GetSessionToRemoteControl(sessionId); - var controllingSession = GetSession(controllingSessionId); - AssertCanControl(session, controllingSession); + if (!string.IsNullOrWhiteSpace(controllingSessionId)) + { + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + } return session.SessionController.SendGeneralCommand(command, cancellationToken); } @@ -1042,11 +1045,14 @@ namespace Emby.Server.Implementations.Session } } - var controllingSession = GetSession(controllingSessionId); - AssertCanControl(session, controllingSession); - if (controllingSession.UserId.HasValue) + if (!string.IsNullOrWhiteSpace(controllingSessionId)) { - command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + if (controllingSession.UserId.HasValue) + { + command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + } } await session.SessionController.SendPlayCommand(command, cancellationToken).ConfigureAwait(false); @@ -1136,11 +1142,14 @@ namespace Emby.Server.Implementations.Session { var session = GetSessionToRemoteControl(sessionId); - var controllingSession = GetSession(controllingSessionId); - AssertCanControl(session, controllingSession); - if (controllingSession.UserId.HasValue) + if (!string.IsNullOrWhiteSpace(controllingSessionId)) { - command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + var controllingSession = GetSession(controllingSessionId); + AssertCanControl(session, controllingSession); + if (controllingSession.UserId.HasValue) + { + command.ControllingUserId = controllingSession.UserId.Value.ToString("N"); + } } return session.SessionController.SendPlaystateCommand(command, cancellationToken); diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 80885271c..ed8449b83 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -127,7 +127,7 @@ namespace MediaBrowser.Api.Playback SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, - request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId); + request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, true, true, true); } else { @@ -169,7 +169,7 @@ namespace MediaBrowser.Api.Playback { var mediaSourceId = request.MediaSourceId; - SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId); + SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, request.EnableDirectStream, request.EnableTranscoding); } return info; @@ -251,13 +251,16 @@ namespace MediaBrowser.Api.Playback int? audioStreamIndex, int? subtitleStreamIndex, int? maxAudioChannels, - string userId) + string userId, + bool enableDirectPlay, + bool enableDirectStream, + bool enableTranscoding) { var item = _libraryManager.GetItemById(itemId); foreach (var mediaSource in result.MediaSources) { - SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId); + SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, enableDirectStream, enableTranscoding); } SortMediaSources(result, maxBitrate); @@ -274,7 +277,10 @@ namespace MediaBrowser.Api.Playback int? subtitleStreamIndex, int? maxAudioChannels, string playSessionId, - string userId) + string userId, + bool enableDirectPlay, + bool enableDirectStream, + bool enableTranscoding) { var streamBuilder = new StreamBuilder(_mediaEncoder, Logger); @@ -297,6 +303,19 @@ namespace MediaBrowser.Api.Playback var user = _userManager.GetUserById(userId); + if (!enableDirectPlay) + { + mediaSource.SupportsDirectPlay = false; + } + if (!enableDirectStream) + { + mediaSource.SupportsDirectStream = false; + } + if (!enableTranscoding) + { + mediaSource.SupportsTranscoding = false; + } + if (mediaSource.SupportsDirectPlay) { var supportsDirectStream = mediaSource.SupportsDirectStream; diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index ea12db7f9..0b94c85fa 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -88,6 +88,8 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is post padding required; otherwise, false. public bool IsPostPaddingRequired { get; set; } + public bool IsManual { get; set; } + /// /// Gets or sets the priority. /// diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index fc4714be3..57a2254b0 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -23,5 +23,16 @@ namespace MediaBrowser.Model.MediaInfo public string LiveStreamId { get; set; } public DeviceProfile DeviceProfile { get; set; } + + public bool EnableDirectPlay { get; set; } + public bool EnableDirectStream { get; set; } + public bool EnableTranscoding { get; set; } + + public PlaybackInfoRequest() + { + EnableDirectPlay = true; + EnableDirectStream = true; + EnableTranscoding = true; + } } } -- cgit v1.2.3 From e391ee1b1724c44c3a12bca24f3dff9389de4b8b Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 23 Feb 2017 14:13:07 -0500 Subject: update components --- Emby.Server.Core/ApplicationHost.cs | 17 +- .../Emby.Server.Implementations.csproj | 19 +- .../EntryPoints/UsageReporter.cs | 3 +- .../Security/PluginSecurityManager.cs | 3 +- .../Sync/AppSyncProvider.cs | 118 -- .../Sync/CloudSyncProfile.cs | 288 ---- .../Sync/IHasSyncQuality.cs | 31 - Emby.Server.Implementations/Sync/MediaSync.cs | 500 ------- .../Sync/MultiProviderSync.cs | 79 -- .../Sync/ServerSyncScheduledTask.cs | 95 -- Emby.Server.Implementations/Sync/SyncConfig.cs | 29 - .../Sync/SyncConvertScheduledTask.cs | 89 -- Emby.Server.Implementations/Sync/SyncHelper.cs | 24 - Emby.Server.Implementations/Sync/SyncJobOptions.cs | 18 - .../Sync/SyncJobProcessor.cs | 998 -------------- Emby.Server.Implementations/Sync/SyncManager.cs | 1372 -------------------- .../Sync/SyncNotificationEntryPoint.cs | 60 - .../Sync/SyncRegistrationInfo.cs | 31 - Emby.Server.Implementations/Sync/SyncRepository.cs | 847 ------------ .../Sync/SyncedMediaSourceProvider.cs | 158 --- .../Sync/TargetDataProvider.cs | 208 --- Emby.Server.Implementations/packages.config | 2 +- MediaBrowser.Api/ConnectService.cs | 177 --- MediaBrowser.Api/LiveTv/LiveTvService.cs | 2 +- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 - MediaBrowser.Controller/LiveTv/ITunerHost.cs | 5 +- .../MediaBrowser.Server.Mono.csproj | 13 +- MediaBrowser.Server.Mono/MonoAppHost.cs | 8 + MediaBrowser.Server.Mono/app.config | 4 + MediaBrowser.Server.Mono/packages.config | 8 +- MediaBrowser.ServerApplication/App.config | 2 +- .../MediaBrowser.ServerApplication.csproj | 13 +- MediaBrowser.ServerApplication/WindowsAppHost.cs | 8 + MediaBrowser.ServerApplication/packages.config | 8 +- 34 files changed, 54 insertions(+), 5184 deletions(-) delete mode 100644 Emby.Server.Implementations/Sync/AppSyncProvider.cs delete mode 100644 Emby.Server.Implementations/Sync/CloudSyncProfile.cs delete mode 100644 Emby.Server.Implementations/Sync/IHasSyncQuality.cs delete mode 100644 Emby.Server.Implementations/Sync/MediaSync.cs delete mode 100644 Emby.Server.Implementations/Sync/MultiProviderSync.cs delete mode 100644 Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncConfig.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncConvertScheduledTask.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncHelper.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncJobOptions.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncJobProcessor.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncManager.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncNotificationEntryPoint.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncRegistrationInfo.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncRepository.cs delete mode 100644 Emby.Server.Implementations/Sync/SyncedMediaSourceProvider.cs delete mode 100644 Emby.Server.Implementations/Sync/TargetDataProvider.cs delete mode 100644 MediaBrowser.Api/ConnectService.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 416155751..4425d1a0b 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -90,7 +90,6 @@ using Emby.Server.Core.Localization; using Emby.Server.Implementations.Migrations; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Social; -using Emby.Server.Implementations.Sync; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Dto; @@ -109,7 +108,6 @@ using Emby.Server.Implementations; using Emby.Server.Implementations.ServerManager; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.Social; -using Emby.Server.Implementations.Sync; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using MediaBrowser.Model.Activity; @@ -526,6 +524,7 @@ namespace Emby.Server.Core } protected abstract IConnectManager CreateConnectManager(); + protected abstract ISyncManager CreateSyncManager(); /// /// Registers resources that classes will depend on @@ -587,9 +586,6 @@ namespace Emby.Server.Core AuthenticationRepository = await GetAuthenticationRepository().ConfigureAwait(false); RegisterSingleInstance(AuthenticationRepository); - SyncRepository = GetSyncRepository(); - RegisterSingleInstance(SyncRepository); - UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this, JsonSerializer, FileSystemManager, CryptographyProvider, _defaultUserNameFactory()); RegisterSingleInstance(UserManager); @@ -627,7 +623,7 @@ namespace Emby.Server.Core TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager); RegisterSingleInstance(TVSeriesManager); - SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager"), UserManager, () => DtoService, this, TVSeriesManager, () => MediaEncoder, FileSystemManager, () => SubtitleEncoder, ServerConfigurationManager, UserDataManager, () => MediaSourceManager, JsonSerializer, TaskManager, MemoryStreamFactory); + SyncManager = CreateSyncManager(); RegisterSingleInstance(SyncManager); DtoService = new DtoService(LogManager.GetLogger("DtoService"), LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager); @@ -944,15 +940,6 @@ namespace Emby.Server.Core return repo; } - private ISyncRepository GetSyncRepository() - { - var repo = new SyncRepository(LogManager.GetLogger("SyncRepository"), JsonSerializer, ServerConfigurationManager.ApplicationPaths); - - repo.Initialize(); - - return repo; - } - /// /// Configures the repositories. /// diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 5514bb1b6..13efb7bf9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -264,23 +264,6 @@ - - - - - - - - - - - - - - - - - @@ -329,7 +312,7 @@ True - ..\packages\SQLitePCLRaw.core.1.1.1\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll True diff --git a/Emby.Server.Implementations/EntryPoints/UsageReporter.cs b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs index 31254c6c2..778c8a6ce 100644 --- a/Emby.Server.Implementations/EntryPoints/UsageReporter.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs @@ -40,8 +40,7 @@ namespace Emby.Server.Implementations.EntryPoints { "serverid", _applicationHost.SystemId }, { "deviceid", _applicationHost.SystemId }, { "ver", _applicationHost.ApplicationVersion.ToString() }, - { "platform", _applicationHost.OperatingSystemDisplayName }, - { "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()} + { "platform", _applicationHost.OperatingSystemDisplayName } }; var users = _userManager.Users.ToList(); diff --git a/Emby.Server.Implementations/Security/PluginSecurityManager.cs b/Emby.Server.Implementations/Security/PluginSecurityManager.cs index f21259137..a26df7625 100644 --- a/Emby.Server.Implementations/Security/PluginSecurityManager.cs +++ b/Emby.Server.Implementations/Security/PluginSecurityManager.cs @@ -277,8 +277,7 @@ namespace Emby.Server.Implementations.Security { "systemid", _appHost.SystemId }, { "mb2equiv", mb2Equivalent }, { "ver", version }, - { "platform", _appHost.OperatingSystemDisplayName }, - { "isservice", _appHost.IsRunningAsService.ToString().ToLower() } + { "platform", _appHost.OperatingSystemDisplayName } }; try diff --git a/Emby.Server.Implementations/Sync/AppSyncProvider.cs b/Emby.Server.Implementations/Sync/AppSyncProvider.cs deleted file mode 100644 index d405a0ff9..000000000 --- a/Emby.Server.Implementations/Sync/AppSyncProvider.cs +++ /dev/null @@ -1,118 +0,0 @@ -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Emby.Server.Implementations.Sync -{ - public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds, IHasSyncQuality, IHasDuplicateCheck - { - private readonly IDeviceManager _deviceManager; - - public AppSyncProvider(IDeviceManager deviceManager) - { - _deviceManager = deviceManager; - } - - public IEnumerable GetSyncTargets(string userId) - { - return _deviceManager.GetDevices(new DeviceQuery - { - SupportsSync = true, - UserId = userId - - }).Items.Select(i => new SyncTarget - { - Id = i.Id, - Name = i.Name - }); - } - - public DeviceProfile GetDeviceProfile(SyncTarget target, string profile, string quality) - { - var caps = _deviceManager.GetCapabilities(target.Id); - - var deviceProfile = caps == null || caps.DeviceProfile == null ? new DeviceProfile() : caps.DeviceProfile; - deviceProfile.MaxStaticBitrate = SyncHelper.AdjustBitrate(deviceProfile.MaxStaticBitrate, quality); - - return deviceProfile; - } - - public string Name - { - get { return "Mobile Sync"; } - } - - public IEnumerable GetAllSyncTargets() - { - return _deviceManager.GetDevices(new DeviceQuery - { - SupportsSync = true - - }).Items.Select(i => new SyncTarget - { - Id = i.Id, - Name = i.Name - }); - } - - public IEnumerable GetQualityOptions(SyncTarget target) - { - return new List - { - new SyncQualityOption - { - Name = "Original", - Id = "original", - Description = "Syncs original files as-is, regardless of whether the device is capable of playing them or not." - }, - new SyncQualityOption - { - Name = "High", - Id = "high", - IsDefault = true - }, - new SyncQualityOption - { - Name = "Medium", - Id = "medium" - }, - new SyncQualityOption - { - Name = "Low", - Id = "low" - }, - new SyncQualityOption - { - Name = "Custom", - Id = "custom" - } - }; - } - - public IEnumerable GetProfileOptions(SyncTarget target) - { - return new List(); - } - - public SyncJobOptions GetSyncJobOptions(SyncTarget target, string profile, string quality) - { - var isConverting = !string.Equals(quality, "original", StringComparison.OrdinalIgnoreCase); - - return new SyncJobOptions - { - DeviceProfile = GetDeviceProfile(target, profile, quality), - IsConverting = isConverting - }; - } - - public bool AllowDuplicateJobItem(SyncJobItem original, SyncJobItem duplicate) - { - return false; - } - } -} diff --git a/Emby.Server.Implementations/Sync/CloudSyncProfile.cs b/Emby.Server.Implementations/Sync/CloudSyncProfile.cs deleted file mode 100644 index c0675df81..000000000 --- a/Emby.Server.Implementations/Sync/CloudSyncProfile.cs +++ /dev/null @@ -1,288 +0,0 @@ -using MediaBrowser.Model.Dlna; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.Sync -{ - public class CloudSyncProfile : DeviceProfile - { - public CloudSyncProfile(bool supportsAc3, bool supportsDca) - { - Name = "Cloud Sync"; - - MaxStreamingBitrate = 20000000; - MaxStaticBitrate = 20000000; - - var mkvAudio = "aac,mp3"; - var mp4Audio = "aac"; - - if (supportsAc3) - { - mkvAudio += ",ac3"; - mp4Audio += ",ac3"; - } - - if (supportsDca) - { - mkvAudio += ",dca,dts"; - } - - var videoProfile = "high|main|baseline|constrained baseline"; - var videoLevel = "40"; - - DirectPlayProfiles = new[] - { - //new DirectPlayProfile - //{ - // Container = "mkv", - // VideoCodec = "h264,mpeg4", - // AudioCodec = mkvAudio, - // Type = DlnaProfileType.Video - //}, - new DirectPlayProfile - { - Container = "mp4,mov,m4v", - VideoCodec = "h264,mpeg4", - AudioCodec = mp4Audio, - Type = DlnaProfileType.Video - }, - new DirectPlayProfile - { - Container = "mp3", - Type = DlnaProfileType.Audio - } - }; - - ContainerProfiles = new[] - { - new ContainerProfile - { - Type = DlnaProfileType.Video, - Conditions = new [] - { - new ProfileCondition - { - Condition = ProfileConditionType.NotEquals, - Property = ProfileConditionValue.NumAudioStreams, - Value = "0", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.NumVideoStreams, - Value = "1", - IsRequired = false - } - } - } - }; - - var codecProfiles = new List - { - new CodecProfile - { - Type = CodecType.Video, - Codec = "h264", - Conditions = new [] - { - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoBitDepth, - Value = "8", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.Width, - Value = "1920", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.Height, - Value = "1080", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.RefFrames, - Value = "4", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoFramerate, - Value = "30", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.IsAnamorphic, - Value = "false", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoLevel, - Value = videoLevel, - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.EqualsAny, - Property = ProfileConditionValue.VideoProfile, - Value = videoProfile, - IsRequired = false - } - } - }, - new CodecProfile - { - Type = CodecType.Video, - Codec = "mpeg4", - Conditions = new [] - { - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoBitDepth, - Value = "8", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.Width, - Value = "1920", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.Height, - Value = "1080", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.RefFrames, - Value = "4", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.VideoFramerate, - Value = "30", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.IsAnamorphic, - Value = "false", - IsRequired = false - } - } - } - }; - - codecProfiles.Add(new CodecProfile - { - Type = CodecType.VideoAudio, - Codec = "ac3", - Conditions = new[] - { - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.AudioChannels, - Value = "6", - IsRequired = false - }, - new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - Property = ProfileConditionValue.AudioBitrate, - Value = "320000", - IsRequired = true - }, - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.IsSecondaryAudio, - Value = "false", - IsRequired = false - } - } - }); - codecProfiles.Add(new CodecProfile - { - Type = CodecType.VideoAudio, - Codec = "aac,mp3", - Conditions = new[] - { - new ProfileCondition - { - Condition = ProfileConditionType.Equals, - Property = ProfileConditionValue.IsSecondaryAudio, - Value = "false", - IsRequired = false - } - } - }); - - CodecProfiles = codecProfiles.ToArray(); - - SubtitleProfiles = new[] - { - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.External - }, - new SubtitleProfile - { - Format = "vtt", - Method = SubtitleDeliveryMethod.External - } - }; - - TranscodingProfiles = new[] - { - new TranscodingProfile - { - Container = "mp3", - AudioCodec = "mp3", - Type = DlnaProfileType.Audio, - Context = EncodingContext.Static - }, - - new TranscodingProfile - { - Container = "mp4", - Type = DlnaProfileType.Video, - AudioCodec = "aac", - VideoCodec = "h264", - Context = EncodingContext.Static - }, - - new TranscodingProfile - { - Container = "jpeg", - Type = DlnaProfileType.Photo, - Context = EncodingContext.Static - } - }; - - } - } -} diff --git a/Emby.Server.Implementations/Sync/IHasSyncQuality.cs b/Emby.Server.Implementations/Sync/IHasSyncQuality.cs deleted file mode 100644 index bec8b37a7..000000000 --- a/Emby.Server.Implementations/Sync/IHasSyncQuality.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Model.Sync; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.Sync -{ - public interface IHasSyncQuality - { - /// - /// Gets the device profile. - /// - /// The target. - /// The profile. - /// The quality. - /// DeviceProfile. - SyncJobOptions GetSyncJobOptions(SyncTarget target, string profile, string quality); - - /// - /// Gets the quality options. - /// - /// The target. - /// IEnumerable<SyncQualityOption>. - IEnumerable GetQualityOptions(SyncTarget target); - - /// - /// Gets the profile options. - /// - /// The target. - /// IEnumerable<SyncQualityOption>. - IEnumerable GetProfileOptions(SyncTarget target); - } -} diff --git a/Emby.Server.Implementations/Sync/MediaSync.cs b/Emby.Server.Implementations/Sync/MediaSync.cs deleted file mode 100644 index fa8388b6c..000000000 --- a/Emby.Server.Implementations/Sync/MediaSync.cs +++ /dev/null @@ -1,500 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Sync; -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 Emby.Server.Implementations.IO; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Sync -{ - public class MediaSync - { - private readonly ISyncManager _syncManager; - private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IConfigurationManager _config; - private readonly ICryptoProvider _cryptographyProvider; - - public const string PathSeparatorString = "/"; - public const char PathSeparatorChar = '/'; - - public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem, IConfigurationManager config, ICryptoProvider cryptographyProvider) - { - _logger = logger; - _syncManager = syncManager; - _appHost = appHost; - _fileSystem = fileSystem; - _config = config; - _cryptographyProvider = cryptographyProvider; - } - - public async Task Sync(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - SyncTarget target, - IProgress progress, - CancellationToken cancellationToken) - { - var serverId = _appHost.SystemId; - var serverName = _appHost.FriendlyName; - - await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); - progress.Report(3); - - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => - { - var totalProgress = pct * .97; - totalProgress += 1; - progress.Report(totalProgress); - }); - await GetNewMedia(provider, dataProvider, target, serverId, serverName, innerProgress, cancellationToken); - - // Do the data sync twice so the server knows what was removed from the device - await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); - - progress.Report(100); - } - - private async Task SyncData(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - string serverId, - SyncTarget target, - CancellationToken cancellationToken) - { - var localItems = await dataProvider.GetLocalItems(target, serverId).ConfigureAwait(false); - var remoteFiles = await provider.GetFiles(target, cancellationToken).ConfigureAwait(false); - var remoteIds = remoteFiles.Items.Select(i => i.FullName).ToList(); - - var jobItemIds = new List(); - - foreach (var localItem in localItems) - { - if (remoteIds.Contains(localItem.FileId, StringComparer.OrdinalIgnoreCase)) - { - jobItemIds.Add(localItem.SyncJobItemId); - } - } - - var result = await _syncManager.SyncData(new SyncDataRequest - { - TargetId = target.Id, - SyncJobItemIds = jobItemIds - - }).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - foreach (var itemIdToRemove in result.ItemIdsToRemove) - { - try - { - await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting item from device. Id: {0}", ex, itemIdToRemove); - } - } - } - - private async Task GetNewMedia(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - SyncTarget target, - string serverId, - string serverName, - IProgress progress, - CancellationToken cancellationToken) - { - var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false); - - var numComplete = 0; - double startingPercent = 0; - double percentPerItem = 1; - if (jobItems.Count > 0) - { - percentPerItem /= jobItems.Count; - } - - foreach (var jobItem in jobItems) - { - cancellationToken.ThrowIfCancellationRequested(); - - var currentPercent = startingPercent; - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => - { - var totalProgress = pct * percentPerItem; - totalProgress += currentPercent; - progress.Report(totalProgress); - }); - - try - { - await GetItem(provider, dataProvider, target, serverId, serverName, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error syncing item", ex); - } - - numComplete++; - startingPercent = numComplete; - startingPercent /= jobItems.Count; - startingPercent *= 100; - progress.Report(startingPercent); - } - } - - private async Task GetItem(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - SyncTarget target, - string serverId, - string serverName, - SyncedItem jobItem, - IProgress progress, - CancellationToken cancellationToken) - { - var libraryItem = jobItem.Item; - var internalSyncJobItem = _syncManager.GetJobItem(jobItem.SyncJobItemId); - var internalSyncJob = _syncManager.GetJob(jobItem.SyncJobId); - - var localItem = CreateLocalItem(provider, jobItem, internalSyncJob, target, libraryItem, serverId, serverName, jobItem.OriginalFileName); - - await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id); - - var transferSuccess = false; - Exception transferException = null; - - var options = _config.GetSyncOptions(); - - try - { - var fileTransferProgress = new ActionableProgress(); - fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); - - var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem.LocalPath.Split(PathSeparatorChar), target, options, fileTransferProgress, cancellationToken).ConfigureAwait(false); - - if (localItem.Item.MediaSources != null) - { - var mediaSource = localItem.Item.MediaSources.FirstOrDefault(); - if (mediaSource != null) - { - mediaSource.Path = sendFileResult.Path; - mediaSource.Protocol = sendFileResult.Protocol; - mediaSource.RequiredHttpHeaders = sendFileResult.RequiredHttpHeaders; - mediaSource.SupportsTranscoding = false; - } - } - - localItem.FileId = sendFileResult.Id; - - // Create db record - await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false); - - if (localItem.Item.MediaSources != null) - { - var mediaSource = localItem.Item.MediaSources.FirstOrDefault(); - if (mediaSource != null) - { - await SendSubtitles(localItem, mediaSource, provider, dataProvider, target, options, cancellationToken).ConfigureAwait(false); - } - } - - progress.Report(92); - - transferSuccess = true; - - progress.Report(99); - } - catch (Exception ex) - { - _logger.ErrorException("Error transferring sync job file", ex); - transferException = ex; - } - - if (transferSuccess) - { - await _syncManager.ReportSyncJobItemTransferred(jobItem.SyncJobItemId).ConfigureAwait(false); - } - else - { - await _syncManager.ReportSyncJobItemTransferFailed(jobItem.SyncJobItemId).ConfigureAwait(false); - - throw transferException; - } - } - - private async Task SendSubtitles(LocalItem localItem, MediaSourceInfo mediaSource, IServerSyncProvider provider, ISyncDataProvider dataProvider, SyncTarget target, SyncOptions options, CancellationToken cancellationToken) - { - var failedSubtitles = new List(); - var requiresSave = false; - - foreach (var mediaStream in mediaSource.MediaStreams - .Where(i => i.Type == MediaStreamType.Subtitle && i.IsExternal) - .ToList()) - { - try - { - var remotePath = GetRemoteSubtitlePath(localItem, mediaStream, provider, target); - var sendFileResult = await SendFile(provider, mediaStream.Path, remotePath, target, options, new Progress(), cancellationToken).ConfigureAwait(false); - - // This is the path that will be used when talking to the provider - mediaStream.ExternalId = sendFileResult.Id; - - // Keep track of all additional files for cleanup later. - localItem.AdditionalFiles.Add(sendFileResult.Id); - - // This is the public path clients will use - mediaStream.Path = sendFileResult.Path; - requiresSave = true; - } - catch (Exception ex) - { - _logger.ErrorException("Error sending subtitle stream", ex); - failedSubtitles.Add(mediaStream); - } - } - - if (failedSubtitles.Count > 0) - { - mediaSource.MediaStreams = mediaSource.MediaStreams.Except(failedSubtitles).ToList(); - requiresSave = true; - } - - if (requiresSave) - { - await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false); - } - } - - private string[] GetRemoteSubtitlePath(LocalItem item, MediaStream stream, IServerSyncProvider provider, SyncTarget target) - { - var filename = GetSubtitleSaveFileName(item, stream.Language, stream.IsForced) + "." + stream.Codec.ToLower(); - - var pathParts = item.LocalPath.Split(PathSeparatorChar); - var list = pathParts.Take(pathParts.Length - 1).ToList(); - list.Add(filename); - - return list.ToArray(); - } - - private string GetSubtitleSaveFileName(LocalItem item, string language, bool isForced) - { - var path = item.LocalPath; - - var name = Path.GetFileNameWithoutExtension(path); - - if (!string.IsNullOrWhiteSpace(language)) - { - name += "." + language.ToLower(); - } - - if (isForced) - { - name += ".foreign"; - } - - return name; - } - - private async Task RemoveItem(IServerSyncProvider provider, - ISyncDataProvider dataProvider, - string serverId, - string syncJobItemId, - SyncTarget target, - CancellationToken cancellationToken) - { - var localItems = await dataProvider.GetItemsBySyncJobItemId(target, serverId, syncJobItemId); - - foreach (var localItem in localItems) - { - var files = localItem.AdditionalFiles.ToList(); - - foreach (var file in files) - { - _logger.Debug("Removing {0} from {1}.", file, target.Name); - await provider.DeleteFile(file, target, cancellationToken).ConfigureAwait(false); - } - - _logger.Debug("Removing {0} from {1}.", localItem.FileId, target.Name); - await provider.DeleteFile(localItem.FileId, target, cancellationToken).ConfigureAwait(false); - - await dataProvider.Delete(target, localItem.Id).ConfigureAwait(false); - } - } - - private async Task SendFile(IServerSyncProvider provider, string inputPath, string[] pathParts, SyncTarget target, SyncOptions options, IProgress progress, CancellationToken cancellationToken) - { - _logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, string.Join("/", pathParts)); - var supportsDirectCopy = provider as ISupportsDirectCopy; - if (supportsDirectCopy != null) - { - return await supportsDirectCopy.SendFile(inputPath, pathParts, target, progress, cancellationToken).ConfigureAwait(false); - } - - using (var fileStream = _fileSystem.GetFileStream(inputPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true)) - { - Stream stream = fileStream; - - if (options.UploadSpeedLimitBytes > 0 && provider is IRemoteSyncProvider) - { - stream = new ThrottledStream(stream, options.UploadSpeedLimitBytes); - } - - return await provider.SendFile(stream, pathParts, target, progress, cancellationToken).ConfigureAwait(false); - } - } - - private string GetLocalId(string jobItemId, string itemId) - { - var bytes = Encoding.UTF8.GetBytes(jobItemId + itemId); - bytes = CreateMd5(bytes); - return BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty); - } - - private byte[] CreateMd5(byte[] value) - { - return _cryptographyProvider.ComputeMD5(value); - } - - public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncJob job, SyncTarget target, BaseItemDto libraryItem, string serverId, string serverName, string originalFileName) - { - var path = GetDirectoryPath(provider, job, syncedItem, libraryItem, serverName); - path.Add(GetLocalFileName(provider, libraryItem, originalFileName)); - - var localPath = string.Join(PathSeparatorString, path.ToArray()); - - foreach (var mediaSource in libraryItem.MediaSources) - { - mediaSource.Path = localPath; - mediaSource.Protocol = MediaProtocol.File; - } - - return new LocalItem - { - Item = libraryItem, - ItemId = libraryItem.Id, - ServerId = serverId, - LocalPath = localPath, - Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id), - SyncJobItemId = syncedItem.SyncJobItemId - }; - } - - private List GetDirectoryPath(IServerSyncProvider provider, SyncJob job, SyncedItem syncedItem, BaseItemDto item, string serverName) - { - var parts = new List - { - serverName - }; - - var profileOption = _syncManager.GetProfileOptions(job.TargetId) - .FirstOrDefault(i => string.Equals(i.Id, job.Profile, StringComparison.OrdinalIgnoreCase)); - - string name; - - if (profileOption != null && !string.IsNullOrWhiteSpace(profileOption.Name)) - { - name = profileOption.Name; - - if (job.Bitrate.HasValue) - { - name += "-" + job.Bitrate.Value.ToString(CultureInfo.InvariantCulture); - } - else - { - var qualityOption = _syncManager.GetQualityOptions(job.TargetId) - .FirstOrDefault(i => string.Equals(i.Id, job.Quality, StringComparison.OrdinalIgnoreCase)); - - if (qualityOption != null && !string.IsNullOrWhiteSpace(qualityOption.Name)) - { - name += "-" + qualityOption.Name; - } - } - } - else - { - name = syncedItem.SyncJobName + "-" + syncedItem.SyncJobDateCreated - .ToLocalTime() - .ToString("g") - .Replace(" ", "-"); - } - - name = GetValidFilename(provider, name); - parts.Add(name); - - if (item.IsType("episode")) - { - parts.Add("TV"); - if (!string.IsNullOrWhiteSpace(item.SeriesName)) - { - parts.Add(item.SeriesName); - } - } - else if (item.IsVideo) - { - parts.Add("Videos"); - parts.Add(item.Name); - } - else if (item.IsAudio) - { - parts.Add("Music"); - - if (!string.IsNullOrWhiteSpace(item.AlbumArtist)) - { - parts.Add(item.AlbumArtist); - } - - if (!string.IsNullOrWhiteSpace(item.Album)) - { - parts.Add(item.Album); - } - } - else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase)) - { - parts.Add("Photos"); - - if (!string.IsNullOrWhiteSpace(item.Album)) - { - parts.Add(item.Album); - } - } - - return parts.Select(i => GetValidFilename(provider, i)).ToList(); - } - - private string GetLocalFileName(IServerSyncProvider provider, BaseItemDto item, string originalFileName) - { - var filename = originalFileName; - - if (string.IsNullOrWhiteSpace(filename)) - { - filename = item.Name; - } - - return GetValidFilename(provider, filename); - } - - private string GetValidFilename(IServerSyncProvider provider, string filename) - { - // We can always add this method to the sync provider if it's really needed - return _fileSystem.GetValidFilename(filename); - } - } -} diff --git a/Emby.Server.Implementations/Sync/MultiProviderSync.cs b/Emby.Server.Implementations/Sync/MultiProviderSync.cs deleted file mode 100644 index 8189b8550..000000000 --- a/Emby.Server.Implementations/Sync/MultiProviderSync.cs +++ /dev/null @@ -1,79 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Sync -{ - public class MultiProviderSync - { - private readonly SyncManager _syncManager; - private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IConfigurationManager _config; - private readonly ICryptoProvider _cryptographyProvider; - - public MultiProviderSync(SyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem, IConfigurationManager config, ICryptoProvider cryptographyProvider) - { - _syncManager = syncManager; - _appHost = appHost; - _logger = logger; - _fileSystem = fileSystem; - _config = config; - _cryptographyProvider = cryptographyProvider; - } - - public async Task Sync(IEnumerable providers, IProgress progress, CancellationToken cancellationToken) - { - var targets = providers - .SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple(i, t))) - .ToList(); - - var numComplete = 0; - double startingPercent = 0; - double percentPerItem = 1; - if (targets.Count > 0) - { - percentPerItem /= targets.Count; - } - - foreach (var target in targets) - { - cancellationToken.ThrowIfCancellationRequested(); - - var currentPercent = startingPercent; - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => - { - var totalProgress = pct * percentPerItem; - totalProgress += currentPercent; - progress.Report(totalProgress); - }); - - var dataProvider = _syncManager.GetDataProvider(target.Item1, target.Item2); - - await new MediaSync(_logger, _syncManager, _appHost, _fileSystem, _config, _cryptographyProvider) - .Sync(target.Item1, dataProvider, target.Item2, innerProgress, cancellationToken) - .ConfigureAwait(false); - - numComplete++; - startingPercent = numComplete; - startingPercent /= targets.Count; - startingPercent *= 100; - progress.Report(startingPercent); - } - } - } -} diff --git a/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs b/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs deleted file mode 100644 index 09a0bfde4..000000000 --- a/Emby.Server.Implementations/Sync/ServerSyncScheduledTask.cs +++ /dev/null @@ -1,95 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - class ServerSyncScheduledTask : IScheduledTask, IConfigurableScheduledTask - { - private readonly ISyncManager _syncManager; - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IServerApplicationHost _appHost; - private readonly IConfigurationManager _config; - private readonly ICryptoProvider _cryptographyProvider; - - public ServerSyncScheduledTask(ISyncManager syncManager, ILogger logger, IFileSystem fileSystem, IServerApplicationHost appHost, IConfigurationManager config, ICryptoProvider cryptographyProvider) - { - _syncManager = syncManager; - _logger = logger; - _fileSystem = fileSystem; - _appHost = appHost; - _config = config; - _cryptographyProvider = cryptographyProvider; - } - - public string Name - { - get { return "Cloud & Folder Sync"; } - } - - public string Description - { - get { return "Sync media to the cloud"; } - } - - public string Category - { - get - { - return "Sync"; - } - } - - public Task Execute(CancellationToken cancellationToken, IProgress progress) - { - return new MultiProviderSync((SyncManager)_syncManager, _appHost, _logger, _fileSystem, _config, _cryptographyProvider) - .Sync(ServerSyncProviders, progress, cancellationToken); - } - - public IEnumerable ServerSyncProviders - { - get { return ((SyncManager)_syncManager).ServerSyncProviders; } - } - - /// - /// Creates the triggers that define when the task will run - /// - public IEnumerable GetDefaultTriggers() - { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(3).Ticks} - }; - } - public bool IsHidden - { - get { return !IsEnabled; } - } - - public bool IsEnabled - { - get { return ServerSyncProviders.Any(); } - } - - public bool IsLogged - { - get { return true; } - } - - public string Key - { - get { return "ServerSync"; } - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncConfig.cs b/Emby.Server.Implementations/Sync/SyncConfig.cs deleted file mode 100644 index 8a97326bd..000000000 --- a/Emby.Server.Implementations/Sync/SyncConfig.cs +++ /dev/null @@ -1,29 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Sync; -using System.Collections.Generic; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncConfigurationFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new List - { - new ConfigurationStore - { - ConfigurationType = typeof(SyncOptions), - Key = "sync" - } - }; - } - } - - public static class SyncExtensions - { - public static SyncOptions GetSyncOptions(this IConfigurationManager config) - { - return config.GetConfiguration("sync"); - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncConvertScheduledTask.cs b/Emby.Server.Implementations/Sync/SyncConvertScheduledTask.cs deleted file mode 100644 index 8dafac7e1..000000000 --- a/Emby.Server.Implementations/Sync/SyncConvertScheduledTask.cs +++ /dev/null @@ -1,89 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Logging; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncConvertScheduledTask : IScheduledTask - { - private readonly ILibraryManager _libraryManager; - private readonly ISyncRepository _syncRepo; - private readonly ISyncManager _syncManager; - private readonly ILogger _logger; - private readonly IUserManager _userManager; - private readonly ITVSeriesManager _tvSeriesManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly ISubtitleEncoder _subtitleEncoder; - private readonly IConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IMediaSourceManager _mediaSourceManager; - - public SyncConvertScheduledTask(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager, ITVSeriesManager tvSeriesManager, IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder, IConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager) - { - _libraryManager = libraryManager; - _syncRepo = syncRepo; - _syncManager = syncManager; - _logger = logger; - _userManager = userManager; - _tvSeriesManager = tvSeriesManager; - _mediaEncoder = mediaEncoder; - _subtitleEncoder = subtitleEncoder; - _config = config; - _fileSystem = fileSystem; - _mediaSourceManager = mediaSourceManager; - } - - public string Name - { - get { return "Convert media"; } - } - - public string Description - { - get { return "Runs scheduled sync jobs"; } - } - - public string Category - { - get - { - return "Sync"; - } - } - - public Task Execute(CancellationToken cancellationToken, IProgress progress) - { - return new SyncJobProcessor(_libraryManager, _syncRepo, (SyncManager)_syncManager, _logger, _userManager, _tvSeriesManager, _mediaEncoder, _subtitleEncoder, _config, _fileSystem, _mediaSourceManager) - .Sync(progress, cancellationToken); - } - - /// - /// Creates the triggers that define when the task will run - /// - /// IEnumerable{BaseTaskTrigger}. - public IEnumerable GetDefaultTriggers() - { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(3).Ticks} - }; - } - - public string Key - { - get { return "SyncPrepare"; } - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncHelper.cs b/Emby.Server.Implementations/Sync/SyncHelper.cs deleted file mode 100644 index 7fe703796..000000000 --- a/Emby.Server.Implementations/Sync/SyncHelper.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncHelper - { - public static long? AdjustBitrate(long? profileBitrate, string quality) - { - if (profileBitrate.HasValue) - { - if (string.Equals(quality, "medium", StringComparison.OrdinalIgnoreCase)) - { - profileBitrate = Math.Min(profileBitrate.Value, 4000000); - } - else if (string.Equals(quality, "low", StringComparison.OrdinalIgnoreCase)) - { - profileBitrate = Math.Min(profileBitrate.Value, 1500000); - } - } - - return profileBitrate; - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncJobOptions.cs b/Emby.Server.Implementations/Sync/SyncJobOptions.cs deleted file mode 100644 index 8e4d8e2ed..000000000 --- a/Emby.Server.Implementations/Sync/SyncJobOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using MediaBrowser.Model.Dlna; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncJobOptions - { - /// - /// Gets or sets the conversion options. - /// - /// The conversion options. - public DeviceProfile DeviceProfile { get; set; } - /// - /// Gets or sets a value indicating whether this instance is converting. - /// - /// true if this instance is converting; otherwise, false. - public bool IsConverting { get; set; } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncJobProcessor.cs b/Emby.Server.Implementations/Sync/SyncJobProcessor.cs deleted file mode 100644 index 17cdef5fc..000000000 --- a/Emby.Server.Implementations/Sync/SyncJobProcessor.cs +++ /dev/null @@ -1,998 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Session; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncJobProcessor - { - private readonly ILibraryManager _libraryManager; - private readonly ISyncRepository _syncRepo; - private readonly SyncManager _syncManager; - private readonly ILogger _logger; - private readonly IUserManager _userManager; - private readonly ITVSeriesManager _tvSeriesManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly ISubtitleEncoder _subtitleEncoder; - private readonly IConfigurationManager _config; - private readonly IFileSystem _fileSystem; - private readonly IMediaSourceManager _mediaSourceManager; - - public SyncJobProcessor(ILibraryManager libraryManager, ISyncRepository syncRepo, SyncManager syncManager, ILogger logger, IUserManager userManager, ITVSeriesManager tvSeriesManager, IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder, IConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager) - { - _libraryManager = libraryManager; - _syncRepo = syncRepo; - _syncManager = syncManager; - _logger = logger; - _userManager = userManager; - _tvSeriesManager = tvSeriesManager; - _mediaEncoder = mediaEncoder; - _subtitleEncoder = subtitleEncoder; - _config = config; - _fileSystem = fileSystem; - _mediaSourceManager = mediaSourceManager; - } - - public async Task EnsureJobItems(SyncJob job, User user) - { - var items = (await GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false)) - .ToList(); - - var jobItems = _syncManager.GetJobItems(new SyncJobItemQuery - { - JobId = job.Id, - AddMetadata = false - - }).Items.ToList(); - - foreach (var item in items) - { - // Respect ItemLimit, if set - if (job.ItemLimit.HasValue) - { - if (jobItems.Count(j => j.Status != SyncJobItemStatus.RemovedFromDevice && j.Status != SyncJobItemStatus.Failed) >= job.ItemLimit.Value) - { - break; - } - } - - var itemId = item.Id.ToString("N"); - - var jobItem = jobItems.FirstOrDefault(i => string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)); - - if (jobItem != null) - { - continue; - } - - var index = jobItems.Count == 0 ? - 0 : - jobItems.Select(i => i.JobItemIndex).Max() + 1; - - jobItem = new SyncJobItem - { - Id = Guid.NewGuid().ToString("N"), - ItemId = itemId, - ItemName = GetSyncJobItemName(item), - JobId = job.Id, - TargetId = job.TargetId, - DateCreated = DateTime.UtcNow, - JobItemIndex = index - }; - - await _syncRepo.Create(jobItem).ConfigureAwait(false); - _syncManager.OnSyncJobItemCreated(jobItem); - - jobItems.Add(jobItem); - } - - jobItems = jobItems - .OrderBy(i => i.DateCreated) - .ToList(); - - await UpdateJobStatus(job, jobItems).ConfigureAwait(false); - } - - private string GetSyncJobItemName(BaseItem item) - { - var name = item.Name; - var episode = item as Episode; - - if (episode != null) - { - if (episode.IndexNumber.HasValue) - { - name = "E" + episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture) + " - " + name; - } - - if (episode.ParentIndexNumber.HasValue) - { - name = "S" + episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture) + ", " + name; - } - } - - return name; - } - - public Task UpdateJobStatus(string id) - { - var job = _syncRepo.GetJob(id); - - if (job == null) - { - return Task.FromResult(true); - } - - var result = _syncManager.GetJobItems(new SyncJobItemQuery - { - JobId = job.Id, - AddMetadata = false - }); - - return UpdateJobStatus(job, result.Items.ToList()); - } - - private async Task UpdateJobStatus(SyncJob job, List jobItems) - { - job.ItemCount = jobItems.Count; - - double pct = 0; - - foreach (var item in jobItems) - { - if (item.Status == SyncJobItemStatus.Failed || item.Status == SyncJobItemStatus.Synced || item.Status == SyncJobItemStatus.RemovedFromDevice || item.Status == SyncJobItemStatus.Cancelled) - { - pct += 100; - } - else - { - pct += item.Progress ?? 0; - } - } - - if (job.ItemCount > 0) - { - pct /= job.ItemCount; - job.Progress = pct; - } - else - { - job.Progress = null; - } - - if (jobItems.Any(i => i.Status == SyncJobItemStatus.Transferring)) - { - job.Status = SyncJobStatus.Transferring; - } - else if (jobItems.Any(i => i.Status == SyncJobItemStatus.Converting)) - { - job.Status = SyncJobStatus.Converting; - } - else if (jobItems.All(i => i.Status == SyncJobItemStatus.Failed)) - { - job.Status = SyncJobStatus.Failed; - } - else if (jobItems.All(i => i.Status == SyncJobItemStatus.Cancelled)) - { - job.Status = SyncJobStatus.Cancelled; - } - else if (jobItems.All(i => i.Status == SyncJobItemStatus.ReadyToTransfer)) - { - job.Status = SyncJobStatus.ReadyToTransfer; - } - else if (jobItems.All(i => i.Status == SyncJobItemStatus.Cancelled || i.Status == SyncJobItemStatus.Failed || i.Status == SyncJobItemStatus.Synced || i.Status == SyncJobItemStatus.RemovedFromDevice)) - { - if (jobItems.Any(i => i.Status == SyncJobItemStatus.Failed)) - { - job.Status = SyncJobStatus.CompletedWithError; - } - else - { - job.Status = SyncJobStatus.Completed; - } - } - else - { - job.Status = SyncJobStatus.Queued; - } - - await _syncRepo.Update(job).ConfigureAwait(false); - - _syncManager.OnSyncJobUpdated(job); - } - - public async Task> GetItemsForSync(SyncCategory? category, string parentId, IEnumerable itemIds, User user, bool unwatchedOnly) - { - var list = new List(); - - if (category.HasValue) - { - list = (await GetItemsForSync(category.Value, parentId, user).ConfigureAwait(false)).ToList(); - } - else - { - foreach (var itemId in itemIds) - { - var subList = await GetItemsForSync(itemId, user).ConfigureAwait(false); - list.AddRange(subList); - } - } - - IEnumerable items = list; - items = items.Where(_syncManager.SupportsSync); - - if (unwatchedOnly) - { - // Avoid implicitly captured closure - var currentUser = user; - - items = items.Where(i => - { - var video = i as Video; - - if (video != null) - { - return !video.IsPlayed(currentUser); - } - - return true; - }); - } - - return items.DistinctBy(i => i.Id); - } - - private async Task> GetItemsForSync(SyncCategory category, string parentId, User user) - { - var parent = string.IsNullOrWhiteSpace(parentId) - ? user.RootFolder - : (Folder)_libraryManager.GetItemById(parentId); - - InternalItemsQuery query; - - switch (category) - { - case SyncCategory.Latest: - query = new InternalItemsQuery - { - IsFolder = false, - SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }, - SortOrder = SortOrder.Descending, - Recursive = true - }; - break; - case SyncCategory.Resume: - query = new InternalItemsQuery - { - IsFolder = false, - SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }, - SortOrder = SortOrder.Descending, - Recursive = true, - IsResumable = true, - MediaTypes = new[] { MediaType.Video } - }; - break; - - case SyncCategory.NextUp: - return _tvSeriesManager.GetNextUp(new NextUpQuery - { - ParentId = parentId, - UserId = user.Id.ToString("N") - }).Items; - - default: - throw new ArgumentException("Unrecognized category: " + category); - } - - if (parent == null) - { - return new List(); - } - - query.User = user; - - var result = await parent.GetItems(query).ConfigureAwait(false); - return result.Items; - } - - private async Task> GetItemsForSync(string id, User user) - { - var item = _libraryManager.GetItemById(id); - - if (item == null) - { - return new List(); - } - - var itemByName = item as IItemByName; - if (itemByName != null) - { - return itemByName.GetTaggedItems(new InternalItemsQuery(user) - { - IsFolder = false, - Recursive = true - }).ToList(); - } - - if (item.IsFolder) - { - var folder = (Folder)item; - var itemsResult = await folder.GetItems(new InternalItemsQuery(user) - { - Recursive = true, - IsFolder = false - - }).ConfigureAwait(false); - - var items = itemsResult.Items; - - if (!folder.IsPreSorted) - { - items = _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending) - .ToArray(); - } - - return items.ToList(); - } - - return new List { item }; - } - - private async Task EnsureSyncJobItems(string targetId, CancellationToken cancellationToken) - { - var jobResult = _syncRepo.GetJobs(new SyncJobQuery - { - SyncNewContent = true, - TargetId = targetId - }); - - foreach (var job in jobResult.Items) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (job.SyncNewContent) - { - var user = _userManager.GetUserById(job.UserId); - - if (user == null) - { - await _syncManager.CancelJob(job.Id).ConfigureAwait(false); - } - else - { - await EnsureJobItems(job, user).ConfigureAwait(false); - } - } - } - } - - public async Task Sync(IProgress progress, CancellationToken cancellationToken) - { - await EnsureSyncJobItems(null, cancellationToken).ConfigureAwait(false); - - // Look job items that are supposedly transfering, but need to be requeued because the synced files have been deleted somehow - await HandleDeletedSyncFiles(cancellationToken).ConfigureAwait(false); - - // If it already has a converting status then is must have been aborted during conversion - var result = _syncManager.GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - AddMetadata = false - }); - - await SyncJobItems(result.Items, true, progress, cancellationToken).ConfigureAwait(false); - - CleanDeadSyncFiles(); - } - - private async Task HandleDeletedSyncFiles(CancellationToken cancellationToken) - { - // Look job items that are supposedly transfering, but need to be requeued because the synced files have been deleted somehow - var result = _syncManager.GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Transferring }, - AddMetadata = false - }); - - foreach (var item in result.Items) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrWhiteSpace(item.OutputPath) || !_fileSystem.FileExists(item.OutputPath)) - { - item.Status = SyncJobItemStatus.Queued; - await _syncManager.UpdateSyncJobItemInternal(item).ConfigureAwait(false); - await UpdateJobStatus(item.JobId).ConfigureAwait(false); - } - } - } - - private void CleanDeadSyncFiles() - { - // TODO - // Clean files in sync temp folder that are not linked to any sync jobs - } - - public async Task SyncJobItems(string targetId, bool enableConversion, IProgress progress, - CancellationToken cancellationToken) - { - await EnsureSyncJobItems(targetId, cancellationToken).ConfigureAwait(false); - - // If it already has a converting status then is must have been aborted during conversion - var result = _syncManager.GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - TargetId = targetId, - AddMetadata = false - }); - - await SyncJobItems(result.Items, enableConversion, progress, cancellationToken).ConfigureAwait(false); - } - - public async Task SyncJobItems(SyncJobItem[] items, bool enableConversion, IProgress progress, CancellationToken cancellationToken) - { - if (items.Length > 0) - { - if (!SyncRegistrationInfo.Instance.IsRegistered) - { - _logger.Debug("Cancelling sync job processing. Please obtain a supporter membership."); - return; - } - } - - var numComplete = 0; - - foreach (var item in items) - { - cancellationToken.ThrowIfCancellationRequested(); - - double percentPerItem = 1; - percentPerItem /= items.Length; - var startingPercent = numComplete * percentPerItem * 100; - - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report(startingPercent + percentPerItem * p)); - - // Pull it fresh from the db just to make sure it wasn't deleted or cancelled while another item was converting - var jobItem = enableConversion ? _syncRepo.GetJobItem(item.Id) : item; - - if (jobItem != null) - { - if (jobItem.Status != SyncJobItemStatus.Cancelled) - { - await ProcessJobItem(jobItem, enableConversion, innerProgress, cancellationToken).ConfigureAwait(false); - } - - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - numComplete++; - double percent = numComplete; - percent /= items.Length; - progress.Report(100 * percent); - } - } - - private async Task ProcessJobItem(SyncJobItem jobItem, bool enableConversion, IProgress progress, CancellationToken cancellationToken) - { - if (jobItem == null) - { - throw new ArgumentNullException("jobItem"); - } - - var item = _libraryManager.GetItemById(jobItem.ItemId); - if (item == null) - { - jobItem.Status = SyncJobItemStatus.Failed; - _logger.Error("Unable to locate library item for JobItem {0}, ItemId {1}", jobItem.Id, jobItem.ItemId); - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - - jobItem.Progress = 0; - - var job = _syncManager.GetJob(jobItem.JobId); - if (job == null) - { - _logger.Error("Job not found. Cannot complete the sync job."); - await _syncManager.CancelJobItem(jobItem.Id).ConfigureAwait(false); - return; - } - - var user = _userManager.GetUserById(job.UserId); - if (user == null) - { - jobItem.Status = SyncJobItemStatus.Failed; - _logger.Error("User not found. Cannot complete the sync job."); - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - - // See if there's already another active job item for the same target - var existingJobItems = _syncManager.GetJobItems(new SyncJobItemQuery - { - AddMetadata = false, - ItemId = jobItem.ItemId, - TargetId = jobItem.TargetId, - Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced, SyncJobItemStatus.Transferring } - }); - - var duplicateJobItems = existingJobItems.Items - .Where(i => !string.Equals(i.Id, jobItem.Id, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (duplicateJobItems.Count > 0) - { - var syncProvider = _syncManager.GetSyncProvider(jobItem) as IHasDuplicateCheck; - - if (!duplicateJobItems.Any(i => AllowDuplicateJobItem(syncProvider, i, jobItem))) - { - _logger.Debug("Cancelling sync job item because there is already another active job for the same target."); - jobItem.Status = SyncJobItemStatus.Cancelled; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - } - - var syncOptions = _config.GetSyncOptions(); - - var video = item as Video; - if (video != null) - { - await Sync(jobItem, video, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false); - } - - else if (item is Audio) - { - await Sync(jobItem, (Audio)item, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false); - } - - else if (item is Photo) - { - await Sync(jobItem, (Photo)item, cancellationToken).ConfigureAwait(false); - } - - else - { - await SyncGeneric(jobItem, item, cancellationToken).ConfigureAwait(false); - } - } - - private bool AllowDuplicateJobItem(IHasDuplicateCheck provider, SyncJobItem original, SyncJobItem duplicate) - { - if (provider != null) - { - return provider.AllowDuplicateJobItem(original, duplicate); - } - - return true; - } - - private async Task Sync(SyncJobItem jobItem, Video item, User user, bool enableConversion, SyncOptions syncOptions, IProgress progress, CancellationToken cancellationToken) - { - var job = _syncManager.GetJob(jobItem.JobId); - var jobOptions = _syncManager.GetVideoOptions(jobItem, job); - var conversionOptions = new VideoOptions - { - Profile = jobOptions.DeviceProfile - }; - - conversionOptions.DeviceId = jobItem.TargetId; - conversionOptions.Context = EncodingContext.Static; - conversionOptions.ItemId = item.Id.ToString("N"); - conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); - - var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(conversionOptions); - var mediaSource = streamInfo.MediaSource; - - // No sense creating external subs if we're already burning one into the video - var externalSubs = streamInfo.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode ? - new List() : - streamInfo.GetExternalSubtitles(false, true, null, null); - - // Mark as requiring conversion if transcoding the video, or if any subtitles need to be extracted - var requiresVideoTranscoding = streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting; - var requiresConversion = requiresVideoTranscoding || externalSubs.Any(i => RequiresExtraction(i, mediaSource)); - - if (requiresConversion && !enableConversion) - { - return; - } - - jobItem.MediaSourceId = streamInfo.MediaSourceId; - jobItem.TemporaryPath = GetTemporaryPath(jobItem); - - if (requiresConversion) - { - jobItem.Status = SyncJobItemStatus.Converting; - } - - if (requiresVideoTranscoding) - { - // Save the job item now since conversion could take a while - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - - try - { - var lastJobUpdate = DateTime.MinValue; - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(async pct => - { - progress.Report(pct); - - if ((DateTime.UtcNow - lastJobUpdate).TotalSeconds >= DatabaseProgressUpdateIntervalSeconds) - { - jobItem.Progress = pct / 2; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - }); - - jobItem.OutputPath = await _mediaEncoder.EncodeVideo(new EncodingJobOptions(streamInfo, conversionOptions.Profile) - { - OutputDirectory = jobItem.TemporaryPath, - CpuCoreLimit = syncOptions.TranscodingCpuCoreLimit, - ReadInputAtNativeFramerate = !syncOptions.EnableFullSpeedTranscoding - - }, innerProgress, cancellationToken); - - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - _syncManager.OnConversionComplete(jobItem); - } - catch (OperationCanceledException) - { - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - } - catch (Exception ex) - { - jobItem.Status = SyncJobItemStatus.Failed; - _logger.ErrorException("Error during sync transcoding", ex); - } - - if (jobItem.Status == SyncJobItemStatus.Failed || jobItem.Status == SyncJobItemStatus.Queued) - { - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - - jobItem.MediaSource = await GetEncodedMediaSource(jobItem.OutputPath, user, true).ConfigureAwait(false); - } - else - { - if (mediaSource.Protocol == MediaProtocol.File) - { - jobItem.OutputPath = mediaSource.Path; - } - else if (mediaSource.Protocol == MediaProtocol.Http) - { - jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); - } - else - { - throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); - } - - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - jobItem.MediaSource = mediaSource; - } - - jobItem.MediaSource.SupportsTranscoding = false; - - if (externalSubs.Count > 0) - { - // Save the job item now since conversion could take a while - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - await ConvertSubtitles(jobItem, externalSubs, streamInfo, cancellationToken).ConfigureAwait(false); - } - - jobItem.Progress = 50; - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - - private bool RequiresExtraction(SubtitleStreamInfo stream, MediaSourceInfo mediaSource) - { - var originalStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Subtitle && i.Index == stream.Index); - - return originalStream != null && !originalStream.IsExternal; - } - - private async Task ConvertSubtitles(SyncJobItem jobItem, - IEnumerable subtitles, - StreamInfo streamInfo, - CancellationToken cancellationToken) - { - var files = new List(); - - var mediaStreams = jobItem.MediaSource.MediaStreams - .Where(i => i.Type != MediaStreamType.Subtitle || !i.IsExternal) - .ToList(); - - var startingIndex = mediaStreams.Count == 0 ? - 0 : - mediaStreams.Select(i => i.Index).Max() + 1; - - foreach (var subtitle in subtitles) - { - var fileInfo = await ConvertSubtitles(jobItem.TemporaryPath, streamInfo, subtitle, cancellationToken).ConfigureAwait(false); - - // Reset this to a value that will be based on the output media - fileInfo.Index = startingIndex; - files.Add(fileInfo); - - mediaStreams.Add(new MediaStream - { - Index = startingIndex, - Codec = subtitle.Format, - IsForced = subtitle.IsForced, - IsExternal = true, - Language = subtitle.Language, - Path = fileInfo.Path, - SupportsExternalStream = true, - Type = MediaStreamType.Subtitle - }); - - startingIndex++; - } - - jobItem.AdditionalFiles.AddRange(files); - - jobItem.MediaSource.MediaStreams = mediaStreams; - } - - private async Task ConvertSubtitles(string temporaryPath, StreamInfo streamInfo, SubtitleStreamInfo subtitleStreamInfo, CancellationToken cancellationToken) - { - var subtitleStreamIndex = subtitleStreamInfo.Index; - - var filename = Guid.NewGuid() + "." + subtitleStreamInfo.Format.ToLower(); - - var path = Path.Combine(temporaryPath, filename); - - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); - - using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, false, cancellationToken).ConfigureAwait(false)) - { - using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - - return new ItemFileInfo - { - Name = Path.GetFileName(path), - Path = path, - Type = ItemFileType.Subtitles, - Index = subtitleStreamIndex - }; - } - - private const int DatabaseProgressUpdateIntervalSeconds = 2; - - private async Task Sync(SyncJobItem jobItem, Audio item, User user, bool enableConversion, SyncOptions syncOptions, IProgress progress, CancellationToken cancellationToken) - { - var job = _syncManager.GetJob(jobItem.JobId); - var jobOptions = _syncManager.GetAudioOptions(jobItem, job); - var conversionOptions = new AudioOptions - { - Profile = jobOptions.DeviceProfile - }; - - conversionOptions.DeviceId = jobItem.TargetId; - conversionOptions.Context = EncodingContext.Static; - conversionOptions.ItemId = item.Id.ToString("N"); - conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList(); - - var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(conversionOptions); - var mediaSource = streamInfo.MediaSource; - - jobItem.MediaSourceId = streamInfo.MediaSourceId; - jobItem.TemporaryPath = GetTemporaryPath(jobItem); - - if (streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting) - { - if (!enableConversion) - { - return; - } - - jobItem.Status = SyncJobItemStatus.Converting; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - - try - { - var lastJobUpdate = DateTime.MinValue; - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(async pct => - { - progress.Report(pct); - - if ((DateTime.UtcNow - lastJobUpdate).TotalSeconds >= DatabaseProgressUpdateIntervalSeconds) - { - jobItem.Progress = pct / 2; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - }); - - jobItem.OutputPath = await _mediaEncoder.EncodeAudio(new EncodingJobOptions(streamInfo, conversionOptions.Profile) - { - OutputDirectory = jobItem.TemporaryPath, - CpuCoreLimit = syncOptions.TranscodingCpuCoreLimit - - }, innerProgress, cancellationToken); - - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - _syncManager.OnConversionComplete(jobItem); - } - catch (OperationCanceledException) - { - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - } - catch (Exception ex) - { - jobItem.Status = SyncJobItemStatus.Failed; - _logger.ErrorException("Error during sync transcoding", ex); - } - - if (jobItem.Status == SyncJobItemStatus.Failed || jobItem.Status == SyncJobItemStatus.Queued) - { - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - return; - } - - jobItem.MediaSource = await GetEncodedMediaSource(jobItem.OutputPath, user, false).ConfigureAwait(false); - } - else - { - if (mediaSource.Protocol == MediaProtocol.File) - { - jobItem.OutputPath = mediaSource.Path; - } - else if (mediaSource.Protocol == MediaProtocol.Http) - { - jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false); - } - else - { - throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol)); - } - - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - jobItem.MediaSource = mediaSource; - } - - jobItem.MediaSource.SupportsTranscoding = false; - - jobItem.Progress = 50; - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - - private async Task Sync(SyncJobItem jobItem, Photo item, CancellationToken cancellationToken) - { - jobItem.OutputPath = item.Path; - - jobItem.Progress = 50; - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - - private async Task SyncGeneric(SyncJobItem jobItem, BaseItem item, CancellationToken cancellationToken) - { - jobItem.OutputPath = item.Path; - - jobItem.Progress = 50; - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - jobItem.ItemDateModifiedTicks = item.DateModified.Ticks; - await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - - private async Task DownloadFile(SyncJobItem jobItem, MediaSourceInfo mediaSource, CancellationToken cancellationToken) - { - // TODO: Download - return mediaSource.Path; - } - - public string GetTemporaryPath(SyncJob job) - { - return GetTemporaryPath(job.Id); - } - - public string GetTemporaryPath(string jobId) - { - var basePath = _config.GetSyncOptions().TemporaryPath; - - if (string.IsNullOrWhiteSpace(basePath)) - { - basePath = Path.Combine(_config.CommonApplicationPaths.ProgramDataPath, "sync"); - } - - return Path.Combine(basePath, jobId); - } - - public string GetTemporaryPath(SyncJobItem jobItem) - { - return Path.Combine(GetTemporaryPath(jobItem.JobId), jobItem.Id); - } - - private async Task GetEncodedMediaSource(string path, User user, bool isVideo) - { - var item = _libraryManager.ResolvePath(_fileSystem.GetFileSystemInfo(path)); - - await item.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); - - var hasMediaSources = item as IHasMediaSources; - - var mediaSources = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false).ToList(); - - var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) - ? new string[] { } - : new[] { user.Configuration.AudioLanguagePreference }; - - var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? new List() : new List { user.Configuration.SubtitleLanguagePreference }; - - foreach (var source in mediaSources) - { - if (isVideo) - { - source.DefaultAudioStreamIndex = - MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack); - - var defaultAudioIndex = source.DefaultAudioStreamIndex; - var audioLangage = defaultAudioIndex == null - ? null - : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); - - source.DefaultAudioStreamIndex = - MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, preferredSubs, user.Configuration.SubtitleMode, audioLangage); - } - else - { - var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - - if (audio != null) - { - source.DefaultAudioStreamIndex = audio.Index; - } - - } - } - - return mediaSources.FirstOrDefault(); - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncManager.cs b/Emby.Server.Implementations/Sync/SyncManager.cs deleted file mode 100644 index 418d42c9a..000000000 --- a/Emby.Server.Implementations/Sync/SyncManager.cs +++ /dev/null @@ -1,1372 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Playlists; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Sync; -using MediaBrowser.Model.Users; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncManager : ISyncManager - { - private readonly ILibraryManager _libraryManager; - private readonly ISyncRepository _repo; - private readonly IImageProcessor _imageProcessor; - private readonly ILogger _logger; - private readonly IUserManager _userManager; - private readonly Func _dtoService; - private readonly IServerApplicationHost _appHost; - private readonly ITVSeriesManager _tvSeriesManager; - private readonly Func _mediaEncoder; - private readonly IFileSystem _fileSystem; - private readonly Func _subtitleEncoder; - private readonly IConfigurationManager _config; - private readonly IUserDataManager _userDataManager; - private readonly Func _mediaSourceManager; - private readonly IJsonSerializer _json; - private readonly ITaskManager _taskManager; - private readonly IMemoryStreamFactory _memoryStreamProvider; - - private ISyncProvider[] _providers = { }; - - public event EventHandler> SyncJobCreated; - public event EventHandler> SyncJobCancelled; - public event EventHandler> SyncJobUpdated; - public event EventHandler> SyncJobItemUpdated; - public event EventHandler> SyncJobItemCreated; - - public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func mediaEncoder, IFileSystem fileSystem, Func subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func mediaSourceManager, IJsonSerializer json, ITaskManager taskManager, IMemoryStreamFactory memoryStreamProvider) - { - _libraryManager = libraryManager; - _repo = repo; - _imageProcessor = imageProcessor; - _logger = logger; - _userManager = userManager; - _dtoService = dtoService; - _appHost = appHost; - _tvSeriesManager = tvSeriesManager; - _mediaEncoder = mediaEncoder; - _fileSystem = fileSystem; - _subtitleEncoder = subtitleEncoder; - _config = config; - _userDataManager = userDataManager; - _mediaSourceManager = mediaSourceManager; - _json = json; - _taskManager = taskManager; - _memoryStreamProvider = memoryStreamProvider; - } - - public void AddParts(IEnumerable providers) - { - _providers = providers.ToArray(); - } - - public IEnumerable ServerSyncProviders - { - get { return _providers.OfType(); } - } - - private readonly ConcurrentDictionary _dataProviders = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - - public ISyncDataProvider GetDataProvider(IServerSyncProvider provider, SyncTarget target) - { - return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths, _memoryStreamProvider)); - } - - public async Task CreateJob(SyncJobRequest request) - { - var processor = GetSyncJobProcessor(); - - var user = _userManager.GetUserById(request.UserId); - - var items = (await processor - .GetItemsForSync(request.Category, request.ParentId, request.ItemIds, user, request.UnwatchedOnly).ConfigureAwait(false)) - .ToList(); - - if (items.Any(i => !SupportsSync(i))) - { - throw new ArgumentException("Item does not support sync."); - } - - if (string.IsNullOrWhiteSpace(request.Name)) - { - if (request.ItemIds.Count == 1) - { - request.Name = GetDefaultName(_libraryManager.GetItemById(request.ItemIds[0])); - } - } - - if (string.IsNullOrWhiteSpace(request.Name)) - { - request.Name = DateTime.Now.ToString("f1", CultureInfo.CurrentCulture); - } - - var target = GetSyncTargets(request.UserId) - .FirstOrDefault(i => string.Equals(request.TargetId, i.Id)); - - if (target == null) - { - throw new ArgumentException("Sync target not found."); - } - - var jobId = Guid.NewGuid().ToString("N"); - - if (string.IsNullOrWhiteSpace(request.Quality)) - { - request.Quality = GetQualityOptions(request.TargetId) - .Where(i => i.IsDefault) - .Select(i => i.Id) - .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); - } - - var job = new SyncJob - { - Id = jobId, - Name = request.Name, - TargetId = target.Id, - UserId = request.UserId, - UnwatchedOnly = request.UnwatchedOnly, - ItemLimit = request.ItemLimit, - RequestedItemIds = request.ItemIds ?? new List(), - DateCreated = DateTime.UtcNow, - DateLastModified = DateTime.UtcNow, - SyncNewContent = request.SyncNewContent, - ItemCount = items.Count, - Category = request.Category, - ParentId = request.ParentId, - Quality = request.Quality, - Profile = request.Profile, - Bitrate = request.Bitrate - }; - - if (!request.Category.HasValue && request.ItemIds != null) - { - var requestedItems = request.ItemIds - .Select(_libraryManager.GetItemById) - .Where(i => i != null); - - // It's just a static list - if (!requestedItems.Any(i => i.IsFolder || i is IItemByName)) - { - job.SyncNewContent = false; - } - } - - await _repo.Create(job).ConfigureAwait(false); - - await processor.EnsureJobItems(job, user).ConfigureAwait(false); - - // If it already has a converting status then is must have been aborted during conversion - var jobItemsResult = GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - JobId = jobId, - AddMetadata = false - }); - - await processor.SyncJobItems(jobItemsResult.Items, false, new Progress(), CancellationToken.None) - .ConfigureAwait(false); - - jobItemsResult = GetJobItems(new SyncJobItemQuery - { - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting }, - JobId = jobId, - AddMetadata = false - }); - - var returnResult = new SyncJobCreationResult - { - Job = GetJob(jobId), - JobItems = jobItemsResult.Items.ToList() - }; - - if (SyncJobCreated != null) - { - EventHelper.FireEventIfNotNull(SyncJobCreated, this, new GenericEventArgs - { - Argument = returnResult - - }, _logger); - } - - if (returnResult.JobItems.Any(i => i.Status == SyncJobItemStatus.Queued || i.Status == SyncJobItemStatus.Converting)) - { - _taskManager.QueueScheduledTask(); - } - - return returnResult; - } - - public async Task UpdateJob(SyncJob job) - { - // Get fresh from the db and only update the fields that are supported to be changed. - var instance = _repo.GetJob(job.Id); - - instance.Name = job.Name; - instance.Quality = job.Quality; - instance.Profile = job.Profile; - instance.UnwatchedOnly = job.UnwatchedOnly; - instance.SyncNewContent = job.SyncNewContent; - instance.ItemLimit = job.ItemLimit; - - await _repo.Update(instance).ConfigureAwait(false); - - OnSyncJobUpdated(instance); - } - - internal void OnSyncJobUpdated(SyncJob job) - { - if (SyncJobUpdated != null) - { - EventHelper.FireEventIfNotNull(SyncJobUpdated, this, new GenericEventArgs - { - Argument = job - - }, _logger); - } - } - - internal async Task UpdateSyncJobItemInternal(SyncJobItem jobItem) - { - await _repo.Update(jobItem).ConfigureAwait(false); - - if (SyncJobUpdated != null) - { - EventHelper.FireEventIfNotNull(SyncJobItemUpdated, this, new GenericEventArgs - { - Argument = jobItem - - }, _logger); - } - } - - internal void OnSyncJobItemCreated(SyncJobItem job) - { - if (SyncJobUpdated != null) - { - EventHelper.FireEventIfNotNull(SyncJobItemCreated, this, new GenericEventArgs - { - Argument = job - - }, _logger); - } - } - - public async Task> GetJobs(SyncJobQuery query) - { - var result = _repo.GetJobs(query); - - foreach (var item in result.Items) - { - await FillMetadata(item).ConfigureAwait(false); - } - - return result; - } - - private async Task FillMetadata(SyncJob job) - { - var user = _userManager.GetUserById(job.UserId); - - if (user == null) - { - return; - } - - var target = GetSyncTargets(job.UserId) - .FirstOrDefault(i => string.Equals(i.Id, job.TargetId, StringComparison.OrdinalIgnoreCase)); - - if (target != null) - { - job.TargetName = target.Name; - } - - var item = job.RequestedItemIds - .Select(_libraryManager.GetItemById) - .FirstOrDefault(i => i != null); - - if (item == null) - { - var processor = GetSyncJobProcessor(); - - item = (await processor - .GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false)) - .FirstOrDefault(); - } - - if (item != null) - { - var hasSeries = item as IHasSeries; - if (hasSeries != null) - { - job.ParentName = hasSeries.SeriesName; - } - - var hasAlbumArtist = item as IHasAlbumArtist; - if (hasAlbumArtist != null) - { - job.ParentName = hasAlbumArtist.AlbumArtists.FirstOrDefault(); - } - - var primaryImage = item.GetImageInfo(ImageType.Primary, 0); - var itemWithImage = item; - - if (primaryImage == null) - { - var parentWithImage = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary)); - - if (parentWithImage != null) - { - itemWithImage = parentWithImage; - primaryImage = parentWithImage.GetImageInfo(ImageType.Primary, 0); - } - } - - if (primaryImage != null) - { - try - { - job.PrimaryImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Primary); - job.PrimaryImageItemId = itemWithImage.Id.ToString("N"); - - } - catch (Exception ex) - { - _logger.ErrorException("Error getting image info", ex); - } - } - } - } - - private void FillMetadata(SyncJobItem jobItem) - { - var item = _libraryManager.GetItemById(jobItem.ItemId); - - if (item == null) - { - return; - } - - var primaryImage = item.GetImageInfo(ImageType.Primary, 0); - var itemWithImage = item; - - if (primaryImage == null) - { - var parentWithImage = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary)); - - if (parentWithImage != null) - { - itemWithImage = parentWithImage; - primaryImage = parentWithImage.GetImageInfo(ImageType.Primary, 0); - } - } - - if (primaryImage != null) - { - try - { - jobItem.PrimaryImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Primary); - jobItem.PrimaryImageItemId = itemWithImage.Id.ToString("N"); - - } - catch (Exception ex) - { - _logger.ErrorException("Error getting image info", ex); - } - } - } - - public async Task CancelJob(string id) - { - var job = GetJob(id); - - if (job == null) - { - throw new ArgumentException("Job not found."); - } - - await _repo.DeleteJob(id).ConfigureAwait(false); - - var path = GetSyncJobProcessor().GetTemporaryPath(id); - - try - { - _fileSystem.DeleteDirectory(path, true); - } - catch (IOException) - { - - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting directory {0}", ex, path); - } - - if (SyncJobCancelled != null) - { - EventHelper.FireEventIfNotNull(SyncJobCancelled, this, new GenericEventArgs - { - Argument = job - - }, _logger); - } - } - - public SyncJob GetJob(string id) - { - return _repo.GetJob(id); - } - - public IEnumerable GetSyncTargets(string userId) - { - return _providers - .SelectMany(i => GetSyncTargets(i, userId)) - .OrderBy(i => i.Name); - } - - private IEnumerable GetSyncTargets(ISyncProvider provider) - { - return provider.GetAllSyncTargets().Select(i => new SyncTarget - { - Name = i.Name, - Id = GetSyncTargetId(provider, i) - }); - } - - private IEnumerable GetSyncTargets(ISyncProvider provider, string userId) - { - return provider.GetSyncTargets(userId).Select(i => new SyncTarget - { - Name = i.Name, - Id = GetSyncTargetId(provider, i) - }); - } - - private string GetSyncTargetId(ISyncProvider provider, SyncTarget target) - { - var hasUniqueId = provider as IHasUniqueTargetIds; - - if (hasUniqueId != null) - { - return target.Id; - } - - return target.Id; - //var providerId = GetSyncProviderId(provider); - //return (providerId + "-" + target.Id).GetMD5().ToString("N"); - } - - private string GetSyncProviderId(ISyncProvider provider) - { - return provider.GetType().Name.GetMD5().ToString("N"); - } - - public bool SupportsSync(BaseItem item) - { - if (item == null) - { - throw new ArgumentNullException("item"); - } - - if (item is Playlist) - { - return true; - } - - if (item is Person) - { - return false; - } - - if (item is Year) - { - return false; - } - - if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.MediaType, MediaType.Book, StringComparison.OrdinalIgnoreCase)) - { - if (item.LocationType == LocationType.Virtual) - { - return false; - } - - var video = item as Video; - if (video != null) - { - if (video.IsPlaceHolder) - { - return false; - } - - if (video.IsShortcut) - { - return false; - } - } - - if (item.SourceType != SourceType.Library) - { - return false; - } - - return true; - } - - if (item.SourceType == SourceType.Channel) - { - return BaseItem.ChannelManager.SupportsSync(item.ChannelId); - } - - return item.LocationType == LocationType.FileSystem || item is Season; - } - - private string GetDefaultName(BaseItem item) - { - return item.Name; - } - - public async Task ReportSyncJobItemTransferred(string id) - { - var jobItem = _repo.GetJobItem(id); - - if (jobItem == null) - { - _logger.Debug("ReportSyncJobItemTransferred: SyncJobItem {0} doesn't exist anymore", id); - return; - } - - jobItem.Status = SyncJobItemStatus.Synced; - jobItem.Progress = 100; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - - if (!string.IsNullOrWhiteSpace(jobItem.TemporaryPath)) - { - try - { - _fileSystem.DeleteDirectory(jobItem.TemporaryPath, true); - } - catch (IOException) - { - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting temporary job file: {0}", ex, jobItem.OutputPath); - } - } - } - - private SyncJobProcessor GetSyncJobProcessor() - { - return new SyncJobProcessor(_libraryManager, _repo, this, _logger, _userManager, _tvSeriesManager, _mediaEncoder(), _subtitleEncoder(), _config, _fileSystem, _mediaSourceManager()); - } - - public SyncJobItem GetJobItem(string id) - { - return _repo.GetJobItem(id); - } - - public QueryResult GetJobItems(SyncJobItemQuery query) - { - var result = _repo.GetJobItems(query); - - if (query.AddMetadata) - { - foreach (var item in result.Items) - { - FillMetadata(item); - } - } - - return result; - } - - private SyncedItem GetJobItemInfo(SyncJobItem jobItem) - { - var job = _repo.GetJob(jobItem.JobId); - - if (job == null) - { - _logger.Error("GetJobItemInfo job id {0} no longer exists", jobItem.JobId); - return null; - } - - var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); - - if (libraryItem == null) - { - _logger.Error("GetJobItemInfo library item with id {0} no longer exists", jobItem.ItemId); - return null; - } - - var syncedItem = new SyncedItem - { - SyncJobId = jobItem.JobId, - SyncJobItemId = jobItem.Id, - ServerId = _appHost.SystemId, - UserId = job.UserId, - SyncJobName = job.Name, - SyncJobDateCreated = job.DateCreated, - AdditionalFiles = jobItem.AdditionalFiles.Select(i => new ItemFileInfo - { - ImageType = i.ImageType, - Name = i.Name, - Type = i.Type, - Index = i.Index - - }).ToList() - }; - - var dtoOptions = new DtoOptions(); - - // Remove some bloat - dtoOptions.Fields.Remove(ItemFields.MediaStreams); - dtoOptions.Fields.Remove(ItemFields.IndexOptions); - dtoOptions.Fields.Remove(ItemFields.MediaSourceCount); - dtoOptions.Fields.Remove(ItemFields.Path); - dtoOptions.Fields.Remove(ItemFields.SeriesGenres); - dtoOptions.Fields.Remove(ItemFields.Settings); - dtoOptions.Fields.Remove(ItemFields.SyncInfo); - dtoOptions.Fields.Remove(ItemFields.BasicSyncInfo); - - syncedItem.Item = _dtoService().GetBaseItemDto(libraryItem, dtoOptions); - - var mediaSource = jobItem.MediaSource; - - syncedItem.Item.MediaSources = new List(); - - syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path); - if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName)) - { - syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path); - } - - // This will be null for items that are not audio/video - if (mediaSource != null) - { - syncedItem.OriginalFileName = Path.ChangeExtension(syncedItem.OriginalFileName, Path.GetExtension(mediaSource.Path)); - syncedItem.Item.MediaSources.Add(mediaSource); - } - if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName)) - { - syncedItem.OriginalFileName = libraryItem.Name; - } - - return syncedItem; - } - - public Task ReportOfflineAction(UserAction action) - { - switch (action.Type) - { - case UserActionType.PlayedItem: - return ReportOfflinePlayedItem(action); - default: - throw new ArgumentException("Unexpected action type"); - } - } - - private Task ReportOfflinePlayedItem(UserAction action) - { - var item = _libraryManager.GetItemById(action.ItemId); - var userData = _userDataManager.GetUserData(action.UserId, item); - - userData.LastPlayedDate = action.Date; - _userDataManager.UpdatePlayState(item, userData, action.PositionTicks); - - return _userDataManager.SaveUserData(new Guid(action.UserId), item, userData, UserDataSaveReason.Import, CancellationToken.None); - } - - public async Task> GetReadySyncItems(string targetId) - { - var processor = GetSyncJobProcessor(); - - await processor.SyncJobItems(targetId, false, new Progress(), CancellationToken.None).ConfigureAwait(false); - - var jobItemResult = GetJobItems(new SyncJobItemQuery - { - TargetId = targetId, - Statuses = new[] - { - SyncJobItemStatus.ReadyToTransfer, - SyncJobItemStatus.Transferring - } - }); - - var readyItems = jobItemResult.Items - .Select(GetJobItemInfo) - .Where(i => i != null) - .ToList(); - - _logger.Debug("Returning {0} ready sync items for targetId {1}", readyItems.Count, targetId); - - return readyItems; - } - - public async Task SyncData(SyncDataRequest request) - { - if (request.SyncJobItemIds != null) - { - return await SyncDataUsingSyncJobItemIds(request).ConfigureAwait(false); - } - - var jobItemResult = GetJobItems(new SyncJobItemQuery - { - TargetId = request.TargetId, - Statuses = new[] { SyncJobItemStatus.Synced } - }); - - var response = new SyncDataResponse(); - - foreach (var jobItem in jobItemResult.Items) - { - var requiresSaving = false; - var removeFromDevice = false; - - if (request.LocalItemIds.Contains(jobItem.ItemId, StringComparer.OrdinalIgnoreCase)) - { - var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); - - var job = _repo.GetJob(jobItem.JobId); - var user = _userManager.GetUserById(job.UserId); - - if (jobItem.IsMarkedForRemoval) - { - // Tell the device to remove it since it has been marked for removal - _logger.Info("Adding ItemIdsToRemove {0} because IsMarkedForRemoval is set.", jobItem.ItemId); - removeFromDevice = true; - } - else if (user == null) - { - // Tell the device to remove it since the user is gone now - _logger.Info("Adding ItemIdsToRemove {0} because the user is no longer valid.", jobItem.ItemId); - removeFromDevice = true; - } - else if (!IsLibraryItemAvailable(libraryItem)) - { - // Tell the device to remove it since it's no longer available - _logger.Info("Adding ItemIdsToRemove {0} because it is no longer available.", jobItem.ItemId); - removeFromDevice = true; - } - else if (job.UnwatchedOnly) - { - if (libraryItem is Video && libraryItem.IsPlayed(user)) - { - // Tell the device to remove it since it has been played - _logger.Info("Adding ItemIdsToRemove {0} because it has been marked played.", jobItem.ItemId); - removeFromDevice = true; - } - } - else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0) - { - _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId); - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - requiresSaving = true; - } - } - else - { - // Content is no longer on the device - if (jobItem.IsMarkedForRemoval) - { - jobItem.Status = SyncJobItemStatus.RemovedFromDevice; - } - else - { - _logger.Info("Setting status to Queued for {0} because it is no longer on the device.", jobItem.ItemId); - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - } - requiresSaving = true; - } - - if (removeFromDevice) - { - response.ItemIdsToRemove.Add(jobItem.ItemId); - jobItem.IsMarkedForRemoval = true; - requiresSaving = true; - } - - if (requiresSaving) - { - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - } - - // Now check each item that's on the device - foreach (var itemId in request.LocalItemIds) - { - // See if it's already marked for removal - if (response.ItemIdsToRemove.Contains(itemId, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - // If there isn't a sync job for this item, mark it for removal - if (!jobItemResult.Items.Any(i => string.Equals(itemId, i.ItemId, StringComparison.OrdinalIgnoreCase))) - { - response.ItemIdsToRemove.Add(itemId); - } - } - - response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - - var itemsOnDevice = request.LocalItemIds - .Except(response.ItemIdsToRemove) - .ToList(); - - SetUserAccess(request, response, itemsOnDevice); - - return response; - } - - private async Task SyncDataUsingSyncJobItemIds(SyncDataRequest request) - { - var jobItemResult = GetJobItems(new SyncJobItemQuery - { - TargetId = request.TargetId, - Statuses = new[] { SyncJobItemStatus.Synced } - }); - - var response = new SyncDataResponse(); - - foreach (var jobItem in jobItemResult.Items) - { - var requiresSaving = false; - var removeFromDevice = false; - - if (request.SyncJobItemIds.Contains(jobItem.Id, StringComparer.OrdinalIgnoreCase)) - { - var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); - - var job = _repo.GetJob(jobItem.JobId); - var user = _userManager.GetUserById(job.UserId); - - if (jobItem.IsMarkedForRemoval) - { - // Tell the device to remove it since it has been marked for removal - _logger.Info("Adding ItemIdsToRemove {0} because IsMarkedForRemoval is set.", jobItem.Id); - removeFromDevice = true; - } - else if (user == null) - { - // Tell the device to remove it since the user is gone now - _logger.Info("Adding ItemIdsToRemove {0} because the user is no longer valid.", jobItem.Id); - removeFromDevice = true; - } - else if (!IsLibraryItemAvailable(libraryItem)) - { - // Tell the device to remove it since it's no longer available - _logger.Info("Adding ItemIdsToRemove {0} because it is no longer available.", jobItem.Id); - removeFromDevice = true; - } - else if (job.UnwatchedOnly) - { - if (libraryItem is Video && libraryItem.IsPlayed(user)) - { - // Tell the device to remove it since it has been played - _logger.Info("Adding ItemIdsToRemove {0} because it has been marked played.", jobItem.Id); - removeFromDevice = true; - } - } - else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0) - { - _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId); - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - requiresSaving = true; - } - } - else - { - // Content is no longer on the device - if (jobItem.IsMarkedForRemoval) - { - jobItem.Status = SyncJobItemStatus.RemovedFromDevice; - } - else - { - _logger.Info("Setting status to Queued for {0} because it is no longer on the device.", jobItem.Id); - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - } - requiresSaving = true; - } - - if (removeFromDevice) - { - response.ItemIdsToRemove.Add(jobItem.Id); - jobItem.IsMarkedForRemoval = true; - requiresSaving = true; - } - - if (requiresSaving) - { - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - } - } - - // Now check each item that's on the device - foreach (var syncJobItemId in request.SyncJobItemIds) - { - // See if it's already marked for removal - if (response.ItemIdsToRemove.Contains(syncJobItemId, StringComparer.OrdinalIgnoreCase)) - { - continue; - } - - // If there isn't a sync job for this item, mark it for removal - if (!jobItemResult.Items.Any(i => string.Equals(syncJobItemId, i.Id, StringComparison.OrdinalIgnoreCase))) - { - response.ItemIdsToRemove.Add(syncJobItemId); - } - } - - response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - - return response; - } - - private void SetUserAccess(SyncDataRequest request, SyncDataResponse response, List itemIds) - { - var users = request.OfflineUserIds - .Select(_userManager.GetUserById) - .Where(i => i != null) - .ToList(); - - foreach (var itemId in itemIds) - { - var item = _libraryManager.GetItemById(itemId); - - if (item != null) - { - response.ItemUserAccess[itemId] = users - .Where(i => IsUserVisible(item, i)) - .Select(i => i.Id.ToString("N")) - .OrderBy(i => i) - .ToList(); - } - } - } - - private bool IsUserVisible(BaseItem item, User user) - { - return item.IsVisibleStandalone(user); - } - - private bool IsLibraryItemAvailable(BaseItem item) - { - if (item == null) - { - return false; - } - - return true; - } - - public async Task ReEnableJobItem(string id) - { - var jobItem = _repo.GetJobItem(id); - - if (jobItem.Status != SyncJobItemStatus.Failed && jobItem.Status != SyncJobItemStatus.Cancelled) - { - throw new ArgumentException("Operation is not valid for this job item"); - } - - jobItem.Status = SyncJobItemStatus.Queued; - jobItem.Progress = 0; - jobItem.IsMarkedForRemoval = false; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - public async Task CancelItems(string targetId, IEnumerable itemIds) - { - foreach (var item in itemIds) - { - var syncJobItemResult = GetJobItems(new SyncJobItemQuery - { - AddMetadata = false, - ItemId = item, - TargetId = targetId, - Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Converting, SyncJobItemStatus.Synced, SyncJobItemStatus.Failed } - }); - - foreach (var jobItem in syncJobItemResult.Items) - { - await CancelJobItem(jobItem.Id).ConfigureAwait(false); - } - - var syncJobResult = await GetJobs(new SyncJobQuery - { - ItemId = item, - TargetId = targetId - - }).ConfigureAwait(false); - - foreach (var job in syncJobResult.Items) - { - await CancelJob(job.Id).ConfigureAwait(false); - } - } - } - - public async Task CancelJobItem(string id) - { - var jobItem = _repo.GetJobItem(id); - - jobItem.Status = SyncJobItemStatus.Cancelled; - - jobItem.Progress = 0; - jobItem.IsMarkedForRemoval = true; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - - var path = processor.GetTemporaryPath(jobItem); - - try - { - _fileSystem.DeleteDirectory(path, true); - } - catch (IOException) - { - - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting directory {0}", ex, path); - } - - var jobItemsResult = GetJobItems(new SyncJobItemQuery - { - AddMetadata = false, - JobId = jobItem.JobId, - Limit = 0, - Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced, SyncJobItemStatus.Transferring } - }); - - if (jobItemsResult.TotalRecordCount == 0) - { - await CancelJob(jobItem.JobId).ConfigureAwait(false); - } - } - - public Task MarkJobItemForRemoval(string id) - { - return CancelJobItem(id); - } - - public async Task UnmarkJobItemForRemoval(string id) - { - var jobItem = _repo.GetJobItem(id); - - if (jobItem.Status != SyncJobItemStatus.Synced) - { - throw new ArgumentException("Operation is not valid for this job item"); - } - - jobItem.IsMarkedForRemoval = false; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - public async Task ReportSyncJobItemTransferBeginning(string id) - { - var jobItem = _repo.GetJobItem(id); - - jobItem.Status = SyncJobItemStatus.Transferring; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - public async Task ReportSyncJobItemTransferFailed(string id) - { - var jobItem = _repo.GetJobItem(id); - - jobItem.Status = SyncJobItemStatus.ReadyToTransfer; - - await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); - - var processor = GetSyncJobProcessor(); - - await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false); - } - - public Dictionary GetSyncedItemProgresses(SyncJobItemQuery query) - { - return _repo.GetSyncedItemProgresses(query); - } - - public SyncJobOptions GetAudioOptions(SyncJobItem jobItem, SyncJob job) - { - var options = GetSyncJobOptions(jobItem.TargetId, null, null); - - if (job.Bitrate.HasValue) - { - options.DeviceProfile.MaxStaticBitrate = job.Bitrate.Value; - } - - return options; - } - - public ISyncProvider GetSyncProvider(SyncJobItem jobItem) - { - foreach (var provider in _providers) - { - foreach (var target in GetSyncTargets(provider)) - { - if (string.Equals(target.Id, jobItem.TargetId, StringComparison.OrdinalIgnoreCase)) - { - return provider; - } - } - } - return null; - } - - public SyncJobOptions GetVideoOptions(SyncJobItem jobItem, SyncJob job) - { - var options = GetSyncJobOptions(jobItem.TargetId, job.Profile, job.Quality); - - if (job.Bitrate.HasValue) - { - options.DeviceProfile.MaxStaticBitrate = job.Bitrate.Value; - } - - return options; - } - - private SyncJobOptions GetSyncJobOptions(string targetId, string profile, string quality) - { - foreach (var provider in _providers) - { - foreach (var target in GetSyncTargets(provider)) - { - if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) - { - return GetSyncJobOptions(provider, target, profile, quality); - } - } - } - - return GetDefaultSyncJobOptions(profile, quality); - } - - private SyncJobOptions GetSyncJobOptions(ISyncProvider provider, SyncTarget target, string profile, string quality) - { - var hasProfile = provider as IHasSyncQuality; - - if (hasProfile != null) - { - return hasProfile.GetSyncJobOptions(target, profile, quality); - } - - return GetDefaultSyncJobOptions(profile, quality); - } - - private SyncJobOptions GetDefaultSyncJobOptions(string profile, string quality) - { - var supportsAc3 = string.Equals(profile, "general", StringComparison.OrdinalIgnoreCase); - - var deviceProfile = new CloudSyncProfile(supportsAc3, false); - deviceProfile.MaxStaticBitrate = SyncHelper.AdjustBitrate(deviceProfile.MaxStaticBitrate, quality); - - return new SyncJobOptions - { - DeviceProfile = deviceProfile, - IsConverting = IsConverting(profile, quality) - }; - } - - private bool IsConverting(string profile, string quality) - { - return !string.Equals(profile, "original", StringComparison.OrdinalIgnoreCase); - } - - public IEnumerable GetQualityOptions(string targetId) - { - return GetQualityOptions(targetId, null); - } - - public IEnumerable GetQualityOptions(string targetId, User user) - { - foreach (var provider in _providers) - { - foreach (var target in GetSyncTargets(provider)) - { - if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) - { - return GetQualityOptions(provider, target, user); - } - } - } - - return new List(); - } - - private IEnumerable GetQualityOptions(ISyncProvider provider, SyncTarget target, User user) - { - var hasQuality = provider as IHasSyncQuality; - if (hasQuality != null) - { - var options = hasQuality.GetQualityOptions(target); - - if (user != null && !user.Policy.EnableSyncTranscoding) - { - options = options.Where(i => i.IsOriginalQuality); - } - - return options; - } - - // Default options for providers that don't override - return new List - { - new SyncQualityOption - { - Name = "High", - Id = "high", - IsDefault = true - }, - new SyncQualityOption - { - Name = "Medium", - Id = "medium" - }, - new SyncQualityOption - { - Name = "Low", - Id = "low" - }, - new SyncQualityOption - { - Name = "Custom", - Id = "custom" - } - }; - } - - public IEnumerable GetProfileOptions(string targetId, User user) - { - foreach (var provider in _providers) - { - foreach (var target in GetSyncTargets(provider)) - { - if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase)) - { - return GetProfileOptions(provider, target, user); - } - } - } - - return new List(); - } - - public IEnumerable GetProfileOptions(string targetId) - { - return GetProfileOptions(targetId, null); - } - - private IEnumerable GetProfileOptions(ISyncProvider provider, SyncTarget target, User user) - { - var hasQuality = provider as IHasSyncQuality; - if (hasQuality != null) - { - return hasQuality.GetProfileOptions(target); - } - - var list = new List(); - - list.Add(new SyncProfileOption - { - Name = "Original", - Id = "Original", - Description = "Syncs original files as-is.", - EnableQualityOptions = false - }); - - if (user == null || user.Policy.EnableSyncTranscoding) - { - list.Add(new SyncProfileOption - { - Name = "Baseline", - Id = "baseline", - Description = "Designed for compatibility with all devices, including web browsers. Targets H264/AAC video and MP3 audio." - }); - - list.Add(new SyncProfileOption - { - Name = "General", - Id = "general", - Description = "Designed for compatibility with Chromecast, Roku, Smart TV's, and other similar devices. Targets H264/AAC/AC3 video and MP3 audio.", - IsDefault = true - }); - } - - return list; - } - - protected internal void OnConversionComplete(SyncJobItem item) - { - var syncProvider = GetSyncProvider(item); - if (syncProvider is AppSyncProvider) - { - return; - } - - _taskManager.QueueIfNotRunning(); - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncNotificationEntryPoint.cs b/Emby.Server.Implementations/Sync/SyncNotificationEntryPoint.cs deleted file mode 100644 index 06e0e66a9..000000000 --- a/Emby.Server.Implementations/Sync/SyncNotificationEntryPoint.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Threading; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Sync; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncNotificationEntryPoint : IServerEntryPoint - { - private readonly ISessionManager _sessionManager; - private readonly ISyncManager _syncManager; - - public SyncNotificationEntryPoint(ISyncManager syncManager, ISessionManager sessionManager) - { - _syncManager = syncManager; - _sessionManager = sessionManager; - } - - public void Run() - { - _syncManager.SyncJobItemUpdated += _syncManager_SyncJobItemUpdated; - } - - private async void _syncManager_SyncJobItemUpdated(object sender, GenericEventArgs e) - { - var item = e.Argument; - - if (item.Status == SyncJobItemStatus.ReadyToTransfer) - { - try - { - await _sessionManager.SendMessageToUserDeviceSessions(item.TargetId, "SyncJobItemReady", item, CancellationToken.None).ConfigureAwait(false); - } - catch - { - - } - } - - if (item.Status == SyncJobItemStatus.Cancelled) - { - try - { - await _sessionManager.SendMessageToUserDeviceSessions(item.TargetId, "SyncJobItemCancelled", item, CancellationToken.None).ConfigureAwait(false); - } - catch - { - - } - } - } - - public void Dispose() - { - _syncManager.SyncJobItemUpdated -= _syncManager_SyncJobItemUpdated; - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncRegistrationInfo.cs b/Emby.Server.Implementations/Sync/SyncRegistrationInfo.cs deleted file mode 100644 index c2658c5c5..000000000 --- a/Emby.Server.Implementations/Sync/SyncRegistrationInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Common.Security; -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncRegistrationInfo : IRequiresRegistration - { - private readonly ISecurityManager _securityManager; - - public static SyncRegistrationInfo Instance; - - public SyncRegistrationInfo(ISecurityManager securityManager) - { - _securityManager = securityManager; - Instance = this; - } - - private bool _registered; - public bool IsRegistered - { - get { return _registered; } - } - - public async Task LoadRegistrationInfoAsync() - { - var info = await _securityManager.GetRegistrationStatus("sync").ConfigureAwait(false); - - _registered = info.IsValid; - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncRepository.cs b/Emby.Server.Implementations/Sync/SyncRepository.cs deleted file mode 100644 index aafce3500..000000000 --- a/Emby.Server.Implementations/Sync/SyncRepository.cs +++ /dev/null @@ -1,847 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Emby.Server.Implementations.Data; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Sync; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncRepository : BaseSqliteRepository, ISyncRepository - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - private readonly IJsonSerializer _json; - - public SyncRepository(ILogger logger, IJsonSerializer json, IServerApplicationPaths appPaths) - : base(logger) - { - _json = json; - DbFilePath = Path.Combine(appPaths.DataPath, "sync14.db"); - } - - private class SyncSummary - { - public Dictionary Items { get; set; } - - public SyncSummary() - { - Items = new Dictionary(); - } - } - - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - string[] queries = { - - "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Profile TEXT, Quality TEXT, Bitrate INT, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)", - - "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT, ItemDateModifiedTicks BIGINT)", - - "drop index if exists idx_SyncJobItems2", - "drop index if exists idx_SyncJobItems3", - "drop index if exists idx_SyncJobs1", - "drop index if exists idx_SyncJobs", - "drop index if exists idx_SyncJobItems1", - "create index if not exists idx_SyncJobItems4 on SyncJobItems(TargetId,ItemId,Status,Progress,DateCreated)", - "create index if not exists idx_SyncJobItems5 on SyncJobItems(TargetId,Status,ItemId,Progress)", - - "create index if not exists idx_SyncJobs2 on SyncJobs(TargetId,Status,ItemIds,Progress)", - - "pragma shrink_memory" - }; - - connection.RunQueries(queries); - - connection.RunInTransaction(db => - { - var existingColumnNames = GetColumnNames(db, "SyncJobs"); - AddColumn(db, "SyncJobs", "Profile", "TEXT", existingColumnNames); - AddColumn(db, "SyncJobs", "Bitrate", "INT", existingColumnNames); - - existingColumnNames = GetColumnNames(db, "SyncJobItems"); - AddColumn(db, "SyncJobItems", "ItemDateModifiedTicks", "BIGINT", existingColumnNames); - }, TransactionMode); - } - } - - protected override bool EnableTempStoreMemory - { - get - { - return true; - } - } - - private const string BaseJobSelectText = "select Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs"; - private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks from SyncJobItems"; - - public SyncJob GetJob(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - CheckDisposed(); - - var guid = new Guid(id); - - if (guid == Guid.Empty) - { - throw new ArgumentNullException("id"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = BaseJobSelectText + " where Id=?"; - var paramList = new List(); - - paramList.Add(guid.ToGuidParamValue()); - - foreach (var row in connection.Query(commandText, paramList.ToArray())) - { - return GetJob(row); - } - - return null; - } - } - } - - private SyncJob GetJob(IReadOnlyList reader) - { - var info = new SyncJob - { - Id = reader[0].ReadGuid().ToString("N"), - TargetId = reader[1].ToString(), - Name = reader[2].ToString() - }; - - if (reader[3].SQLiteType != SQLiteType.Null) - { - info.Profile = reader[3].ToString(); - } - - if (reader[4].SQLiteType != SQLiteType.Null) - { - info.Quality = reader[4].ToString(); - } - - if (reader[5].SQLiteType != SQLiteType.Null) - { - info.Bitrate = reader[5].ToInt(); - } - - if (reader[6].SQLiteType != SQLiteType.Null) - { - info.Status = (SyncJobStatus)Enum.Parse(typeof(SyncJobStatus), reader[6].ToString(), true); - } - - if (reader[7].SQLiteType != SQLiteType.Null) - { - info.Progress = reader[7].ToDouble(); - } - - if (reader[8].SQLiteType != SQLiteType.Null) - { - info.UserId = reader[8].ToString(); - } - - if (reader[9].SQLiteType != SQLiteType.Null) - { - info.RequestedItemIds = reader[9].ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - } - - if (reader[10].SQLiteType != SQLiteType.Null) - { - info.Category = (SyncCategory)Enum.Parse(typeof(SyncCategory), reader[10].ToString(), true); - } - - if (reader[11].SQLiteType != SQLiteType.Null) - { - info.ParentId = reader[11].ToString(); - } - - if (reader[12].SQLiteType != SQLiteType.Null) - { - info.UnwatchedOnly = reader[12].ToBool(); - } - - if (reader[13].SQLiteType != SQLiteType.Null) - { - info.ItemLimit = reader[13].ToInt(); - } - - info.SyncNewContent = reader[14].ToBool(); - - info.DateCreated = reader[15].ReadDateTime(); - info.DateLastModified = reader[16].ReadDateTime(); - info.ItemCount = reader[17].ToInt(); - - return info; - } - - public Task Create(SyncJob job) - { - return InsertOrUpdate(job, true); - } - - public Task Update(SyncJob job) - { - return InsertOrUpdate(job, false); - } - - private async Task InsertOrUpdate(SyncJob job, bool insert) - { - if (job == null) - { - throw new ArgumentNullException("job"); - } - - CheckDisposed(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - string commandText; - - if (insert) - { - commandText = "insert into SyncJobs (Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Profile, @Quality, @Bitrate, @Status, @Progress, @UserId, @ItemIds, @Category, @ParentId, @UnwatchedOnly, @ItemLimit, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)"; - } - else - { - commandText = "update SyncJobs set TargetId=@TargetId,Name=@Name,Profile=@Profile,Quality=@Quality,Bitrate=@Bitrate,Status=@Status,Progress=@Progress,UserId=@UserId,ItemIds=@ItemIds,Category=@Category,ParentId=@ParentId,UnwatchedOnly=@UnwatchedOnly,ItemLimit=@ItemLimit,SyncNewContent=@SyncNewContent,DateCreated=@DateCreated,DateLastModified=@DateLastModified,ItemCount=@ItemCount where Id=@Id"; - } - - connection.RunInTransaction(conn => - { - using (var statement = PrepareStatementSafe(connection, commandText)) - { - statement.TryBind("@TargetId", job.TargetId); - statement.TryBind("@Name", job.Name); - statement.TryBind("@Profile", job.Profile); - statement.TryBind("@Quality", job.Quality); - statement.TryBind("@Bitrate", job.Bitrate); - statement.TryBind("@Status", job.Status.ToString()); - statement.TryBind("@Progress", job.Progress); - statement.TryBind("@UserId", job.UserId); - - statement.TryBind("@ItemIds", string.Join(",", job.RequestedItemIds.ToArray())); - - if (job.Category.HasValue) - { - statement.TryBind("@Category", job.Category.Value.ToString()); - } - else - { - statement.TryBindNull("@Category"); - } - - if (!string.IsNullOrWhiteSpace(job.ParentId)) - { - statement.TryBind("@ParentId", job.ParentId); - } - else - { - statement.TryBindNull("@ParentId"); - } - - statement.TryBind("@UnwatchedOnly", job.UnwatchedOnly); - - if (job.ItemLimit.HasValue) - { - statement.TryBind("@ItemLimit", job.ItemLimit); - } - else - { - statement.TryBindNull("@ItemLimit"); - } - - statement.TryBind("@SyncNewContent", job.SyncNewContent); - - statement.TryBind("@DateCreated", job.DateCreated.ToDateTimeParamValue()); - statement.TryBind("@DateLastModified", job.DateLastModified.ToDateTimeParamValue()); - - statement.TryBind("@ItemCount", job.ItemCount); - statement.TryBind("@Id", job.Id.ToGuidParamValue()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - - public async Task DeleteJob(string id) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException("id"); - } - - CheckDisposed(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(conn => - { - conn.Execute("delete from SyncJobs where Id=?", id.ToGuidParamValue()); - conn.Execute("delete from SyncJobItems where JobId=?", id); - }, TransactionMode); - } - } - } - - public QueryResult GetJobs(SyncJobQuery query) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - CheckDisposed(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = BaseJobSelectText; - var paramList = new List(); - - var whereClauses = new List(); - - if (query.Statuses.Length > 0) - { - var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray()); - - whereClauses.Add(string.Format("Status in ({0})", statuses)); - } - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - whereClauses.Add("TargetId=?"); - paramList.Add(query.TargetId); - } - if (!string.IsNullOrWhiteSpace(query.ExcludeTargetIds)) - { - var excludeIds = (query.ExcludeTargetIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - if (excludeIds.Length == 1) - { - whereClauses.Add("TargetId<>?"); - paramList.Add(excludeIds[0]); - } - else if (excludeIds.Length > 1) - { - whereClauses.Add("TargetId<>?"); - paramList.Add(excludeIds[0]); - } - } - if (!string.IsNullOrWhiteSpace(query.UserId)) - { - whereClauses.Add("UserId=?"); - paramList.Add(query.UserId); - } - if (!string.IsNullOrWhiteSpace(query.ItemId)) - { - whereClauses.Add("ItemIds like ?"); - paramList.Add("%" + query.ItemId + "%"); - } - if (query.SyncNewContent.HasValue) - { - whereClauses.Add("SyncNewContent=?"); - paramList.Add(query.SyncNewContent.Value); - } - - commandText += " mainTable"; - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - var startIndex = query.StartIndex ?? 0; - if (startIndex > 0) - { - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobs ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC LIMIT {0})", - startIndex.ToString(_usCulture))); - } - - if (whereClauses.Count > 0) - { - commandText += " where " + string.Join(" AND ", whereClauses.ToArray()); - } - - commandText += " ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC"; - - if (query.Limit.HasValue) - { - commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); - } - - var list = new List(); - var count = connection.Query("select count (Id) from SyncJobs" + whereTextWithoutPaging, paramList.ToArray()) - .SelectScalarInt() - .First(); - - foreach (var row in connection.Query(commandText, paramList.ToArray())) - { - list.Add(GetJob(row)); - } - - return new QueryResult() - { - Items = list.ToArray(), - TotalRecordCount = count - }; - } - } - } - - public SyncJobItem GetJobItem(string id) - { - if (string.IsNullOrEmpty(id)) - { - throw new ArgumentNullException("id"); - } - - CheckDisposed(); - - var guid = new Guid(id); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = BaseJobItemSelectText + " where Id=?"; - var paramList = new List(); - - paramList.Add(guid.ToGuidParamValue()); - - foreach (var row in connection.Query(commandText, paramList.ToArray())) - { - return GetJobItem(row); - } - - return null; - } - } - } - - private QueryResult GetJobItemReader(SyncJobItemQuery query, string baseSelectText, Func, T> itemFactory) - { - if (query == null) - { - throw new ArgumentNullException("query"); - } - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = baseSelectText; - var paramList = new List(); - - var whereClauses = new List(); - - if (!string.IsNullOrWhiteSpace(query.JobId)) - { - whereClauses.Add("JobId=?"); - paramList.Add(query.JobId); - } - if (!string.IsNullOrWhiteSpace(query.ItemId)) - { - whereClauses.Add("ItemId=?"); - paramList.Add(query.ItemId); - } - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - whereClauses.Add("TargetId=?"); - paramList.Add(query.TargetId); - } - - if (query.Statuses.Length > 0) - { - var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray()); - - whereClauses.Add(string.Format("Status in ({0})", statuses)); - } - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - var startIndex = query.StartIndex ?? 0; - if (startIndex > 0) - { - whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY JobItemIndex, DateCreated LIMIT {0})", - startIndex.ToString(_usCulture))); - } - - if (whereClauses.Count > 0) - { - commandText += " where " + string.Join(" AND ", whereClauses.ToArray()); - } - - commandText += " ORDER BY JobItemIndex, DateCreated"; - - if (query.Limit.HasValue) - { - commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture); - } - - var list = new List(); - var count = connection.Query("select count (Id) from SyncJobItems" + whereTextWithoutPaging, paramList.ToArray()) - .SelectScalarInt() - .First(); - - foreach (var row in connection.Query(commandText, paramList.ToArray())) - { - list.Add(itemFactory(row)); - } - - return new QueryResult() - { - Items = list.ToArray(), - TotalRecordCount = count - }; - } - } - } - - public Dictionary GetSyncedItemProgresses(SyncJobItemQuery query) - { - var result = new Dictionary(); - - var now = DateTime.UtcNow; - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - var commandText = "select ItemId,Status,Progress from SyncJobItems"; - var whereClauses = new List(); - - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - whereClauses.Add("TargetId=@TargetId"); - } - - if (query.Statuses.Length > 0) - { - var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray()); - - whereClauses.Add(string.Format("Status in ({0})", statuses)); - } - - if (whereClauses.Count > 0) - { - commandText += " where " + string.Join(" AND ", whereClauses.ToArray()); - } - - var statementTexts = new List - { - commandText - }; - - commandText = commandText - .Replace("select ItemId,Status,Progress from SyncJobItems", "select ItemIds,Status,Progress from SyncJobs") - .Replace("'Synced'", "'Completed','CompletedWithError'"); - - statementTexts.Add(commandText); - - var statements = connection.PrepareAll(string.Join(";", statementTexts.ToArray())) - .ToList(); - - using (var statement = statements[0]) - { - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - statement.TryBind("@TargetId", query.TargetId); - } - - foreach (var row in statement.ExecuteQuery()) - { - AddStatusResult(row, result, false); - } - LogQueryTime("GetSyncedItemProgresses", commandText, now); - } - - now = DateTime.UtcNow; - - using (var statement = statements[1]) - { - if (!string.IsNullOrWhiteSpace(query.TargetId)) - { - statement.TryBind("@TargetId", query.TargetId); - } - - foreach (var row in statement.ExecuteQuery()) - { - AddStatusResult(row, result, true); - } - LogQueryTime("GetSyncedItemProgresses", commandText, now); - } - } - } - - return result; - } - - private void LogQueryTime(string methodName, string commandText, DateTime startDate) - { - var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds; - - var slowThreshold = 1000; - -#if DEBUG - slowThreshold = 50; -#endif - - if (elapsed >= slowThreshold) - { - Logger.Debug("{2} query time (slow): {0}ms. Query: {1}", - Convert.ToInt32(elapsed), - commandText, - methodName); - } - else - { - //Logger.Debug("{2} query time: {0}ms. Query: {1}", - // Convert.ToInt32(elapsed), - // cmd.CommandText, - // methodName); - } - } - - private void AddStatusResult(IReadOnlyList reader, Dictionary result, bool multipleIds) - { - if (reader[0].SQLiteType == SQLiteType.Null) - { - return; - } - - var itemIds = new List(); - - var ids = reader[0].ToString(); - - if (multipleIds) - { - itemIds = ids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - } - else - { - itemIds.Add(ids); - } - - if (reader[1].SQLiteType != SQLiteType.Null) - { - SyncJobItemStatus status; - var statusString = reader[1].ToString(); - if (string.Equals(statusString, "Completed", StringComparison.OrdinalIgnoreCase) || - string.Equals(statusString, "CompletedWithError", StringComparison.OrdinalIgnoreCase)) - { - status = SyncJobItemStatus.Synced; - } - else - { - status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), statusString, true); - } - - if (status == SyncJobItemStatus.Synced) - { - foreach (var itemId in itemIds) - { - result[itemId] = new SyncedItemProgress - { - Status = SyncJobItemStatus.Synced - }; - } - } - else - { - double progress = reader[2].SQLiteType == SQLiteType.Null ? 0.0 : reader[2].ToDouble(); - - foreach (var itemId in itemIds) - { - SyncedItemProgress currentStatus; - if (!result.TryGetValue(itemId, out currentStatus) || (currentStatus.Status != SyncJobItemStatus.Synced && progress >= currentStatus.Progress)) - { - result[itemId] = new SyncedItemProgress - { - Status = status, - Progress = progress - }; - } - } - } - } - } - - public QueryResult GetJobItems(SyncJobItemQuery query) - { - return GetJobItemReader(query, BaseJobItemSelectText, GetJobItem); - } - - public Task Create(SyncJobItem jobItem) - { - return InsertOrUpdate(jobItem, true); - } - - public Task Update(SyncJobItem jobItem) - { - return InsertOrUpdate(jobItem, false); - } - - private async Task InsertOrUpdate(SyncJobItem jobItem, bool insert) - { - if (jobItem == null) - { - throw new ArgumentNullException("jobItem"); - } - - CheckDisposed(); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - string commandText; - - if (insert) - { - commandText = "insert into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - } - else - { - // cmd - commandText = "update SyncJobItems set ItemId=?,ItemName=?,MediaSourceId=?,JobId=?,TemporaryPath=?,OutputPath=?,Status=?,TargetId=?,DateCreated=?,Progress=?,AdditionalFiles=?,MediaSource=?,IsMarkedForRemoval=?,JobItemIndex=?,ItemDateModifiedTicks=? where Id=?"; - } - - var paramList = new List(); - paramList.Add(jobItem.ItemId); - paramList.Add(jobItem.ItemName); - paramList.Add(jobItem.MediaSourceId); - paramList.Add(jobItem.JobId); - paramList.Add(jobItem.TemporaryPath); - paramList.Add(jobItem.OutputPath); - paramList.Add(jobItem.Status.ToString()); - - paramList.Add(jobItem.TargetId); - paramList.Add(jobItem.DateCreated.ToDateTimeParamValue()); - paramList.Add(jobItem.Progress); - paramList.Add(_json.SerializeToString(jobItem.AdditionalFiles)); - paramList.Add(jobItem.MediaSource == null ? null : _json.SerializeToString(jobItem.MediaSource)); - paramList.Add(jobItem.IsMarkedForRemoval); - paramList.Add(jobItem.JobItemIndex); - paramList.Add(jobItem.ItemDateModifiedTicks); - - if (insert) - { - paramList.Insert(0, jobItem.Id.ToGuidParamValue()); - } - else - { - paramList.Add(jobItem.Id.ToGuidParamValue()); - } - - connection.RunInTransaction(conn => - { - conn.Execute(commandText, paramList.ToArray()); - }, TransactionMode); - } - } - } - - private SyncJobItem GetJobItem(IReadOnlyList reader) - { - var info = new SyncJobItem - { - Id = reader[0].ReadGuid().ToString("N"), - ItemId = reader[1].ToString() - }; - - if (reader[2].SQLiteType != SQLiteType.Null) - { - info.ItemName = reader[2].ToString(); - } - - if (reader[3].SQLiteType != SQLiteType.Null) - { - info.MediaSourceId = reader[3].ToString(); - } - - info.JobId = reader[4].ToString(); - - if (reader[5].SQLiteType != SQLiteType.Null) - { - info.TemporaryPath = reader[5].ToString(); - } - if (reader[6].SQLiteType != SQLiteType.Null) - { - info.OutputPath = reader[6].ToString(); - } - - if (reader[7].SQLiteType != SQLiteType.Null) - { - info.Status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader[7].ToString(), true); - } - - info.TargetId = reader[8].ToString(); - - info.DateCreated = reader[9].ReadDateTime(); - - if (reader[10].SQLiteType != SQLiteType.Null) - { - info.Progress = reader[10].ToDouble(); - } - - if (reader[11].SQLiteType != SQLiteType.Null) - { - var json = reader[11].ToString(); - - if (!string.IsNullOrWhiteSpace(json)) - { - info.AdditionalFiles = _json.DeserializeFromString>(json); - } - } - - if (reader[12].SQLiteType != SQLiteType.Null) - { - var json = reader[12].ToString(); - - if (!string.IsNullOrWhiteSpace(json)) - { - info.MediaSource = _json.DeserializeFromString(json); - } - } - - info.IsMarkedForRemoval = reader[13].ToBool(); - info.JobItemIndex = reader[14].ToInt(); - - if (reader[15].SQLiteType != SQLiteType.Null) - { - info.ItemDateModifiedTicks = reader[15].ToInt64(); - } - - return info; - } - } -} diff --git a/Emby.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/Emby.Server.Implementations/Sync/SyncedMediaSourceProvider.cs deleted file mode 100644 index 1e54885e6..000000000 --- a/Emby.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ /dev/null @@ -1,158 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.Sync -{ - public class SyncedMediaSourceProvider : IMediaSourceProvider - { - private readonly SyncManager _syncManager; - private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; - - public SyncedMediaSourceProvider(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger) - { - _appHost = appHost; - _logger = logger; - _syncManager = (SyncManager)syncManager; - } - - public async Task> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken) - { - var jobItemResult = _syncManager.GetJobItems(new SyncJobItemQuery - { - AddMetadata = false, - Statuses = new[] { SyncJobItemStatus.Synced }, - ItemId = item.Id.ToString("N") - }); - - var list = new List(); - - if (jobItemResult.Items.Length > 0) - { - var targets = _syncManager.ServerSyncProviders - .SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple(i, t))) - .ToList(); - - var serverId = _appHost.SystemId; - - foreach (var jobItem in jobItemResult.Items) - { - var targetTuple = targets.FirstOrDefault(i => string.Equals(i.Item2.Id, jobItem.TargetId, StringComparison.OrdinalIgnoreCase)); - - if (targetTuple != null) - { - var syncTarget = targetTuple.Item2; - var syncProvider = targetTuple.Item1; - var dataProvider = _syncManager.GetDataProvider(targetTuple.Item1, syncTarget); - - var localItems = await dataProvider.GetItems(syncTarget, serverId, item.Id.ToString("N")).ConfigureAwait(false); - - foreach (var localItem in localItems) - { - foreach (var mediaSource in localItem.Item.MediaSources) - { - AddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget); - } - } - } - } - } - - return list; - } - - private void AddMediaSource(List list, - LocalItem item, - MediaSourceInfo mediaSource, - IServerSyncProvider provider, - SyncTarget target) - { - SetStaticMediaSourceInfo(item, mediaSource); - - var requiresDynamicAccess = provider as IHasDynamicAccess; - - if (requiresDynamicAccess != null) - { - mediaSource.RequiresOpening = true; - - var keyList = new List(); - keyList.Add(provider.GetType().FullName.GetMD5().ToString("N")); - keyList.Add(target.Id.GetMD5().ToString("N")); - keyList.Add(item.Id); - mediaSource.OpenToken = string.Join(StreamIdDelimeterString, keyList.ToArray()); - } - - list.Add(mediaSource); - } - - // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message. - private const string StreamIdDelimeterString = "_"; - - public async Task> OpenMediaSource(string openToken, CancellationToken cancellationToken) - { - var openKeys = openToken.Split(new[] { StreamIdDelimeterString[0] }, 3); - - var provider = _syncManager.ServerSyncProviders - .FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); - - var target = provider.GetAllSyncTargets() - .FirstOrDefault(i => string.Equals(openKeys[1], i.Id.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); - - var dataProvider = _syncManager.GetDataProvider(provider, target); - var localItem = await dataProvider.Get(target, openKeys[2]).ConfigureAwait(false); - - var fileId = localItem.FileId; - if (string.IsNullOrWhiteSpace(fileId)) - { - } - - var requiresDynamicAccess = (IHasDynamicAccess)provider; - var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(fileId, target, cancellationToken).ConfigureAwait(false); - - var mediaSource = localItem.Item.MediaSources.First(); - mediaSource.LiveStreamId = Guid.NewGuid().ToString(); - SetStaticMediaSourceInfo(localItem, mediaSource); - - foreach (var stream in mediaSource.MediaStreams) - { - if (!string.IsNullOrWhiteSpace(stream.ExternalId)) - { - var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); - stream.Path = dynamicStreamInfo.Path; - } - } - - mediaSource.Path = dynamicInfo.Path; - mediaSource.Protocol = dynamicInfo.Protocol; - mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders; - - return new Tuple(mediaSource, null); - } - - private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource) - { - mediaSource.Id = item.Id; - mediaSource.SupportsTranscoding = false; - if (mediaSource.Protocol == MediaBrowser.Model.MediaInfo.MediaProtocol.File) - { - mediaSource.ETag = item.Id; - } - } - - public Task CloseMediaSource(string liveStreamId) - { - throw new NotImplementedException(); - } - } -} diff --git a/Emby.Server.Implementations/Sync/TargetDataProvider.cs b/Emby.Server.Implementations/Sync/TargetDataProvider.cs deleted file mode 100644 index cac8f0cd8..000000000 --- a/Emby.Server.Implementations/Sync/TargetDataProvider.cs +++ /dev/null @@ -1,208 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Sync; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Sync; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.Sync -{ - public class TargetDataProvider : ISyncDataProvider - { - private readonly SyncTarget _target; - private readonly IServerSyncProvider _provider; - - private readonly SemaphoreSlim _dataLock = new SemaphoreSlim(1, 1); - private readonly SemaphoreSlim _remoteDataLock = new SemaphoreSlim(1, 1); - private List _items; - - private readonly ILogger _logger; - private readonly IJsonSerializer _json; - private readonly IFileSystem _fileSystem; - private readonly IApplicationPaths _appPaths; - private readonly IServerApplicationHost _appHost; - private readonly IMemoryStreamFactory _memoryStreamProvider; - - public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths, IMemoryStreamFactory memoryStreamProvider) - { - _logger = logger; - _json = json; - _provider = provider; - _target = target; - _fileSystem = fileSystem; - _appPaths = appPaths; - _memoryStreamProvider = memoryStreamProvider; - _appHost = appHost; - } - - private string[] GetRemotePath() - { - var parts = new List - { - _appHost.FriendlyName, - "data.json" - }; - - parts = parts.Select(i => GetValidFilename(_provider, i)).ToList(); - - return parts.ToArray(); - } - - private string GetValidFilename(IServerSyncProvider provider, string filename) - { - // We can always add this method to the sync provider if it's really needed - return _fileSystem.GetValidFilename(filename); - } - - private async Task> RetrieveItems(CancellationToken cancellationToken) - { - _logger.Debug("Getting {0} from {1}", string.Join(MediaSync.PathSeparatorString, GetRemotePath().ToArray()), _provider.Name); - - await _remoteDataLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - var fileResult = await _provider.GetFiles(GetRemotePath().ToArray(), _target, cancellationToken).ConfigureAwait(false); - - if (fileResult.Items.Length > 0) - { - using (var stream = await _provider.GetFile(fileResult.Items[0].FullName, _target, new Progress(), cancellationToken)) - { - return _json.DeserializeFromStream>(stream); - } - } - } - finally - { - _remoteDataLock.Release(); - } - - return new List(); - } - - private async Task EnsureData(CancellationToken cancellationToken) - { - if (_items == null) - { - _items = await RetrieveItems(cancellationToken).ConfigureAwait(false); - } - } - - private async Task SaveData(List items, CancellationToken cancellationToken) - { - using (var stream = _memoryStreamProvider.CreateNew()) - { - _json.SerializeToStream(items, stream); - - // Save to sync provider - stream.Position = 0; - var remotePath = GetRemotePath(); - - await _remoteDataLock.WaitAsync(cancellationToken).ConfigureAwait(false); - - try - { - _logger.Debug("Saving data.json to {0}. Remote path: {1}", _provider.Name, string.Join("/", remotePath)); - - await _provider.SendFile(stream, remotePath, _target, new Progress(), cancellationToken).ConfigureAwait(false); - } - finally - { - _remoteDataLock.Release(); - } - } - } - - private async Task GetData(bool enableCache, Func, T> dataFactory) - { - if (!enableCache) - { - var items = await RetrieveItems(CancellationToken.None).ConfigureAwait(false); - var newCache = items.ToList(); - var result = dataFactory(items); - await UpdateCache(newCache).ConfigureAwait(false); - return result; - } - - await _dataLock.WaitAsync().ConfigureAwait(false); - - try - { - await EnsureData(CancellationToken.None).ConfigureAwait(false); - - return dataFactory(_items); - } - finally - { - _dataLock.Release(); - } - } - - private async Task UpdateData(Func, List> action) - { - var items = await RetrieveItems(CancellationToken.None).ConfigureAwait(false); - items = action(items); - await SaveData(items.ToList(), CancellationToken.None).ConfigureAwait(false); - - await UpdateCache(null).ConfigureAwait(false); - } - - private async Task UpdateCache(List list) - { - await _dataLock.WaitAsync().ConfigureAwait(false); - - try - { - _items = list; - } - finally - { - _dataLock.Release(); - } - } - - public Task> GetLocalItems(SyncTarget target, string serverId) - { - return GetData(false, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).ToList()); - } - - public Task AddOrUpdate(SyncTarget target, LocalItem item) - { - return UpdateData(items => - { - var list = items.Where(i => !string.Equals(i.Id, item.Id, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - list.Add(item); - - return list; - }); - } - - public Task Delete(SyncTarget target, string id) - { - return UpdateData(items => items.Where(i => !string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)).ToList()); - } - - public Task Get(SyncTarget target, string id) - { - return GetData(true, items => items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase))); - } - - public Task> GetItems(SyncTarget target, string serverId, string itemId) - { - return GetData(true, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)).ToList()); - } - - public Task> GetItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId) - { - return GetData(false, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.SyncJobItemId, syncJobItemId, StringComparison.OrdinalIgnoreCase)).ToList()); - } - } -} diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 88627957b..7e638e171 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -3,6 +3,6 @@ - + \ No newline at end of file diff --git a/MediaBrowser.Api/ConnectService.cs b/MediaBrowser.Api/ConnectService.cs deleted file mode 100644 index 304dc366b..000000000 --- a/MediaBrowser.Api/ConnectService.cs +++ /dev/null @@ -1,177 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Connect; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Connect; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api -{ - [Route("/Users/{Id}/Connect/Link", "POST", Summary = "Creates a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class CreateConnectLink : IReturn - { - [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "ConnectUsername", Description = "Connect username", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ConnectUsername { get; set; } - } - - [Route("/Users/{Id}/Connect/Link", "DELETE", Summary = "Removes a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class DeleteConnectLink : IReturnVoid - { - [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Connect/Invite", "POST", Summary = "Creates a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class CreateConnectInvite : IReturn - { - [ApiMember(Name = "ConnectUsername", Description = "Connect username", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string ConnectUsername { get; set; } - - [ApiMember(Name = "SendingUserId", Description = "Sending User Id", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string SendingUserId { get; set; } - - [ApiMember(Name = "EnabledLibraries", Description = "EnabledLibraries", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string EnabledLibraries { get; set; } - - [ApiMember(Name = "EnabledChannels", Description = "EnabledChannels", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string EnabledChannels { get; set; } - - [ApiMember(Name = "EnableLiveTv", Description = "EnableLiveTv", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public bool EnableLiveTv { get; set; } - } - - - [Route("/Connect/Pending", "GET", Summary = "Creates a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class GetPendingGuests : IReturn> - { - } - - - [Route("/Connect/Pending", "DELETE", Summary = "Deletes a Connect link for a user")] - [Authenticated(Roles = "Admin")] - public class DeleteAuthorization : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Authorization Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Connect/Exchange", "GET", Summary = "Gets the corresponding local user from a connect user id")] - [Authenticated] - public class GetLocalUser : IReturn - { - [ApiMember(Name = "ConnectUserId", Description = "ConnectUserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ConnectUserId { get; set; } - } - - public class ConnectService : BaseApiService - { - private readonly IConnectManager _connectManager; - private readonly ISessionManager _sessionManager; - private readonly IAuthorizationContext _authContext; - - public ConnectService(IConnectManager connectManager, ISessionManager sessionManager, IAuthorizationContext authContext) - { - _connectManager = connectManager; - _sessionManager = sessionManager; - _authContext = authContext; - } - - public object Post(CreateConnectLink request) - { - return _connectManager.LinkUser(request.Id, request.ConnectUsername); - } - - public object Post(CreateConnectInvite request) - { - var enabledLibraries = (request.EnabledLibraries ?? string.Empty) - .Split(',') - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToArray(); - - var enabledChannels = (request.EnabledChannels ?? string.Empty) - .Split(',') - .Where(i => !string.IsNullOrWhiteSpace(i)) - .ToArray(); - - return _connectManager.InviteUser(new ConnectAuthorizationRequest - { - ConnectUserName = request.ConnectUsername, - SendingUserId = request.SendingUserId, - EnabledLibraries = enabledLibraries, - EnabledChannels = enabledChannels, - EnableLiveTv = request.EnableLiveTv - }); - } - - public void Delete(DeleteConnectLink request) - { - var task = _connectManager.RemoveConnect(request.Id); - - Task.WaitAll(task); - } - - public async Task Get(GetPendingGuests request) - { - var result = await _connectManager.GetPendingGuests().ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public void Delete(DeleteAuthorization request) - { - var task = _connectManager.CancelAuthorization(request.Id); - - Task.WaitAll(task); - } - - public async Task Get(GetLocalUser request) - { - var user = await _connectManager.GetLocalUser(request.ConnectUserId).ConfigureAwait(false); - - if (user == null) - { - throw new ResourceNotFoundException(); - } - - var auth = _authContext.GetAuthorizationInfo(Request); - - if (string.IsNullOrWhiteSpace(auth.Client)) - { - return ToOptimizedResult(new ConnectAuthenticationExchangeResult - { - AccessToken = user.ConnectAccessKey, - LocalUserId = user.Id.ToString("N") - }); - } - - var session = await _sessionManager.CreateNewSession(new AuthenticationRequest - { - App = auth.Client, - AppVersion = auth.Version, - DeviceId = auth.DeviceId, - DeviceName = auth.Device, - RemoteEndPoint = Request.RemoteIp, - Username = user.Name, - UserId = user.Id.ToString("N") - - }).ConfigureAwait(false); - - return ToOptimizedResult(new ConnectAuthenticationExchangeResult - { - AccessToken = session.AccessToken, - LocalUserId = session.User.Id - }); - } - } -} diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 909fc0623..dc4e57155 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -791,7 +791,7 @@ namespace MediaBrowser.Api.LiveTv ProviderChannels = providerChannels.Select(i => new NameIdPair { Name = i.Name, - Id = string.IsNullOrWhiteSpace(i.TunerChannelId) ? i.Id : i.TunerChannelId + Id = i.Id }).ToList(), diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 6c8c6b2ab..55ef65311 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -51,7 +51,6 @@ - diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 89d035649..5615649c2 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -1,5 +1,4 @@ -using System; -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.LiveTv; using System.Collections.Generic; using System.Threading; @@ -23,7 +22,7 @@ namespace MediaBrowser.Controller.LiveTv /// Gets the channels. /// /// Task<IEnumerable<ChannelInfo>>. - Task> GetChannels(bool enableCache, CancellationToken cancellationToken); + Task> GetChannels(bool enableCache, CancellationToken cancellationToken); /// /// Gets the tuner infos. /// diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index 7dd92b5e8..62c3e00a8 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -67,12 +67,15 @@ ..\ThirdParty\emby\Emby.Server.Core.dll + + ..\ThirdParty\emby\Emby.Server.Sync.dll + False ..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll - ..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll + ..\packages\NLog.4.4.3\lib\net45\NLog.dll True @@ -83,16 +86,16 @@ ..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll True - - ..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll + + ..\packages\SimpleInjector.3.3.2\lib\net45\SimpleInjector.dll True - ..\packages\SQLitePCLRaw.core.1.1.1\lib\net45\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll True - ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.1\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll True diff --git a/MediaBrowser.Server.Mono/MonoAppHost.cs b/MediaBrowser.Server.Mono/MonoAppHost.cs index 32f6b74ce..e9ded5cd5 100644 --- a/MediaBrowser.Server.Mono/MonoAppHost.cs +++ b/MediaBrowser.Server.Mono/MonoAppHost.cs @@ -4,7 +4,9 @@ using System.Reflection; using Emby.Server.Connect; using Emby.Server.Core; using Emby.Server.Implementations; +using Emby.Server.Sync; using MediaBrowser.Controller.Connect; +using MediaBrowser.Controller.Sync; using MediaBrowser.IsoMounter; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -32,6 +34,11 @@ namespace MediaBrowser.Server.Mono return new ConnectManager(); } + protected override ISyncManager CreateSyncManager() + { + return new SyncManager(); + } + protected override void RestartInternal() { MainClass.Restart(StartupOptions); @@ -53,6 +60,7 @@ namespace MediaBrowser.Server.Mono list.Add(typeof(LinuxIsoManager).Assembly); list.Add(typeof(ConnectManager).Assembly); + list.Add(typeof(SyncManager).Assembly); return list; } diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config index a77a3a506..07c113f3e 100644 --- a/MediaBrowser.Server.Mono/app.config +++ b/MediaBrowser.Server.Mono/app.config @@ -20,6 +20,10 @@ + + + + diff --git a/MediaBrowser.Server.Mono/packages.config b/MediaBrowser.Server.Mono/packages.config index 465a05c08..81da308f5 100644 --- a/MediaBrowser.Server.Mono/packages.config +++ b/MediaBrowser.Server.Mono/packages.config @@ -1,10 +1,10 @@  - + - - - + + + \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config index 4bac6bb70..fae013d6e 100644 --- a/MediaBrowser.ServerApplication/App.config +++ b/MediaBrowser.ServerApplication/App.config @@ -49,7 +49,7 @@ - + diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 656b295c2..50b0aa21f 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -73,12 +73,15 @@ ..\ThirdParty\emby\Emby.Server.Core.dll + + ..\ThirdParty\emby\Emby.Server.Sync.dll + False ..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll - ..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll + ..\packages\NLog.4.4.3\lib\net45\NLog.dll True @@ -89,16 +92,16 @@ ..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll True - - ..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll + + ..\packages\SimpleInjector.3.3.2\lib\net45\SimpleInjector.dll True - ..\packages\SQLitePCLRaw.core.1.1.1\lib\net45\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll True - ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.1\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll True diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index d4753f57a..9f11dc322 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -9,7 +9,9 @@ using Emby.Server.Core; using Emby.Server.Implementations; using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.FFMpeg; +using Emby.Server.Sync; using MediaBrowser.Controller.Connect; +using MediaBrowser.Controller.Sync; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.System; @@ -34,6 +36,11 @@ namespace MediaBrowser.ServerApplication return new ConnectManager(); } + protected override ISyncManager CreateSyncManager() + { + return new SyncManager(); + } + protected override void RestartInternal() { MainStartup.Restart(); @@ -49,6 +56,7 @@ namespace MediaBrowser.ServerApplication } list.Add(typeof(ConnectManager).Assembly); + list.Add(typeof(SyncManager).Assembly); list.Add(GetType().Assembly); return list; diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index 2cebb9aea..68d0a7fda 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -1,10 +1,10 @@  - + - - - + + + \ No newline at end of file -- cgit v1.2.3 From cb5ae69fb950a135f09d42629755cff47e91d5f8 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 23 Feb 2017 23:23:56 -0500 Subject: fixes #2485 - mpeg4 is still trying to be transcoded by VAAPI --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index c6cbc8986..8407948cf 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -87,10 +87,7 @@ namespace MediaBrowser.Controller.MediaEncoding // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) { - if (videoStream.Level == -99 || videoStream.Level == 15) - { - return false; - } + return false; } } return true; -- cgit v1.2.3 From 868f0afc955de78ddb64f2e4193b0d7ba1106c79 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 24 Feb 2017 15:07:26 -0500 Subject: 3.2.3.1 --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- SharedVersion.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 8407948cf..4e1a0a8d7 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1486,7 +1486,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (state.RunTimeTicks.HasValue) + if (state.RunTimeTicks.HasValue && string.IsNullOrWhiteSpace(encodingOptions.HardwareAccelerationType)) { foreach (var stream in state.MediaSource.MediaStreams) { diff --git a/SharedVersion.cs b/SharedVersion.cs index 5e662ebe6..069f6e9fc 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.2.1")] +[assembly: AssemblyVersion("3.2.3.1")] -- cgit v1.2.3