diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index eee6bfb65..9a3659f07 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1453,7 +1453,8 @@ namespace MediaBrowser.Api.Playback // Make sure we don't request a bitrate higher than the source var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value; - return request.AudioBitRate.Value; + // Don't encode any higher than this + return Math.Min(384000, request.AudioBitRate.Value); //return Math.Min(currentBitrate, request.AudioBitRate.Value); } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 8a14948d2..c7258d72f 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -113,6 +113,8 @@ namespace MediaBrowser.Api.Playback.Hls args += GetGraphicalSubtitleParam(state, codec); } + args += " -flags -global_header"; + return args; } diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 94bc24b9e..ea21ba46e 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -1,10 +1,16 @@ using MediaBrowser.Model.LiveTv; using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.LiveTv { public class TimerInfo { + public TimerInfo() + { + Genres = new List(); + } + /// /// Id of the recording. /// @@ -15,7 +21,7 @@ namespace MediaBrowser.Controller.LiveTv /// /// The series timer identifier. public string SeriesTimerId { get; set; } - + /// /// ChannelId of the recording. /// @@ -26,7 +32,7 @@ namespace MediaBrowser.Controller.LiveTv /// /// The program identifier. public string ProgramId { get; set; } - + /// /// Name of the recording. /// @@ -76,7 +82,7 @@ namespace MediaBrowser.Controller.LiveTv /// /// true if this instance is post padding required; otherwise, false. public bool IsPostPaddingRequired { get; set; } - + /// /// Gets or sets the priority. /// @@ -98,5 +104,10 @@ namespace MediaBrowser.Controller.LiveTv public string EpisodeTitle { get; set; } public DateTime? OriginalAirDate { get; set; } public bool IsProgramSeries { get; set; } + public string HomePageUrl { get; set; } + public float? CommunityRating { get; set; } + public string ShortOverview { get; set; } + public string OfficialRating { get; set; } + public List Genres { get; set; } } } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 700af682b..5805127e6 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -393,6 +393,20 @@ namespace MediaBrowser.MediaEncoding.Probing }; } + private string NormalizeSubtitleCodec(string codec) + { + if ((codec ?? string.Empty).IndexOf("PGS", StringComparison.OrdinalIgnoreCase) != -1) + { + codec = "PGSSUB"; + } + else if ((codec ?? string.Empty).IndexOf("DVD", StringComparison.OrdinalIgnoreCase) != -1) + { + codec = "DVDSUB"; + } + + return codec; + } + /// /// Converts ffprobe stream info to our MediaStream class /// @@ -474,6 +488,7 @@ namespace MediaBrowser.MediaEncoding.Probing else if (string.Equals(streamInfo.codec_type, "subtitle", StringComparison.OrdinalIgnoreCase)) { stream.Type = MediaStreamType.Subtitle; + stream.Codec = NormalizeSubtitleCodec(stream.Codec); } else if (string.Equals(streamInfo.codec_type, "video", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 98bef334b..30d1498bf 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -609,26 +609,13 @@ namespace MediaBrowser.Model.Dlna defaultBitrate = StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3") ? 192000 : 128000; } - if (targetAudioChannels.HasValue) + if (StringHelper.EqualsIgnoreCase(subProtocol, "hls")) { - if (targetAudioChannels.Value >= 5 && (maxTotalBitrate ?? 0) >= 1200000) - { - if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3")) - { - if (StringHelper.EqualsIgnoreCase(subProtocol, "hls")) - { - defaultBitrate = Math.Max(384000, defaultBitrate); - } - else - { - defaultBitrate = Math.Max(448000, defaultBitrate); - } - } - else - { - defaultBitrate = Math.Max(320000, defaultBitrate); - } - } + defaultBitrate = Math.Min(384000, defaultBitrate); + } + else + { + defaultBitrate = Math.Min(448000, defaultBitrate); } int encoderAudioBitrateLimit = int.MaxValue; @@ -647,6 +634,14 @@ namespace MediaBrowser.Model.Dlna } } + if (maxTotalBitrate.HasValue) + { + if (maxTotalBitrate.Value < 640000) + { + defaultBitrate = Math.Min(128000, defaultBitrate); + } + } + return Math.Min(defaultBitrate, encoderAudioBitrateLimit); } diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 8e28aa2a0..3df7a03d4 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations.ScheduledTasks; +using MoreLinq; namespace MediaBrowser.Server.Implementations.IO { @@ -136,9 +137,10 @@ namespace MediaBrowser.Server.Implementations.IO private async Task ProcessPathChanges(List paths) { var itemsToRefresh = paths + .Distinct(StringComparer.OrdinalIgnoreCase) .Select(GetAffectedBaseItem) .Where(item => item != null) - .Distinct() + .DistinctBy(i => i.Id) .ToList(); foreach (var p in paths) diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 8bb40a00e..6e0f9a3c4 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -404,7 +404,20 @@ namespace MediaBrowser.Server.Implementations.IO { Logger.Debug("Changed detected of type " + e.ChangeType + " to " + e.FullPath); - ReportFileSystemChanged(e.FullPath); + var path = e.FullPath; + + // For deletes, use the parent path + if (e.ChangeType == WatcherChangeTypes.Deleted) + { + var parentPath = Path.GetDirectoryName(path); + + if (!string.IsNullOrWhiteSpace(parentPath)) + { + path = parentPath; + } + } + + ReportFileSystemChanged(path); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 3f9475480..d4ebb8457 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers return false; } - if (IgnoreFiles.Any(i => filename.IndexOf("-" + i, StringComparison.OrdinalIgnoreCase) != -1)) + if (IgnoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) { return false; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index b508110cf..2dd23857a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -27,6 +27,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; using CommonIO; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Configuration; @@ -1026,6 +1027,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV result.Item3.Release(); isResourceOpen = false; + + SaveNfo(timer, recordPath, seriesPath); }; var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration); @@ -1071,7 +1074,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Status = RecordingStatus.Completed; _timerProvider.Delete(timer); - OnSuccessfulRecording(timer, recordPath, seriesPath); + OnSuccessfulRecording(timer, recordPath); } else if (DateTime.UtcNow < timer.EndDate) { @@ -1139,7 +1142,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return new DirectRecorder(_logger, _httpClient, _fileSystem); } - private async void OnSuccessfulRecording(TimerInfo timer, string path, string seriesPath) + private async void OnSuccessfulRecording(TimerInfo timer, string path) { if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize) { @@ -1163,15 +1166,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.ErrorException("Error processing new recording", ex); } } - - SaveNfo(timer, path, seriesPath); } private void SaveNfo(TimerInfo timer, string recordingPath, string seriesPath) { - if (timer.IsProgramSeries) + try { - SaveSeriesNfo(timer, recordingPath, seriesPath); + if (timer.IsProgramSeries) + { + SaveSeriesNfo(timer, recordingPath, seriesPath); + } + else if (!timer.IsMovie || timer.IsSports) + { + SaveVideoNfo(timer, recordingPath); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error saving nfo", ex); } } @@ -1209,6 +1221,79 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } + public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; + private void SaveVideoNfo(TimerInfo timer, string recordingPath) + { + var nfoPath = Path.ChangeExtension(recordingPath, ".nfo"); + + if (File.Exists(nfoPath)) + { + return; + } + + using (var stream = _fileSystem.GetFileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + var settings = new XmlWriterSettings + { + Indent = true, + Encoding = Encoding.UTF8, + CloseOutput = false + }; + + using (XmlWriter writer = XmlWriter.Create(stream, settings)) + { + writer.WriteStartDocument(true); + writer.WriteStartElement("movie"); + + if (!string.IsNullOrWhiteSpace(timer.Name)) + { + writer.WriteElementString("title", timer.Name); + } + + writer.WriteElementString("dateadded", DateTime.UtcNow.ToLocalTime().ToString(DateAddedFormat)); + + if (timer.ProductionYear.HasValue) + { + writer.WriteElementString("year", timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)); + } + if (!string.IsNullOrEmpty(timer.OfficialRating)) + { + writer.WriteElementString("mpaa", timer.OfficialRating); + } + + var overview = (timer.Overview ?? string.Empty) + .StripHtml() + .Replace(""", "'"); + + writer.WriteElementString("plot", overview); + writer.WriteElementString("lockdata", true.ToString().ToLower()); + + if (timer.CommunityRating.HasValue) + { + writer.WriteElementString("rating", timer.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); + } + + foreach (var genre in timer.Genres) + { + writer.WriteElementString("genre", genre); + } + + if (!string.IsNullOrWhiteSpace(timer.ShortOverview)) + { + writer.WriteElementString("outline", timer.ShortOverview); + } + + if (!string.IsNullOrWhiteSpace(timer.HomePageUrl)) + { + writer.WriteElementString("website", timer.HomePageUrl); + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + } + } + private ProgramInfo GetProgramInfoFromCache(string channelId, string programId) { var epgData = GetEpgDataForChannel(channelId); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 236439bc5..67356da2f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -46,6 +46,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timerInfo.EpisodeTitle = programInfo.EpisodeTitle; timerInfo.OriginalAirDate = programInfo.OriginalAirDate; timerInfo.IsProgramSeries = programInfo.IsSeries; + + timerInfo.HomePageUrl = programInfo.HomePageUrl; + timerInfo.CommunityRating = programInfo.CommunityRating; + timerInfo.ShortOverview = programInfo.ShortOverview; + timerInfo.OfficialRating = programInfo.OfficialRating; } public static string GetRecordingName(TimerInfo info) diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index c30263388..0bc84a2e5 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -171,7 +171,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var data = images[imageIndex].data ?? new List(); data = data.OrderByDescending(GetSizeOrder).ToList(); - programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 1280); + programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 800); //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false); //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? @@ -536,7 +536,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings } return false; - }) ?? matches.FirstOrDefault(); + }); + + if (match == null) + { + // Get the second lowest quality image, when possible + if (matches.Count > 1) + { + match = matches[matches.Count - 2]; + } + else + { + match = matches.FirstOrDefault(); + } + } if (match == null) {