aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs13
-rw-r--r--MediaBrowser.Api/ConnectService.cs34
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs33
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs18
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs4
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs2
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs8
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs2
-rw-r--r--MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj6
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs6
-rw-r--r--MediaBrowser.Common.Implementations/packages.config4
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs14
-rw-r--r--MediaBrowser.Controller/Entities/IHasMetadata.cs3
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceManager.cs5
-rw-r--r--MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs5
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerInfo.cs33
-rw-r--r--MediaBrowser.Controller/Session/AuthenticationRequest.cs1
-rw-r--r--MediaBrowser.Dlna/Main/DlnaEntryPoint.cs50
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToController.cs7
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs24
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs76
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs15
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs35
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs4
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs9
-rw-r--r--MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs7
-rw-r--r--MediaBrowser.Server.Implementations/IO/FileRefresher.cs10
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs24
-rw-r--r--MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs22
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs5
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs282
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs27
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs85
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs19
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs4
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs46
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs3
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs17
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj4
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs9
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs23
-rw-r--r--MediaBrowser.Server.Implementations/packages.config2
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs31
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs2
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/IVersionMigration.cs5
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/MovieDbEpisodeProviderMigration.cs3
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs20
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs71
-rw-r--r--MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj8
-rw-r--r--MediaBrowser.ServerApplication/packages.config2
-rw-r--r--MediaBrowser.WebDashboard/Api/PackageCreator.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs26
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec4
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("&quot;", "'");
+
+ 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>