From 2d7c82d0dd8b51af58a2e187ef30639fdbfd918a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 3 Sep 2016 13:16:36 -0400 Subject: add loopback handler --- .../Library/Resolvers/TV/SeriesResolver.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Library/Resolvers/TV') diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index aefb29f1a6..4c6254330e 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -54,14 +54,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { if (args.IsDirectory) { + if (args.HasParent()) + { + return null; + } + var collectionType = args.GetCollectionType(); if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - if (args.HasParent()) - { - return null; - } - var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path); if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { @@ -76,11 +76,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV { if (string.IsNullOrWhiteSpace(collectionType)) { - if (args.HasParent()) - { - return null; - } - if (args.Parent.IsRoot) { return null; -- cgit v1.2.3 From cc6680f48f76278fecc0efc6b6732effe6fe1e2a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 15 Sep 2016 19:19:27 -0400 Subject: save nfo during recording process --- .../Library/Resolvers/TV/SeriesResolver.cs | 5 +- .../LiveTv/EmbyTV/EmbyTV.cs | 83 ++++++++++++++++++---- .../LiveTv/Listings/SchedulesDirect.cs | 49 ++++++++----- 3 files changed, 102 insertions(+), 35 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Library/Resolvers/TV') diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 4c6254330e..207486f26a 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()) + if (args.HasParent() || args.HasParent()) { 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 3eb032ba88..b508110cf9 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -22,12 +22,15 @@ 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.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.FileOrganization; using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV @@ -867,10 +870,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private string GetRecordingPath(TimerInfo timer) + private string GetRecordingPath(TimerInfo timer, out string seriesPath) { var recordPath = RecordingPath; var config = GetConfiguration(); + seriesPath = null; if (timer.IsProgramSeries) { @@ -892,6 +896,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV // 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); + seriesPath = recordPath; + if (timer.SeasonNumber.HasValue) { folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); @@ -980,7 +986,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); } - var recordPath = GetRecordingPath(timer); + string seriesPath = null; + var recordPath = GetRecordingPath(timer, out seriesPath); var recordingStatus = RecordingStatus.New; var isResourceOpen = false; SemaphoreSlim semaphore = null; @@ -1064,7 +1071,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.Status = RecordingStatus.Completed; _timerProvider.Delete(timer); - OnSuccessfulRecording(timer.IsProgramSeries, recordPath); + OnSuccessfulRecording(timer, recordPath, seriesPath); } else if (DateTime.UtcNow < timer.EndDate) { @@ -1132,26 +1139,72 @@ 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, string seriesPath) { - 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); + // 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 organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); + var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false); - var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false); + if (result.Status == FileSortingStatus.Success) + { + return; } - catch (Exception ex) + } + catch (Exception ex) + { + _logger.ErrorException("Error processing new recording", ex); + } + } + + SaveNfo(timer, path, seriesPath); + } + + private void SaveNfo(TimerInfo timer, string recordingPath, string seriesPath) + { + if (timer.IsProgramSeries) + { + SaveSeriesNfo(timer, recordingPath, seriesPath); + } + } + + private void SaveSeriesNfo(TimerInfo timer, string recordingPath, string seriesPath) + { + var nfoPath = Path.Combine(seriesPath, "tvshow.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("tvshow"); + + if (!string.IsNullOrWhiteSpace(timer.Name)) { - _logger.ErrorException("Error processing new recording", ex); + writer.WriteElementString("title", timer.Name); } + + writer.WriteEndElement(); + writer.WriteEndDocument(); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index d863c45875..c302633889 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(); data = data.OrderByDescending(GetSizeOrder).ToList(); - programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true); + programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 1280); //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; } @@ -517,20 +507,43 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return date; } - private string GetProgramImage(string apiUrl, List images, string category, bool returnDefaultImage) + private string GetProgramImage(string apiUrl, List 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 uri = images[logoIndex].uri; + + var match = matches.FirstOrDefault(i => + { + if (!string.IsNullOrWhiteSpace(i.width)) + { + int value; + if (int.TryParse(i.width, out value)) + { + return value <= desiredWidth; + } + } + + return false; + }) ?? matches.FirstOrDefault(); + + if (match == null) + { + return null; + } + + var uri = match.uri; if (!string.IsNullOrWhiteSpace(uri)) { -- cgit v1.2.3 From daaae69df575f1d7692ba29d6f5ddd4c59516f82 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 9 Oct 2016 03:18:43 -0400 Subject: add playback of in-progress recordings --- MediaBrowser.Api/Library/LibraryService.cs | 32 +----- MediaBrowser.Api/LiveTv/LiveTvService.cs | 29 ++++- .../Channels/IChannelManager.cs | 10 -- .../Entities/AggregateFolder.cs | 6 + MediaBrowser.Controller/Entities/Audio/Audio.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 28 +++-- MediaBrowser.Controller/Entities/Folder.cs | 41 ++++--- MediaBrowser.Controller/Entities/Game.cs | 11 +- MediaBrowser.Controller/Entities/IHasThemeMedia.cs | 23 ---- .../Entities/InternalItemsQuery.cs | 1 + MediaBrowser.Controller/Entities/Movies/Movie.cs | 7 +- MediaBrowser.Controller/Entities/TV/Season.cs | 2 - MediaBrowser.Controller/Entities/UserView.cs | 1 - .../Entities/UserViewBuilder.cs | 16 +-- MediaBrowser.Controller/Entities/Video.cs | 8 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 10 +- MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs | 1 + .../LiveTv/LiveTvAudioRecording.cs | 3 +- .../LiveTv/LiveTvVideoRecording.cs | 3 +- .../MediaBrowser.Controller.csproj | 1 - .../Providers/BaseItemXmlParser.cs | 4 +- .../ContentDirectory/ControlHandler.cs | 61 +--------- MediaBrowser.Dlna/Main/DlnaEntryPoint.cs | 2 +- .../Savers/PersonXmlSaver.cs | 4 +- MediaBrowser.Model/Configuration/DlnaOptions.cs | 1 - MediaBrowser.Model/Dto/BaseItemDto.cs | 1 - MediaBrowser.Model/LiveTv/RecordingStatus.cs | 1 - MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs | 24 ++++ MediaBrowser.Model/Querying/ItemFields.cs | 2 - MediaBrowser.Providers/Manager/ProviderUtils.cs | 8 ++ .../Movies/GenericMovieDbInfo.cs | 12 +- .../People/MovieDbPersonProvider.cs | 6 +- .../People/PersonMetadataService.cs | 8 -- .../Channels/ChannelManager.cs | 92 --------------- .../Dto/DtoService.cs | 14 +-- .../SocketSharp/WebSocketSharpRequest.cs | 2 +- .../Library/LibraryManager.cs | 13 +-- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 2 +- .../Library/Resolvers/Audio/MusicArtistResolver.cs | 13 +-- .../Library/Resolvers/FolderResolver.cs | 1 - .../Library/Resolvers/TV/SeriesResolver.cs | 34 +++--- .../LiveTv/EmbyTV/EmbyTV.cs | 104 +++++++++++++++-- .../LiveTv/LiveTvDtoService.cs | 29 +++++ .../LiveTv/LiveTvManager.cs | 43 ++++--- .../LiveTv/LiveTvMediaSourceProvider.cs | 4 +- .../Persistence/SqliteItemRepository.cs | 124 ++++++++++++++++++++- .../ApplicationHost.cs | 106 ++++++++++++++++++ MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 7 +- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 10 +- 49 files changed, 570 insertions(+), 397 deletions(-) delete mode 100644 MediaBrowser.Controller/Entities/IHasThemeMedia.cs (limited to 'MediaBrowser.Server.Implementations/Library/Resolvers/TV') diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index c6b637f015..e0dfcb9ad2 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -835,14 +835,14 @@ namespace MediaBrowser.Api.Library : (Folder)_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null) + while (item.ThemeSongIds.Count == 0 && request.InheritFromParent && item.GetParent() != null) { item = item.GetParent(); } var dtoOptions = GetDtoOptions(request); - var dtos = GetThemeSongIds(item).Select(_libraryManager.GetItemById) + var dtos = item.ThemeSongIds.Select(_libraryManager.GetItemById) .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); @@ -879,14 +879,14 @@ namespace MediaBrowser.Api.Library : (Folder)_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null) + while (item.ThemeVideoIds.Count == 0 && request.InheritFromParent && item.GetParent() != null) { item = item.GetParent(); } var dtoOptions = GetDtoOptions(request); - var dtos = GetThemeVideoIds(item).Select(_libraryManager.GetItemById) + var dtos = item.ThemeVideoIds.Select(_libraryManager.GetItemById) .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); @@ -901,30 +901,6 @@ namespace MediaBrowser.Api.Library }; } - private List GetThemeVideoIds(BaseItem item) - { - var i = item as IHasThemeMedia; - - if (i != null) - { - return i.ThemeVideoIds; - } - - return new List(); - } - - private List GetThemeSongIds(BaseItem item) - { - var i = item as IHasThemeMedia; - - if (i != null) - { - return i.ThemeSongIds; - } - - return new List(); - } - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); public object Get(GetYearIndex request) diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index ebfced0f41..4217cd6abe 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -677,6 +677,12 @@ namespace MediaBrowser.Api.LiveTv public string Container { get; set; } } + [Route("/LiveTv/LiveRecordings/{Id}/stream", "GET", Summary = "Gets a live tv channel")] + public class GetLiveRecordingFile + { + public string Id { get; set; } + } + public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; @@ -698,13 +704,32 @@ namespace MediaBrowser.Api.LiveTv _fileSystem = fileSystem; } + public async Task Get(GetLiveRecordingFile request) + { + var path = EmbyTV.Current.GetActiveRecordingPath(request.Id); + + if (path == null) + { + throw new FileNotFoundException(); + } + + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + + outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); + + var streamSource = new ProgressiveFileCopier(_fileSystem, path, outputHeaders, null, Logger, CancellationToken.None) + { + AllowEndOfFile = false + }; + return ResultFactory.GetAsyncStreamWriter(streamSource); + } + public async Task Get(GetLiveStreamFile request) { var directStreamProvider = (await EmbyTV.Current.GetLiveStream(request.Id).ConfigureAwait(false)) as IDirectStreamProvider; var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - // TODO: Don't hardcode this - outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file.ts"); + outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); var streamSource = new ProgressiveFileCopier(directStreamProvider, outputHeaders, null, Logger, CancellationToken.None) { diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index 3c46247a72..9177e2d813 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -134,15 +134,5 @@ namespace MediaBrowser.Controller.Channels /// The cancellation token. /// BaseItemDto. Task GetChannelFolder(string userId, CancellationToken cancellationToken); - - /// - /// Downloads the channel item. - /// - /// The item. - /// The destination path. - /// The progress. - /// The cancellation token. - /// Task. - Task DownloadChannelItem(BaseItem item, string destinationPath, IProgress progress, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index efc4502481..9709813dcd 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -34,6 +34,12 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public override bool IsPhysicalRoot + { + get { return true; } + } + public override bool CanDelete() { return false; diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 00657370e1..891fb7d522 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Controller.Entities.Audio [IgnoreDataMember] public override bool SupportsAddingToPlaylist { - get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; } + get { return true; } } [IgnoreDataMember] diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index eb84765fba..cc4a8fdb9c 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -37,6 +37,8 @@ namespace MediaBrowser.Controller.Entities { protected BaseItem() { + ThemeSongIds = new List(); + ThemeVideoIds = new List(); Keywords = new List(); Tags = new List(); Genres = new List(); @@ -45,6 +47,7 @@ namespace MediaBrowser.Controller.Entities LockedFields = new List(); ImageInfos = new List(); InheritedTags = new List(); + ProductionLocations = new List(); } public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; @@ -65,6 +68,9 @@ namespace MediaBrowser.Controller.Entities public static string ThemeSongFilename = "theme"; public static string ThemeVideosFolderName = "backdrops"; + public List ThemeSongIds { get; set; } + public List ThemeVideoIds { get; set; } + [IgnoreDataMember] public string PreferredMetadataCountryCode { get; set; } [IgnoreDataMember] @@ -876,6 +882,7 @@ namespace MediaBrowser.Controller.Entities public List Tags { get; set; } public List Keywords { get; set; } + public List ProductionLocations { get; set; } /// /// Gets or sets the home page URL. @@ -991,7 +998,7 @@ namespace MediaBrowser.Controller.Entities /// Loads the theme songs. /// /// List{Audio.Audio}. - private IEnumerable LoadThemeSongs(List fileSystemChildren, IDirectoryService directoryService) + private static IEnumerable LoadThemeSongs(List fileSystemChildren, IDirectoryService directoryService) { var files = fileSystemChildren.Where(i => i.IsDirectory) .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase)) @@ -1027,7 +1034,7 @@ namespace MediaBrowser.Controller.Entities /// Loads the video backdrops. /// /// List{Video}. - private IEnumerable