diff options
56 files changed, 866 insertions, 329 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 7c5f7cde4..214fb7488 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Model.Dto; namespace MediaBrowser.Api { @@ -187,7 +188,8 @@ namespace MediaBrowser.Api CancellationTokenSource = cancellationTokenSource, Id = transcodingJobId, PlaySessionId = playSessionId, - LiveStreamId = liveStreamId + LiveStreamId = liveStreamId, + MediaSource = state.MediaSource }; _activeTranscodingJobs.Add(job); @@ -281,6 +283,14 @@ namespace MediaBrowser.Api } } + public TranscodingJob GetTranscodingJob(string playSessionId) + { + lock (_activeTranscodingJobs) + { + return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase)); + } + } + /// <summary> /// Called when [transcode begin request]. /// </summary> @@ -656,6 +666,7 @@ namespace MediaBrowser.Api /// Gets or sets the path. /// </summary> /// <value>The path.</value> + public MediaSourceInfo MediaSource { get; set; } public string Path { get; set; } /// <summary> /// Gets or sets the type. diff --git a/MediaBrowser.Api/ConnectService.cs b/MediaBrowser.Api/ConnectService.cs index 4bcd33d9e..494a6e756 100644 --- a/MediaBrowser.Api/ConnectService.cs +++ b/MediaBrowser.Api/ConnectService.cs @@ -7,6 +7,7 @@ using ServiceStack; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Controller.Session; namespace MediaBrowser.Api { @@ -76,12 +77,12 @@ namespace MediaBrowser.Api public class ConnectService : BaseApiService { private readonly IConnectManager _connectManager; - private readonly IUserManager _userManager; + private readonly ISessionManager _sessionManager; - public ConnectService(IConnectManager connectManager, IUserManager userManager) + public ConnectService(IConnectManager connectManager, ISessionManager sessionManager) { _connectManager = connectManager; - _userManager = userManager; + _sessionManager = sessionManager; } public object Post(CreateConnectLink request) @@ -141,10 +142,33 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException(); } + var auth = AuthorizationContext.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 = user.ConnectAccessKey, - LocalUserId = user.Id.ToString("N") + AccessToken = session.AccessToken, + LocalUserId = session.User.Id }); } } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 4a62da6f6..7ef9a1d5b 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -367,6 +367,8 @@ namespace MediaBrowser.Api.Playback { param += " -crf 23"; } + + param += " -tune zerolatency"; } else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) @@ -1232,7 +1234,7 @@ namespace MediaBrowser.Api.Playback private void StartThrottler(StreamState state, TranscodingJob transcodingJob) { - if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EnableThrottling(state)) { transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager); state.TranscodingThrottler.Start(); @@ -1453,7 +1455,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); } @@ -1847,18 +1850,30 @@ namespace MediaBrowser.Api.Playback var archivable = item as IArchivable; state.IsInputArchive = archivable != null && archivable.IsArchive; - MediaSourceInfo mediaSource; + MediaSourceInfo mediaSource = null; if (string.IsNullOrWhiteSpace(request.LiveStreamId)) { - var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList(); + //TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? + // ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) + // : null; - mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); + //if (currentJob != null) + //{ + // mediaSource = currentJob.MediaSource; + //} - if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + if (mediaSource == null) { - mediaSource = mediaSources.First(); + var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList(); + + mediaSource = string.IsNullOrEmpty(request.MediaSourceId) + ? mediaSources.First() + : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); + + if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase)) + { + mediaSource = mediaSources.First(); + } } } else diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index a0ac96b9d..761b1eb4e 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Api.Playback.Hls throw; } - var waitForSegments = state.SegmentLength >= 10 ? 2 : 3; + var waitForSegments = state.SegmentLength >= 10 ? 2 : (state.SegmentLength > 3 || !isLive ? 3 : 4); await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false); } } @@ -128,10 +128,9 @@ namespace MediaBrowser.Api.Playback.Hls var audioBitrate = state.OutputAudioBitrate ?? 0; var videoBitrate = state.OutputVideoBitrate ?? 0; - var appendBaselineStream = false; var baselineStreamBitrate = 64000; - var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); + var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); @@ -151,9 +150,10 @@ namespace MediaBrowser.Api.Playback.Hls { var text = reader.ReadToEnd(); + text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT"); + var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture); - // ffmpeg pads the reported length by a full second text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase); return text; @@ -161,7 +161,7 @@ namespace MediaBrowser.Api.Playback.Hls } } - private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate) + private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate) { var builder = new StringBuilder(); @@ -175,14 +175,6 @@ namespace MediaBrowser.Api.Playback.Hls var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8"); builder.AppendLine(playlistUrl); - // Low bitrate stream - if (includeBaselineStream) - { - builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + baselineStreamBitrate.ToString(UsCulture)); - playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "-low/stream.m3u8"); - builder.AppendLine(playlistUrl); - } - return builder.ToString(); } diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 27deaf25e..976fed3f0 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -68,8 +68,6 @@ namespace MediaBrowser.Api.Playback.Hls [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] public class GetHlsVideoSegmentLegacy : VideoStreamRequest { - // TODO: Deprecate with new iOS app - public string PlaylistId { get; set; } /// <summary> @@ -113,7 +111,7 @@ namespace MediaBrowser.Api.Playback.Hls var file = request.SegmentId + Path.GetExtension(Request.PathInfo); file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file); - var normalizedPlaylistId = request.PlaylistId.Replace("-low", string.Empty); + var normalizedPlaylistId = request.PlaylistId; var playlistPath = Directory.EnumerateFiles(_config.ApplicationPaths.TranscodingTempPath, "*") .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); 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.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 2e92c4a49..109aa85de 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -73,6 +73,10 @@ namespace MediaBrowser.Api.Playback { get { + if (!RunTimeTicks.HasValue) + { + return 6; + } if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { var userAgent = UserAgent ?? string.Empty; @@ -91,6 +95,10 @@ namespace MediaBrowser.Api.Playback return 6; } + if (!RunTimeTicks.HasValue) + { + return 6; + } return 3; } } diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index fe13e8b21..b07a31a87 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Api.Subtitles { var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); - var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, false).ConfigureAwait(false); + var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false); var builder = new StringBuilder(); diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index ced2dd5a3..7e8823bc0 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -55,7 +55,7 @@ <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath> </Reference> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> - <HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath> + <HintPath>..\packages\NLog.4.3.8\lib\net45\NLog.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="Patterns.Logging"> @@ -65,8 +65,8 @@ <SpecificVersion>False</SpecificVersion> <HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath> </Reference> - <Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> - <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath> + <Reference Include="SimpleInjector, Version=3.2.2.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> + <HintPath>..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="System" /> diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 798e18ef5..ced85780f 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -334,7 +334,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks trigger.Stop(); - TaskManager.QueueScheduledTask(ScheduledTask); + TaskManager.QueueScheduledTask(ScheduledTask, e.Argument); await Task.Delay(1000).ConfigureAwait(false); @@ -390,13 +390,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks try { - var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress); - if (options != null && options.MaxRuntimeMs.HasValue) { CurrentCancellationTokenSource.CancelAfter(options.MaxRuntimeMs.Value); } + var localTask = ScheduledTask.Execute(CurrentCancellationTokenSource.Token, progress); + await localTask.ConfigureAwait(false); status = TaskCompletionStatus.Completed; diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config index 594b4c7c5..f444a3a05 100644 --- a/MediaBrowser.Common.Implementations/packages.config +++ b/MediaBrowser.Common.Implementations/packages.config @@ -2,7 +2,7 @@ <packages> <package id="CommonIO" version="1.0.0.9" targetFramework="net45" /> <package id="morelinq" version="1.4.0" targetFramework="net45" /> - <package id="NLog" version="4.3.6" targetFramework="net45" /> + <package id="NLog" version="4.3.8" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> - <package id="SimpleInjector" version="3.2.0" targetFramework="net45" /> + <package id="SimpleInjector" version="3.2.2" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 2a49168ed..6e0a33620 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -44,6 +44,7 @@ namespace MediaBrowser.Controller.Entities ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); LockedFields = new List<MetadataFields>(); ImageInfos = new List<ItemImageInfo>(); + InheritedTags = new List<string>(); } public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; @@ -254,6 +255,16 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public string ExternalSeriesId + { + get { return this.GetProviderId("ProviderExternalSeriesId"); } + set + { + this.SetProviderId("ProviderExternalSeriesId", value); + } + } + /// <summary> /// Gets or sets the etag. /// </summary> @@ -784,6 +795,9 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public int InheritedParentalRatingValue { get; set; } + [IgnoreDataMember] + public List<string> InheritedTags { get; set; } + /// <summary> /// Gets or sets the critic rating. /// </summary> diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs index cf2f7db64..aee58b445 100644 --- a/MediaBrowser.Controller/Entities/IHasMetadata.cs +++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { @@ -62,5 +63,7 @@ namespace MediaBrowser.Controller.Entities int? GetInheritedParentalRatingValue(); int InheritedParentalRatingValue { get; set; } + List<string> GetInheritedTags(); + List<string> InheritedTags { get; set; } } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index a77d88049..1df77cdc9 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -60,11 +60,8 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Gets the static media source. /// </summary> - /// <param name="item">The item.</param> - /// <param name="mediaSourceId">The media source identifier.</param> - /// <param name="enablePathSubstitution">if set to <c>true</c> [enable path substitution].</param> /// <returns>MediaSourceInfo.</returns> - Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution); + Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken); /// <summary> /// Opens the media source. diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 2d79473f0..12308adda 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -53,6 +53,10 @@ namespace MediaBrowser.Controller.LiveTv /// <value><c>true</c> if [record any channel]; otherwise, <c>false</c>.</value> public bool RecordAnyChannel { get; set; } + public int KeepUpTo { get; set; } + + public bool SkipEpisodesInLibrary { get; set; } + /// <summary> /// Gets or sets a value indicating whether [record new only]. /// </summary> @@ -104,6 +108,7 @@ namespace MediaBrowser.Controller.LiveTv public SeriesTimerInfo() { Days = new List<DayOfWeek>(); + SkipEpisodesInLibrary = true; } } } diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 5d92a212f..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<string>(); + } + /// <summary> /// Id of the recording. /// </summary> @@ -15,7 +21,7 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <value>The series timer identifier.</value> public string SeriesTimerId { get; set; } - + /// <summary> /// ChannelId of the recording. /// </summary> @@ -26,7 +32,7 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <value>The program identifier.</value> public string ProgramId { get; set; } - + /// <summary> /// Name of the recording. /// </summary> @@ -76,11 +82,32 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value> public bool IsPostPaddingRequired { get; set; } - + /// <summary> /// Gets or sets the priority. /// </summary> /// <value>The priority.</value> public int Priority { get; set; } + + + // Program properties + public int? SeasonNumber { get; set; } + /// <summary> + /// Gets or sets the episode number. + /// </summary> + /// <value>The episode number.</value> + public int? EpisodeNumber { get; set; } + public bool IsMovie { get; set; } + public bool IsKids { get; set; } + public bool IsSports { get; set; } + public int? ProductionYear { get; set; } + 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<string> Genres { get; set; } } } diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs index bfd7f928b..362f5b2b9 100644 --- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -4,6 +4,7 @@ namespace MediaBrowser.Controller.Session public class AuthenticationRequest { public string Username { get; set; } + public string UserId { get; set; } public string PasswordSha1 { get; set; } public string PasswordMd5 { get; set; } public string App { get; set; } diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index 0ab41020e..5d2231da8 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -15,6 +15,7 @@ using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using MediaBrowser.Controller.MediaEncoding; using Rssdp; @@ -110,18 +111,6 @@ namespace MediaBrowser.Dlna.Main { var options = _config.GetDlnaConfiguration(); - if (!options.EnableServer && !options.EnablePlayTo && !_config.Configuration.EnableUPnP) - { - if (_ssdpHandlerStarted) - { - // Sat/ip live tv depends on device discovery, as well as hd homerun detection - // In order to allow this to be disabled, we need a modular way of knowing if there are - // any parts of the system that are dependant on it - // DisposeSsdpHandler(); - } - return; - } - if (!_ssdpHandlerStarted) { StartSsdpHandler(); @@ -231,10 +220,12 @@ namespace MediaBrowser.Dlna.Main return; } - var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds*2; - _Publisher.SupportPnpRootDevice = true; + var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds * 2; + _Publisher.SupportPnpRootDevice = false; + + var addresses = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).ToList(); - foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false)) + foreach (var address in addresses) { //if (IPAddress.IsLoopback(address)) //{ @@ -244,6 +235,8 @@ namespace MediaBrowser.Dlna.Main var addressString = address.ToString(); + var udn = (addressString).GetMD5().ToString("N"); + var services = new List<string> { "urn:schemas-upnp-org:device:MediaServer:1", @@ -252,10 +245,10 @@ namespace MediaBrowser.Dlna.Main "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" }; - var udn = (addressString).GetMD5().ToString("N"); - foreach (var fullService in services) { + _logger.Info("Registering publisher for {0} on {1}", fullService, addressString); + var descriptorURI = "/dlna/" + udn + "/description.xml"; var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI); @@ -265,7 +258,7 @@ namespace MediaBrowser.Dlna.Main var deviceTypeNamespace = serviceParts[0].Replace('.', '-'); - _Publisher.AddDevice(new SsdpRootDevice + var device = new SsdpRootDevice { CacheLifetime = TimeSpan.FromSeconds(cacheLength), //How long SSDP clients can cache this info. Location = uri, // Must point to the URL that serves your devices UPnP description document. @@ -275,8 +268,11 @@ namespace MediaBrowser.Dlna.Main FriendlyName = "Emby Server", Manufacturer = "Emby", ModelName = "Emby Server", - Uuid = udn // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. - }); + Uuid = udn + // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. + }; + + _Publisher.AddDevice(device); } } } @@ -344,17 +340,11 @@ namespace MediaBrowser.Dlna.Main { var devices = _Publisher.Devices.ToList(); - Parallel.ForEach(devices, device => + foreach (var device in devices) { - try - { - _Publisher.RemoveDevice(device); - } - catch (Exception ex) - { - _logger.ErrorException("Error sending bye bye", ex); - } - }); + var task = _Publisher.RemoveDevice(device); + Task.WaitAll(task); + } //foreach (var device in devices) //{ // try diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index d958d0e37..6345e2105 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -827,6 +827,7 @@ namespace MediaBrowser.Dlna.PlayTo public string DeviceId { get; set; } public string MediaSourceId { get; set; } + public string LiveStreamId { get; set; } public BaseItem Item { get; set; } public MediaSourceInfo MediaSource { get; set; } @@ -910,6 +911,10 @@ namespace MediaBrowser.Dlna.PlayTo { request.StartPositionTicks = long.Parse(val, CultureInfo.InvariantCulture); } + else if (i == 22) + { + request.LiveStreamId = val; + } } request.Item = string.IsNullOrWhiteSpace(request.ItemId) @@ -920,7 +925,7 @@ namespace MediaBrowser.Dlna.PlayTo request.MediaSource = hasMediaSources == null ? null - : (await mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, false).ConfigureAwait(false)); + : (await mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, request.LiveStreamId, false, CancellationToken.None).ConfigureAwait(false)); return request; } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index a450097fd..24de4e77e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -26,6 +26,30 @@ namespace MediaBrowser.MediaEncoding.Encoder return new Tuple<List<string>, List<string>>(decoders, encoders); } + public bool ValidateVersion(string encoderAppPath) + { + string output = string.Empty; + try + { + output = GetProcessOutput(encoderAppPath, "-version"); + } + catch + { + } + + if (string.IsNullOrWhiteSpace(output)) + { + return false; + } + + if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) + { + return false; + } + + return true; + } + private List<string> GetDecoders(string encoderAppPath) { string output = string.Empty; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a2707aace..d3131eb5a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -82,6 +82,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); private readonly bool _hasExternalEncoder; + private string _originalFFMpegPath; + private string _originalFFProbePath; public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient) { @@ -100,6 +102,8 @@ namespace MediaBrowser.MediaEncoding.Encoder _zipClient = zipClient; FFProbePath = ffProbePath; FFMpegPath = ffMpegPath; + _originalFFProbePath = ffProbePath; + _originalFFMpegPath = ffMpegPath; _hasExternalEncoder = hasExternalEncoder; } @@ -108,11 +112,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { get { - if (_hasExternalEncoder) - { - return "External"; - } - if (string.IsNullOrWhiteSpace(FFMpegPath)) { return null; @@ -177,12 +176,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { ConfigureEncoderPaths(); - if (_hasExternalEncoder) - { - LogPaths(); - return; - } - // If the path was passed in, save it into config now. var encodingOptions = GetEncodingOptions(); var appPath = encodingOptions.EncoderAppPath; @@ -207,11 +200,6 @@ namespace MediaBrowser.MediaEncoding.Encoder public async Task UpdateEncoderPath(string path, string pathType) { - if (_hasExternalEncoder) - { - return; - } - Tuple<string, string> newPaths; if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) @@ -247,6 +235,11 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ResourceNotFoundException("ffprobe not found"); } + if (!ValidateVersion(path)) + { + throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); + } + var config = GetEncodingOptions(); config.EncoderAppPath = path; ConfigurationManager.SaveConfiguration("encoding", config); @@ -254,13 +247,13 @@ namespace MediaBrowser.MediaEncoding.Encoder Init(); } - private void ConfigureEncoderPaths() + private bool ValidateVersion(string path) { - if (_hasExternalEncoder) - { - return; - } + return new EncoderValidator(_logger).ValidateVersion(path); + } + private void ConfigureEncoderPaths() + { var appPath = GetEncodingOptions().EncoderAppPath; if (string.IsNullOrWhiteSpace(appPath)) @@ -308,45 +301,22 @@ namespace MediaBrowser.MediaEncoding.Encoder string encoderPath = null; string probePath = null; - if (TestSystemInstalled("ffmpeg")) - { - encoderPath = "ffmpeg"; - } - if (TestSystemInstalled("ffprobe")) + if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath)) { - probePath = "ffprobe"; + encoderPath = _originalFFMpegPath; + probePath = _originalFFProbePath; } - return new Tuple<string, string>(encoderPath, probePath); - } - - private bool TestSystemInstalled(string app) - { - try + if (string.IsNullOrWhiteSpace(encoderPath)) { - var startInfo = new ProcessStartInfo + if (ValidateVersion("ffmpeg") && ValidateVersion("ffprobe")) { - FileName = app, - Arguments = "-v", - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }; - - using (var process = Process.Start(startInfo)) - { - process.WaitForExit(); + encoderPath = "ffmpeg"; + probePath = "ffprobe"; } - - _logger.Debug("System app installed: " + app); - return true; - } - catch - { - _logger.Debug("System app not installed: " + app); - return false; } + + return new Tuple<string, string>(encoderPath, probePath); } private Tuple<string, string> GetPathsFromDirectory(string path) 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; + } + /// <summary> /// Converts ffprobe stream info to our MediaStream class /// </summary> @@ -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 13d559773..30d1498bf 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -602,33 +602,20 @@ namespace MediaBrowser.Model.Dlna private int GetAudioBitrate(string subProtocol, int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream) { - var defaultBitrate = audioStream == null ? 192000 : audioStream.BitRate ?? 192000; + int defaultBitrate = audioStream == null ? 192000 : audioStream.BitRate ?? 192000; // Reduce the bitrate if we're downmixing if (targetAudioChannels.HasValue && audioStream != null && audioStream.Channels.HasValue && targetAudioChannels.Value < audioStream.Channels.Value) { 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 (string.Equals(subProtocol, "hls", StringComparison.OrdinalIgnoreCase)) - { - 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.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs index 4b88e42f3..9d4bab40c 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs @@ -18,12 +18,16 @@ namespace MediaBrowser.Model.LiveTv /// <value><c>true</c> if [record any time]; otherwise, <c>false</c>.</value> public bool RecordAnyTime { get; set; } + public bool SkipEpisodesInLibrary { get; set; } + /// <summary> /// Gets or sets a value indicating whether [record any channel]. /// </summary> /// <value><c>true</c> if [record any channel]; otherwise, <c>false</c>.</value> public bool RecordAnyChannel { get; set; } + public int KeepUpTo { get; set; } + /// <summary> /// Gets or sets a value indicating whether [record new only]. /// </summary> diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 9c9cf5520..48f8ebb04 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -316,6 +316,13 @@ namespace MediaBrowser.Providers.Manager updateType |= ItemUpdateType.MetadataImport; } + var inheritedTags = item.GetInheritedTags(); + if (!inheritedTags.SequenceEqual(item.InheritedTags, StringComparer.Ordinal)) + { + item.InheritedTags = inheritedTags; + updateType |= ItemUpdateType.MetadataImport; + } + return updateType; } @@ -547,8 +554,6 @@ namespace MediaBrowser.Providers.Manager } catch (Exception ex) { - refreshResult.Failures++; - Logger.ErrorException("Error in {0}", ex, provider.Name); // If a local provider fails, consider that a failure diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs index 1bf4ed6c0..54302f39a 100644 --- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs +++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs @@ -14,6 +14,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Model.Extensions; namespace MediaBrowser.Providers.Movies { @@ -203,7 +204,11 @@ namespace MediaBrowser.Providers.Movies if (ourRelease != null) { var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-"; - movie.OfficialRating = ratingPrefix + ourRelease.certification; + var newRating = ratingPrefix + ourRelease.certification; + + newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase); + + movie.OfficialRating = newRating; } else if (usRelease != null) { diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 2f4605c5c..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 { @@ -68,6 +69,11 @@ namespace MediaBrowser.Server.Implementations.IO lock (_timerLock) { + if (_disposed) + { + return; + } + if (_timer == null) { _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); @@ -131,9 +137,10 @@ namespace MediaBrowser.Server.Implementations.IO private async Task ProcessPathChanges(List<string> paths) { var itemsToRefresh = paths + .Distinct(StringComparer.OrdinalIgnoreCase) .Select(GetAffectedBaseItem) .Where(item => item != null) - .Distinct() + .DistinctBy(i => i.Id) .ToList(); foreach (var p in paths) @@ -287,6 +294,7 @@ namespace MediaBrowser.Server.Implementations.IO if (_timer != null) { _timer.Dispose(); + _timer = null; } } } diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 80364bb55..6e0f9a3c4 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -106,7 +106,14 @@ namespace MediaBrowser.Server.Implementations.IO if (refreshPath) { - ReportFileSystemChanged(path); + try + { + ReportFileSystemChanged(path); + } + catch (Exception ex) + { + Logger.ErrorException("Error in ReportFileSystemChanged for {0}", ex, path); + } } } @@ -397,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/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs index 4a43befed..9b23d5be4 100644 --- a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs +++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs @@ -99,8 +99,9 @@ namespace MediaBrowser.Server.Implementations.Intros IncludeItemTypes = new[] { typeof(Trailer).Name }, TrailerTypes = trailerTypes.ToArray(), SimilarTo = item, - IsPlayed = config.EnableIntrosForWatchedContent ? (bool?) null : false, + IsPlayed = config.EnableIntrosForWatchedContent ? (bool?)null : false, MaxParentalRating = config.EnableIntrosParentalControl ? ratingLevel : null, + BlockUnratedItems = config.EnableIntrosParentalControl ? new[] { UnratedItem.Trailer } : new UnratedItem[] { }, Limit = config.TrailerLimit }); @@ -110,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.Intros Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer, LibraryManager = _libraryManager })); - } + } return GetResult(item, candidates, config); } @@ -197,7 +198,7 @@ namespace MediaBrowser.Server.Implementations.Intros } returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1)); - + return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase); } catch (IOException) diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index bd408c9d3..1f8c77953 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -695,7 +695,7 @@ namespace MediaBrowser.Server.Implementations.Library public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, - Folder parent, + Folder parent, LibraryOptions libraryOptions, string collectionType, IItemResolver[] resolvers) @@ -1490,10 +1490,10 @@ namespace MediaBrowser.Server.Implementations.Library private void AddUserToQuery(InternalItemsQuery query, User user) { - if (query.AncestorIds.Length == 0 && - !query.ParentId.HasValue && - query.ChannelIds.Length == 0 && - query.TopParentIds.Length == 0 && + if (query.AncestorIds.Length == 0 && + !query.ParentId.HasValue && + query.ChannelIds.Length == 0 && + query.TopParentIds.Length == 0 && string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey) && query.ItemIds.Length == 0) { @@ -2552,7 +2552,7 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentNullException("to"); } - var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase); + var newPath = path.Replace(from.Trim(), to.Trim(), StringComparison.OrdinalIgnoreCase); if (!string.Equals(newPath, path)) { diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs index 4a533ff93..c20245a6e 100644 --- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs @@ -221,8 +221,28 @@ namespace MediaBrowser.Server.Implementations.Library } } - public async Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, bool enablePathSubstitution) + public async Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken) { + if (!string.IsNullOrWhiteSpace(liveStreamId)) + { + return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false); + } + //await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + //try + //{ + // var stream = _openStreams.Values.FirstOrDefault(i => string.Equals(i.MediaSource.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); + + // if (stream != null) + // { + // return stream.MediaSource; + // } + //} + //finally + //{ + // _liveStreamSemaphore.Release(); + //} + var sources = await GetPlayackMediaSources(item.Id.ToString("N"), null, enablePathSubstitution, new[] { MediaType.Audio, MediaType.Video }, CancellationToken.None).ConfigureAwait(false); 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/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 4c6254330..207486f26 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { if (args.IsDirectory) { - if (args.HasParent<Series>()) + if (args.HasParent<Series>() || args.HasParent<Season>()) { return null; } @@ -80,7 +80,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { return null; } - if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false)) + if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false) || + args.ContainsFileSystemEntryByName("tvshow.nfo")) { return new Series { diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 46351ce95..a3ca54b4a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -22,12 +22,16 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; 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; +using MediaBrowser.Model.FileOrganization; using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV @@ -461,11 +465,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return CreateSeriesTimer(info, cancellationToken); } - public Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken) + public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken) { - info.Id = Guid.NewGuid().ToString("N"); - _timerProvider.Add(info); - return Task.FromResult(info.Id); + timer.Id = Guid.NewGuid().ToString("N"); + + ProgramInfo programInfo = null; + + if (!string.IsNullOrWhiteSpace(timer.ProgramId)) + { + programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); + } + if (programInfo == null) + { + _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); + programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); + } + + if (programInfo != null) + { + RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); + } + + _timerProvider.Add(timer); + return Task.FromResult(timer.Id); } public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken) @@ -519,6 +541,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV instance.RecordAnyChannel = info.RecordAnyChannel; instance.RecordAnyTime = info.RecordAnyTime; instance.RecordNewOnly = info.RecordNewOnly; + instance.SkipEpisodesInLibrary = info.SkipEpisodesInLibrary; + instance.KeepUpTo = info.KeepUpTo; instance.StartDate = info.StartDate; _seriesTimerProvider.Update(instance); @@ -821,6 +845,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (recordingEndDate <= DateTime.UtcNow) { _logger.Warn("Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id); + _timerProvider.Delete(timer); return; } @@ -849,12 +874,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private string GetRecordingPath(TimerInfo timer, ProgramInfo info) + private string GetRecordingPath(TimerInfo timer, out string seriesPath) { var recordPath = RecordingPath; var config = GetConfiguration(); + seriesPath = null; - if (info.IsSeries) + if (timer.IsProgramSeries) { var customRecordingPath = config.SeriesRecordingPath; var allowSubfolder = true; @@ -869,29 +895,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = Path.Combine(recordPath, "Series"); } - var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); - var folderNameWithYear = folderName; - if (info.ProductionYear.HasValue) - { - folderNameWithYear += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; - } + var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); - if (Directory.Exists(Path.Combine(recordPath, folderName))) - { - recordPath = Path.Combine(recordPath, folderName); - } - else - { - recordPath = Path.Combine(recordPath, folderNameWithYear); - } + // Can't use the year here in the folder name because it is the year of the episode, not the series. + recordPath = Path.Combine(recordPath, folderName); - if (info.SeasonNumber.HasValue) + seriesPath = recordPath; + + if (timer.SeasonNumber.HasValue) { - folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); + folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); recordPath = Path.Combine(recordPath, folderName); } } - else if (info.IsMovie) + else if (timer.IsMovie) { var customRecordingPath = config.MovieRecordingPath; var allowSubfolder = true; @@ -906,34 +923,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = Path.Combine(recordPath, "Movies"); } - var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); - if (info.ProductionYear.HasValue) + var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); + if (timer.ProductionYear.HasValue) { - folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; + folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; } recordPath = Path.Combine(recordPath, folderName); } - else if (info.IsKids) + else if (timer.IsKids) { if (config.EnableRecordingSubfolders) { recordPath = Path.Combine(recordPath, "Kids"); } - var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); - if (info.ProductionYear.HasValue) + var folderName = _fileSystem.GetValidFilename(timer.Name).Trim(); + if (timer.ProductionYear.HasValue) { - folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; + folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; } recordPath = Path.Combine(recordPath, folderName); } - else if (info.IsSports) + else if (timer.IsSports) { if (config.EnableRecordingSubfolders) { recordPath = Path.Combine(recordPath, "Sports"); } - recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim()); + recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim()); } else { @@ -941,10 +958,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { recordPath = Path.Combine(recordPath, "Other"); } - recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim()); + recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim()); } - var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts"; + var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer)).Trim() + ".ts"; return Path.Combine(recordPath, recordingFileName); } @@ -956,29 +973,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV throw new ArgumentNullException("timer"); } - ProgramInfo info = null; + ProgramInfo programInfo = null; - if (string.IsNullOrWhiteSpace(timer.ProgramId)) + if (!string.IsNullOrWhiteSpace(timer.ProgramId)) { - _logger.Info("Timer {0} has null programId", timer.Id); + programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); } - else - { - info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); - } - - if (info == null) + if (programInfo == null) { _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); - info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); + programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); } - if (info == null) + if (programInfo != null) { - throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId)); + RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); } - var recordPath = GetRecordingPath(timer, info); + string seriesPath = null; + var recordPath = GetRecordingPath(timer, out seriesPath); var recordingStatus = RecordingStatus.New; var isResourceOpen = false; SemaphoreSlim semaphore = null; @@ -1017,6 +1030,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV result.Item3.Release(); isResourceOpen = false; + + SaveNfo(timer, recordPath, seriesPath); }; var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration); @@ -1062,7 +1077,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Status = RecordingStatus.Completed; _timerProvider.Delete(timer); - OnSuccessfulRecording(info.IsSeries, recordPath); + OnSuccessfulRecording(timer, recordPath); } else if (DateTime.UtcNow < timer.EndDate) { @@ -1130,30 +1145,175 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return new DirectRecorder(_logger, _httpClient, _fileSystem); } - private async void OnSuccessfulRecording(bool isSeries, string path) + private async void OnSuccessfulRecording(TimerInfo timer, string path) { - if (GetConfiguration().EnableAutoOrganize) + if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize) { - if (isSeries) + try { - try + // this is to account for the library monitor holding a lock for additional time after the change is complete. + // ideally this shouldn't be hard-coded + await Task.Delay(30000).ConfigureAwait(false); + + var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); + + var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false); + + if (result.Status == FileSortingStatus.Success) { - // this is to account for the library monitor holding a lock for additional time after the change is complete. - // ideally this shouldn't be hard-coded - await Task.Delay(30000).ConfigureAwait(false); + return; + } + } + catch (Exception ex) + { + _logger.ErrorException("Error processing new recording", ex); + } + } + } + + private void SaveNfo(TimerInfo timer, string recordingPath, string seriesPath) + { + try + { + 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); + } + } - var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); + private void SaveSeriesNfo(TimerInfo timer, string recordingPath, string seriesPath) + { + var nfoPath = Path.Combine(seriesPath, "tvshow.nfo"); + + if (File.Exists(nfoPath)) + { + return; + } - var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false); + 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("tvshow"); + + if (!string.IsNullOrWhiteSpace(timer.Name)) + { + writer.WriteElementString("title", timer.Name); } - catch (Exception ex) + + writer.WriteEndElement(); + writer.WriteEndDocument(); + } + } + } + + 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) { - _logger.ErrorException("Error processing new recording", ex); + 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)); + } + + if (timer.IsSports) + { + AddGenre(timer.Genres, "Sports"); + } + if (timer.IsKids) + { + AddGenre(timer.Genres, "Kids"); + } + + 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 void AddGenre(List<string> genres, string genre) + { + if (!genres.Contains(genre, StringComparer.OrdinalIgnoreCase)) + { + genres.Add(genre); + } + } + private ProgramInfo GetProgramInfoFromCache(string channelId, string programId) { var epgData = GetEpgDataForChannel(channelId); @@ -1176,7 +1336,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers) { var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList(); - + var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); if (registration.IsValid) @@ -1223,7 +1383,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); - if (filterByCurrentRecordings) + if (filterByCurrentRecordings && seriesTimer.SkipEpisodesInLibrary) { allPrograms = allPrograms.Where(i => !IsProgramAlreadyInLibrary(i)); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 9485e0325..67356da2f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -30,19 +30,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Overview = parent.Overview; timer.SeriesTimerId = series.Id; + CopyProgramInfoToTimerInfo(parent, timer); + return timer; } - public static string GetRecordingName(TimerInfo timer, ProgramInfo info) + public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo) { - if (info == null) - { - return timer.ProgramId; - } + timerInfo.SeasonNumber = programInfo.SeasonNumber; + timerInfo.EpisodeNumber = programInfo.EpisodeNumber; + timerInfo.IsMovie = programInfo.IsMovie; + timerInfo.IsKids = programInfo.IsKids; + timerInfo.IsSports = programInfo.IsSports; + timerInfo.ProductionYear = programInfo.ProductionYear; + 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) + { var name = info.Name; - if (info.IsSeries) + if (info.IsProgramSeries) { var addHyphen = true; diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 6d2f79fa0..c3907c045 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -171,22 +171,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var data = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>(); data = data.OrderByDescending(GetSizeOrder).ToList(); - programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true); + 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) ?? // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? // GetProgramImage(ApiUrl, data, "Banner-LOT", false); - - if (!string.IsNullOrWhiteSpace(programEntry.thumbImage)) - { - var b = true; - } - - if (!string.IsNullOrWhiteSpace(programEntry.bannerImage)) - { - var b = true; - } } } @@ -201,10 +191,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings private int GetSizeOrder(ScheduleDirect.ImageData image) { - if (!string.IsNullOrWhiteSpace(image.size)) + if (!string.IsNullOrWhiteSpace(image.height)) { int value; - if (int.TryParse(image.size, out value)) + if (int.TryParse(image.height, out value)) { return value; } @@ -349,9 +339,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings channelNumber = channelNumber.TrimStart('0'); _logger.Debug("Found channel: " + channelNumber + " in Schedules Direct"); - var schChannel = root.stations.FirstOrDefault(item => item.stationID == map.stationID); - AddToChannelPairCache(listingsId, channelNumber, schChannel); + var schChannel = (root.stations ?? new List<ScheduleDirect.Station>()).FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase)); + if (schChannel != null) + { + AddToChannelPairCache(listingsId, channelNumber, schChannel); + } + else + { + AddToChannelPairCache(listingsId, channelNumber, new ScheduleDirect.Station + { + stationID = map.stationID + }); + } } _logger.Info("Added " + GetChannelPairCacheCount(listingsId) + " channels to the dictionary"); @@ -366,8 +366,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings channel.ImageUrl = station.logo.URL; channel.HasImage = true; } - string channelName = station.name; - channel.Name = channelName; + + if (!string.IsNullOrWhiteSpace(station.name)) + { + channel.Name = station.name; + } } else { @@ -512,20 +515,56 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return date; } - private string GetProgramImage(string apiUrl, List<ScheduleDirect.ImageData> images, string category, bool returnDefaultImage) + private string GetProgramImage(string apiUrl, List<ScheduleDirect.ImageData> images, string category, bool returnDefaultImage, int desiredWidth) { string url = null; - var logoIndex = images.FindIndex(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase)); - if (logoIndex == -1) + var matches = images + .Where(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (matches.Count == 0) { if (!returnDefaultImage) { return null; } - logoIndex = 0; + matches = images; + } + + var match = matches.FirstOrDefault(i => + { + if (!string.IsNullOrWhiteSpace(i.width)) + { + int value; + if (int.TryParse(i.width, out value)) + { + return value <= desiredWidth; + } + } + + return false; + }); + + if (match == null) + { + // Get the second lowest quality image, when possible + if (matches.Count > 1) + { + match = matches[matches.Count - 2]; + } + else + { + match = matches.FirstOrDefault(); + } } - var uri = images[logoIndex].uri; + + if (match == null) + { + return null; + } + + var uri = match.uri; if (!string.IsNullOrWhiteSpace(uri)) { @@ -962,7 +1001,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var name = channelNumber; var station = GetStation(listingsId, channelNumber, null); - if (station != null) + if (station != null && !string.IsNullOrWhiteSpace(station.name)) { name = station.name; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index d1d8df2e8..d3549aef5 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -11,6 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.XmlTv.Classes; +using Emby.XmlTv.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -115,7 +116,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings var reader = new XmlTvReader(path, GetLanguage(), null); var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken); - return results.Select(p => new ProgramInfo() + return results.Select(p => GetProgramInfo(p, info)); + } + + private ProgramInfo GetProgramInfo(XmlTvProgram p, ListingsProviderInfo info) + { + var programInfo = new ProgramInfo { ChannelId = p.ChannelId, EndDate = GetDate(p.EndDate), @@ -141,7 +147,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings OfficialRating = p.Rating != null && !String.IsNullOrEmpty(p.Rating.Value) ? p.Rating.Value : null, CommunityRating = p.StarRating.HasValue ? p.StarRating.Value : (float?)null, SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null - }); + }; + + if (programInfo.IsMovie) + { + programInfo.IsSeries = false; + programInfo.EpisodeNumber = null; + programInfo.EpisodeTitle = null; + } + + return programInfo; } private DateTime GetDate(DateTime date) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 683377c61..4681d0a7b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -100,6 +100,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv Priority = info.Priority, RecordAnyChannel = info.RecordAnyChannel, RecordAnyTime = info.RecordAnyTime, + SkipEpisodesInLibrary = info.SkipEpisodesInLibrary, + KeepUpTo = info.KeepUpTo, RecordNewOnly = info.RecordNewOnly, ExternalChannelId = info.ChannelId, ExternalProgramId = info.ProgramId, @@ -308,6 +310,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv Priority = dto.Priority, RecordAnyChannel = dto.RecordAnyChannel, RecordAnyTime = dto.RecordAnyTime, + SkipEpisodesInLibrary = dto.SkipEpisodesInLibrary, + KeepUpTo = dto.KeepUpTo, RecordNewOnly = dto.RecordNewOnly, ProgramId = dto.ExternalProgramId, ChannelId = dto.ExternalChannelId, diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index b3ced55a5..df1ef3962 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -668,6 +668,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.EpisodeTitle = info.EpisodeTitle; item.ExternalId = info.Id; + item.ExternalSeriesId = info.SeriesId; item.Genres = info.Genres; item.IsHD = info.IsHD; item.IsKids = info.IsKids; @@ -903,8 +904,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user); - var list = new List<Tuple<BaseItemDto, string, string>>(); - list.Add(new Tuple<BaseItemDto, string, string>(dto, program.ServiceName, program.ExternalId)); + var list = new List<Tuple<BaseItemDto, string, string, string>>(); + list.Add(new Tuple<BaseItemDto, string, string, string>(dto, program.ServiceName, program.ExternalId, program.ExternalSeriesId)); await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false); @@ -1011,7 +1012,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false); - programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1) + programs = programList.OrderBy(i => i.StartDate.Date) .ThenByDescending(i => GetRecommendationScore(i, user.Id, factorChannelWatchCount)) .ThenBy(i => i.StartDate); @@ -1092,15 +1093,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv return score; } - private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken) + private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string, string>> programs, CancellationToken cancellationToken) { var timers = new Dictionary<string, List<TimerInfo>>(); + var seriesTimers = new Dictionary<string, List<SeriesTimerInfo>>(); foreach (var programTuple in programs) { var program = programTuple.Item1; var serviceName = programTuple.Item2; var externalProgramId = programTuple.Item3; + string externalSeriesId = programTuple.Item4; if (string.IsNullOrWhiteSpace(serviceName)) { @@ -1123,6 +1126,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase)); + var foundSeriesTimer = false; if (timer != null) { @@ -1133,8 +1137,38 @@ namespace MediaBrowser.Server.Implementations.LiveTv { program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, timer.SeriesTimerId) .ToString("N"); + + foundSeriesTimer = true; } } + + if (foundSeriesTimer || string.IsNullOrWhiteSpace(externalSeriesId)) + { + continue; + } + + List<SeriesTimerInfo> seriesTimerList; + if (!seriesTimers.TryGetValue(serviceName, out seriesTimerList)) + { + try + { + var tempTimers = await GetService(serviceName).GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); + seriesTimers[serviceName] = seriesTimerList = tempTimers.ToList(); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting series timer infos", ex); + seriesTimers[serviceName] = seriesTimerList = new List<SeriesTimerInfo>(); + } + } + + var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase)); + + if (seriesTimer != null) + { + program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, seriesTimer.Id) + .ToString("N"); + } } } @@ -1659,7 +1693,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, List<ItemFields> fields, User user = null) { - var recordingTuples = new List<Tuple<BaseItemDto, string, string>>(); + var recordingTuples = new List<Tuple<BaseItemDto, string, string, string>>(); foreach (var tuple in tuples) { @@ -1727,7 +1761,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.ServiceName = serviceName; } - recordingTuples.Add(new Tuple<BaseItemDto, string, string>(dto, serviceName, program.ExternalId)); + recordingTuples.Add(new Tuple<BaseItemDto, string, string, string>(dto, serviceName, program.ExternalId, program.ExternalSeriesId)); } await AddRecordingInfo(recordingTuples, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 7aa9eb1cf..3f6bb140b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -223,8 +223,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } } - var stream = - await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); + var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); if (EnableMediaProbing) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index fd4775938..c5bd648cf 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -319,18 +319,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun videoBitrate = 1000000; } - if (string.IsNullOrWhiteSpace(videoCodec)) + var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); + var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase)); + if (channel != null) { - var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); - var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase)); - if (channel != null) + if (string.IsNullOrWhiteSpace(videoCodec)) { videoCodec = channel.VideoCodec; - audioCodec = channel.AudioCodec; + } + audioCodec = channel.AudioCodec; + if (!videoBitrate.HasValue) + { videoBitrate = (channel.IsHD ?? true) ? 15000000 : 2000000; - audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000; } + audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000; } // normalize @@ -380,7 +383,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun BitRate = audioBitrate } }, - RequiresOpening = false, + RequiresOpening = true, RequiresClosing = false, BufferMs = 0, Container = "ts", diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index e182ad6a5..b25c07fe3 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -69,8 +69,8 @@ <Reference Include="ServiceStack.Api.Swagger"> <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath> </Reference> - <Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> - <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath> + <Reference Include="SimpleInjector, Version=3.2.2.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> + <HintPath>..\packages\SimpleInjector.3.2.2\lib\net45\SimpleInjector.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="SocketHttpListener, Version=1.0.6063.4624, Culture=neutral, processorArchitecture=MSIL"> diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 5ece3dd82..05c282687 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -412,7 +412,8 @@ namespace MediaBrowser.Server.Implementations.Persistence "SeriesId", "SeriesSortName", "PresentationUniqueKey", - "InheritedParentalRatingValue" + "InheritedParentalRatingValue", + "InheritedTags" }; private readonly string[] _mediaStreamSaveColumns = @@ -1459,6 +1460,12 @@ namespace MediaBrowser.Server.Implementations.Persistence } index++; + if (!reader.IsDBNull(index)) + { + item.InheritedTags = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + index++; + return item; } diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index b21fcddd4..f56af5b61 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -307,9 +307,9 @@ namespace MediaBrowser.Server.Implementations.Session } } - private Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId) + private Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId) { - return _mediaSourceManager.GetMediaSource(item, mediaSourceId, false); + return _mediaSourceManager.GetMediaSource(item, mediaSourceId, liveStreamId, false, CancellationToken.None); } /// <summary> @@ -337,7 +337,7 @@ namespace MediaBrowser.Server.Implementations.Session var hasMediaSources = libraryItem as IHasMediaSources; if (hasMediaSources != null) { - mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false); + mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); if (mediaSource != null) { @@ -792,7 +792,7 @@ namespace MediaBrowser.Server.Implementations.Session var hasMediaSources = libraryItem as IHasMediaSources; if (hasMediaSources != null) { - mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId).ConfigureAwait(false); + mediaSource = await GetMediaSource(hasMediaSources, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); } info.Item = GetItemInfo(libraryItem, libraryItem, mediaSource); @@ -1341,8 +1341,19 @@ namespace MediaBrowser.Server.Implementations.Session private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) { - var user = _userManager.Users - .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); + User user = null; + if (!string.IsNullOrWhiteSpace(request.UserId)) + { + var idGuid = new Guid(request.UserId); + user = _userManager.Users + .FirstOrDefault(i => i.Id == idGuid); + } + + if (user == null) + { + user = _userManager.Users + .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); + } if (user != null && !string.IsNullOrWhiteSpace(request.DeviceId)) { diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 94522cd50..5d58aea19 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -7,6 +7,6 @@ <package id="MediaBrowser.Naming" version="1.0.0.55" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
+ <package id="SimpleInjector" version="3.2.2" targetFramework="net45" />
<package id="SocketHttpListener" version="1.0.0.39" targetFramework="net45" />
</packages>
\ No newline at end of file diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index f5419e5cf..4bd2f6c72 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -315,6 +315,8 @@ namespace MediaBrowser.Server.Startup.Common /// </summary> public override async Task RunStartupTasks() { + await PerformPreInitMigrations().ConfigureAwait(false); + if (ServerConfigurationManager.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion && ServerConfigurationManager.Configuration.IsStartupWizardCompleted) { @@ -366,23 +368,21 @@ namespace MediaBrowser.Server.Startup.Common HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; - PerformPreInitMigrations(); - return base.Init(progress); } - private void PerformPreInitMigrations() + private async Task PerformPreInitMigrations() { var migrations = new List<IVersionMigration> { - new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename) + new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename, Logger) }; foreach (var task in migrations) { try { - task.Run(); + await task.Run().ConfigureAwait(false); } catch (Exception ex) { @@ -851,12 +851,12 @@ namespace MediaBrowser.Server.Startup.Common { var prefixes = new List<string> { - "http://"+i+":" + ServerConfigurationManager.Configuration.HttpServerPortNumber + "/" + "http://"+i+":" + HttpPort + "/" }; if (!string.IsNullOrWhiteSpace(CertificatePath)) { - prefixes.Add("https://" + i + ":" + ServerConfigurationManager.Configuration.HttpsPortNumber + "/"); + prefixes.Add("https://" + i + ":" + HttpsPort + "/"); } return prefixes; @@ -873,6 +873,23 @@ namespace MediaBrowser.Server.Startup.Common try { ServerManager.Start(GetUrlPrefixes(), CertificatePath); + return; + } + catch (Exception ex) + { + Logger.ErrorException("Error starting http server", ex); + + if (HttpPort == 8096) + { + throw; + } + } + + HttpPort = 8096; + + try + { + ServerManager.Start(GetUrlPrefixes(), CertificatePath); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs index f0cb9e84e..6bcdcca87 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Server.Startup.Common.Migrations _taskManager = taskManager; } - public void Run() + public async Task Run() { // If a forced migration is required, do that now if (_config.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion) diff --git a/MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs index a6a8c1a35..6ef08fae9 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs @@ -1,8 +1,9 @@ - +using System.Threading.Tasks; + namespace MediaBrowser.Server.Startup.Common.Migrations { public interface IVersionMigration { - void Run(); + Task Run(); } } diff --git a/MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs index 3ad5f577f..cd2122e57 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Configuration; using System.Linq; +using System.Threading.Tasks; namespace MediaBrowser.Server.Startup.Common.Migrations { @@ -13,7 +14,7 @@ namespace MediaBrowser.Server.Startup.Common.Migrations _config = config; } - public void Run() + public async Task Run() { var migrationKey = this.GetType().FullName; var migrationKeyList = _config.Configuration.Migrations.ToList(); diff --git a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs index 4afd5bd34..8483ca904 100644 --- a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs +++ b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs @@ -6,6 +6,7 @@ using MediaBrowser.Common.Implementations.Updates; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Updates; @@ -18,17 +19,19 @@ namespace MediaBrowser.Server.Startup.Common.Migrations private readonly IHttpClient _httpClient; private readonly IJsonSerializer _jsonSerializer; private readonly string _releaseAssetFilename; + private readonly ILogger _logger; - public UpdateLevelMigration(IServerConfigurationManager config, IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, string releaseAssetFilename) + public UpdateLevelMigration(IServerConfigurationManager config, IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, string releaseAssetFilename, ILogger logger) { _config = config; _appHost = appHost; _httpClient = httpClient; _jsonSerializer = jsonSerializer; _releaseAssetFilename = releaseAssetFilename; + _logger = logger; } - public async void Run() + public async Task Run() { var lastVersion = _config.Configuration.LastVersion; var currentVersion = _appHost.ApplicationVersion; @@ -44,9 +47,9 @@ namespace MediaBrowser.Server.Startup.Common.Migrations await CheckVersion(currentVersion, updateLevel, CancellationToken.None).ConfigureAwait(false); } - catch + catch (Exception ex) { - + _logger.ErrorException("Error in update migration", ex); } } @@ -109,10 +112,13 @@ namespace MediaBrowser.Server.Startup.Common.Migrations private Version ParseVersion(string versionString) { - var parts = versionString.Split('.'); - if (parts.Length == 3) + if (!string.IsNullOrWhiteSpace(versionString)) { - versionString += ".0"; + var parts = versionString.Split('.'); + if (parts.Length == 3) + { + versionString += ".0"; + } } Version version; diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 5d4fba32d..90e688733 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -33,6 +33,7 @@ namespace MediaBrowser.ServerApplication private static ILogger _logger; private static bool _isRunningAsService = false; + private static bool _canRestartService = false; private static bool _appHostDisposed; [DllImport("kernel32.dll", SetLastError = true)] @@ -68,6 +69,11 @@ namespace MediaBrowser.ServerApplication var options = new StartupOptions(); _isRunningAsService = options.ContainsOption("-service"); + if (_isRunningAsService) + { + _canRestartService = CanRestartWindowsService(); + } + var currentProcess = Process.GetCurrentProcess(); var applicationPath = currentProcess.MainModule.FileName; @@ -261,7 +267,14 @@ namespace MediaBrowser.ServerApplication { get { - return !_isRunningAsService; + if (_isRunningAsService) + { + return _canRestartService; + } + else + { + return true; + } } } @@ -273,7 +286,14 @@ namespace MediaBrowser.ServerApplication { get { - return !_isRunningAsService; + if (_isRunningAsService) + { + return _canRestartService; + } + else + { + return true; + } } } @@ -614,7 +634,11 @@ namespace MediaBrowser.ServerApplication { DisposeAppHost(); - if (!_isRunningAsService) + if (_isRunningAsService) + { + RestartWindowsService(); + } + else { //_logger.Info("Hiding server notify icon"); //_serverNotifyIcon.Visible = false; @@ -669,6 +693,47 @@ namespace MediaBrowser.ServerApplication } } + private static void RestartWindowsService() + { + _logger.Info("Restarting background service"); + + var startInfo = new ProcessStartInfo + { + FileName = "cmd.exe", + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + Verb = "runas", + ErrorDialog = false, + Arguments = String.Format("/c sc stop {0} & sc start {0}", BackgroundService.GetExistingServiceName()) + }; + Process.Start(startInfo); + } + + private static bool CanRestartWindowsService() + { + var startInfo = new ProcessStartInfo + { + FileName = "cmd.exe", + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + Verb = "runas", + ErrorDialog = false, + Arguments = String.Format("/c sc query {0}", BackgroundService.GetExistingServiceName()) + }; + using (var process = Process.Start(startInfo)) + { + process.WaitForExit(); + if (process.ExitCode == 0) + { + return true; + } + else + { + return false; + } + } + } + private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger) { // Reference diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 65b91e6f7..0a7a3a6a5 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -83,8 +83,8 @@ <Reference Include="System.Configuration" /> <Reference Include="System.Configuration.Install" /> <Reference Include="System.Core" /> - <Reference Include="System.Data.SQLite, Version=1.0.102.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> - <HintPath>..\packages\System.Data.SQLite.Core.1.0.102.0\lib\net46\System.Data.SQLite.dll</HintPath> + <Reference Include="System.Data.SQLite, Version=1.0.103.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Data.SQLite.Core.1.0.103\lib\net46\System.Data.SQLite.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="System.Drawing" /> @@ -1113,12 +1113,12 @@ <PostBuildEvent> </PostBuildEvent> </PropertyGroup> - <Import Project="..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets')" /> + <Import Project="..\packages\System.Data.SQLite.Core.1.0.103\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.103\build\net46\System.Data.SQLite.Core.targets')" /> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <PropertyGroup> <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> </PropertyGroup> - <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets'))" /> + <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.103\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.103\build\net46\System.Data.SQLite.Core.targets'))" /> </Target> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index a573c6fd9..63d3bab8a 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -3,5 +3,5 @@ <package id="CommonIO" version="1.0.0.9" targetFramework="net45" /> <package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> - <package id="System.Data.SQLite.Core" version="1.0.102.0" targetFramework="net46" /> + <package id="System.Data.SQLite.Core" version="1.0.103" targetFramework="net462" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index e7247a11f..02e8ad6f2 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -346,7 +346,7 @@ namespace MediaBrowser.WebDashboard.Api if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) { - sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem:; connect-src ws: *\">"); + sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem:;\">"); } else { diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs index d57d22d5c..0795b97d2 100644 --- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs @@ -64,21 +64,21 @@ namespace MediaBrowser.XbmcMetadata.Savers writer.WriteElementString("aired", episode.PremiereDate.Value.ToLocalTime().ToString(formatString)); } - if (episode.AirsAfterSeasonNumber.HasValue && episode.AirsAfterSeasonNumber.Value != -1) + if (!episode.ParentIndexNumber.HasValue || episode.ParentIndexNumber.Value == 0) { - writer.WriteElementString("airsafter_season", episode.AirsAfterSeasonNumber.Value.ToString(UsCulture)); - } - if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) - { - writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture)); - } - if (episode.AirsBeforeSeasonNumber.HasValue && episode.AirsBeforeSeasonNumber.Value != -1) - { - writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(UsCulture)); - } + if (episode.AirsAfterSeasonNumber.HasValue && episode.AirsAfterSeasonNumber.Value != -1) + { + writer.WriteElementString("airsafter_season", episode.AirsAfterSeasonNumber.Value.ToString(UsCulture)); + } + if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) + { + writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture)); + } + if (episode.AirsBeforeSeasonNumber.HasValue && episode.AirsBeforeSeasonNumber.Value != -1) + { + writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(UsCulture)); + } - if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0) - { if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) { writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture)); diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index ff52aebce..d4f3df3a5 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -13,8 +13,8 @@ <copyright>Copyright © Emby 2013</copyright> <dependencies> <dependency id="MediaBrowser.Common" version="3.0.658" /> - <dependency id="NLog" version="4.3.6" /> - <dependency id="SimpleInjector" version="3.2.0" /> + <dependency id="NLog" version="4.3.8" /> + <dependency id="SimpleInjector" version="3.2.2" /> </dependencies> </metadata> <files> |
