diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 018d8a92ef..177435b6f5 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -29,12 +29,16 @@ namespace MediaBrowser.Api /// The logger. private ILogger Logger { get; set; } + /// + /// The application paths + /// private readonly IServerApplicationPaths AppPaths; - + /// /// Initializes a new instance of the class. /// /// The logger. + /// The application paths. public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths) { Logger = logger; @@ -52,6 +56,10 @@ namespace MediaBrowser.Api { DeleteEncodedMediaCache(); } + catch (DirectoryNotFoundException) + { + // Don't clutter the log + } catch (IOException ex) { Logger.ErrorException("Error deleting encoded media cache", ex); diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index ddce1ddcd1..62fcbd280e 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using System.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; @@ -51,6 +52,11 @@ namespace MediaBrowser.Api return ResultFactory.GetOptimizedResult(Request, result); } + protected object ToStreamResult(Stream stream, string contentType) + { + return ResultFactory.GetResult(stream, contentType); + } + /// /// To the optimized result using cache. /// diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 80c9313e57..e2280cdc81 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -174,6 +174,13 @@ namespace MediaBrowser.Api.LiveTv { } + [Route("/LiveTv/Recordings/{Id}/Stream", "GET")] + public class GetInternalRecordingStream + { + [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; @@ -364,5 +371,12 @@ namespace MediaBrowser.Api.LiveTv Task.WaitAll(task); } + + public object Get(GetInternalRecordingStream request) + { + var stream = _liveTvManager.GetRecordingStream(request.Id, CancellationToken.None).Result; + + return ToStreamResult(stream.Stream, stream.MimeType); + } } } \ No newline at end of file diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 34ff64e837..158ffe6df6 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -359,7 +359,7 @@ namespace MediaBrowser.Api.Playback if (request.Width.HasValue) { var widthParam = request.Width.Value.ToString(UsCulture); - + return isH264Output ? string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) : string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam); @@ -369,7 +369,7 @@ namespace MediaBrowser.Api.Playback if (request.Height.HasValue) { var heightParam = request.Height.Value.ToString(UsCulture); - + return isH264Output ? string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) : string.Format(" -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam); @@ -379,7 +379,7 @@ namespace MediaBrowser.Api.Playback if (request.MaxWidth.HasValue && (!request.MaxHeight.HasValue || state.VideoStream == null)) { var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture); - + return isH264Output ? string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam) : string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", maxWidthParam, assSubtitleParam); @@ -389,7 +389,7 @@ namespace MediaBrowser.Api.Playback if (request.MaxHeight.HasValue && (!request.MaxWidth.HasValue || state.VideoStream == null)) { var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture); - + return isH264Output ? string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam) : string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam); @@ -890,6 +890,14 @@ namespace MediaBrowser.Api.Playback state.MediaPath = recording.RecordingInfo.Url; state.IsRemote = true; } + else + { + state.MediaPath = string.Format("http://localhost:{0}/mediabrowser/LiveTv/Recordings/{1}/Stream", + ServerConfigurationManager.Configuration.HttpServerPortNumber, + request.Id); + + state.IsRemote = true; + } item = recording; } @@ -899,7 +907,7 @@ namespace MediaBrowser.Api.Playback state.MediaPath = item.Path; state.IsRemote = item.LocationType == LocationType.Remote; - + var video = item as Video; if (video != null) diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 8b18754c03..d7504a86e4 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -124,6 +124,11 @@ namespace MediaBrowser.Controller.Library { var filename = Path.GetFileName(path); + if (string.Equals(path, "specials", StringComparison.OrdinalIgnoreCase)) + { + return 0; + } + // Look for one of the season folder names foreach (var name in SeasonFolderNames) { diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 40cc4bae5c..1d98dc7cf8 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using System.IO; +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using System.Collections.Generic; @@ -153,6 +154,14 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// LiveTvRecording. Task GetInternalRecording(string id, CancellationToken cancellationToken); + + /// + /// Gets the recording stream. + /// + /// The identifier. + /// The cancellation token. + /// Task{Stream}. + Task GetRecordingStream(string id, CancellationToken cancellationToken); /// /// Gets the program. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index 3a6cbf583c..31dbd8e998 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.IO; using System.Threading; using System.Threading.Tasks; @@ -85,7 +84,7 @@ namespace MediaBrowser.Controller.LiveTv /// The channel identifier. /// The cancellation token. /// Task{Stream}. - Task GetChannelImageAsync(string channelId, CancellationToken cancellationToken); + Task GetChannelImageAsync(string channelId, CancellationToken cancellationToken); /// /// Gets the recording image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to RecordingInfo @@ -93,7 +92,7 @@ namespace MediaBrowser.Controller.LiveTv /// The recording identifier. /// The cancellation token. /// Task{ImageResponseInfo}. - Task GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken); + Task GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken); /// /// Gets the program image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ProgramInfo @@ -102,7 +101,7 @@ namespace MediaBrowser.Controller.LiveTv /// The channel identifier. /// The cancellation token. /// Task{ImageResponseInfo}. - Task GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken); + Task GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken); /// /// Gets the recordings asynchronous. @@ -146,7 +145,7 @@ namespace MediaBrowser.Controller.LiveTv /// The recording identifier. /// The cancellation token. /// Task{Stream}. - Task GetRecordingStream(string recordingId, CancellationToken cancellationToken); + Task GetRecordingStream(string recordingId, CancellationToken cancellationToken); /// /// Gets the channel stream. @@ -154,6 +153,6 @@ namespace MediaBrowser.Controller.LiveTv /// The recording identifier. /// The cancellation token. /// Task{Stream}. - Task GetChannelStream(string recordingId, CancellationToken cancellationToken); + Task GetChannelStream(string recordingId, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index babd9f54c5..abacc0c18e 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.LiveTv; namespace MediaBrowser.Controller.LiveTv { @@ -15,13 +16,15 @@ namespace MediaBrowser.Controller.LiveTv public ProgramInfo ProgramInfo { get; set; } + public ChannelType ChannelType { get; set; } + public string ServiceName { get; set; } public override string MediaType { get { - return ProgramInfo.IsVideo ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio; + return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio; } } diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index 671c4b5914..72ecb3ec99 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -127,12 +127,6 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is series; otherwise, false. public bool IsSeries { get; set; } - /// - /// Gets or sets a value indicating whether this instance is video. - /// - /// true if this instance is video; otherwise, false. - public bool IsVideo { get; set; } - /// /// Gets or sets a value indicating whether this instance is live. /// diff --git a/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs similarity index 92% rename from MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs rename to MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs index d454a1ef8d..c3b438c5ee 100644 --- a/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs +++ b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs @@ -2,7 +2,7 @@ namespace MediaBrowser.Controller.LiveTv { - public class ImageResponseInfo + public class StreamResponseInfo { /// /// Gets or sets the stream. diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index a200f610b5..de9aa3b9f9 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -112,7 +112,7 @@ - + diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index db6c21ff15..a94c9958cb 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -134,6 +134,7 @@ + diff --git a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs index 4427e60e43..3e7597e0d6 100644 --- a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs +++ b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs @@ -50,7 +50,12 @@ namespace MediaBrowser.Providers.TV /// true if XXXX, false otherwise public override bool Supports(BaseItem item) { - return item is Episode && item.LocationType != LocationType.Virtual && item.LocationType != LocationType.Remote; + if (item is Episode) + { + var locationType = item.LocationType; + return locationType != LocationType.Virtual && locationType != LocationType.Remote; + } + return false; } /// diff --git a/MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs b/MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs new file mode 100644 index 0000000000..5937842018 --- /dev/null +++ b/MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs @@ -0,0 +1,83 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.TV +{ + class SeasonIndexNumberProvider : BaseMetadataProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The log manager. + /// The configuration manager. + public SeasonIndexNumberProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + : base(logManager, configurationManager) + { + } + + protected override bool RefreshOnVersionChange + { + get + { + return true; + } + } + + protected override string ProviderVersion + { + get + { + return "2"; + } + } + + /// + /// Supportses the specified item. + /// + /// The item. + /// true if XXXX, false otherwise + public override bool Supports(BaseItem item) + { + if (item is Season) + { + var locationType = item.LocationType; + return locationType != LocationType.Virtual && locationType != LocationType.Remote; + } + return false; + } + + /// + /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// + /// The item. + /// if set to true [force]. + /// The provider information. + /// The cancellation token. + /// Task{System.Boolean}. + public override Task FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + { + item.IndexNumber = TVUtils.GetSeasonNumberFromPath(item.Path); + + SetLastRefreshed(item, DateTime.UtcNow, providerInfo); + + return TrueTaskResult; + } + + /// + /// Gets the priority. + /// + /// The priority. + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.First; } + } + } +} diff --git a/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs b/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs index 2127234dc7..9fbcad7c0c 100644 --- a/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs +++ b/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV /// The priority. public override MetadataProviderPriority Priority { - get { return MetadataProviderPriority.First; } + get { return MetadataProviderPriority.Second; } } private const string XmlFileName = "season.xml"; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index c8067beae9..d800b99480 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -152,6 +152,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv return await GetRecording(recording, service.Name, cancellationToken).ConfigureAwait(false); } + public async Task GetRecordingStream(string id, CancellationToken cancellationToken) + { + var service = ActiveService; + + var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); + + var recording = recordings.FirstOrDefault(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id)); + + return await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false); + } + private async Task GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken) { var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name)); @@ -202,7 +213,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return item; } - private async Task GetProgram(ProgramInfo info, string serviceName, CancellationToken cancellationToken) + private async Task GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken) { var isNew = false; @@ -223,6 +234,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv isNew = true; } + item.ChannelType = channelType; item.ProgramInfo = info; item.ServiceName = serviceName; @@ -283,7 +295,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(i => { var programChannelId = i.ProgramInfo.ChannelId; - + var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId, i.ProgramInfo.ChannelName); return guids.Contains(internalProgramChannelId); @@ -366,7 +378,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false); - var programTasks = channelPrograms.Select(program => GetProgram(program, service.Name, cancellationToken)); + var programTasks = channelPrograms.Select(program => GetProgram(program, item.ChannelInfo.ChannelType, service.Name, cancellationToken)); var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false); programs.AddRange(programEntities); @@ -433,7 +445,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } var returnArray = entities - .Select(i => _tvDtoService.GetRecordingInfoDto(i, ActiveService, user)) + .Select(i => _tvDtoService.GetRecordingInfoDto(i, service, user)) .OrderByDescending(i => i.StartDate) .ToArray(); @@ -489,7 +501,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var program = string.IsNullOrEmpty(i.ProgramId) ? null : GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N")); - return _tvDtoService.GetTimerInfoDto(i, ActiveService, program); + return _tvDtoService.GetTimerInfoDto(i, service, program); }) .OrderBy(i => i.StartDate) .ToArray(); @@ -574,18 +586,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken) { - var list = new List(); + var service = ActiveService; - if (ActiveService != null) - { - var timers = await ActiveService.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); + var timers = await service.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false); - var dtos = timers.Select(i => _tvDtoService.GetSeriesTimerInfoDto(i, ActiveService)); - - list.AddRange(dtos); - } - - var returnArray = list.OrderByDescending(i => i.StartDate) + var returnArray = timers + .Select(i => _tvDtoService.GetSeriesTimerInfoDto(i, service)) + .OrderByDescending(i => i.StartDate) .ToArray(); return new QueryResult @@ -606,9 +613,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task GetNewTimerDefaults(CancellationToken cancellationToken) { - var info = await ActiveService.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false); + var service = ActiveService; - var obj = _tvDtoService.GetSeriesTimerInfoDto(info, ActiveService); + var info = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false); + + var obj = _tvDtoService.GetSeriesTimerInfoDto(info, service); obj.Id = obj.ExternalId = string.Empty; diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 057fcc92ec..bdf3476070 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.288 + 3.0.289 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 00f1fc20ff..7f5f649047 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.288 + 3.0.289 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index f624c3e03b..d99d6be97a 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.288 + 3.0.289 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - +