aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs12
-rw-r--r--MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs4
-rw-r--r--MediaBrowser.Api/BaseApiService.cs8
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs188
-rw-r--r--MediaBrowser.Api/Images/ImageWriter.cs5
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs10
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs23
-rw-r--r--MediaBrowser.Api/LibraryService.cs37
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvImageService.cs195
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs117
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj1
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs291
-rw-r--r--MediaBrowser.Api/Playback/Hls/AudioHlsService.cs7
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs29
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs9
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs18
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs84
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs90
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs6
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs25
-rw-r--r--MediaBrowser.Api/SearchService.cs5
-rw-r--r--MediaBrowser.Api/UserService.cs4
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs210
-rw-r--r--MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs42
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs11
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs11
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs7
-rw-r--r--MediaBrowser.Common/IO/IFileSystem.cs7
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs4
-rw-r--r--MediaBrowser.Common/Net/IHttpClient.cs7
-rw-r--r--MediaBrowser.Common/Net/MimeTypes.cs5
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs8
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs2
-rw-r--r--MediaBrowser.Controller/Entities/AdultVideo.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs16
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs10
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs142
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs19
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs163
-rw-r--r--MediaBrowser.Controller/Entities/Game.cs24
-rw-r--r--MediaBrowser.Controller/Entities/GameSystem.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs1
-rw-r--r--MediaBrowser.Controller/Entities/IHasImages.cs115
-rw-r--r--MediaBrowser.Controller/Entities/IHasLanguage.cs15
-rw-r--r--MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs21
-rw-r--r--MediaBrowser.Controller/Entities/IHasUserData.cs15
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs18
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs17
-rw-r--r--MediaBrowser.Controller/Entities/MusicVideo.cs6
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs6
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs7
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs20
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs18
-rw-r--r--MediaBrowser.Controller/Kernel.cs30
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs5
-rw-r--r--MediaBrowser.Controller/Library/IUserDataManager.cs2
-rw-r--r--MediaBrowser.Controller/Library/TVUtils.cs9
-rw-r--r--MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/Channel.cs75
-rw-r--r--MediaBrowser.Controller/LiveTv/ChannelInfo.cs17
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs76
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvService.cs46
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvChannel.cs57
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvException.cs18
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs36
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvRecording.cs43
-rw-r--r--MediaBrowser.Controller/LiveTv/ProgramInfo.cs60
-rw-r--r--MediaBrowser.Controller/LiveTv/RecordingInfo.cs76
-rw-r--r--MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs50
-rw-r--r--MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs (renamed from MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs)2
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerInfo.cs35
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj13
-rw-r--r--MediaBrowser.Controller/MediaInfo/FFMpegManager.cs169
-rw-r--r--MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs40
-rw-r--r--MediaBrowser.Controller/Providers/BaseItemXmlParser.cs4
-rw-r--r--MediaBrowser.Controller/Providers/IImageEnhancer.cs8
-rw-r--r--MediaBrowser.Controller/Providers/IImageProvider.cs6
-rw-r--r--MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs13
-rw-r--r--MediaBrowser.Controller/Session/ISessionControllerFactory.cs16
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs9
-rw-r--r--MediaBrowser.Controller/Session/SessionInfo.cs6
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj8
-rw-r--r--MediaBrowser.Model.Portable/packages.config2
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj6
-rw-r--r--MediaBrowser.Model/Configuration/ImageDownloadOptions.cs (renamed from MediaBrowser.Model/Entities/ImageDownloadOptions.cs)18
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs79
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs7
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs11
-rw-r--r--MediaBrowser.Model/Entities/MetadataFields.cs14
-rw-r--r--MediaBrowser.Model/LiveTv/ChannelInfoDto.cs6
-rw-r--r--MediaBrowser.Model/LiveTv/ProgramInfoDto.cs85
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingInfoDto.cs80
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingQuery.cs6
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingStatus.cs9
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs48
-rw-r--r--MediaBrowser.Model/LiveTv/TimerInfoDto.cs56
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj7
-rw-r--r--MediaBrowser.Model/Search/SearchHint.cs6
-rw-r--r--MediaBrowser.Model/Session/SessionInfoDto.cs6
-rw-r--r--MediaBrowser.Model/packages.config2
-rw-r--r--MediaBrowser.Mono.userprefs2
-rw-r--r--MediaBrowser.Providers/ImageFromMediaLocationProvider.cs21
-rw-r--r--MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs4
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj3
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs53
-rw-r--r--MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs4
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs46
-rw-r--r--MediaBrowser.Providers/Movies/FanArtMovieProvider.cs7
-rw-r--r--MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs15
-rw-r--r--MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs20
-rw-r--r--MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs47
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs20
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs22
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbProvider.cs105
-rw-r--r--MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs48
-rw-r--r--MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs10
-rw-r--r--MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs236
-rw-r--r--MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs1
-rw-r--r--MediaBrowser.Providers/Music/FanArtAlbumProvider.cs8
-rw-r--r--MediaBrowser.Providers/Music/FanArtArtistProvider.cs71
-rw-r--r--MediaBrowser.Providers/Music/LastFmImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Music/LastfmHelper.cs16
-rw-r--r--MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs16
-rw-r--r--MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs12
-rw-r--r--MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/Savers/ChannelXmlSaver.cs2
-rw-r--r--MediaBrowser.Providers/Savers/XmlSaverHelpers.cs10
-rw-r--r--MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs7
-rw-r--r--MediaBrowser.Providers/TV/FanArtSeasonProvider.cs5
-rw-r--r--MediaBrowser.Providers/TV/FanArtTVProvider.cs73
-rw-r--r--MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs15
-rw-r--r--MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs8
-rw-r--r--MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs16
-rw-r--r--MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs19
-rw-r--r--MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs83
-rw-r--r--MediaBrowser.Providers/TV/SeasonProviderFromXml.cs2
-rw-r--r--MediaBrowser.Providers/TV/SeriesPostScanTask.cs3
-rw-r--r--MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/TV/TvdbPrescanTask.cs50
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeasonProvider.cs17
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs39
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeriesProvider.cs14
-rw-r--r--MediaBrowser.Providers/VirtualItemImageValidator.cs48
-rw-r--r--MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs22
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs19
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs9
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs49
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserDataManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserManager.cs57
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs99
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs301
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs486
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs156
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs156
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Ratings/au.txt4
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj30
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs18
-rw-r--r--MediaBrowser.Server.Implementations/Providers/ImageSaver.cs41
-rw-r--r--MediaBrowser.Server.Implementations/Providers/ProviderManager.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs32
-rw-r--r--MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs149
-rw-r--r--MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs21
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs37
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs35
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs10
-rw-r--r--MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs17
-rw-r--r--MediaBrowser.Server.Implementations/packages.config2
-rw-r--r--MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs57
-rw-r--r--MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj8
-rw-r--r--MediaBrowser.Server.Mono/Program.cs4
-rw-r--r--MediaBrowser.ServerApplication/App.xaml.cs3
-rw-r--r--MediaBrowser.ServerApplication/ApplicationHost.cs27
-rw-r--r--MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs10
-rw-r--r--MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs33
-rw-r--r--MediaBrowser.ServerApplication/MainWindow.xaml.cs18
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs41
-rw-r--r--MediaBrowser.WebDashboard/ApiClient.js60
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj819
-rw-r--r--MediaBrowser.WebDashboard/packages.config2
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec4
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
-rw-r--r--README.md6
190 files changed, 5350 insertions, 2270 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 018d8a92e..785cc395c 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -29,12 +29,16 @@ namespace MediaBrowser.Api
/// <value>The logger.</value>
private ILogger Logger { get; set; }
+ /// <summary>
+ /// The application paths
+ /// </summary>
private readonly IServerApplicationPaths AppPaths;
-
+
/// <summary>
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
+ /// <param name="appPaths">The application paths.</param>
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);
@@ -88,7 +96,7 @@ namespace MediaBrowser.Api
{
var jobCount = _activeTranscodingJobs.Count;
- Parallel.ForEach(_activeTranscodingJobs, KillTranscodingJob);
+ Parallel.ForEach(_activeTranscodingJobs.ToList(), KillTranscodingJob);
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
if (jobCount > 0)
diff --git a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs
index a8b34b8bd..8f9babd06 100644
--- a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs
+++ b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs
@@ -63,7 +63,9 @@ namespace MediaBrowser.Api
if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version))
{
- SessionManager.LogSessionActivity(client, version, deviceId, device, user);
+ var remoteEndPoint = request.RemoteIp;
+
+ SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user);
}
}
}
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index ddce1ddcd..62fcbd280 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);
+ }
+
/// <summary>
/// To the optimized result using cache.
/// </summary>
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index ba6809f4d..663e1be28 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -1,17 +1,17 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using ServiceStack;
+using ServiceStack.Text.Controller;
+using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -19,8 +19,6 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using ServiceStack.Text.Controller;
-using ServiceStack.Web;
namespace MediaBrowser.Api.Images
{
@@ -39,18 +37,6 @@ namespace MediaBrowser.Api.Images
public string Id { get; set; }
}
- [Route("/LiveTv/Channels/{Id}/Images", "GET")]
- [Api(Description = "Gets information about an item's images")]
- public class GetChannelImageInfos : IReturn<List<ImageInfo>>
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
[Route("/Artists/{Name}/Images", "GET")]
[Route("/Genres/{Name}/Images", "GET")]
[Route("/GameGenres/{Name}/Images", "GET")]
@@ -80,20 +66,7 @@ namespace MediaBrowser.Api.Images
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
-
- [Route("/LiveTv/Channels/{Id}/Images/{Type}", "GET")]
- [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "GET")]
- [Api(Description = "Gets an item image")]
- public class GetChannelImage : ImageRequest
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
+
/// <summary>
/// Class UpdateItemImageIndex
/// </summary>
@@ -270,19 +243,6 @@ namespace MediaBrowser.Api.Images
public Guid Id { get; set; }
}
- [Route("/LiveTv/Channels/{Id}/Images/{Type}", "DELETE")]
- [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "DELETE")]
- [Api(Description = "Deletes an item image")]
- public class DeleteChannelImage : DeleteImageRequest, IReturnVoid
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
/// <summary>
/// Class PostUserImage
/// </summary>
@@ -358,38 +318,13 @@ namespace MediaBrowser.Api.Images
public Stream RequestStream { get; set; }
}
- [Route("/LiveTv/Channels/{Id}/Images/{Type}", "POST")]
- [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "POST")]
- [Api(Description = "Posts an item image")]
- public class PostChannelImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
-
- /// <summary>
- /// The raw Http Request Input Stream
- /// </summary>
- /// <value>The request stream.</value>
- public Stream RequestStream { get; set; }
- }
-
/// <summary>
/// Class ImageService
/// </summary>
public class ImageService : BaseApiService
{
- /// <summary>
- /// The _user manager
- /// </summary>
private readonly IUserManager _userManager;
- /// <summary>
- /// The _library manager
- /// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IApplicationPaths _appPaths;
@@ -400,12 +335,11 @@ namespace MediaBrowser.Api.Images
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
- private readonly ILiveTvManager _liveTv;
/// <summary>
/// Initializes a new instance of the <see cref="ImageService" /> class.
/// </summary>
- public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, ILiveTvManager liveTv)
+ public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor)
{
_userManager = userManager;
_libraryManager = libraryManager;
@@ -414,7 +348,6 @@ namespace MediaBrowser.Api.Images
_itemRepo = itemRepo;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
- _liveTv = liveTv;
}
/// <summary>
@@ -431,15 +364,6 @@ namespace MediaBrowser.Api.Images
return ToOptimizedResult(result);
}
- public object Get(GetChannelImageInfos request)
- {
- var item = _liveTv.GetChannel(request.Id);
-
- var result = GetItemImageInfos(item);
-
- return ToOptimizedResult(result);
- }
-
public object Get(GetItemByNameImageInfos request)
{
var result = GetItemByNameImageInfos(request);
@@ -540,7 +464,7 @@ namespace MediaBrowser.Api.Images
return list;
}
- private ImageInfo GetImageInfo(string path, BaseItem item, int? imageIndex, ImageType type)
+ private ImageInfo GetImageInfo(string path, IHasImages item, int? imageIndex, ImageType type)
{
try
{
@@ -567,13 +491,6 @@ namespace MediaBrowser.Api.Images
}
}
- public object Get(GetChannelImage request)
- {
- var item = _liveTv.GetChannel(request.Id);
-
- return GetImage(request, item);
- }
-
/// <summary>
/// Gets the specified request.
/// </summary>
@@ -659,20 +576,6 @@ namespace MediaBrowser.Api.Images
Task.WaitAll(task);
}
- public void Post(PostChannelImage request)
- {
- var pathInfo = PathInfo.Parse(Request.PathInfo);
- var id = pathInfo.GetArgumentValue<string>(2);
-
- request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(4), true);
-
- var item = _liveTv.GetChannel(id);
-
- var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType);
-
- Task.WaitAll(task);
- }
-
/// <summary>
/// Deletes the specified request.
/// </summary>
@@ -699,15 +602,6 @@ namespace MediaBrowser.Api.Images
Task.WaitAll(task);
}
- public void Delete(DeleteChannelImage request)
- {
- var item = _liveTv.GetChannel(request.Id);
-
- var task = item.DeleteImage(request.Type, request.Index);
-
- Task.WaitAll(task);
- }
-
/// <summary>
/// Deletes the specified request.
/// </summary>
@@ -762,71 +656,9 @@ namespace MediaBrowser.Api.Images
/// <param name="newIndex">The new index.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentException">The change index operation is only applicable to backdrops and screenshots</exception>
- private Task UpdateItemIndex(BaseItem item, ImageType type, int currentIndex, int newIndex)
+ private Task UpdateItemIndex(IHasImages item, ImageType type, int currentIndex, int newIndex)
{
- string file1;
- string file2;
-
- if (type == ImageType.Screenshot)
- {
- var hasScreenshots = (IHasScreenshots)item;
- file1 = hasScreenshots.ScreenshotImagePaths[currentIndex];
- file2 = hasScreenshots.ScreenshotImagePaths[newIndex];
- }
- else if (type == ImageType.Backdrop)
- {
- file1 = item.BackdropImagePaths[currentIndex];
- file2 = item.BackdropImagePaths[newIndex];
- }
- else
- {
- throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
- }
-
- SwapFiles(file1, file2);
-
- // Directory watchers should repeat this, but do a quick refresh first
- return item.RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false);
- }
-
- /// <summary>
- /// Swaps the files.
- /// </summary>
- /// <param name="file1">The file1.</param>
- /// <param name="file2">The file2.</param>
- private void SwapFiles(string file1, string file2)
- {
- Directory.CreateDirectory(_appPaths.TempDirectory);
-
- var temp1 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
- var temp2 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
-
- // Copying over will fail against hidden files
- RemoveHiddenAttribute(file1);
- RemoveHiddenAttribute(file2);
-
- File.Copy(file1, temp1);
- File.Copy(file2, temp2);
-
- File.Copy(temp1, file2, true);
- File.Copy(temp2, file1, true);
-
- File.Delete(temp1);
- File.Delete(temp2);
- }
-
- private void RemoveHiddenAttribute(string path)
- {
- var currentFile = new FileInfo(path);
-
- // This will fail if the file is hidden
- if (currentFile.Exists)
- {
- if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
- {
- currentFile.Attributes &= ~FileAttributes.Hidden;
- }
- }
+ return item.SwapImages(type, currentIndex, newIndex);
}
/// <summary>
@@ -837,7 +669,7 @@ namespace MediaBrowser.Api.Images
/// <returns>System.Object.</returns>
/// <exception cref="ResourceNotFoundException">
/// </exception>
- private object GetImage(ImageRequest request, BaseItem item)
+ public object GetImage(ImageRequest request, IHasImages item)
{
var imagePath = GetImagePath(request, item);
@@ -926,7 +758,7 @@ namespace MediaBrowser.Api.Images
/// <param name="request">The request.</param>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
- private string GetImagePath(ImageRequest request, BaseItem item)
+ private string GetImagePath(ImageRequest request, IHasImages item)
{
var index = request.Index ?? 0;
@@ -941,7 +773,7 @@ namespace MediaBrowser.Api.Images
/// <param name="imageType">Type of the image.</param>
/// <param name="mimeType">Type of the MIME.</param>
/// <returns>Task.</returns>
- private async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
+ public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
{
using (var reader = new StreamReader(inputStream))
{
diff --git a/MediaBrowser.Api/Images/ImageWriter.cs b/MediaBrowser.Api/Images/ImageWriter.cs
index 5d1ee140d..2ace05125 100644
--- a/MediaBrowser.Api/Images/ImageWriter.cs
+++ b/MediaBrowser.Api/Images/ImageWriter.cs
@@ -2,12 +2,11 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using ServiceStack;
+using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
-using ServiceStack.Web;
namespace MediaBrowser.Api.Images
{
@@ -27,7 +26,7 @@ namespace MediaBrowser.Api.Images
/// Gets or sets the item.
/// </summary>
/// <value>The item.</value>
- public BaseItem Item { get; set; }
+ public IHasImages Item { get; set; }
/// <summary>
/// The original image date modified
/// </summary>
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 17a87a861..6c5b279d0 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -146,7 +146,7 @@ namespace MediaBrowser.Api
private async Task UpdateItem(UpdateChannel request)
{
- var item = _liveTv.GetChannel(request.Id);
+ var item = _liveTv.GetInternalChannel(request.Id);
UpdateItem(request, item);
@@ -311,10 +311,12 @@ namespace MediaBrowser.Api
SetProductionLocations(item, request);
- var hasLanguage = item as IHasLanguage;
- if (hasLanguage != null)
+ var hasLang = item as IHasPreferredMetadataLanguage;
+
+ if (hasLang != null)
{
- hasLanguage.Language = request.Language;
+ hasLang.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
+ hasLang.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
}
var hasAspectRatio = item as IHasAspectRatio;
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index 843e39f78..f3d5824da 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Common;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
using ServiceStack;
using System;
using System.Collections.Generic;
@@ -67,7 +68,27 @@ namespace MediaBrowser.Api.Library
/// <returns>System.Object.</returns>
public object Get(GetPhyscialPaths request)
{
- var result = _libraryManager.RootFolder.Children.SelectMany(c => c.ResolveArgs.PhysicalLocations).ToList();
+ var result = _libraryManager.RootFolder.Children
+ .SelectMany(c =>
+ {
+ var locationType = c.LocationType;
+
+ if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
+ {
+ try
+ {
+ return c.ResolveArgs.PhysicalLocations;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error getting ResolveArgs for {0}", ex, c.Path);
+ }
+
+ }
+
+ return new List<string>();
+ })
+ .ToList();
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs
index 7dc8301fe..d9442b63d 100644
--- a/MediaBrowser.Api/LibraryService.cs
+++ b/MediaBrowser.Api/LibraryService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
@@ -32,6 +33,21 @@ namespace MediaBrowser.Api
public string Id { get; set; }
}
+ [Route("/Videos/{Id}/Subtitle/{Index}", "GET")]
+ [Api(Description = "Gets an external subtitle file")]
+ public class GetSubtitle
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+
+ [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
+ public int Index { get; set; }
+ }
+
/// <summary>
/// Class GetCriticReviews
/// </summary>
@@ -240,6 +256,25 @@ namespace MediaBrowser.Api
return ToStaticFileResult(item.Path);
}
+ public object Get(GetSubtitle request)
+ {
+ var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery
+ {
+
+ Index = request.Index,
+ ItemId = new Guid(request.Id),
+ Type = MediaStreamType.Subtitle
+
+ }).FirstOrDefault();
+
+ if (subtitleStream == null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
+ return ToStaticFileResult(subtitleStream.Path);
+ }
+
/// <summary>
/// Gets the specified request.
/// </summary>
diff --git a/MediaBrowser.Api/LiveTv/LiveTvImageService.cs b/MediaBrowser.Api/LiveTv/LiveTvImageService.cs
new file mode 100644
index 000000000..65c4e5e23
--- /dev/null
+++ b/MediaBrowser.Api/LiveTv/LiveTvImageService.cs
@@ -0,0 +1,195 @@
+using MediaBrowser.Api.Images;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using ServiceStack;
+using ServiceStack.Text.Controller;
+using ServiceStack.Web;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.LiveTv
+{
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}", "POST")]
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "POST")]
+ [Api(Description = "Posts an item image")]
+ public class PostChannelImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public string Id { get; set; }
+
+ /// <summary>
+ /// The raw Http Request Input Stream
+ /// </summary>
+ /// <value>The request stream.</value>
+ public Stream RequestStream { get; set; }
+ }
+
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}", "DELETE")]
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "DELETE")]
+ [Api(Description = "Deletes an item image")]
+ public class DeleteChannelImage : DeleteImageRequest, IReturnVoid
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
+ public string Id { get; set; }
+ }
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}", "GET")]
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "GET")]
+ [Api(Description = "Gets an item image")]
+ public class GetChannelImage : ImageRequest
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/Recordings/{Id}/Images/{Type}", "GET")]
+ [Route("/LiveTv/Recordings/{Id}/Images/{Type}/{Index}", "GET")]
+ [Api(Description = "Gets an item image")]
+ public class GetRecordingImage : ImageRequest
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/Programs/{Id}/Images/{Type}", "GET")]
+ [Route("/LiveTv/Programs/{Id}/Images/{Type}/{Index}", "GET")]
+ [Api(Description = "Gets an item image")]
+ public class GetProgramImage : ImageRequest
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Program Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/Channels/{Id}/Images", "GET")]
+ [Api(Description = "Gets information about an item's images")]
+ public class GetChannelImageInfos : IReturn<List<ImageInfo>>
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ public class LiveTvImageService : BaseApiService
+ {
+ private readonly ILiveTvManager _liveTv;
+
+ private readonly IUserManager _userManager;
+
+ private readonly ILibraryManager _libraryManager;
+
+ private readonly IApplicationPaths _appPaths;
+
+ private readonly IProviderManager _providerManager;
+
+ private readonly IItemRepository _itemRepo;
+ private readonly IDtoService _dtoService;
+ private readonly IImageProcessor _imageProcessor;
+
+ public LiveTvImageService(ILiveTvManager liveTv, IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor)
+ {
+ _liveTv = liveTv;
+ _userManager = userManager;
+ _libraryManager = libraryManager;
+ _appPaths = appPaths;
+ _providerManager = providerManager;
+ _itemRepo = itemRepo;
+ _dtoService = dtoService;
+ _imageProcessor = imageProcessor;
+ }
+
+ public object Get(GetChannelImageInfos request)
+ {
+ var item = _liveTv.GetInternalChannel(request.Id);
+
+ var result = GetImageService().GetItemImageInfos(item);
+
+ return ToOptimizedResult(result);
+ }
+
+ public object Get(GetChannelImage request)
+ {
+ var item = _liveTv.GetInternalChannel(request.Id);
+
+ return GetImageService().GetImage(request, item);
+ }
+
+ public object Get(GetRecordingImage request)
+ {
+ var item = _liveTv.GetInternalRecording(request.Id, CancellationToken.None).Result;
+
+ return GetImageService().GetImage(request, item);
+ }
+
+ public object Get(GetProgramImage request)
+ {
+ var item = _liveTv.GetInternalProgram(request.Id);
+
+ return GetImageService().GetImage(request, item);
+ }
+
+ public void Post(PostChannelImage request)
+ {
+ var pathInfo = PathInfo.Parse(Request.PathInfo);
+ var id = pathInfo.GetArgumentValue<string>(2);
+
+ request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(4), true);
+
+ var item = _liveTv.GetInternalChannel(id);
+
+ var task = GetImageService().PostImage(item, request.RequestStream, request.Type, Request.ContentType);
+
+ Task.WaitAll(task);
+ }
+
+ public void Delete(DeleteChannelImage request)
+ {
+ var item = _liveTv.GetInternalChannel(request.Id);
+
+ var task = item.DeleteImage(request.Type, request.Index);
+
+ Task.WaitAll(task);
+ }
+
+ private ImageService GetImageService()
+ {
+ return new ImageService(_userManager, _libraryManager, _appPaths, _providerManager, _itemRepo, _dtoService,
+ _imageProcessor)
+ {
+ ResultFactory = ResultFactory,
+ Request = Request
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 979088ee0..e2280cdc8 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -73,6 +73,14 @@ namespace MediaBrowser.Api.LiveTv
public string Id { get; set; }
}
+ [Route("/LiveTv/Timers/Defaults", "GET")]
+ [Api(Description = "Gets default values for a new timer")]
+ public class GetDefaultTimer : IReturn<SeriesTimerInfoDto>
+ {
+ [ApiMember(Name = "ProgramId", Description = "Optional, to attach default values based on a program.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string ProgramId { get; set; }
+ }
+
[Route("/LiveTv/Timers", "GET")]
[Api(Description = "Gets live tv timers")]
public class GetTimers : IReturn<QueryResult<TimerInfoDto>>
@@ -92,6 +100,18 @@ namespace MediaBrowser.Api.LiveTv
public string UserId { get; set; }
}
+ [Route("/LiveTv/Programs/{Id}", "GET")]
+ [Api(Description = "Gets a live tv program")]
+ public class GetProgram : IReturn<ProgramInfoDto>
+ {
+ [ApiMember(Name = "Id", Description = "Program Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+
+ [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string UserId { get; set; }
+ }
+
+
[Route("/LiveTv/Recordings/{Id}", "DELETE")]
[Api(Description = "Deletes a live tv recording")]
public class DeleteRecording : IReturnVoid
@@ -114,7 +134,13 @@ namespace MediaBrowser.Api.LiveTv
{
}
- [Route("/LiveTv/Timers/{Id}", "GET")]
+ [Route("/LiveTv/Timers", "POST")]
+ [Api(Description = "Creates a live tv timer")]
+ public class CreateTimer : TimerInfoDto, IReturnVoid
+ {
+ }
+
+ [Route("/LiveTv/SeriesTimers/{Id}", "GET")]
[Api(Description = "Gets a live tv series timer")]
public class GetSeriesTimer : IReturn<TimerInfoDto>
{
@@ -128,6 +154,33 @@ namespace MediaBrowser.Api.LiveTv
{
}
+ [Route("/LiveTv/SeriesTimers/{Id}", "DELETE")]
+ [Api(Description = "Cancels a live tv series timer")]
+ public class CancelSeriesTimer : IReturnVoid
+ {
+ [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/SeriesTimers/{Id}", "POST")]
+ [Api(Description = "Updates a live tv series timer")]
+ public class UpdateSeriesTimer : SeriesTimerInfoDto, IReturnVoid
+ {
+ }
+
+ [Route("/LiveTv/SeriesTimers", "POST")]
+ [Api(Description = "Creates a live tv series timer")]
+ public class CreateSeriesTimer : SeriesTimerInfoDto, IReturnVoid
+ {
+ }
+
+ [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;
@@ -252,7 +305,7 @@ namespace MediaBrowser.Api.LiveTv
public object Get(GetSeriesTimers request)
{
var result = _liveTvManager.GetSeriesTimers(new SeriesTimerQuery
- {
+ {
}, CancellationToken.None).Result;
@@ -265,5 +318,65 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(result);
}
+
+ public void Delete(CancelSeriesTimer request)
+ {
+ var task = _liveTvManager.CancelSeriesTimer(request.Id);
+
+ Task.WaitAll(task);
+ }
+
+ public void Post(UpdateSeriesTimer request)
+ {
+ var task = _liveTvManager.UpdateSeriesTimer(request, CancellationToken.None);
+
+ Task.WaitAll(task);
+ }
+
+ public object Get(GetDefaultTimer request)
+ {
+ if (string.IsNullOrEmpty(request.ProgramId))
+ {
+ var result = _liveTvManager.GetNewTimerDefaults(CancellationToken.None).Result;
+
+ return ToOptimizedResult(result);
+ }
+ else
+ {
+ var result = _liveTvManager.GetNewTimerDefaults(request.ProgramId, CancellationToken.None).Result;
+
+ return ToOptimizedResult(result);
+ }
+ }
+
+ public object Get(GetProgram request)
+ {
+ var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
+
+ var result = _liveTvManager.GetProgram(request.Id, CancellationToken.None, user).Result;
+
+ return ToOptimizedResult(result);
+ }
+
+ public void Post(CreateSeriesTimer request)
+ {
+ var task = _liveTvManager.CreateSeriesTimer(request, CancellationToken.None);
+
+ Task.WaitAll(task);
+ }
+
+ public void Post(CreateTimer request)
+ {
+ var task = _liveTvManager.CreateTimer(request, CancellationToken.None);
+
+ 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/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index bae3716bc..0732ee00c 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -90,6 +90,7 @@
<Compile Include="Library\LibraryHelpers.cs" />
<Compile Include="Library\LibraryService.cs" />
<Compile Include="Library\LibraryStructureService.cs" />
+ <Compile Include="LiveTv\LiveTvImageService.cs" />
<Compile Include="LiveTv\LiveTvService.cs" />
<Compile Include="LocalizationService.cs" />
<Compile Include="MoviesService.cs" />
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 90996296d..c04648c37 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -1,13 +1,14 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -32,7 +33,7 @@ namespace MediaBrowser.Api.Playback
/// Gets or sets the application paths.
/// </summary>
/// <value>The application paths.</value>
- protected IServerApplicationPaths ApplicationPaths { get; private set; }
+ protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
/// <summary>
/// Gets or sets the user manager.
@@ -62,21 +63,26 @@ namespace MediaBrowser.Api.Playback
protected IFileSystem FileSystem { get; private set; }
protected IItemRepository ItemRepository { get; private set; }
+ protected ILiveTvManager LiveTvManager { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
- /// <param name="appPaths">The app paths.</param>
+ /// <param name="serverConfig">The server configuration.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="isoManager">The iso manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
- protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
+ /// <param name="dtoService">The dto service.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="itemRepository">The item repository.</param>
+ protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
{
+ LiveTvManager = liveTvManager;
ItemRepository = itemRepository;
FileSystem = fileSystem;
DtoService = dtoService;
- ApplicationPaths = appPaths;
+ ServerConfigurationManager = serverConfig;
UserManager = userManager;
LibraryManager = libraryManager;
IsoManager = isoManager;
@@ -105,7 +111,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected virtual string GetOutputFileExtension(StreamState state)
{
- return Path.GetExtension(state.Url);
+ return Path.GetExtension(state.RequestedUrl);
}
/// <summary>
@@ -115,7 +121,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected virtual string GetOutputFilePath(StreamState state)
{
- var folder = ApplicationPaths.EncodedMediaCachePath;
+ var folder = ServerConfigurationManager.ApplicationPaths.EncodedMediaCachePath;
var outputFileExtension = GetOutputFileExtension(state);
@@ -182,7 +188,7 @@ namespace MediaBrowser.Api.Playback
{
var args = string.Empty;
- if (state.Item.LocationType == LocationType.Remote)
+ if (state.IsRemote || !state.HasMediaStreams)
{
return string.Empty;
}
@@ -191,6 +197,10 @@ namespace MediaBrowser.Api.Playback
{
args += string.Format("-map 0:{0}", state.VideoStream.Index);
}
+ else if (!state.HasMediaStreams)
+ {
+ args += string.Format("-map 0:{0}", 0);
+ }
else
{
args += "-map -0:v";
@@ -200,6 +210,10 @@ namespace MediaBrowser.Api.Playback
{
args += string.Format(" -map 0:{0}", state.AudioStream.Index);
}
+ else if (!state.HasMediaStreams)
+ {
+ args += string.Format(" -map 0:{0}", 1);
+ }
else
{
@@ -247,6 +261,64 @@ namespace MediaBrowser.Api.Playback
}
/// <summary>
+ /// Gets the number of threads.
+ /// </summary>
+ /// <returns>System.Int32.</returns>
+ /// <exception cref="System.Exception">Unrecognized EncodingQuality value.</exception>
+ protected int GetNumberOfThreads()
+ {
+ var quality = ServerConfigurationManager.Configuration.EncodingQuality;
+
+ switch (quality)
+ {
+ case EncodingQuality.Auto:
+ return 0;
+ case EncodingQuality.HighSpeed:
+ return 2;
+ case EncodingQuality.HighQuality:
+ return 2;
+ case EncodingQuality.MaxQuality:
+ return 0;
+ default:
+ throw new Exception("Unrecognized EncodingQuality value.");
+ }
+ }
+
+ /// <summary>
+ /// Gets the video bitrate to specify on the command line
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="videoCodec">The video codec.</param>
+ /// <returns>System.String.</returns>
+ protected string GetVideoQualityParam(StreamState state, string videoCodec)
+ {
+ var args = string.Empty;
+
+ // webm
+ if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase))
+ {
+ args = "-speed 16 -quality good -profile:v 0 -slices 8";
+ }
+
+ // asf/wmv
+ else if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase))
+ {
+ args = "-g 100 -qmax 15";
+ }
+
+ else if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ args = "-preset superfast";
+ }
+ else if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ args = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
+ }
+
+ return args.Trim();
+ }
+
+ /// <summary>
/// If we're going to put a fixed size on the command line, this will calculate it
/// </summary>
/// <param name="state">The state.</param>
@@ -268,14 +340,17 @@ namespace MediaBrowser.Api.Playback
string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
{
- assSubtitleParam = GetTextSubtitleParam((Video)state.Item, state.SubtitleStream, request.StartTimeTicks, performTextSubtitleConversion);
+ assSubtitleParam = GetTextSubtitleParam(state, request.StartTimeTicks, performTextSubtitleConversion);
}
}
// If fixed dimensions were supplied
if (request.Width.HasValue && request.Height.HasValue)
{
- return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", request.Width.Value, request.Height.Value, assSubtitleParam);
+ var widthParam = request.Width.Value.ToString(UsCulture);
+ var heightParam = request.Height.Value.ToString(UsCulture);
+
+ return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
}
var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
@@ -283,33 +358,41 @@ namespace MediaBrowser.Api.Playback
// If a fixed width was requested
if (request.Width.HasValue)
{
+ var widthParam = request.Width.Value.ToString(UsCulture);
+
return isH264Output ?
- string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", request.Width.Value, assSubtitleParam) :
- string.Format(" -vf \"scale={0}:-1{1}\"", request.Width.Value, assSubtitleParam);
+ string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) :
+ string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam);
}
// If a fixed height was requested
if (request.Height.HasValue)
{
+ var heightParam = request.Height.Value.ToString(UsCulture);
+
return isH264Output ?
- string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", request.Height.Value, assSubtitleParam) :
- string.Format(" -vf \"scale=-1:{0}{1}\"", request.Height.Value, assSubtitleParam);
+ string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) :
+ string.Format(" -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam);
}
// If a max width was requested
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}\"", request.MaxWidth.Value, assSubtitleParam) :
- string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", request.MaxWidth.Value, assSubtitleParam);
+ 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);
}
// If a max height was requested
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}\"", request.MaxHeight.Value, assSubtitleParam) :
- string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", request.MaxHeight.Value, assSubtitleParam);
+ 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);
}
if (state.VideoStream == null)
@@ -329,7 +412,10 @@ namespace MediaBrowser.Api.Playback
// If we're encoding with libx264, it can't handle odd numbered widths or heights, so we'll have to fix that
if (isH264Output)
{
- return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", outputSize.Width, outputSize.Height, assSubtitleParam);
+ var widthParam = outputSize.Width.ToString(UsCulture);
+ var heightParam = outputSize.Height.ToString(UsCulture);
+
+ return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
}
// Otherwise use -vf scale since ffmpeg will ensure internally that the aspect ratio is preserved
@@ -339,14 +425,14 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the text subtitle param.
/// </summary>
- /// <param name="video">The video.</param>
- /// <param name="subtitleStream">The subtitle stream.</param>
+ /// <param name="state">The state.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
- protected string GetTextSubtitleParam(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
+ protected string GetTextSubtitleParam(StreamState state, long? startTimeTicks, bool performConversion)
{
- var path = subtitleStream.IsExternal ? GetConvertedAssPath(video, subtitleStream, startTimeTicks, performConversion) : GetExtractedAssPath(video, subtitleStream, startTimeTicks, performConversion);
+ var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, startTimeTicks, performConversion) :
+ GetExtractedAssPath(state, startTimeTicks, performConversion);
if (string.IsNullOrEmpty(path))
{
@@ -359,22 +445,21 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the extracted ass path.
/// </summary>
- /// <param name="video">The video.</param>
- /// <param name="subtitleStream">The subtitle stream.</param>
+ /// <param name="state">The state.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
- private string GetExtractedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
+ private string GetExtractedAssPath(StreamState state, long? startTimeTicks, bool performConversion)
{
var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
- var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
+ var path = FFMpegManager.Instance.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream, offset, ".ass");
if (performConversion)
{
InputType type;
- var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, null, state.PlayableStreamFileNames, out type);
try
{
@@ -382,7 +467,7 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath);
- var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, subtitleStream.Index, offset, path, CancellationToken.None);
+ var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, offset, path, CancellationToken.None);
Task.WaitAll(task);
}
@@ -398,22 +483,16 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the converted ass path.
/// </summary>
- /// <param name="video">The video.</param>
+ /// <param name="mediaPath">The media path.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
- private string GetConvertedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
+ private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
{
- // If it's already ass, no conversion neccessary
- //if (string.Equals(Path.GetExtension(subtitleStream.Path), ".ass", StringComparison.OrdinalIgnoreCase))
- //{
- // return subtitleStream.Path;
- //}
-
var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
- var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
+ var path = FFMpegManager.Instance.GetSubtitleCachePath(mediaPath, subtitleStream, offset, ".ass");
if (performConversion)
{
@@ -461,25 +540,15 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the probe size argument.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="mediaPath">The media path.</param>
+ /// <param name="isVideo">if set to <c>true</c> [is video].</param>
+ /// <param name="videoType">Type of the video.</param>
+ /// <param name="isoType">Type of the iso.</param>
/// <returns>System.String.</returns>
- protected string GetProbeSizeArgument(BaseItem item)
+ protected string GetProbeSizeArgument(string mediaPath, bool isVideo, VideoType? videoType, IsoType? isoType)
{
- var type = InputType.AudioFile;
-
- if (item is Audio)
- {
- type = MediaEncoderHelpers.GetInputType(item.Path, null, null);
- }
- else
- {
- var video = item as Video;
-
- if (video != null)
- {
- type = MediaEncoderHelpers.GetInputType(item.Path, video.VideoType, video.IsoType);
- }
- }
+ var type = !isVideo ? MediaEncoderHelpers.GetInputType(mediaPath, null, null) :
+ MediaEncoderHelpers.GetInputType(mediaPath, videoType, isoType);
return MediaEncoder.GetProbeSizeArgument(type);
}
@@ -589,22 +658,19 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the input argument.
/// </summary>
- /// <param name="item">The item.</param>
- /// <param name="isoMount">The iso mount.</param>
+ /// <param name="state">The state.</param>
/// <returns>System.String.</returns>
- protected string GetInputArgument(BaseItem item, IIsoMount isoMount)
+ protected string GetInputArgument(StreamState state)
{
var type = InputType.AudioFile;
- var inputPath = new[] { item.Path };
-
- var video = item as Video;
+ var inputPath = new[] { state.MediaPath };
- if (video != null)
+ if (state.IsInputVideo)
{
- if (!(video.VideoType == VideoType.Iso && isoMount == null))
+ if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
{
- inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
+ inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, state.IsoMount, state.PlayableStreamFileNames, out type);
}
}
@@ -623,11 +689,9 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath);
- var video = state.Item as Video;
-
- if (video != null && video.VideoType == VideoType.Iso && video.IsoType.HasValue && IsoManager.CanMount(video.Path))
+ if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
{
- state.IsoMount = await IsoManager.Mount(video.Path, CancellationToken.None).ConfigureAwait(false);
+ state.IsoMount = await IsoManager.Mount(state.MediaPath, CancellationToken.None).ConfigureAwait(false);
}
var process = new Process
@@ -652,11 +716,11 @@ namespace MediaBrowser.Api.Playback
EnableRaisingEvents = true
};
- ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, video != null, state.Request.StartTimeTicks, state.Item.Path, state.Request.DeviceId);
+ ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.IsInputVideo, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
Logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
- var logFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid() + ".txt");
+ var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid() + ".txt");
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
@@ -691,13 +755,13 @@ namespace MediaBrowser.Api.Playback
}
// Allow a small amount of time to buffer a little
- if (state.Item is Video)
+ if (state.IsInputVideo)
{
await Task.Delay(500).ConfigureAwait(false);
}
// This is arbitrary, but add a little buffer time when internet streaming
- if (state.Item.LocationType == LocationType.Remote)
+ if (state.IsRemote)
{
await Task.Delay(4000).ConfigureAwait(false);
}
@@ -724,11 +788,11 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the user agent param.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="path">The path.</param>
/// <returns>System.String.</returns>
- protected string GetUserAgentParam(BaseItem item)
+ protected string GetUserAgentParam(string path)
{
- var useragent = GetUserAgent(item);
+ var useragent = GetUserAgent(path);
if (!string.IsNullOrEmpty(useragent))
{
@@ -741,11 +805,16 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the user agent.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="path">The path.</param>
/// <returns>System.String.</returns>
- protected string GetUserAgent(BaseItem item)
+ protected string GetUserAgent(string path)
{
- if (item.Path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1)
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+
+ }
+ if (path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1)
{
return "QuickTime/7.7.4";
}
@@ -784,13 +853,10 @@ namespace MediaBrowser.Api.Playback
/// Gets the state.
/// </summary>
/// <param name="request">The request.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>StreamState.</returns>
- protected StreamState GetState(StreamRequest request)
+ protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken)
{
- var item = DtoService.GetItemByDtoId(request.Id);
-
- var media = (IHasMediaStreams)item;
-
var url = Request.PathInfo;
if (!request.AudioCodec.HasValue)
@@ -800,11 +866,62 @@ namespace MediaBrowser.Api.Playback
var state = new StreamState
{
- Item = item,
Request = request,
- Url = url
+ RequestedUrl = url
};
+ BaseItem item;
+
+ if (string.Equals(request.Type, "Recording", StringComparison.OrdinalIgnoreCase))
+ {
+ var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
+
+ state.VideoType = VideoType.VideoFile;
+ state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+ state.PlayableStreamFileNames = new List<string>();
+
+ if (!string.IsNullOrEmpty(recording.RecordingInfo.Path) && File.Exists(recording.RecordingInfo.Path))
+ {
+ state.MediaPath = recording.RecordingInfo.Path;
+ state.IsRemote = false;
+ }
+ else if (!string.IsNullOrEmpty(recording.RecordingInfo.Url))
+ {
+ 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;
+ }
+ else
+ {
+ item = DtoService.GetItemByDtoId(request.Id);
+
+ state.MediaPath = item.Path;
+ state.IsRemote = item.LocationType == LocationType.Remote;
+
+ var video = item as Video;
+
+ if (video != null)
+ {
+ state.IsInputVideo = true;
+ state.VideoType = video.VideoType;
+ state.IsoType = video.IsoType;
+
+ state.PlayableStreamFileNames = video.PlayableStreamFileNames == null
+ ? new List<string>()
+ : video.PlayableStreamFileNames.ToList();
+ }
+ }
+
var videoRequest = request as VideoStreamRequest;
var mediaStreams = ItemRepository.GetMediaStreams(new MediaStreamQuery
@@ -829,6 +946,8 @@ namespace MediaBrowser.Api.Playback
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
}
+ state.HasMediaStreams = mediaStreams.Count > 0;
+
return state;
}
diff --git a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
index efcc3f07a..d5bf22362 100644
--- a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
@@ -1,8 +1,9 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
@@ -26,8 +27,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public class AudioHlsService : BaseHlsService
{
- public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+ public AudioHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
{
}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 1e5e8b82d..68342e91d 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -2,10 +2,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
@@ -13,6 +13,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Hls
@@ -22,14 +23,14 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
- protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+ protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
{
}
protected override string GetOutputFilePath(StreamState state)
{
- var folder = ApplicationPaths.EncodedMediaCachePath;
+ var folder = ServerConfigurationManager.ApplicationPaths.EncodedMediaCachePath;
var outputFileExtension = GetOutputFileExtension(state);
@@ -73,7 +74,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.Object.</returns>
protected object ProcessRequest(StreamRequest request)
{
- var state = GetState(request);
+ var state = GetState(request, CancellationToken.None).Result;
return ProcessRequestAsync(state).Result;
}
@@ -247,7 +248,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.String.</returns>
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
{
- var probeSize = GetProbeSizeArgument(state.Item);
+ var probeSize = GetProbeSizeArgument(state.MediaPath, state.IsInputVideo, state.VideoType, state.IsoType);
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
@@ -257,13 +258,16 @@ namespace MediaBrowser.Api.Playback.Hls
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds);
- var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads 0 {6} {7} -sc_threshold 0 {8} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{9}\"",
+ var threads = GetNumberOfThreads();
+
+ var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"",
itsOffset,
probeSize,
- GetUserAgentParam(state.Item),
+ GetUserAgentParam(state.MediaPath),
GetFastSeekCommandLineParameter(state.Request),
- GetInputArgument(state.Item, state.IsoMount),
+ GetInputArgument(state),
GetSlowSeekCommandLineParameter(state.Request),
+ threads,
GetMapArgs(state),
GetVideoArguments(state, performSubtitleConversions),
GetAudioArguments(state),
@@ -272,13 +276,14 @@ namespace MediaBrowser.Api.Playback.Hls
if (hlsVideoRequest != null)
{
- if (hlsVideoRequest.AppendBaselineStream && state.Item is Video)
+ if (hlsVideoRequest.AppendBaselineStream && state.IsInputVideo)
{
var lowBitratePath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + "-low.m3u8");
var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000;
- var lowBitrateParams = string.Format(" -threads 0 -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{0}\"",
+ var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {2} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{1}\"",
+ threads,
lowBitratePath,
bitrate / 2);
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index 9d335d2d3..99772d2ec 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -1,8 +1,9 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;
@@ -32,8 +33,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public class VideoHlsService : BaseHlsService
{
- public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+ public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
{
}
@@ -123,7 +124,7 @@ namespace MediaBrowser.Api.Playback.Hls
(state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
- var args = "-codec:v:0 " + codec + " -preset superfast" + keyFrameArg;
+ var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264") + keyFrameArg;
var bitrate = GetVideoBitrateParam(state);
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index 86ab498f6..baf7f48fe 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -1,9 +1,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;
@@ -41,8 +42,8 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class AudioService : BaseProgressiveStreamingService
{
- public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor, fileSystem)
+ public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IImageProcessor imageProcessor)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, imageProcessor)
{
}
@@ -101,13 +102,16 @@ namespace MediaBrowser.Api.Playback.Progressive
const string vn = " -vn";
- return string.Format("{0} -i {1}{2} -threads 0{5} {3} -id3v2_version 3 -write_id3v1 1 \"{4}\"",
+ var threads = GetNumberOfThreads();
+
+ return string.Format("{0} -i {1}{2} -threads {3}{4} {5} -id3v2_version 3 -write_id3v1 1 \"{6}\"",
GetFastSeekCommandLineParameter(request),
- GetInputArgument(state.Item, state.IsoMount),
+ GetInputArgument(state),
GetSlowSeekCommandLineParameter(request),
+ threads,
+ vn,
string.Join(" ", audioTranscodeParams.ToArray()),
- outputPath,
- vn).Trim();
+ outputPath).Trim();
}
}
}
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 1fea32219..e367801d2 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -1,20 +1,18 @@
-using MediaBrowser.Api.Images;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Progressive
@@ -26,8 +24,8 @@ namespace MediaBrowser.Api.Playback.Progressive
{
protected readonly IImageProcessor ImageProcessor;
- protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepository, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) :
- base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+ protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IImageProcessor imageProcessor)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
{
ImageProcessor = imageProcessor;
}
@@ -51,9 +49,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// Try to infer based on the desired video codec
if (videoRequest != null && videoRequest.VideoCodec.HasValue)
{
- var video = state.Item as Video;
-
- if (video != null)
+ if (state.IsInputVideo)
{
switch (videoRequest.VideoCodec.Value)
{
@@ -72,9 +68,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// Try to infer based on the desired audio codec
if (state.Request.AudioCodec.HasValue)
{
- var audio = state.Item as Audio;
-
- if (audio != null)
+ if (!state.IsInputVideo)
{
switch (state.Request.AudioCodec.Value)
{
@@ -186,18 +180,13 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <returns>Task.</returns>
protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
{
- var state = GetState(request);
-
- if (request.AlbumArt)
- {
- return GetAlbumArtResponse(state);
- }
+ var state = GetState(request, CancellationToken.None).Result;
var responseHeaders = new Dictionary<string, string>();
- if (request.Static && state.Item.LocationType == LocationType.Remote)
+ if (request.Static && state.IsRemote)
{
- return GetStaticRemoteStreamResult(state.Item, responseHeaders, isHeadRequest).Result;
+ return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result;
}
var outputPath = GetOutputFilePath(state);
@@ -210,7 +199,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (request.Static)
{
- return ResultFactory.GetStaticFileResult(Request, state.Item.Path, FileShare.Read, responseHeaders, isHeadRequest);
+ return ResultFactory.GetStaticFileResult(Request, state.MediaPath, FileShare.Read, responseHeaders, isHeadRequest);
}
if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
@@ -224,19 +213,19 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <summary>
/// Gets the static remote stream result.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="mediaPath">The media path.</param>
/// <param name="responseHeaders">The response headers.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>Task{System.Object}.</returns>
- private async Task<object> GetStaticRemoteStreamResult(BaseItem item, Dictionary<string, string> responseHeaders, bool isHeadRequest)
+ private async Task<object> GetStaticRemoteStreamResult(string mediaPath, Dictionary<string, string> responseHeaders, bool isHeadRequest)
{
responseHeaders["Accept-Ranges"] = "none";
var httpClient = new HttpClient();
- using (var message = new HttpRequestMessage(HttpMethod.Get, item.Path))
+ using (var message = new HttpRequestMessage(HttpMethod.Get, mediaPath))
{
- var useragent = GetUserAgent(item);
+ var useragent = GetUserAgent(mediaPath);
if (!string.IsNullOrEmpty(useragent))
{
@@ -273,47 +262,6 @@ namespace MediaBrowser.Api.Playback.Progressive
}
/// <summary>
- /// Gets the album art response.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.Object.</returns>
- private object GetAlbumArtResponse(StreamState state)
- {
- var request = new GetItemImage
- {
- MaxWidth = 800,
- MaxHeight = 800,
- Type = ImageType.Primary,
- Id = state.Item.Id.ToString()
- };
-
- // Try and find some image to return
- if (!state.Item.HasImage(ImageType.Primary))
- {
- if (state.Item.HasImage(ImageType.Backdrop))
- {
- request.Type = ImageType.Backdrop;
- }
- else if (state.Item.HasImage(ImageType.Thumb))
- {
- request.Type = ImageType.Thumb;
- }
- else if (state.Item.HasImage(ImageType.Logo))
- {
- request.Type = ImageType.Logo;
- }
- }
-
- return new ImageService(UserManager, LibraryManager, ApplicationPaths, null, ItemRepository, DtoService, ImageProcessor, null)
- {
- Logger = Logger,
- Request = Request,
- ResultFactory = ResultFactory
-
- }.Get(request);
- }
-
- /// <summary>
/// Gets the stream result.
/// </summary>
/// <param name="state">The state.</param>
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 40c7492ff..31dbcaafb 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -1,10 +1,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;
@@ -55,8 +55,8 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class VideoService : BaseProgressiveStreamingService
{
- public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor, fileSystem)
+ public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IImageProcessor imageProcessor)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, imageProcessor)
{
}
@@ -89,9 +89,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <returns>System.String.</returns>
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
{
- var video = (Video)state.Item;
-
- var probeSize = GetProbeSizeArgument(state.Item);
+ var probeSize = GetProbeSizeArgument(state.MediaPath, state.IsInputVideo, state.VideoType, state.IsoType);
// Get the output codec name
var videoCodec = GetVideoCodec(state.VideoRequest);
@@ -104,13 +102,13 @@ namespace MediaBrowser.Api.Playback.Progressive
format = " -f mp4 -movflags frag_keyframe+empty_moov";
}
- var threads = string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) ? 2 : 0;
+ var threads = string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) ? 2 : GetNumberOfThreads();
return string.Format("{0} {1} {2} -i {3}{4}{5} {6} {7} -threads {8} {9}{10} \"{11}\"",
probeSize,
- GetUserAgentParam(state.Item),
+ GetUserAgentParam(state.MediaPath),
GetFastSeekCommandLineParameter(state.Request),
- GetInputArgument(video, state.IsoMount),
+ GetInputArgument(state),
GetSlowSeekCommandLineParameter(state.Request),
keyFrame,
GetMapArgs(state),
@@ -165,9 +163,16 @@ namespace MediaBrowser.Api.Playback.Progressive
var qualityParam = GetVideoQualityParam(state, codec);
+ var bitrate = GetVideoBitrateParam(state);
+
+ if (bitrate.HasValue)
+ {
+ qualityParam += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
if (!string.IsNullOrEmpty(qualityParam))
{
- args += " " + qualityParam;
+ args += " " + qualityParam.Trim();
}
args += " -vsync vfr";
@@ -213,9 +218,9 @@ namespace MediaBrowser.Api.Playback.Progressive
{
return "-acodec copy";
}
-
+
var args = "-acodec " + codec;
-
+
// Add the number of audio channels
var channels = GetNumAudioChannelsParam(request, state.AudioStream);
@@ -231,64 +236,23 @@ namespace MediaBrowser.Api.Playback.Progressive
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
- var volParam = string.Empty;
- var AudioSampleRate = string.Empty;
-
- // Boost volume to 200% when downsampling from 6ch to 2ch
- if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
- {
- volParam = ",volume=2.000000";
- }
-
- if (state.Request.AudioSampleRate.HasValue)
- {
- AudioSampleRate= state.Request.AudioSampleRate.Value + ":";
- }
-
- args += string.Format(" -af \"aresample={0}async=1000{1}\"",AudioSampleRate, volParam);
-
- return args;
- }
-
- /// <summary>
- /// Gets the video bitrate to specify on the command line
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="videoCodec">The video codec.</param>
- /// <returns>System.String.</returns>
- private string GetVideoQualityParam(StreamState state, string videoCodec)
- {
- var args = string.Empty;
+ var volParam = string.Empty;
+ var AudioSampleRate = string.Empty;
- // webm
- if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase))
+ // Boost volume to 200% when downsampling from 6ch to 2ch
+ if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
{
- args = "-speed 16 -quality good -profile:v 0 -slices 8";
+ volParam = ",volume=2.000000";
}
- // asf/wmv
- else if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase))
+ if (state.Request.AudioSampleRate.HasValue)
{
- args = "-g 100 -qmax 15";
+ AudioSampleRate = state.Request.AudioSampleRate.Value + ":";
}
- else if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase))
- {
- args = "-preset superfast";
- }
- else if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase))
- {
- args = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
- }
-
- var bitrate = GetVideoBitrateParam(state);
-
- if (bitrate.HasValue)
- {
- args += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
- }
+ args += string.Format(" -af \"aresample={0}async=1000{1}\"", AudioSampleRate, volParam);
- return args.Trim();
+ return args;
}
}
}
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 1486c0de7..454cc411c 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -65,6 +65,12 @@ namespace MediaBrowser.Api.Playback
/// No need to put this in api docs since it's dlna only
/// </summary>
public bool AlbumArt { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public string Type { get; set; }
}
public class VideoStreamRequest : StreamRequest
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 3c2ea5a13..be1ad85eb 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -1,14 +1,13 @@
-using System.IO;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
+using System.Collections.Generic;
+using System.IO;
namespace MediaBrowser.Api.Playback
{
public class StreamState
{
- public string Url { get; set; }
+ public string RequestedUrl { get; set; }
public StreamRequest Request { get; set; }
@@ -29,12 +28,24 @@ namespace MediaBrowser.Api.Playback
public MediaStream SubtitleStream { get; set; }
- public BaseItem Item { get; set; }
-
/// <summary>
/// Gets or sets the iso mount.
/// </summary>
/// <value>The iso mount.</value>
public IIsoMount IsoMount { get; set; }
+
+ public string MediaPath { get; set; }
+
+ public bool IsRemote { get; set; }
+
+ public bool IsInputVideo { get; set; }
+
+ public VideoType VideoType { get; set; }
+
+ public IsoType? IsoType { get; set; }
+
+ public List<string> PlayableStreamFileNames { get; set; }
+
+ public bool HasMediaStreams { get; set; }
}
}
diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs
index 25e22ab59..78ff1bc07 100644
--- a/MediaBrowser.Api/SearchService.cs
+++ b/MediaBrowser.Api/SearchService.cs
@@ -148,12 +148,13 @@ namespace MediaBrowser.Api
MediaType = item.MediaType,
MatchedTerm = hintInfo.MatchedTerm,
DisplayMediaType = item.DisplayMediaType,
- RunTimeTicks = item.RunTimeTicks
+ RunTimeTicks = item.RunTimeTicks,
+ ProductionYear = item.ProductionYear
};
if (item.HasImage(ImageType.Primary))
{
- result.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, item.GetImage(ImageType.Primary));
+ result.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, item.GetImagePath(ImageType.Primary));
}
var episode = item as Episode;
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index a64152246..254fa6ff1 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -269,10 +269,12 @@ namespace MediaBrowser.Api
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(AuthenticateUser request)
+ public object Post(AuthenticateUser request)
{
// No response needed. Will throw an exception on failure.
var result = AuthenticateUser(request).Result;
+
+ return result;
}
public object Post(AuthenticateUserByName request)
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index b5317319f..a5b241b4b 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -64,6 +64,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
_logger = logger;
_fileSystem = fileSystem;
_appPaths = appPaths;
+
+ // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
+ ServicePointManager.Expect100Continue = false;
}
/// <summary>
@@ -132,7 +135,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
#if __MonoCS__
return GetMonoRequest(options, method, enableHttpCompression);
#endif
-
+
var request = HttpWebRequest.CreateHttp(options.Url);
if (!string.IsNullOrEmpty(options.AcceptHeader))
@@ -172,9 +175,64 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// <returns>Task{HttpResponseInfo}.</returns>
/// <exception cref="HttpException">
/// </exception>
- public async Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
+ public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
+ {
+ return SendAsync(options, "GET");
+ }
+
+ /// <summary>
+ /// Performs a GET request and returns the resulting stream
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>Task{Stream}.</returns>
+ /// <exception cref="HttpException"></exception>
+ /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
+ public async Task<Stream> Get(HttpRequestOptions options)
+ {
+ var response = await GetResponse(options).ConfigureAwait(false);
+
+ return response.Content;
+ }
+
+ /// <summary>
+ /// Performs a GET request and returns the resulting stream
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="resourcePool">The resource pool.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ public Task<Stream> Get(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
- ValidateParams(options.Url, options.CancellationToken);
+ return Get(new HttpRequestOptions
+ {
+ Url = url,
+ ResourcePool = resourcePool,
+ CancellationToken = cancellationToken,
+ });
+ }
+
+ /// <summary>
+ /// Gets the specified URL.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ public Task<Stream> Get(string url, CancellationToken cancellationToken)
+ {
+ return Get(url, null, cancellationToken);
+ }
+
+ /// <summary>
+ /// send as an asynchronous operation.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <param name="httpMethod">The HTTP method.</param>
+ /// <returns>Task{HttpResponseInfo}.</returns>
+ /// <exception cref="HttpException">
+ /// </exception>
+ private async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
+ {
+ ValidateParams(options);
options.CancellationToken.ThrowIfCancellationRequested();
@@ -185,7 +243,17 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) { IsTimedOut = true };
}
- var httpWebRequest = GetRequest(options, "GET", options.EnableHttpCompression);
+ var httpWebRequest = GetRequest(options, httpMethod, options.EnableHttpCompression);
+
+ if (!string.IsNullOrEmpty(options.RequestContent) || string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
+ {
+ var content = options.RequestContent ?? string.Empty;
+ var bytes = Encoding.UTF8.GetBytes(content);
+
+ httpWebRequest.ContentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
+ httpWebRequest.ContentLength = bytes.Length;
+ httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
+ }
if (options.ResourcePool != null)
{
@@ -202,7 +270,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
throw new HttpException(string.Format("Connection to {0} timed out", options.Url)) { IsTimedOut = true };
}
- _logger.Info("HttpClientManager.GET url: {0}", options.Url);
+ _logger.Info("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url);
try
{
@@ -275,46 +343,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
}
- /// <summary>
- /// Performs a GET request and returns the resulting stream
- /// </summary>
- /// <param name="options">The options.</param>
- /// <returns>Task{Stream}.</returns>
- /// <exception cref="HttpException"></exception>
- /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
- public async Task<Stream> Get(HttpRequestOptions options)
- {
- var response = await GetResponse(options).ConfigureAwait(false);
-
- return response.Content;
- }
-
- /// <summary>
- /// Performs a GET request and returns the resulting stream
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="resourcePool">The resource pool.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{Stream}.</returns>
- public Task<Stream> Get(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
- {
- return Get(new HttpRequestOptions
- {
- Url = url,
- ResourcePool = resourcePool,
- CancellationToken = cancellationToken,
- });
- }
-
- /// <summary>
- /// Gets the specified URL.
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{Stream}.</returns>
- public Task<Stream> Get(string url, CancellationToken cancellationToken)
+ public Task<HttpResponseInfo> Post(HttpRequestOptions options)
{
- return Get(url, null, cancellationToken);
+ return SendAsync(options, "POST");
}
/// <summary>
@@ -329,82 +360,15 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
public async Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData)
{
- ValidateParams(options.Url, options.CancellationToken);
-
- options.CancellationToken.ThrowIfCancellationRequested();
-
- var httpWebRequest = GetRequest(options, "POST", options.EnableHttpCompression);
-
var strings = postData.Keys.Select(key => string.Format("{0}={1}", key, postData[key]));
var postContent = string.Join("&", strings.ToArray());
- var bytes = Encoding.UTF8.GetBytes(postContent);
-
- httpWebRequest.ContentType = "application/x-www-form-urlencoded";
- httpWebRequest.ContentLength = bytes.Length;
- httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
-
- if (options.ResourcePool != null)
- {
- await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
- }
- _logger.Info("HttpClientManager.POST url: {0}", options.Url);
+ options.RequestContent = postContent;
+ options.RequestContentType = "application/x-www-form-urlencoded";
- try
- {
- options.CancellationToken.ThrowIfCancellationRequested();
+ var response = await Post(options).ConfigureAwait(false);
- using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false))
- {
- var httpResponse = (HttpWebResponse)response;
-
- EnsureSuccessStatusCode(httpResponse);
-
- options.CancellationToken.ThrowIfCancellationRequested();
-
- using (var stream = httpResponse.GetResponseStream())
- {
- var memoryStream = new MemoryStream();
-
- await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
- memoryStream.Position = 0;
-
- return memoryStream;
- }
- }
- }
- catch (OperationCanceledException ex)
- {
- var exception = GetCancellationException(options.Url, options.CancellationToken, ex);
-
- throw exception;
- }
- catch (HttpRequestException ex)
- {
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- throw new HttpException(ex.Message, ex);
- }
- catch (WebException ex)
- {
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- throw new HttpException(ex.Message, ex);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- throw;
- }
- finally
- {
- if (options.ResourcePool != null)
- {
- options.ResourcePool.Release();
- }
- }
+ return response.Content;
}
/// <summary>
@@ -443,7 +407,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
public async Task<HttpResponseInfo> GetTempFileResponse(HttpRequestOptions options)
{
- ValidateParams(options.Url, options.CancellationToken);
+ ValidateParams(options);
Directory.CreateDirectory(_appPaths.TempDirectory);
@@ -592,7 +556,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
return new HttpException(ex.Message, ex);
}
-
+
return ex;
}
@@ -608,17 +572,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
}
- /// <summary>
- /// Validates the params.
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <exception cref="System.ArgumentNullException">url</exception>
- private void ValidateParams(string url, CancellationToken cancellationToken)
+ private void ValidateParams(HttpRequestOptions options)
{
- if (string.IsNullOrEmpty(url))
+ if (string.IsNullOrEmpty(options.Url))
{
- throw new ArgumentNullException("url");
+ throw new ArgumentNullException("options");
}
}
diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
index ed9baf3b2..616981d50 100644
--- a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
+++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
@@ -216,6 +216,48 @@ namespace MediaBrowser.Common.Implementations.IO
return new FileStream(path, mode, access, share);
}
+
+ /// <summary>
+ /// Swaps the files.
+ /// </summary>
+ /// <param name="file1">The file1.</param>
+ /// <param name="file2">The file2.</param>
+ public void SwapFiles(string file1, string file2)
+ {
+ var temp1 = Path.GetTempFileName();
+ var temp2 = Path.GetTempFileName();
+
+ // Copying over will fail against hidden files
+ RemoveHiddenAttribute(file1);
+ RemoveHiddenAttribute(file2);
+
+ File.Copy(file1, temp1, true);
+ File.Copy(file2, temp2, true);
+
+ File.Copy(temp1, file2, true);
+ File.Copy(temp2, file1, true);
+
+ File.Delete(temp1);
+ File.Delete(temp2);
+ }
+
+ /// <summary>
+ /// Removes the hidden attribute.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ private void RemoveHiddenAttribute(string path)
+ {
+ var currentFile = new FileInfo(path);
+
+ // This will fail if the file is hidden
+ if (currentFile.Exists)
+ {
+ if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+ {
+ currentFile.Attributes &= ~FileAttributes.Hidden;
+ }
+ }
+ }
}
/// <summary>
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index 6d886bc69..d02984bad 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <summary>
/// Deletes old cache files
/// </summary>
- public class DeleteCacheFileTask : IScheduledTask
+ public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// Gets or sets the application paths.
@@ -160,5 +160,14 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
return "Maintenance";
}
}
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is hidden.
+ /// </summary>
+ /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+ public bool IsHidden
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
index 7c7833ae6..e5cb7aa10 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <summary>
/// Deletes old log files
/// </summary>
- public class DeleteLogFileTask : IScheduledTask
+ public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// Gets or sets the configuration manager.
@@ -115,5 +115,14 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
return "Maintenance";
}
}
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is hidden.
+ /// </summary>
+ /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+ public bool IsHidden
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs
index 00928255c..9a65046bf 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <summary>
/// Class ReloadLoggerFileTask
/// </summary>
- public class ReloadLoggerFileTask : IScheduledTask
+ public class ReloadLoggerFileTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// Gets or sets the log manager.
@@ -91,5 +91,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
get { return "Application"; }
}
+
+ public bool IsHidden
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common/IO/IFileSystem.cs b/MediaBrowser.Common/IO/IFileSystem.cs
index d307b74e5..8fba63195 100644
--- a/MediaBrowser.Common/IO/IFileSystem.cs
+++ b/MediaBrowser.Common/IO/IFileSystem.cs
@@ -74,5 +74,12 @@ namespace MediaBrowser.Common.IO
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
/// <returns>FileStream.</returns>
FileStream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share, bool isAsync = false);
+
+ /// <summary>
+ /// Swaps the files.
+ /// </summary>
+ /// <param name="file1">The file1.</param>
+ /// <param name="file2">The file2.</param>
+ void SwapFiles(string file1, string file2);
}
}
diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs
index 977a6aabe..db78fc927 100644
--- a/MediaBrowser.Common/Net/HttpRequestOptions.cs
+++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs
@@ -66,6 +66,10 @@ namespace MediaBrowser.Common.Net
public Dictionary<string, string> RequestHeaders { get; private set; }
+ public string RequestContentType { get; set; }
+
+ public string RequestContent { get; set; }
+
private string GetHeaderValue(string name)
{
string value;
diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs
index 54d6665e2..a3d90cb0d 100644
--- a/MediaBrowser.Common/Net/IHttpClient.cs
+++ b/MediaBrowser.Common/Net/IHttpClient.cs
@@ -65,6 +65,13 @@ namespace MediaBrowser.Common.Net
Task<Stream> Post(string url, Dictionary<string, string> postData, CancellationToken cancellationToken);
/// <summary>
+ /// Posts the specified options.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>Task{HttpResponseInfo}.</returns>
+ Task<HttpResponseInfo> Post(HttpRequestOptions options);
+
+ /// <summary>
/// Downloads the contents of a given url into a temporary location
/// </summary>
/// <param name="options">The options.</param>
diff --git a/MediaBrowser.Common/Net/MimeTypes.cs b/MediaBrowser.Common/Net/MimeTypes.cs
index c11ff59d5..47536a341 100644
--- a/MediaBrowser.Common/Net/MimeTypes.cs
+++ b/MediaBrowser.Common/Net/MimeTypes.cs
@@ -218,6 +218,11 @@ namespace MediaBrowser.Common.Net
return "image/svg+xml";
}
+ if (ext.Equals(".srt", StringComparison.OrdinalIgnoreCase))
+ {
+ return "text/plain";
+ }
+
throw new ArgumentException("Argument not supported: " + path);
}
}
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index 1a8583489..2ecf3ec9a 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>IEnumerable{IImageEnhancer}.</returns>
- IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType);
+ IEnumerable<IImageEnhancer> GetSupportedEnhancers(IHasImages item, ImageType imageType);
/// <summary>
/// Gets the image cache tag.
@@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="imageType">Type of the image.</param>
/// <param name="imagePath">The image path.</param>
/// <returns>Guid.</returns>
- Guid GetImageCacheTag(BaseItem item, ImageType imageType, string imagePath);
+ Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath);
/// <summary>
/// Gets the image cache tag.
@@ -67,7 +67,7 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="dateModified">The date modified.</param>
/// <param name="imageEnhancers">The image enhancers.</param>
/// <returns>Guid.</returns>
- Guid GetImageCacheTag(BaseItem item, ImageType imageType, string originalImagePath, DateTime dateModified,
+ Guid GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified,
List<IImageEnhancer> imageEnhancers);
/// <summary>
@@ -85,6 +85,6 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{System.String}.</returns>
- Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex);
+ Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex);
}
}
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index ce4bf6c32..506d6fd3d 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Drawing
{
public class ImageProcessingOptions
{
- public BaseItem Item { get; set; }
+ public IHasImages Item { get; set; }
public ImageType ImageType { get; set; }
diff --git a/MediaBrowser.Controller/Entities/AdultVideo.cs b/MediaBrowser.Controller/Entities/AdultVideo.cs
index 9bb0f8355..f81cfa1f6 100644
--- a/MediaBrowser.Controller/Entities/AdultVideo.cs
+++ b/MediaBrowser.Controller/Entities/AdultVideo.cs
@@ -1,7 +1,14 @@

namespace MediaBrowser.Controller.Entities
{
- public class AdultVideo : Video
+ public class AdultVideo : Video, IHasPreferredMetadataLanguage
{
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 63c907c1f..028fc964d 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -1,4 +1,5 @@
-using System;
+using MediaBrowser.Model.Configuration;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
@@ -8,7 +9,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary>
/// Class Audio
/// </summary>
- public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLanguage
+ public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres
{
public Audio()
{
@@ -16,12 +17,6 @@ namespace MediaBrowser.Controller.Entities.Audio
}
/// <summary>
- /// Gets or sets the language.
- /// </summary>
- /// <value>The language.</value>
- public string Language { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether this instance has embedded image.
/// </summary>
/// <value><c>true</c> if this instance has embedded image; otherwise, <c>false</c>.</value>
@@ -131,5 +126,10 @@ namespace MediaBrowser.Controller.Entities.Audio
return base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMusic;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 3facccec1..203e6dc43 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -109,6 +110,11 @@ namespace MediaBrowser.Controller.Entities.Audio
return base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMusic;
+ }
}
public class MusicAlbumDisc : Folder
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 3be555f49..860d34fd8 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
@@ -96,7 +97,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
- public static string GetUserDataKey(BaseItem item)
+ private static string GetUserDataKey(MusicArtist item)
{
var id = item.GetProviderId(MetadataProviders.Musicbrainz);
@@ -107,5 +108,10 @@ namespace MediaBrowser.Controller.Entities.Audio
return "Artist-" + item.Name;
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMusic;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 541887598..a02369b2c 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
@@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class BaseItem
/// </summary>
- public abstract class BaseItem : IHasProviderIds, ILibraryItem
+ public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData
{
protected BaseItem()
{
@@ -132,8 +133,8 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public string PrimaryImagePath
{
- get { return GetImage(ImageType.Primary); }
- set { SetImage(ImageType.Primary, value); }
+ get { return this.GetImagePath(ImageType.Primary); }
+ set { this.SetImagePath(ImageType.Primary, value); }
}
/// <summary>
@@ -956,6 +957,66 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
+ /// Gets the preferred metadata language.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public string GetPreferredMetadataLanguage()
+ {
+ string lang = null;
+
+ var hasLang = this as IHasPreferredMetadataLanguage;
+
+ if (hasLang != null)
+ {
+ lang = hasLang.PreferredMetadataLanguage;
+ }
+
+ if (string.IsNullOrEmpty(lang))
+ {
+ lang = Parents.OfType<IHasPreferredMetadataLanguage>()
+ .Select(i => i.PreferredMetadataLanguage)
+ .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+ }
+
+ if (string.IsNullOrEmpty(lang))
+ {
+ lang = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+ }
+
+ return lang;
+ }
+
+ /// <summary>
+ /// Gets the preferred metadata language.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public string GetPreferredMetadataCountryCode()
+ {
+ string lang = null;
+
+ var hasLang = this as IHasPreferredMetadataLanguage;
+
+ if (hasLang != null)
+ {
+ lang = hasLang.PreferredMetadataCountryCode;
+ }
+
+ if (string.IsNullOrEmpty(lang))
+ {
+ lang = Parents.OfType<IHasPreferredMetadataLanguage>()
+ .Select(i => i.PreferredMetadataCountryCode)
+ .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+ }
+
+ if (string.IsNullOrEmpty(lang))
+ {
+ lang = ConfigurationManager.Configuration.MetadataCountryCode;
+ }
+
+ return lang;
+ }
+
+ /// <summary>
/// Determines if a given user has access to this item
/// </summary>
/// <param name="user">The user.</param>
@@ -985,7 +1046,7 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrEmpty(rating))
{
- return !user.Configuration.BlockNotRated;
+ return !GetBlockUnratedValue(user.Configuration);
}
var value = localizationManager.GetRatingLevel(rating);
@@ -1000,6 +1061,16 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
+ /// Gets the block unrated value.
+ /// </summary>
+ /// <param name="config">The configuration.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ protected virtual bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockNotRated;
+ }
+
+ /// <summary>
/// Determines if this folder should be visible to a given user.
/// Default is just parental allowed. Can be overridden for more functionality.
/// </summary>
@@ -1310,31 +1381,10 @@ namespace MediaBrowser.Controller.Entities
/// Gets an image
/// </summary>
/// <param name="type">The type.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
- public string GetImage(ImageType type)
- {
- if (type == ImageType.Backdrop)
- {
- throw new ArgumentException("Backdrops should be accessed using Item.Backdrops");
- }
- if (type == ImageType.Screenshot)
- {
- throw new ArgumentException("Screenshots should be accessed using Item.Screenshots");
- }
-
- string val;
- Images.TryGetValue(type, out val);
- return val;
- }
-
- /// <summary>
- /// Gets an image
- /// </summary>
- /// <param name="type">The type.</param>
+ /// <param name="imageIndex">Index of the image.</param>
/// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
- public bool HasImage(ImageType type)
+ public bool HasImage(ImageType type, int imageIndex)
{
if (type == ImageType.Backdrop)
{
@@ -1345,16 +1395,10 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentException("Screenshots should be accessed using Item.Screenshots");
}
- return !string.IsNullOrEmpty(GetImage(type));
+ return !string.IsNullOrEmpty(this.GetImagePath(type));
}
- /// <summary>
- /// Sets an image
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="path">The path.</param>
- /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
- public void SetImage(ImageType type, string path)
+ public void SetImagePath(ImageType type, int index, string path)
{
if (type == ImageType.Backdrop)
{
@@ -1423,10 +1467,10 @@ namespace MediaBrowser.Controller.Entities
else
{
// Delete the source file
- DeleteImagePath(GetImage(type));
+ DeleteImagePath(this.GetImagePath(type));
// Remove it from the item
- SetImage(type, null);
+ this.SetImagePath(type, null);
}
// Refresh metadata
@@ -1597,13 +1641,13 @@ namespace MediaBrowser.Controller.Entities
{
if (imageType == ImageType.Backdrop)
{
- return BackdropImagePaths[imageIndex];
+ return BackdropImagePaths.Count > imageIndex ? BackdropImagePaths[imageIndex] : null;
}
if (imageType == ImageType.Screenshot)
{
var hasScreenshots = (IHasScreenshots)this;
- return hasScreenshots.ScreenshotImagePaths[imageIndex];
+ return hasScreenshots.ScreenshotImagePaths.Count > imageIndex ? hasScreenshots.ScreenshotImagePaths[imageIndex] : null;
}
if (imageType == ImageType.Chapter)
@@ -1611,7 +1655,9 @@ namespace MediaBrowser.Controller.Entities
return ItemRepository.GetChapter(Id, imageIndex).ImagePath;
}
- return GetImage(imageType);
+ string val;
+ Images.TryGetValue(imageType, out val);
+ return val;
}
/// <summary>
@@ -1658,5 +1704,21 @@ namespace MediaBrowser.Controller.Entities
{
return new[] { Path };
}
+
+ public Task SwapImages(ImageType type, int index1, int index2)
+ {
+ if (type != ImageType.Screenshot && type != ImageType.Backdrop)
+ {
+ throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
+ }
+
+ var file1 = GetImagePath(type, index1);
+ var file2 = GetImagePath(type, index2);
+
+ FileSystem.SwapFiles(file1, file2);
+
+ // Directory watchers should repeat this, but do a quick refresh first
+ return RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false);
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs
index 87b90b824..298941378 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -1,8 +1,9 @@
-using System.Collections.Generic;
+using MediaBrowser.Model.Configuration;
+using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
- public class Book : BaseItem, IHasTags
+ public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage
{
public override string MediaType
{
@@ -11,6 +12,7 @@ namespace MediaBrowser.Controller.Entities
return Model.Entities.MediaType.Book;
}
}
+
/// <summary>
/// Gets or sets the tags.
/// </summary>
@@ -19,6 +21,14 @@ namespace MediaBrowser.Controller.Entities
public string SeriesName { get; set; }
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
/// <summary>
///
/// </summary>
@@ -42,5 +52,10 @@ namespace MediaBrowser.Controller.Entities
{
Tags = new List<string>();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedBooks;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 8ef86e54e..fed206a30 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities;
using MoreLinq;
using System;
using System.Collections;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -365,123 +364,125 @@ namespace MediaBrowser.Controller.Entities
{
var locationType = LocationType;
- // Nothing to do here
- if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
- {
- return;
- }
-
cancellationToken.ThrowIfCancellationRequested();
- IEnumerable<BaseItem> nonCachedChildren;
+ var validChildren = new List<Tuple<BaseItem, bool>>();
- try
- {
- nonCachedChildren = GetNonCachedChildren();
- }
- catch (IOException ex)
+ if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
{
- nonCachedChildren = new BaseItem[] { };
+ IEnumerable<BaseItem> nonCachedChildren;
- Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
- }
+ try
+ {
+ nonCachedChildren = GetNonCachedChildren();
+ }
+ catch (IOException ex)
+ {
+ nonCachedChildren = new BaseItem[] {};
- if (nonCachedChildren == null) return; //nothing to validate
+ Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
+ }
- progress.Report(5);
+ if (nonCachedChildren == null) return; //nothing to validate
- //build a dictionary of the current children we have now by Id so we can compare quickly and easily
- var currentChildren = ActualChildren.ToDictionary(i => i.Id);
+ progress.Report(5);
- //create a list for our validated children
- var validChildren = new List<Tuple<BaseItem, bool>>();
- var newItems = new List<BaseItem>();
+ //build a dictionary of the current children we have now by Id so we can compare quickly and easily
+ var currentChildren = ActualChildren.ToDictionary(i => i.Id);
- cancellationToken.ThrowIfCancellationRequested();
+ //create a list for our validated children
+ var newItems = new List<BaseItem>();
- foreach (var child in nonCachedChildren)
- {
- BaseItem currentChild;
+ cancellationToken.ThrowIfCancellationRequested();
- if (currentChildren.TryGetValue(child.Id, out currentChild))
+ foreach (var child in nonCachedChildren)
{
- currentChild.ResetResolveArgs(child.ResolveArgs);
+ BaseItem currentChild;
- //existing item - check if it has changed
- if (currentChild.HasChanged(child))
+ if (currentChildren.TryGetValue(child.Id, out currentChild))
{
- var currentChildLocationType = currentChild.LocationType;
- if (currentChildLocationType != LocationType.Remote &&
- currentChildLocationType != LocationType.Virtual)
+ currentChild.ResetResolveArgs(child.ResolveArgs);
+
+ //existing item - check if it has changed
+ if (currentChild.HasChanged(child))
+ {
+ var currentChildLocationType = currentChild.LocationType;
+ if (currentChildLocationType != LocationType.Remote &&
+ currentChildLocationType != LocationType.Virtual)
+ {
+ EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
+ }
+
+ validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
+ }
+ else
{
- EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
+ validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false));
}
- validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
+ currentChild.IsOffline = false;
}
else
{
- validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false));
- }
+ //brand new item - needs to be added
+ newItems.Add(child);
- currentChild.IsOffline = false;
+ validChildren.Add(new Tuple<BaseItem, bool>(child, true));
+ }
}
- else
+
+ // If any items were added or removed....
+ if (newItems.Count > 0 || currentChildren.Count != validChildren.Count)
{
- //brand new item - needs to be added
- newItems.Add(child);
+ var newChildren = validChildren.Select(c => c.Item1).ToList();
- validChildren.Add(new Tuple<BaseItem, bool>(child, true));
- }
- }
+ // That's all the new and changed ones - now see if there are any that are missing
+ var itemsRemoved = currentChildren.Values.Except(newChildren).ToList();
- // If any items were added or removed....
- if (newItems.Count > 0 || currentChildren.Count != validChildren.Count)
- {
- var newChildren = validChildren.Select(c => c.Item1).ToList();
+ var actualRemovals = new List<BaseItem>();
- // That's all the new and changed ones - now see if there are any that are missing
- var itemsRemoved = currentChildren.Values.Except(newChildren).ToList();
+ foreach (var item in itemsRemoved)
+ {
+ if (item.LocationType == LocationType.Virtual ||
+ item.LocationType == LocationType.Remote)
+ {
+ // Don't remove these because there's no way to accurately validate them.
+ validChildren.Add(new Tuple<BaseItem, bool>(item, false));
+ }
- var actualRemovals = new List<BaseItem>();
+ else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
+ {
+ item.IsOffline = true;
- foreach (var item in itemsRemoved)
- {
- if (item.LocationType == LocationType.Virtual ||
- item.LocationType == LocationType.Remote)
- {
- // Don't remove these because there's no way to accurately validate them.
- continue;
+ validChildren.Add(new Tuple<BaseItem, bool>(item, false));
+ }
+ else
+ {
+ item.IsOffline = false;
+ actualRemovals.Add(item);
+ }
}
-
- if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
- {
- item.IsOffline = true;
- validChildren.Add(new Tuple<BaseItem, bool>(item, false));
- }
- else
+ if (actualRemovals.Count > 0)
{
- item.IsOffline = false;
- actualRemovals.Add(item);
- }
- }
+ RemoveChildrenInternal(actualRemovals);
- if (actualRemovals.Count > 0)
- {
- RemoveChildrenInternal(actualRemovals);
-
- foreach (var item in actualRemovals)
- {
- LibraryManager.ReportItemRemoved(item);
+ foreach (var item in actualRemovals)
+ {
+ LibraryManager.ReportItemRemoved(item);
+ }
}
- }
- await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
+ await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
- AddChildrenInternal(newItems);
+ AddChildrenInternal(newItems);
- await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+ await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ validChildren.AddRange(ActualChildren.Select(i => new Tuple<BaseItem, bool>(i, false)));
}
progress.Report(10);
diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs
index c15a31dd3..da95b7c44 100644
--- a/MediaBrowser.Controller/Entities/Game.cs
+++ b/MediaBrowser.Controller/Entities/Game.cs
@@ -1,16 +1,25 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
- public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasLanguage, IHasScreenshots
+ public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, IHasPreferredMetadataLanguage
{
public List<Guid> SoundtrackIds { get; set; }
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
public Game()
{
MultiPartGameFiles = new List<string>();
@@ -23,12 +32,6 @@ namespace MediaBrowser.Controller.Entities
ScreenshotImagePaths = new List<string>();
}
- /// <summary>
- /// Gets or sets the language.
- /// </summary>
- /// <value>The language.</value>
- public string Language { get; set; }
-
public List<Guid> LocalTrailerIds { get; set; }
/// <summary>
@@ -129,5 +132,10 @@ namespace MediaBrowser.Controller.Entities
return base.GetDeletePaths();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedGames;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs
index 054071b35..63af8082a 100644
--- a/MediaBrowser.Controller/Entities/GameSystem.cs
+++ b/MediaBrowser.Controller/Entities/GameSystem.cs
@@ -1,4 +1,5 @@
-using System;
+using MediaBrowser.Model.Configuration;
+using System;
namespace MediaBrowser.Controller.Entities
{
@@ -38,5 +39,11 @@ namespace MediaBrowser.Controller.Entities
}
return base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ // Don't block. Determine by game
+ return false;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index 0fa49639b..53bc64194 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Model.Dto;
-using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs
new file mode 100644
index 000000000..d800acd9b
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IHasImages.cs
@@ -0,0 +1,115 @@
+using MediaBrowser.Model.Entities;
+using System;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Entities
+{
+ public interface IHasImages
+ {
+ /// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ string Name { get; }
+
+ /// <summary>
+ /// Gets the path.
+ /// </summary>
+ /// <value>The path.</value>
+ string Path { get; }
+
+ /// <summary>
+ /// Gets the identifier.
+ /// </summary>
+ /// <value>The identifier.</value>
+ Guid Id { get; }
+
+ /// <summary>
+ /// Gets the image path.
+ /// </summary>
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="imageIndex">Index of the image.</param>
+ /// <returns>System.String.</returns>
+ string GetImagePath(ImageType imageType, int imageIndex);
+
+ /// <summary>
+ /// Gets the image date modified.
+ /// </summary>
+ /// <param name="imagePath">The image path.</param>
+ /// <returns>DateTime.</returns>
+ DateTime GetImageDateModified(string imagePath);
+
+ /// <summary>
+ /// Sets the image.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <param name="index">The index.</param>
+ /// <param name="path">The path.</param>
+ void SetImagePath(ImageType type, int index, string path);
+
+ /// <summary>
+ /// Determines whether the specified type has image.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <param name="imageIndex">Index of the image.</param>
+ /// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns>
+ bool HasImage(ImageType type, int imageIndex);
+
+ /// <summary>
+ /// Swaps the images.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <param name="index1">The index1.</param>
+ /// <param name="index2">The index2.</param>
+ /// <returns>Task.</returns>
+ Task SwapImages(ImageType type, int index1, int index2);
+
+ /// <summary>
+ /// Gets the display type of the media.
+ /// </summary>
+ /// <value>The display type of the media.</value>
+ string DisplayMediaType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the primary image path.
+ /// </summary>
+ /// <value>The primary image path.</value>
+ string PrimaryImagePath { get; set; }
+
+ /// <summary>
+ /// Gets the preferred metadata language.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ string GetPreferredMetadataLanguage();
+ }
+
+ public static class HasImagesExtensions
+ {
+ /// <summary>
+ /// Gets the image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="imageType">Type of the image.</param>
+ /// <returns>System.String.</returns>
+ public static string GetImagePath(this IHasImages item, ImageType imageType)
+ {
+ return item.GetImagePath(imageType, 0);
+ }
+
+ public static bool HasImage(this IHasImages item, ImageType imageType)
+ {
+ return item.HasImage(imageType, 0);
+ }
+
+ /// <summary>
+ /// Sets the image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="path">The path.</param>
+ public static void SetImagePath(this IHasImages item, ImageType imageType, string path)
+ {
+ item.SetImagePath(imageType, 0, path);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/IHasLanguage.cs b/MediaBrowser.Controller/Entities/IHasLanguage.cs
deleted file mode 100644
index a1bb80098..000000000
--- a/MediaBrowser.Controller/Entities/IHasLanguage.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-
-namespace MediaBrowser.Controller.Entities
-{
- /// <summary>
- /// Interface IHasLanguage
- /// </summary>
- public interface IHasLanguage
- {
- /// <summary>
- /// Gets or sets the language.
- /// </summary>
- /// <value>The language.</value>
- string Language { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs b/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs
new file mode 100644
index 000000000..e3a233e49
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs
@@ -0,0 +1,21 @@
+
+namespace MediaBrowser.Controller.Entities
+{
+ /// <summary>
+ /// Interface IHasPreferredMetadataLanguage
+ /// </summary>
+ public interface IHasPreferredMetadataLanguage
+ {
+ /// <summary>
+ /// Gets or sets the preferred metadata language.
+ /// </summary>
+ /// <value>The preferred metadata language.</value>
+ string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ string PreferredMetadataCountryCode { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs
new file mode 100644
index 000000000..780181a61
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IHasUserData.cs
@@ -0,0 +1,15 @@
+
+namespace MediaBrowser.Controller.Entities
+{
+ /// <summary>
+ /// Interface IHasUserData
+ /// </summary>
+ public interface IHasUserData
+ {
+ /// <summary>
+ /// Gets the user data key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ string GetUserDataKey();
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index a1154482c..6144bdd71 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -1,5 +1,6 @@
-using System;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities.Movies
@@ -7,7 +8,7 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <summary>
/// Class BoxSet
/// </summary>
- public class BoxSet : Folder, IHasTrailers, IHasTags
+ public class BoxSet : Folder, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage
{
public BoxSet()
{
@@ -29,5 +30,18 @@ namespace MediaBrowser.Controller.Entities.Movies
/// </summary>
/// <value>The tags.</value>
public List<string> Tags { get; set; }
+
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMovies;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index b4cf6c047..f9d3f845c 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.IO;
@@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <summary>
/// Class Movie
/// </summary>
- public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags
+ public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage
{
public List<Guid> SpecialFeatureIds { get; set; }
@@ -19,6 +20,14 @@ namespace MediaBrowser.Controller.Entities.Movies
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
+ public string PreferredMetadataLanguage { get; set; }
public Movie()
{
@@ -180,5 +189,9 @@ namespace MediaBrowser.Controller.Entities.Movies
});
}
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMovies;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs
index 68ad4630a..d9eff8fbe 100644
--- a/MediaBrowser.Controller/Entities/MusicVideo.cs
+++ b/MediaBrowser.Controller/Entities/MusicVideo.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System;
@@ -48,5 +49,10 @@ namespace MediaBrowser.Controller.Entities
{
return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMusic;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 7f94ab8e8..42897e09f 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Entities.TV
{
@@ -292,5 +293,10 @@ namespace MediaBrowser.Controller.Entities.TV
{
return new[] { Path };
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedSeries;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 5727b316b..2d781118e 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using System;
@@ -260,5 +261,11 @@ namespace MediaBrowser.Controller.Entities.TV
{
return GetEpisodes(user);
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ // Don't block. Let either the entire series rating or episode rating determine it
+ return false;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 1565de4f8..f7e78ccd4 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using System;
@@ -13,13 +14,19 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Series
/// </summary>
- public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags
+ public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage
{
public List<Guid> SpecialFeatureIds { get; set; }
public List<Guid> SoundtrackIds { get; set; }
public int SeasonCount { get; set; }
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
public Series()
{
AirDays = new List<DayOfWeek>();
@@ -183,7 +190,9 @@ namespace MediaBrowser.Controller.Entities.TV
episodes = episodes.Where(i => !i.IsVirtualUnaired);
}
- return LibraryManager.Sort(episodes, user, new[] { ItemSortBy.AiredEpisodeOrder }, SortOrder.Ascending)
+ var sortBy = seasonNumber == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
+
+ return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
.Cast<Episode>();
}
@@ -215,5 +224,12 @@ namespace MediaBrowser.Controller.Entities.TV
return false;
});
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedSeries;
+ }
+
+ public string PreferredMetadataLanguage { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index 591fea14a..7000d04d3 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
@@ -8,9 +9,17 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class Trailer
/// </summary>
- public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasTaglines, IHasTags
+ public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage
{
public List<Guid> SoundtrackIds { get; set; }
+
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
public Trailer()
{
@@ -113,5 +122,10 @@ namespace MediaBrowser.Controller.Entities
return base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedTrailers;
+ }
}
}
diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs
deleted file mode 100644
index 37a1648c1..000000000
--- a/MediaBrowser.Controller/Kernel.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using MediaBrowser.Controller.MediaInfo;
-
-namespace MediaBrowser.Controller
-{
- /// <summary>
- /// Class Kernel
- /// </summary>
- public class Kernel
- {
- /// <summary>
- /// Gets the instance.
- /// </summary>
- /// <value>The instance.</value>
- public static Kernel Instance { get; private set; }
-
- /// <summary>
- /// Gets the FFMPEG controller.
- /// </summary>
- /// <value>The FFMPEG controller.</value>
- public FFMpegManager FFMpegManager { get; set; }
-
- /// <summary>
- /// Creates a kernel based on a Data path, which is akin to our current programdata path
- /// </summary>
- public Kernel()
- {
- Instance = this;
- }
- }
-}
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 338edd568..ae34621cb 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -116,6 +116,11 @@ namespace MediaBrowser.Controller.Library
Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken);
/// <summary>
+ /// Queues the library scan.
+ /// </summary>
+ void QueueLibraryScan();
+
+ /// <summary>
/// Gets the default view.
/// </summary>
/// <returns>IEnumerable{VirtualFolderInfo}.</returns>
diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs
index d6d5f99aa..2bec9e3de 100644
--- a/MediaBrowser.Controller/Library/IUserDataManager.cs
+++ b/MediaBrowser.Controller/Library/IUserDataManager.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="reason">The reason.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken);
+ Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken);
/// <summary>
/// Gets the user data.
diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs
index 65308bd10..d7504a86e 100644
--- a/MediaBrowser.Controller/Library/TVUtils.cs
+++ b/MediaBrowser.Controller/Library/TVUtils.cs
@@ -32,7 +32,9 @@ namespace MediaBrowser.Controller.Library
"sæson",
"temporada",
"saison",
- "staffel"
+ "staffel",
+ "series",
+ "сезон"
};
/// <summary>
@@ -122,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/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
index 87e7f647a..ba328ff75 100644
--- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
+++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
@@ -37,6 +37,6 @@ namespace MediaBrowser.Controller.Library
/// Gets or sets the item.
/// </summary>
/// <value>The item.</value>
- public BaseItem Item { get; set; }
+ public IHasUserData Item { get; set; }
}
}
diff --git a/MediaBrowser.Controller/LiveTv/Channel.cs b/MediaBrowser.Controller/LiveTv/Channel.cs
deleted file mode 100644
index 7186cfaf3..000000000
--- a/MediaBrowser.Controller/LiveTv/Channel.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.LiveTv;
-using System;
-using System.Collections.Generic;
-using System.Runtime.Serialization;
-
-namespace MediaBrowser.Controller.LiveTv
-{
- public class Channel : BaseItem, IItemByName
- {
- public Channel()
- {
- UserItemCountList = new List<ItemByNameCounts>();
- }
-
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- public override string GetUserDataKey()
- {
- return "Channel-" + Name;
- }
-
- [IgnoreDataMember]
- public List<ItemByNameCounts> UserItemCountList { get; set; }
-
- /// <summary>
- /// Gets or sets the number.
- /// </summary>
- /// <value>The number.</value>
- public string ChannelNumber { get; set; }
-
- /// <summary>
- /// Get or sets the Id.
- /// </summary>
- /// <value>The id of the channel.</value>
- public string ChannelId { get; set; }
-
- /// <summary>
- /// Gets or sets the name of the service.
- /// </summary>
- /// <value>The name of the service.</value>
- public string ServiceName { get; set; }
-
- /// <summary>
- /// Gets or sets the type of the channel.
- /// </summary>
- /// <value>The type of the channel.</value>
- public ChannelType ChannelType { get; set; }
-
- public bool? HasProviderImage { get; set; }
-
- protected override string CreateSortName()
- {
- double number = 0;
-
- if (!string.IsNullOrEmpty(ChannelNumber))
- {
- double.TryParse(ChannelNumber, out number);
- }
-
- return number.ToString("000-") + (Name ?? string.Empty);
- }
-
- public override string MediaType
- {
- get
- {
- return ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video;
- }
- }
- }
-}
diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
index bb0636673..cdc9c76c8 100644
--- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
@@ -32,9 +32,22 @@ namespace MediaBrowser.Controller.LiveTv
public ChannelType ChannelType { get; set; }
/// <summary>
- /// Set this value to true or false if it is known via channel info whether there is an image or not.
- /// Leave it null if the only way to determine is by requesting the image and handling the failure.
+ /// Supply the image path if it can be accessed directly from the file system
/// </summary>
+ /// <value>The image path.</value>
+ public string ImagePath { get; set; }
+
+ /// <summary>
+ /// Supply the image url if it can be downloaded
+ /// </summary>
+ /// <value>The image URL.</value>
+ public string ImageUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has image.
+ /// </summary>
+ /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
public bool? HasImage { get; set; }
+
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index 9ed7b633d..1d98dc7cf 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;
@@ -25,13 +26,21 @@ namespace MediaBrowser.Controller.LiveTv
IReadOnlyList<ILiveTvService> Services { get; }
/// <summary>
- /// Schedules the recording.
+ /// Gets the new timer defaults asynchronous.
/// </summary>
- /// <param name="programId">The program identifier.</param>
- /// <returns>Task.</returns>
- Task ScheduleRecording(string programId);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{TimerInfo}.</returns>
+ Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken);
/// <summary>
+ /// Gets the new timer defaults.
+ /// </summary>
+ /// <param name="programId">The program identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{SeriesTimerInfoDto}.</returns>
+ Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken);
+
+ /// <summary>
/// Deletes the recording.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -46,6 +55,13 @@ namespace MediaBrowser.Controller.LiveTv
Task CancelTimer(string id);
/// <summary>
+ /// Cancels the series timer.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <returns>Task.</returns>
+ Task CancelSeriesTimer(string id);
+
+ /// <summary>
/// Adds the parts.
/// </summary>
/// <param name="services">The services.</param>
@@ -122,9 +138,41 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>Channel.</returns>
- Channel GetChannel(string id);
+ LiveTvChannel GetInternalChannel(string id);
+
+ /// <summary>
+ /// Gets the internal program.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <returns>LiveTvProgram.</returns>
+ LiveTvProgram GetInternalProgram(string id);
+
+ /// <summary>
+ /// Gets the recording.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>LiveTvRecording.</returns>
+ Task<LiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken);
/// <summary>
+ /// Gets the recording stream.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ Task<StreamResponseInfo> GetRecordingStream(string id, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the program.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="user">The user.</param>
+ /// <returns>Task{ProgramInfoDto}.</returns>
+ Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null);
+
+ /// <summary>
/// Gets the programs.
/// </summary>
/// <param name="query">The query.</param>
@@ -147,5 +195,21 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Creates the timer.
+ /// </summary>
+ /// <param name="timer">The timer.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Creates the series timer.
+ /// </summary>
+ /// <param name="timer">The timer.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
index 8b1801f9e..31dbd8e99 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
@@ -31,6 +31,14 @@ namespace MediaBrowser.Controller.LiveTv
Task CancelTimerAsync(string timerId, CancellationToken cancellationToken);
/// <summary>
+ /// Cancels the series timer asynchronous.
+ /// </summary>
+ /// <param name="timerId">The timer identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken);
+
+ /// <summary>
/// Deletes the recording asynchronous.
/// </summary>
/// <param name="recordingId">The recording identifier.</param>
@@ -71,28 +79,29 @@ namespace MediaBrowser.Controller.LiveTv
Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken);
/// <summary>
- /// Gets the channel image asynchronous.
+ /// Gets the channel image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ChannelInfo
/// </summary>
/// <param name="channelId">The channel identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
- Task<ImageResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken);
+ Task<StreamResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken);
/// <summary>
- /// Gets the recording image asynchronous.
+ /// Gets the recording image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to RecordingInfo
/// </summary>
- /// <param name="channelId">The channel identifier.</param>
+ /// <param name="recordingId">The recording identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{ImageResponseInfo}.</returns>
- Task<ImageResponseInfo> GetRecordingImageAsync(string channelId, CancellationToken cancellationToken);
+ Task<StreamResponseInfo> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken);
/// <summary>
- /// Gets the program image asynchronous.
+ /// Gets the program image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ProgramInfo
/// </summary>
+ /// <param name="programId">The program identifier.</param>
/// <param name="channelId">The channel identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{ImageResponseInfo}.</returns>
- Task<ImageResponseInfo> GetProgramImageAsync(string channelId, CancellationToken cancellationToken);
+ Task<StreamResponseInfo> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken);
/// <summary>
/// Gets the recordings asynchronous.
@@ -109,6 +118,13 @@ namespace MediaBrowser.Controller.LiveTv
Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken);
/// <summary>
+ /// Gets the timer defaults asynchronous.
+ /// </summary>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{TimerInfo}.</returns>
+ Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken);
+
+ /// <summary>
/// Gets the series timers asynchronous.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -122,5 +138,21 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{ProgramInfo}}.</returns>
Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the recording stream.
+ /// </summary>
+ /// <param name="recordingId">The recording identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ Task<StreamResponseInfo> GetRecordingStream(string recordingId, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the channel stream.
+ /// </summary>
+ /// <param name="recordingId">The recording identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ Task<StreamResponseInfo> GetChannelStream(string recordingId, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
new file mode 100644
index 000000000..1e6d74ce8
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
@@ -0,0 +1,57 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class LiveTvChannel : BaseItem, IItemByName
+ {
+ public LiveTvChannel()
+ {
+ UserItemCountList = new List<ItemByNameCounts>();
+ }
+
+ /// <summary>
+ /// Gets the user data key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public override string GetUserDataKey()
+ {
+ return GetClientTypeName() + "-" + Name;
+ }
+
+ [IgnoreDataMember]
+ public List<ItemByNameCounts> UserItemCountList { get; set; }
+
+ public ChannelInfo ChannelInfo { get; set; }
+
+ public string ServiceName { get; set; }
+
+ protected override string CreateSortName()
+ {
+ double number = 0;
+
+ if (!string.IsNullOrEmpty(ChannelInfo.Number))
+ {
+ double.TryParse(ChannelInfo.Number, out number);
+ }
+
+ return number.ToString("000-") + (Name ?? string.Empty);
+ }
+
+ public override string MediaType
+ {
+ get
+ {
+ return ChannelInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video;
+ }
+ }
+
+ public override string GetClientTypeName()
+ {
+ return "Channel";
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvException.cs b/MediaBrowser.Controller/LiveTv/LiveTvException.cs
new file mode 100644
index 000000000..0a68180ca
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveTvException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ /// <summary>
+ /// Class LiveTvException.
+ /// </summary>
+ public class LiveTvException : Exception
+ {
+ }
+
+ /// <summary>
+ /// Class LiveTvConflictException.
+ /// </summary>
+ public class LiveTvConflictException : LiveTvException
+ {
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
new file mode 100644
index 000000000..abacc0c18
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -0,0 +1,36 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class LiveTvProgram : BaseItem
+ {
+ /// <summary>
+ /// Gets the user data key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public override string GetUserDataKey()
+ {
+ return GetClientTypeName() + "-" + Name;
+ }
+
+ public ProgramInfo ProgramInfo { get; set; }
+
+ public ChannelType ChannelType { get; set; }
+
+ public string ServiceName { get; set; }
+
+ public override string MediaType
+ {
+ get
+ {
+ return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio;
+ }
+ }
+
+ public override string GetClientTypeName()
+ {
+ return "Program";
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs
new file mode 100644
index 000000000..1c453ab5a
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs
@@ -0,0 +1,43 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class LiveTvRecording : BaseItem
+ {
+ /// <summary>
+ /// Gets the user data key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public override string GetUserDataKey()
+ {
+ return GetClientTypeName() + "-" + Name;
+ }
+
+ public RecordingInfo RecordingInfo { get; set; }
+
+ public string ServiceName { get; set; }
+
+ public override string MediaType
+ {
+ get
+ {
+ return RecordingInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video;
+ }
+ }
+
+ public override LocationType LocationType
+ {
+ get
+ {
+ return LocationType.Remote;
+ }
+ }
+
+ public override string GetClientTypeName()
+ {
+ return "Recording";
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index ce7a4a598..2c7b40415 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -92,11 +92,65 @@ namespace MediaBrowser.Controller.LiveTv
public string EpisodeTitle { get; set; }
/// <summary>
- /// Set this value to true or false if it is known via program info whether there is an image or not.
- /// Leave it null if the only way to determine is by requesting the image and handling the failure.
+ /// Supply the image path if it can be accessed directly from the file system
/// </summary>
+ /// <value>The image path.</value>
+ public string ImagePath { get; set; }
+
+ /// <summary>
+ /// Supply the image url if it can be downloaded
+ /// </summary>
+ /// <value>The image URL.</value>
+ public string ImageUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ public bool IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is premiere.
+ /// </summary>
+ /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
+ public bool IsPremiere { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has image.
+ /// </summary>
+ /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
public bool? HasImage { get; set; }
-
+
public ProgramInfo()
{
Genres = new List<string>();
diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
index 4fc8c0f7a..6a0d135c8 100644
--- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
@@ -12,14 +12,15 @@ namespace MediaBrowser.Controller.LiveTv
public string Id { get; set; }
/// <summary>
- /// ChannelId of the recording.
+ /// Gets or sets the series timer identifier.
/// </summary>
- public string ChannelId { get; set; }
-
+ /// <value>The series timer identifier.</value>
+ public string SeriesTimerId { get; set; }
+
/// <summary>
- /// ChannelName of the recording.
+ /// ChannelId of the recording.
/// </summary>
- public string ChannelName { get; set; }
+ public string ChannelId { get; set; }
/// <summary>
/// Gets or sets the type of the channel.
@@ -39,6 +40,12 @@ namespace MediaBrowser.Controller.LiveTv
public string Path { get; set; }
/// <summary>
+ /// Gets or sets the URL.
+ /// </summary>
+ /// <value>The URL.</value>
+ public string Url { get; set; }
+
+ /// <summary>
/// Gets or sets the overview.
/// </summary>
/// <value>The overview.</value>
@@ -96,6 +103,48 @@ namespace MediaBrowser.Controller.LiveTv
public ProgramAudio? Audio { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ public bool IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is premiere.
+ /// </summary>
+ /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
+ public bool IsPremiere { get; set; }
+
+ /// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
@@ -108,11 +157,24 @@ namespace MediaBrowser.Controller.LiveTv
public float? CommunityRating { get; set; }
/// <summary>
- /// Set this value to true or false if it is known via recording info whether there is an image or not.
- /// Leave it null if the only way to determine is by requesting the image and handling the failure.
+ /// Supply the image path if it can be accessed directly from the file system
+ /// </summary>
+ /// <value>The image path.</value>
+ public string ImagePath { get; set; }
+
+ /// <summary>
+ /// Supply the image url if it can be downloaded
/// </summary>
+ /// <value>The image URL.</value>
+ public string ImageUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has image.
+ /// </summary>
+ /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
public bool? HasImage { get; set; }
+
public RecordingInfo()
{
Genres = new List<string>();
diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
index 607282796..1be6549ff 100644
--- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.LiveTv;
-using System;
+using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.LiveTv
@@ -17,11 +16,6 @@ namespace MediaBrowser.Controller.LiveTv
public string ChannelId { get; set; }
/// <summary>
- /// ChannelName of the recording.
- /// </summary>
- public string ChannelName { get; set; }
-
- /// <summary>
/// Gets or sets the program identifier.
/// </summary>
/// <value>The program identifier.</value>
@@ -48,12 +42,24 @@ namespace MediaBrowser.Controller.LiveTv
public DateTime EndDate { get; set; }
/// <summary>
- /// Gets or sets the type of the recurrence.
+ /// Gets or sets a value indicating whether [record any time].
/// </summary>
- /// <value>The type of the recurrence.</value>
- public RecurrenceType RecurrenceType { get; set; }
+ /// <value><c>true</c> if [record any time]; otherwise, <c>false</c>.</value>
+ public bool RecordAnyTime { 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; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [record new only].
+ /// </summary>
+ /// <value><c>true</c> if [record new only]; otherwise, <c>false</c>.</value>
+ public bool RecordNewOnly { get; set; }
+
+ /// <summary>
/// Gets or sets the days.
/// </summary>
/// <value>The days.</value>
@@ -66,28 +72,28 @@ namespace MediaBrowser.Controller.LiveTv
public int Priority { get; set; }
/// <summary>
- /// Gets or sets the requested pre padding seconds.
+ /// Gets or sets the pre padding seconds.
/// </summary>
- /// <value>The requested pre padding seconds.</value>
- public int RequestedPrePaddingSeconds { get; set; }
+ /// <value>The pre padding seconds.</value>
+ public int PrePaddingSeconds { get; set; }
/// <summary>
- /// Gets or sets the requested post padding seconds.
+ /// Gets or sets the post padding seconds.
/// </summary>
- /// <value>The requested post padding seconds.</value>
- public int RequestedPostPaddingSeconds { get; set; }
+ /// <value>The post padding seconds.</value>
+ public int PostPaddingSeconds { get; set; }
/// <summary>
- /// Gets or sets the required pre padding seconds.
+ /// Gets or sets a value indicating whether this instance is pre padding required.
/// </summary>
- /// <value>The required pre padding seconds.</value>
- public int RequiredPrePaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value>
+ public bool IsPrePaddingRequired { get; set; }
/// <summary>
- /// Gets or sets the required post padding seconds.
+ /// Gets or sets a value indicating whether this instance is post padding required.
/// </summary>
- /// <value>The required post padding seconds.</value>
- public int RequiredPostPaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
+ public bool IsPostPaddingRequired { get; set; }
public SeriesTimerInfo()
{
diff --git a/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs
index d454a1ef8..c3b438c5e 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
{
/// <summary>
/// Gets or sets the stream.
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
index 786858e09..5d92a212f 100644
--- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -22,11 +22,6 @@ namespace MediaBrowser.Controller.LiveTv
public string ChannelId { get; set; }
/// <summary>
- /// ChannelName of the recording.
- /// </summary>
- public string ChannelName { get; set; }
-
- /// <summary>
/// Gets or sets the program identifier.
/// </summary>
/// <value>The program identifier.</value>
@@ -59,27 +54,33 @@ namespace MediaBrowser.Controller.LiveTv
public RecordingStatus Status { get; set; }
/// <summary>
- /// Gets or sets the requested pre padding seconds.
+ /// Gets or sets the pre padding seconds.
/// </summary>
- /// <value>The requested pre padding seconds.</value>
- public int RequestedPrePaddingSeconds { get; set; }
+ /// <value>The pre padding seconds.</value>
+ public int PrePaddingSeconds { get; set; }
/// <summary>
- /// Gets or sets the requested post padding seconds.
+ /// Gets or sets the post padding seconds.
/// </summary>
- /// <value>The requested post padding seconds.</value>
- public int RequestedPostPaddingSeconds { get; set; }
+ /// <value>The post padding seconds.</value>
+ public int PostPaddingSeconds { get; set; }
/// <summary>
- /// Gets or sets the required pre padding seconds.
+ /// Gets or sets a value indicating whether this instance is pre padding required.
/// </summary>
- /// <value>The required pre padding seconds.</value>
- public int RequiredPrePaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value>
+ public bool IsPrePaddingRequired { get; set; }
/// <summary>
- /// Gets or sets the required post padding seconds.
+ /// Gets or sets a value indicating whether this instance is post padding required.
+ /// </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 required post padding seconds.</value>
- public int RequiredPostPaddingSeconds { get; set; }
+ /// <value>The priority.</value>
+ public int Priority { get; set; }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index df108b590..0c5c0a5cd 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -85,8 +85,9 @@
<Compile Include="Entities\IHasAspectRatio.cs" />
<Compile Include="Entities\IHasBudget.cs" />
<Compile Include="Entities\IHasCriticRating.cs" />
- <Compile Include="Entities\IHasLanguage.cs" />
+ <Compile Include="Entities\IHasImages.cs" />
<Compile Include="Entities\IHasMediaStreams.cs" />
+ <Compile Include="Entities\IHasPreferredMetadataLanguage.cs" />
<Compile Include="Entities\IHasProductionLocations.cs" />
<Compile Include="Entities\IHasScreenshots.cs" />
<Compile Include="Entities\IHasSoundtracks.cs" />
@@ -94,6 +95,7 @@
<Compile Include="Entities\IHasTags.cs" />
<Compile Include="Entities\IHasThemeMedia.cs" />
<Compile Include="Entities\IHasTrailers.cs" />
+ <Compile Include="Entities\IHasUserData.cs" />
<Compile Include="Entities\IItemByName.cs" />
<Compile Include="Entities\ILibraryItem.cs" />
<Compile Include="Entities\ImageSourceInfo.cs" />
@@ -106,11 +108,14 @@
<Compile Include="Library\ItemUpdateType.cs" />
<Compile Include="Library\IUserDataManager.cs" />
<Compile Include="Library\UserDataSaveEventArgs.cs" />
- <Compile Include="LiveTv\Channel.cs" />
+ <Compile Include="LiveTv\LiveTvChannel.cs" />
<Compile Include="LiveTv\ChannelInfo.cs" />
<Compile Include="LiveTv\ILiveTvManager.cs" />
<Compile Include="LiveTv\ILiveTvService.cs" />
- <Compile Include="LiveTv\ImageResponseInfo.cs" />
+ <Compile Include="LiveTv\LiveTvException.cs" />
+ <Compile Include="LiveTv\StreamResponseInfo.cs" />
+ <Compile Include="LiveTv\LiveTvProgram.cs" />
+ <Compile Include="LiveTv\LiveTvRecording.cs" />
<Compile Include="LiveTv\ProgramInfo.cs" />
<Compile Include="LiveTv\RecordingInfo.cs" />
<Compile Include="LiveTv\SeriesTimerInfo.cs" />
@@ -188,10 +193,10 @@
<Compile Include="Library\TVUtils.cs" />
<Compile Include="Library\ItemResolveArgs.cs" />
<Compile Include="IO\FileData.cs" />
- <Compile Include="Kernel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\BaseMetadataProvider.cs" />
<Compile Include="Session\ISessionController.cs" />
+ <Compile Include="Session\ISessionControllerFactory.cs" />
<Compile Include="Session\PlaybackInfo.cs" />
<Compile Include="Session\PlaybackProgressInfo.cs" />
<Compile Include="Session\PlaybackStopInfo.cs" />
diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
index e53acfc02..ced53299d 100644
--- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
+++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
@@ -1,12 +1,16 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -19,79 +23,89 @@ namespace MediaBrowser.Controller.MediaInfo
/// </summary>
public class FFMpegManager
{
- /// <summary>
- /// Gets or sets the video image cache.
- /// </summary>
- /// <value>The video image cache.</value>
- internal FileSystemRepository VideoImageCache { get; set; }
-
- /// <summary>
- /// Gets or sets the subtitle cache.
- /// </summary>
- /// <value>The subtitle cache.</value>
- internal FileSystemRepository SubtitleCache { get; set; }
-
- private readonly IServerApplicationPaths _appPaths;
+ private readonly IServerConfigurationManager _config;
private readonly IMediaEncoder _encoder;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
private readonly IFileSystem _fileSystem;
+ public static FFMpegManager Instance { get; private set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="FFMpegManager" /> class.
/// </summary>
- /// <param name="appPaths">The app paths.</param>
/// <param name="encoder">The encoder.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repo.</param>
/// <exception cref="System.ArgumentNullException">zipClient</exception>
- public FFMpegManager(IServerApplicationPaths appPaths, IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem)
+ public FFMpegManager(IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem, IServerConfigurationManager config)
{
- _appPaths = appPaths;
_encoder = encoder;
_logger = logger;
_itemRepo = itemRepo;
_fileSystem = fileSystem;
+ _config = config;
- VideoImageCache = new FileSystemRepository(VideoImagesDataPath);
- SubtitleCache = new FileSystemRepository(SubtitleCachePath);
+ // TODO: Remove this static instance
+ Instance = this;
}
/// <summary>
- /// Gets the video images data path.
+ /// Gets the chapter images data path.
/// </summary>
- /// <value>The video images data path.</value>
- public string VideoImagesDataPath
+ /// <value>The chapter images data path.</value>
+ public string ChapterImagesPath
{
get
{
- return Path.Combine(_appPaths.DataPath, "extracted-video-images");
+ return Path.Combine(_config.ApplicationPaths.DataPath, "chapter-images");
}
}
/// <summary>
- /// Gets the audio images data path.
+ /// Gets the subtitle cache path.
/// </summary>
- /// <value>The audio images data path.</value>
- public string AudioImagesDataPath
+ /// <value>The subtitle cache path.</value>
+ private string SubtitleCachePath
{
get
{
- return Path.Combine(_appPaths.DataPath, "extracted-audio-images");
+ return Path.Combine(_config.ApplicationPaths.CachePath, "subtitles");
}
}
/// <summary>
- /// Gets the subtitle cache path.
+ /// Determines whether [is eligible for chapter image extraction] [the specified video].
/// </summary>
- /// <value>The subtitle cache path.</value>
- public string SubtitleCachePath
+ /// <param name="video">The video.</param>
+ /// <returns><c>true</c> if [is eligible for chapter image extraction] [the specified video]; otherwise, <c>false</c>.</returns>
+ private bool IsEligibleForChapterImageExtraction(Video video)
{
- get
+ if (video is Movie)
+ {
+ if (!_config.Configuration.EnableMovieChapterImageExtraction)
+ {
+ return false;
+ }
+ }
+ else if (video is Episode)
{
- return Path.Combine(_appPaths.CachePath, "subtitles");
+ if (!_config.Configuration.EnableEpisodeChapterImageExtraction)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!_config.Configuration.EnableOtherVideoChapterImageExtraction)
+ {
+ return false;
+ }
}
+
+ // Can't extract images if there are no video streams
+ return video.DefaultVideoStreamIndex.HasValue;
}
/// <summary>
@@ -111,8 +125,7 @@ namespace MediaBrowser.Controller.MediaInfo
/// <exception cref="System.ArgumentNullException"></exception>
public async Task<bool> PopulateChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
{
- // Can't extract images if there are no video streams
- if (!video.DefaultVideoStreamIndex.HasValue)
+ if (!IsEligibleForChapterImageExtraction(video))
{
return true;
}
@@ -122,6 +135,8 @@ namespace MediaBrowser.Controller.MediaInfo
var runtimeTicks = video.RunTimeTicks ?? 0;
+ var currentImages = GetSavedChapterImages(video);
+
foreach (var chapter in chapters)
{
if (chapter.StartPositionTicks >= runtimeTicks)
@@ -130,11 +145,9 @@ namespace MediaBrowser.Controller.MediaInfo
break;
}
- var filename = video.Path + "_" + video.DateModified.Ticks + "_" + chapter.StartPositionTicks;
-
- var path = VideoImageCache.GetResourcePath(filename, ".jpg");
+ var path = GetChapterImagePath(video, chapter.StartPositionTicks);
- if (!File.Exists(path))
+ if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase))
{
if (extractImages)
{
@@ -157,7 +170,7 @@ namespace MediaBrowser.Controller.MediaInfo
InputType type;
- var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, false, video.VideoType, video.IsoType, null, video.PlayableStreamFileNames, out type);
try
{
@@ -188,39 +201,87 @@ namespace MediaBrowser.Controller.MediaInfo
await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
}
+ DeleteDeadImages(currentImages, chapters);
+
return success;
}
+ private void DeleteDeadImages(IEnumerable<string> images, IEnumerable<ChapterInfo> chapters)
+ {
+ var deadImages = images
+ .Except(chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)), StringComparer.OrdinalIgnoreCase)
+ .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparer.OrdinalIgnoreCase))
+ .ToList();
+
+ foreach (var image in deadImages)
+ {
+ _logger.Debug("Deleting dead chapter image {0}", image);
+
+ try
+ {
+ File.Delete(image);
+ }
+ catch (IOException ex)
+ {
+ _logger.ErrorException("Error deleting {0}.", ex, image);
+ }
+ }
+ }
+
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
/// <summary>
/// Gets the subtitle cache path.
/// </summary>
- /// <param name="input">The input.</param>
- /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
+ /// <param name="mediaPath">The media path.</param>
+ /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputExtension">The output extension.</param>
/// <returns>System.String.</returns>
- public string GetSubtitleCachePath(Video input, int subtitleStreamIndex, TimeSpan? offset, string outputExtension)
+ public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, TimeSpan? offset, string outputExtension)
{
var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : "";
- var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery
+ if (subtitleStream.IsExternal)
{
- ItemId = input.Id,
- Index = subtitleStreamIndex
+ ticksParam += _fileSystem.GetLastWriteTimeUtc(subtitleStream.Path).Ticks;
+ }
+
+ var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
- }).FirstOrDefault();
+ var filename = (mediaPath + "_" + subtitleStream.Index.ToString(_usCulture) + "_" + date.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputExtension;
- if (stream == null)
+ var prefix = filename.Substring(0, 1);
+
+ return Path.Combine(SubtitleCachePath, prefix, filename);
+ }
+
+ public string GetChapterImagePath(Video video, long chapterPositionTicks)
+ {
+ var filename = video.DateModified.Ticks.ToString(_usCulture) + "_" + chapterPositionTicks.ToString(_usCulture) + ".jpg";
+
+ var videoId = video.Id.ToString();
+ var prefix = videoId.Substring(0, 1);
+
+ return Path.Combine(ChapterImagesPath, prefix, videoId, filename);
+ }
+
+ public List<string> GetSavedChapterImages(Video video)
+ {
+ var videoId = video.Id.ToString();
+ var prefix = videoId.Substring(0, 1);
+
+ var path = Path.Combine(ChapterImagesPath, prefix, videoId);
+
+ try
{
- return null;
+ return Directory.EnumerateFiles(path)
+ .ToList();
}
-
- if (stream.IsExternal)
+ catch (DirectoryNotFoundException)
{
- ticksParam += _fileSystem.GetLastWriteTimeUtc(stream.Path).Ticks;
+ return new List<string>();
}
-
- return SubtitleCache.GetResourcePath(input.Id + "_" + subtitleStreamIndex + "_" + input.DateModified.Ticks + ticksParam, outputExtension);
}
}
}
diff --git a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs
index 8c2f7c219..904ecdf93 100644
--- a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs
+++ b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs
@@ -1,5 +1,8 @@
-using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller.Entities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -13,43 +16,47 @@ namespace MediaBrowser.Controller.MediaInfo
/// <summary>
/// Gets the input argument.
/// </summary>
- /// <param name="video">The video.</param>
+ /// <param name="videoPath">The video path.</param>
+ /// <param name="isRemote">if set to <c>true</c> [is remote].</param>
+ /// <param name="videoType">Type of the video.</param>
+ /// <param name="isoType">Type of the iso.</param>
/// <param name="isoMount">The iso mount.</param>
+ /// <param name="playableStreamFileNames">The playable stream file names.</param>
/// <param name="type">The type.</param>
/// <returns>System.String[][].</returns>
- public static string[] GetInputArgument(Video video, IIsoMount isoMount, out InputType type)
+ public static string[] GetInputArgument(string videoPath, bool isRemote, VideoType videoType, IsoType? isoType, IIsoMount isoMount, IEnumerable<string> playableStreamFileNames, out InputType type)
{
- var inputPath = isoMount == null ? new[] { video.Path } : new[] { isoMount.MountedPath };
+ var inputPath = isoMount == null ? new[] { videoPath } : new[] { isoMount.MountedPath };
type = InputType.VideoFile;
- switch (video.VideoType)
+ switch (videoType)
{
case VideoType.BluRay:
type = InputType.Bluray;
break;
case VideoType.Dvd:
type = InputType.Dvd;
- inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray();
+ inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray();
break;
case VideoType.Iso:
- if (video.IsoType.HasValue)
+ if (isoType.HasValue)
{
- switch (video.IsoType.Value)
+ switch (isoType.Value)
{
case IsoType.BluRay:
type = InputType.Bluray;
break;
case IsoType.Dvd:
type = InputType.Dvd;
- inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray();
+ inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray();
break;
}
}
break;
case VideoType.VideoFile:
{
- if (video.LocationType == LocationType.Remote)
+ if (isRemote)
{
type = InputType.Url;
}
@@ -60,6 +67,17 @@ namespace MediaBrowser.Controller.MediaInfo
return inputPath;
}
+ public static List<string> GetPlayableStreamFiles(string rootPath, IEnumerable<string> filenames)
+ {
+ var allFiles = Directory
+ .EnumerateFiles(rootPath, "*", SearchOption.AllDirectories)
+ .ToList();
+
+ return filenames.Select(name => allFiles.FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase)))
+ .Where(f => !string.IsNullOrEmpty(f))
+ .ToList();
+ }
+
/// <summary>
/// Gets the type of the input.
/// </summary>
diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
index c9f57a927..799f339f1 100644
--- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
+++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
@@ -269,10 +269,10 @@ namespace MediaBrowser.Controller.Providers
{
var val = reader.ReadElementContentAsString();
- var hasLanguage = item as IHasLanguage;
+ var hasLanguage = item as IHasPreferredMetadataLanguage;
if (hasLanguage != null)
{
- hasLanguage.Language = val;
+ hasLanguage.PreferredMetadataLanguage = val;
}
break;
diff --git a/MediaBrowser.Controller/Providers/IImageEnhancer.cs b/MediaBrowser.Controller/Providers/IImageEnhancer.cs
index 54ba6d322..ae605ec0d 100644
--- a/MediaBrowser.Controller/Providers/IImageEnhancer.cs
+++ b/MediaBrowser.Controller/Providers/IImageEnhancer.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns>
- bool Supports(BaseItem item, ImageType imageType);
+ bool Supports(IHasImages item, ImageType imageType);
/// <summary>
/// Gets the priority or order in which this enhancer should be run.
@@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>Cache key relating to the current state of this item and configuration</returns>
- string GetConfigurationCacheKey(BaseItem item, ImageType imageType);
+ string GetConfigurationCacheKey(IHasImages item, ImageType imageType);
/// <summary>
/// Gets the size of the enhanced image.
@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="imageIndex">Index of the image.</param>
/// <param name="originalImageSize">Size of the original image.</param>
/// <returns>ImageSize.</returns>
- ImageSize GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageSize originalImageSize);
+ ImageSize GetEnhancedImageSize(IHasImages item, ImageType imageType, int imageIndex, ImageSize originalImageSize);
/// <summary>
/// Enhances the image async.
@@ -49,6 +49,6 @@ namespace MediaBrowser.Controller.Providers
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{Image}.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
- Task<Image> EnhanceImageAsync(BaseItem item, Image originalImage, ImageType imageType, int imageIndex);
+ Task<Image> EnhanceImageAsync(IHasImages item, Image originalImage, ImageType imageType, int imageIndex);
}
} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/IImageProvider.cs b/MediaBrowser.Controller/Providers/IImageProvider.cs
index d70532b59..ccf199844 100644
--- a/MediaBrowser.Controller/Providers/IImageProvider.cs
+++ b/MediaBrowser.Controller/Providers/IImageProvider.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- bool Supports(BaseItem item);
+ bool Supports(IHasImages item);
/// <summary>
/// Gets the images.
@@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="imageType">Type of the image.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
- Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken);
+ Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken);
/// <summary>
/// Gets the images.
@@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
- Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken);
+ Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken);
/// <summary>
/// Gets the priority.
diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
index 7d9739448..3cd38da45 100644
--- a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
+++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
@@ -133,6 +133,19 @@ namespace MediaBrowser.Controller.Resolvers
/// <param name="includeCreationTime">if set to <c>true</c> [include creation time].</param>
public static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args, bool includeCreationTime)
{
+ if (fileSystem == null)
+ {
+ throw new ArgumentNullException("fileSystem");
+ }
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ if (args == null)
+ {
+ throw new ArgumentNullException("args");
+ }
+
// See if a different path came out of the resolver than what went in
if (!string.Equals(args.Path, item.Path, StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Controller/Session/ISessionControllerFactory.cs b/MediaBrowser.Controller/Session/ISessionControllerFactory.cs
new file mode 100644
index 000000000..92862e462
--- /dev/null
+++ b/MediaBrowser.Controller/Session/ISessionControllerFactory.cs
@@ -0,0 +1,16 @@
+
+namespace MediaBrowser.Controller.Session
+{
+ /// <summary>
+ /// Interface ISesssionControllerFactory
+ /// </summary>
+ public interface ISessionControllerFactory
+ {
+ /// <summary>
+ /// Gets the session controller.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <returns>ISessionController.</returns>
+ ISessionController GetSessionController(SessionInfo session);
+ }
+}
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index 771d8f72e..ec138bfb4 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -35,16 +35,23 @@ namespace MediaBrowser.Controller.Session
IEnumerable<SessionInfo> Sessions { get; }
/// <summary>
+ /// Adds the parts.
+ /// </summary>
+ /// <param name="sessionFactories">The session factories.</param>
+ void AddParts(IEnumerable<ISessionControllerFactory> sessionFactories);
+
+ /// <summary>
/// Logs the user activity.
/// </summary>
/// <param name="clientType">Type of the client.</param>
/// <param name="appVersion">The app version.</param>
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
+ /// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">user</exception>
- Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, User user);
+ Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user);
/// <summary>
/// Used to report that playback has started for an item
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index ed2fcda67..82e9328ac 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Session
}
/// <summary>
+ /// Gets or sets the remote end point.
+ /// </summary>
+ /// <value>The remote end point.</value>
+ public string RemoteEndPoint { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether this instance can seek.
/// </summary>
/// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index c89dcf4a5..afdabbf44 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -16,7 +16,7 @@
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
- <FodyPath>..\packages\Fody.1.13.12</FodyPath>
+ <FodyPath>..\packages\Fody.1.19.1.0</FodyPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -77,6 +77,9 @@
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Configuration\ImageDownloadOptions.cs">
+ <Link>Configuration\ImageDownloadOptions.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs">
<Link>Configuration\ManualLoginCategory.cs</Link>
</Compile>
@@ -146,9 +149,6 @@
<Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
<Link>Entities\IHasProviderIds.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Entities\ImageDownloadOptions.cs">
- <Link>Entities\ImageDownloadOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Entities\ImageType.cs">
<Link>Entities\ImageType.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.Portable/packages.config b/MediaBrowser.Model.Portable/packages.config
index 23768650a..2d74129b8 100644
--- a/MediaBrowser.Model.Portable/packages.config
+++ b/MediaBrowser.Model.Portable/packages.config
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="Fody" version="1.13.12" targetFramework="portable-net45+sl40+wp71+win" />
+ <package id="Fody" version="1.19.1.0" targetFramework="portable-win+net45+sl40+wp71" developmentDependency="true" />
<package id="Microsoft.Bcl" version="1.0.19" targetFramework="portable-win+net45+sl40+wp71" />
<package id="Microsoft.Bcl.Async" version="1.0.16" targetFramework="portable-win+net45+sl40+wp71" />
<package id="Microsoft.Bcl.Build" version="1.0.8" targetFramework="portable-win+net45+sl40+wp71" />
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index 78f970969..7937bc403 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -64,6 +64,9 @@
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Configuration\ImageDownloadOptions.cs">
+ <Link>Configuration\ImageDownloadOptions.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs">
<Link>Configuration\ManualLoginCategory.cs</Link>
</Compile>
@@ -133,9 +136,6 @@
<Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
<Link>Entities\IHasProviderIds.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Entities\ImageDownloadOptions.cs">
- <Link>Entities\ImageDownloadOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Entities\ImageType.cs">
<Link>Entities\ImageType.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model/Entities/ImageDownloadOptions.cs b/MediaBrowser.Model/Configuration/ImageDownloadOptions.cs
index 92e989a34..603112110 100644
--- a/MediaBrowser.Model/Entities/ImageDownloadOptions.cs
+++ b/MediaBrowser.Model/Configuration/ImageDownloadOptions.cs
@@ -1,5 +1,5 @@

-namespace MediaBrowser.Model.Entities
+namespace MediaBrowser.Model.Configuration
{
/// <summary>
/// Class ImageDownloadOptions
@@ -62,4 +62,20 @@ namespace MediaBrowser.Model.Entities
Banner = true;
}
}
+
+ /// <summary>
+ /// Class MetadataOptions.
+ /// </summary>
+ public class MetadataOptions
+ {
+ public int MaxBackdrops { get; set; }
+
+ public int MinBackdropWidth { get; set; }
+
+ public MetadataOptions()
+ {
+ MaxBackdrops = 3;
+ MinBackdropWidth = 1280;
+ }
+ }
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 1f75969be..c8c205404 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Weather;
+using MediaBrowser.Model.Weather;
using System;
namespace MediaBrowser.Model.Configuration
@@ -88,12 +87,6 @@ namespace MediaBrowser.Model.Configuration
public string MetadataCountryCode { get; set; }
/// <summary>
- /// Gets or sets the max backdrops.
- /// </summary>
- /// <value>The max backdrops.</value>
- public int MaxBackdrops { get; set; }
-
- /// <summary>
/// Options for specific art to download for movies.
/// </summary>
public ImageDownloadOptions DownloadMovieImages { get; set; }
@@ -143,12 +136,6 @@ namespace MediaBrowser.Model.Configuration
public bool ShowLogWindow { get; set; }
/// <summary>
- /// The list of types that will NOT be allowed to have internet providers run against them even if they are turned on.
- /// </summary>
- /// <value>The internet provider exclude types.</value>
- public string[] InternetProviderExcludeTypes { get; set; }
-
- /// <summary>
/// Gets or sets the recent item days.
/// </summary>
/// <value>The recent item days.</value>
@@ -181,12 +168,6 @@ namespace MediaBrowser.Model.Configuration
public int FileWatcherDelay { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether [enable developer tools].
- /// </summary>
- /// <value><c>true</c> if [enable developer tools]; otherwise, <c>false</c>.</value>
- public bool EnableDeveloperTools { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether [enable dashboard response caching].
/// Allows potential contributors without visual studio to modify production dashboard code and test changes.
/// </summary>
@@ -217,22 +198,26 @@ namespace MediaBrowser.Model.Configuration
public ImageSavingConvention ImageSavingConvention { get; set; }
/// <summary>
- /// Gets or sets the width of the min movie backdrop.
+ /// Gets or sets a value indicating whether [enable people prefix sub folders].
/// </summary>
- /// <value>The width of the min movie backdrop.</value>
- public int MinMovieBackdropDownloadWidth { get; set; }
+ /// <value><c>true</c> if [enable people prefix sub folders]; otherwise, <c>false</c>.</value>
+ public bool EnablePeoplePrefixSubFolders { get; set; }
/// <summary>
- /// Gets or sets the width of the min series backdrop.
+ /// Gets or sets the encoding quality.
/// </summary>
- /// <value>The width of the min series backdrop.</value>
- public int MinSeriesBackdropDownloadWidth { get; set; }
+ /// <value>The encoding quality.</value>
+ public EncodingQuality EncodingQuality { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether [enable people prefix sub folders].
- /// </summary>
- /// <value><c>true</c> if [enable people prefix sub folders]; otherwise, <c>false</c>.</value>
- public bool EnablePeoplePrefixSubFolders { get; set; }
+ public bool EnableMovieChapterImageExtraction { get; set; }
+ public bool EnableEpisodeChapterImageExtraction { get; set; }
+ public bool EnableOtherVideoChapterImageExtraction { get; set; }
+
+ public MetadataOptions MovieOptions { get; set; }
+ public MetadataOptions TvOptions { get; set; }
+ public MetadataOptions MusicOptions { get; set; }
+ public MetadataOptions GameOptions { get; set; }
+ public MetadataOptions BookOptions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
@@ -247,9 +232,9 @@ namespace MediaBrowser.Model.Configuration
EnableDashboardResponseCaching = true;
EnableVideoImageExtraction = true;
-#if (DEBUG)
- EnableDeveloperTools = true;
-#endif
+ EnableMovieChapterImageExtraction = true;
+ EnableEpisodeChapterImageExtraction = false;
+ EnableOtherVideoChapterImageExtraction = false;
MinResumePct = 5;
MaxResumePct = 90;
@@ -260,7 +245,6 @@ namespace MediaBrowser.Model.Configuration
RecentItemDays = 10;
EnableInternetProviders = true; //initial installs will need these
- InternetProviderExcludeTypes = new string[] { };
ManualLoginClients = new ManualLoginCategory[] { };
@@ -275,7 +259,6 @@ namespace MediaBrowser.Model.Configuration
};
DownloadMusicArtistImages = new ImageDownloadOptions();
DownloadMusicAlbumImages = new ImageDownloadOptions();
- MaxBackdrops = 3;
SortReplaceCharacters = new[] { ".", "+", "%" };
SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" };
@@ -283,8 +266,20 @@ namespace MediaBrowser.Model.Configuration
SeasonZeroDisplayName = "Specials";
- MinMovieBackdropDownloadWidth = 1280;
- MinSeriesBackdropDownloadWidth = 1280;
+ MovieOptions = new MetadataOptions();
+ TvOptions = new MetadataOptions();
+
+ MusicOptions = new MetadataOptions()
+ {
+ MaxBackdrops = 1
+ };
+
+ GameOptions = new MetadataOptions();
+
+ BookOptions = new MetadataOptions
+ {
+ MaxBackdrops = 1
+ };
}
}
@@ -293,4 +288,12 @@ namespace MediaBrowser.Model.Configuration
Legacy,
Compatible
}
+
+ public enum EncodingQuality
+ {
+ Auto,
+ HighSpeed,
+ HighQuality,
+ MaxQuality
+ }
}
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index b736474e0..90accff94 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -60,6 +60,13 @@ namespace MediaBrowser.Model.Configuration
public bool DisplayUnairedEpisodes { get; set; }
public bool EnableRemoteControlOfOtherUsers { get; set; }
+ public bool BlockUnratedMovies { get; set; }
+ public bool BlockUnratedTrailers { get; set; }
+ public bool BlockUnratedSeries { get; set; }
+ public bool BlockUnratedMusic { get; set; }
+ public bool BlockUnratedGames { get; set; }
+ public bool BlockUnratedBooks { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary>
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 0690de5ce..9adfcfa99 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -36,6 +36,9 @@ namespace MediaBrowser.Model.Dto
public int? AbsoluteEpisodeNumber { get; set; }
public bool? DisplaySpecialsWithSeasons { get; set; }
+ public string PreferredMetadataLanguage { get; set; }
+ public string PreferredMetadataCountryCode { get; set; }
+
/// <summary>
/// Gets or sets the DVD season number.
/// </summary>
@@ -88,7 +91,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
-
+
/// <summary>
/// Gets or sets the official rating.
/// </summary>
@@ -210,12 +213,6 @@ namespace MediaBrowser.Model.Dto
public Dictionary<string, string> ProviderIds { get; set; }
/// <summary>
- /// Gets or sets the language.
- /// </summary>
- /// <value>The language.</value>
- public string Language { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether this instance is HD.
/// </summary>
/// <value><c>null</c> if [is HD] contains no value, <c>true</c> if [is HD]; otherwise, <c>false</c>.</value>
diff --git a/MediaBrowser.Model/Entities/MetadataFields.cs b/MediaBrowser.Model/Entities/MetadataFields.cs
index 85f2da31e..a99fd0fe0 100644
--- a/MediaBrowser.Model/Entities/MetadataFields.cs
+++ b/MediaBrowser.Model/Entities/MetadataFields.cs
@@ -41,6 +41,18 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// The official rating
/// </summary>
- OfficialRating
+ OfficialRating,
+ /// <summary>
+ /// The images
+ /// </summary>
+ Images,
+ /// <summary>
+ /// The backdrops
+ /// </summary>
+ Backdrops,
+ /// <summary>
+ /// The screenshots
+ /// </summary>
+ Screenshots
}
}
diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
index 020771e5e..89c92e6fd 100644
--- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
@@ -23,6 +23,12 @@ namespace MediaBrowser.Model.LiveTv
public string Id { get; set; }
/// <summary>
+ /// Gets or sets the external identifier.
+ /// </summary>
+ /// <value>The external identifier.</value>
+ public string ExternalId { get; set; }
+
+ /// <summary>
/// Gets or sets the image tags.
/// </summary>
/// <value>The image tags.</value>
diff --git a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
index b3542fcf8..8b8b0cd07 100644
--- a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
@@ -1,6 +1,7 @@
-using System;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using System;
using System.Collections.Generic;
-using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.LiveTv
{
@@ -12,6 +13,18 @@ namespace MediaBrowser.Model.LiveTv
public string Id { get; set; }
/// <summary>
+ /// Gets or sets the timer identifier.
+ /// </summary>
+ /// <value>The timer identifier.</value>
+ public string TimerId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the series timer identifier.
+ /// </summary>
+ /// <value>The series timer identifier.</value>
+ public string SeriesTimerId { get; set; }
+
+ /// <summary>
/// Gets or sets the external identifier.
/// </summary>
/// <value>The external identifier.</value>
@@ -24,6 +37,12 @@ namespace MediaBrowser.Model.LiveTv
public string ChannelId { get; set; }
/// <summary>
+ /// Gets or sets the name of the channel.
+ /// </summary>
+ /// <value>The name of the channel.</value>
+ public string ChannelName { get; set; }
+
+ /// <summary>
/// Gets or sets the community rating.
/// </summary>
/// <value>The community rating.</value>
@@ -103,20 +122,80 @@ namespace MediaBrowser.Model.LiveTv
public string EpisodeTitle { get; set; }
/// <summary>
+ /// Gets or sets the image tags.
+ /// </summary>
+ /// <value>The image tags.</value>
+ public Dictionary<ImageType, Guid> ImageTags { get; set; }
+
+ /// <summary>
/// Gets or sets the user data.
/// </summary>
/// <value>The user data.</value>
public UserItemDataDto UserData { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public string Type { get; set; }
+
+ /// <summary>
+ /// Gets or sets the run time ticks.
+ /// </summary>
+ /// <value>The run time ticks.</value>
+ public long? RunTimeTicks { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ public bool IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is premiere.
+ /// </summary>
+ /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
+ public bool IsPremiere { get; set; }
+
public ProgramInfoDto()
{
Genres = new List<string>();
+ ImageTags = new Dictionary<ImageType, Guid>();
}
}
public enum ProgramAudio
{
- Unspecified,
Mono,
Stereo,
Dolby,
diff --git a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
index d17ebee6d..3cf44f5c4 100644
--- a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.LiveTv
{
@@ -12,6 +13,12 @@ namespace MediaBrowser.Model.LiveTv
public string Id { get; set; }
/// <summary>
+ /// Gets or sets the series timer identifier.
+ /// </summary>
+ /// <value>The series timer identifier.</value>
+ public string SeriesTimerId { get; set; }
+
+ /// <summary>
/// Gets or sets the external identifier.
/// </summary>
/// <value>The external identifier.</value>
@@ -34,6 +41,12 @@ namespace MediaBrowser.Model.LiveTv
public string ChannelName { get; set; }
/// <summary>
+ /// Gets or sets the name of the service.
+ /// </summary>
+ /// <value>The name of the service.</value>
+ public string ServiceName { get; set; }
+
+ /// <summary>
/// Name of the recording.
/// </summary>
public string Name { get; set; }
@@ -45,6 +58,12 @@ namespace MediaBrowser.Model.LiveTv
public string Path { get; set; }
/// <summary>
+ /// Gets or sets the URL.
+ /// </summary>
+ /// <value>The URL.</value>
+ public string Url { get; set; }
+
+ /// <summary>
/// Overview of the recording.
/// </summary>
public string Overview { get; set; }
@@ -83,10 +102,10 @@ namespace MediaBrowser.Model.LiveTv
public string EpisodeTitle { get; set; }
/// <summary>
- /// Gets or sets the duration ms.
+ /// Gets or sets the run time ticks.
/// </summary>
- /// <value>The duration ms.</value>
- public int DurationMs { get; set; }
+ /// <value>The run time ticks.</value>
+ public long? RunTimeTicks { get; set; }
/// <summary>
/// Gets or sets the type of the media.
@@ -125,14 +144,69 @@ namespace MediaBrowser.Model.LiveTv
public ProgramAudio? Audio { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ public bool IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is premiere.
+ /// </summary>
+ /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
+ public bool IsPremiere { get; set; }
+
+ /// <summary>
+ /// Gets or sets the image tags.
+ /// </summary>
+ /// <value>The image tags.</value>
+ public Dictionary<ImageType, Guid> ImageTags { get; set; }
+
+ /// <summary>
/// Gets or sets the user data.
/// </summary>
/// <value>The user data.</value>
public UserItemDataDto UserData { get; set; }
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public string Type { get; set; }
+
public RecordingInfoDto()
{
Genres = new List<string>();
+ ImageTags = new Dictionary<ImageType, Guid>();
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index 3aa94ff83..20a7074a5 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -16,6 +16,12 @@
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the identifier.
+ /// </summary>
+ /// <value>The identifier.</value>
+ public string Id { get; set; }
}
public class TimerQuery
diff --git a/MediaBrowser.Model/LiveTv/RecordingStatus.cs b/MediaBrowser.Model/LiveTv/RecordingStatus.cs
index 06bc98e63..95e9dcb01 100644
--- a/MediaBrowser.Model/LiveTv/RecordingStatus.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingStatus.cs
@@ -14,15 +14,6 @@ namespace MediaBrowser.Model.LiveTv
Error
}
- public enum RecurrenceType
- {
- Manual,
- NewProgramEventsOneChannel,
- AllProgramEventsOneChannel,
- NewProgramEventsAllChannels,
- AllProgramEventsAllChannels
- }
-
public enum DayPattern
{
Daily,
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
index 3862c0745..a8c6a2e37 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
@@ -22,6 +22,12 @@ namespace MediaBrowser.Model.LiveTv
public string ChannelId { get; set; }
/// <summary>
+ /// Gets or sets the name of the service.
+ /// </summary>
+ /// <value>The name of the service.</value>
+ public string ServiceName { get; set; }
+
+ /// <summary>
/// Gets or sets the external channel identifier.
/// </summary>
/// <value>The external channel identifier.</value>
@@ -65,10 +71,22 @@ namespace MediaBrowser.Model.LiveTv
public DateTime EndDate { get; set; }
/// <summary>
- /// Gets or sets the type of the recurrence.
+ /// Gets or sets a value indicating whether [record any time].
+ /// </summary>
+ /// <value><c>true</c> if [record any time]; otherwise, <c>false</c>.</value>
+ public bool RecordAnyTime { 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; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [record new only].
/// </summary>
- /// <value>The type of the recurrence.</value>
- public RecurrenceType RecurrenceType { get; set; }
+ /// <value><c>true</c> if [record new only]; otherwise, <c>false</c>.</value>
+ public bool RecordNewOnly { get; set; }
/// <summary>
/// Gets or sets the days.
@@ -89,28 +107,28 @@ namespace MediaBrowser.Model.LiveTv
public int Priority { get; set; }
/// <summary>
- /// Gets or sets the requested pre padding seconds.
+ /// Gets or sets the pre padding seconds.
/// </summary>
- /// <value>The requested pre padding seconds.</value>
- public int RequestedPrePaddingSeconds { get; set; }
+ /// <value>The pre padding seconds.</value>
+ public int PrePaddingSeconds { get; set; }
/// <summary>
- /// Gets or sets the requested post padding seconds.
+ /// Gets or sets the post padding seconds.
/// </summary>
- /// <value>The requested post padding seconds.</value>
- public int RequestedPostPaddingSeconds { get; set; }
+ /// <value>The post padding seconds.</value>
+ public int PostPaddingSeconds { get; set; }
/// <summary>
- /// Gets or sets the required pre padding seconds.
+ /// Gets or sets a value indicating whether this instance is pre padding required.
/// </summary>
- /// <value>The required pre padding seconds.</value>
- public int RequiredPrePaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value>
+ public bool IsPrePaddingRequired { get; set; }
/// <summary>
- /// Gets or sets the required post padding seconds.
+ /// Gets or sets a value indicating whether this instance is post padding required.
/// </summary>
- /// <value>The required post padding seconds.</value>
- public int RequiredPostPaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
+ public bool IsPostPaddingRequired { get; set; }
public SeriesTimerInfoDto()
{
diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
index 6b7ab42d3..507ba0947 100644
--- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
@@ -32,12 +32,24 @@ namespace MediaBrowser.Model.LiveTv
public string ChannelName { get; set; }
/// <summary>
+ /// Gets or sets the name of the service.
+ /// </summary>
+ /// <value>The name of the service.</value>
+ public string ServiceName { get; set; }
+
+ /// <summary>
/// Gets or sets the program identifier.
/// </summary>
/// <value>The program identifier.</value>
public string ProgramId { get; set; }
/// <summary>
+ /// Gets or sets the external program identifier.
+ /// </summary>
+ /// <value>The external program identifier.</value>
+ public string ExternalProgramId { get; set; }
+
+ /// <summary>
/// Name of the recording.
/// </summary>
public string Name { get; set; }
@@ -74,35 +86,47 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value>The external series timer identifier.</value>
public string ExternalSeriesTimerId { get; set; }
-
+
+ /// <summary>
+ /// Gets or sets the pre padding seconds.
+ /// </summary>
+ /// <value>The pre padding seconds.</value>
+ public int PrePaddingSeconds { get; set; }
+
+ /// <summary>
+ /// Gets or sets the post padding seconds.
+ /// </summary>
+ /// <value>The post padding seconds.</value>
+ public int PostPaddingSeconds { get; set; }
+
/// <summary>
- /// Gets or sets the requested pre padding seconds.
+ /// Gets or sets a value indicating whether this instance is pre padding required.
/// </summary>
- /// <value>The requested pre padding seconds.</value>
- public int RequestedPrePaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value>
+ public bool IsPrePaddingRequired { get; set; }
/// <summary>
- /// Gets or sets the requested post padding seconds.
+ /// Gets or sets a value indicating whether this instance is post padding required.
/// </summary>
- /// <value>The requested post padding seconds.</value>
- public int RequestedPostPaddingSeconds { get; set; }
+ /// <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 required pre padding seconds.
+ /// Gets or sets the run time ticks.
/// </summary>
- /// <value>The required pre padding seconds.</value>
- public int RequiredPrePaddingSeconds { get; set; }
+ /// <value>The run time ticks.</value>
+ public long? RunTimeTicks { get; set; }
/// <summary>
- /// Gets or sets the required post padding seconds.
+ /// Gets or sets the priority.
/// </summary>
- /// <value>The required post padding seconds.</value>
- public int RequiredPostPaddingSeconds { get; set; }
+ /// <value>The priority.</value>
+ public int Priority { get; set; }
/// <summary>
- /// Gets or sets the duration ms.
+ /// Gets or sets the program information.
/// </summary>
- /// <value>The duration ms.</value>
- public int DurationMs { get; set; }
+ /// <value>The program information.</value>
+ public ProgramInfoDto ProgramInfo { get; set; }
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 071f5bdde..bb6fedb9f 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -12,7 +12,7 @@
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
- <FodyPath>..\packages\Fody.1.17.0.0</FodyPath>
+ <FodyPath>..\packages\Fody.1.19.1.0</FodyPath>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
@@ -118,7 +118,7 @@
<Compile Include="Session\MessageCommand.cs" />
<Compile Include="Session\PlayRequest.cs" />
<Compile Include="Session\PlaystateCommand.cs" />
- <Compile Include="Entities\ImageDownloadOptions.cs" />
+ <Compile Include="Configuration\ImageDownloadOptions.cs" />
<Compile Include="Logging\ILogManager.cs" />
<Compile Include="MediaInfo\BlurayDiscInfo.cs" />
<Compile Include="Entities\ChapterInfo.cs" />
@@ -209,8 +209,9 @@
<PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
)</PostBuildEvent>
+ <PostBuildEvent Condition=" '$(ConfigurationName)' == 'Release Mono' ">cp -fu "$(MSBuildProjectDirectory)\..\packages\PropertyChanged.Fody.1.41.0.0\PropertyChanged.Fody.dll" "$(MSBuildProjectDirectory)\..\Tools\Fody\"</PostBuildEvent>
</PropertyGroup>
- <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
+ <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<Import Project="Fody.targets" />
<!-- 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.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs
index 1e16b0492..bebe23734 100644
--- a/MediaBrowser.Model/Search/SearchHint.cs
+++ b/MediaBrowser.Model/Search/SearchHint.cs
@@ -32,6 +32,12 @@ namespace MediaBrowser.Model.Search
public int? IndexNumber { get; set; }
/// <summary>
+ /// Gets or sets the production year.
+ /// </summary>
+ /// <value>The production year.</value>
+ public int? ProductionYear { get; set; }
+
+ /// <summary>
/// Gets or sets the parent index number.
/// </summary>
/// <value>The parent index number.</value>
diff --git a/MediaBrowser.Model/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs
index 02b7f0226..80f6ea2c0 100644
--- a/MediaBrowser.Model/Session/SessionInfoDto.cs
+++ b/MediaBrowser.Model/Session/SessionInfoDto.cs
@@ -14,6 +14,12 @@ namespace MediaBrowser.Model.Session
public bool CanSeek { get; set; }
/// <summary>
+ /// Gets or sets the remote end point.
+ /// </summary>
+ /// <value>The remote end point.</value>
+ public string RemoteEndPoint { get; set; }
+
+ /// <summary>
/// Gets or sets the queueable media types.
/// </summary>
/// <value>The queueable media types.</value>
diff --git a/MediaBrowser.Model/packages.config b/MediaBrowser.Model/packages.config
index 622e6f72f..3d7793afb 100644
--- a/MediaBrowser.Model/packages.config
+++ b/MediaBrowser.Model/packages.config
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="Fody" version="1.17.0.0" targetFramework="net45" />
+ <package id="Fody" version="1.19.1.0" targetFramework="net45" developmentDependency="true" />
<package id="PropertyChanged.Fody" version="1.41.0.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Mono.userprefs b/MediaBrowser.Mono.userprefs
index fa9dbfdc2..4a043b8b8 100644
--- a/MediaBrowser.Mono.userprefs
+++ b/MediaBrowser.Mono.userprefs
@@ -2,7 +2,7 @@
<MonoDevelop.Ide.Workspace ActiveConfiguration="Release Mono" />
<MonoDevelop.Ide.Workbench ActiveDocument="MediaBrowser.Server.Mono\app.config">
<Files>
- <File FileName="MediaBrowser.Server.Mono\app.config" Line="3" Column="15" />
+ <File FileName="MediaBrowser.Server.Mono\app.config" Line="14" Column="17" />
<File FileName="MediaBrowser.Server.Mono\FFMpeg\FFMpegDownloadInfo.cs" Line="1" Column="1" />
<File FileName="MediaBrowser.Server.Mono\Program.cs" Line="1" Column="1" />
<File FileName="MediaBrowser.Server.Implementations\Library\UserManager.cs" Line="1" Column="1" />
diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
index 65e8afd7a..0b6accf33 100644
--- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
+++ b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
@@ -212,7 +212,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Logo, image.FullName);
+ item.SetImagePath(ImageType.Logo, image.FullName);
}
// Clearart
@@ -220,7 +220,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Art, image.FullName);
+ item.SetImagePath(ImageType.Art, image.FullName);
}
// Disc
@@ -229,7 +229,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Disc, image.FullName);
+ item.SetImagePath(ImageType.Disc, image.FullName);
}
// Box Image
@@ -237,7 +237,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Box, image.FullName);
+ item.SetImagePath(ImageType.Box, image.FullName);
}
// BoxRear Image
@@ -245,7 +245,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.BoxRear, image.FullName);
+ item.SetImagePath(ImageType.BoxRear, image.FullName);
}
// Thumbnail Image
@@ -253,7 +253,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Menu, image.FullName);
+ item.SetImagePath(ImageType.Menu, image.FullName);
}
PopulateBanner(item, args);
@@ -311,7 +311,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Primary, image.FullName);
+ item.SetImagePath(ImageType.Primary, image.FullName);
}
}
@@ -339,7 +339,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Banner, image.FullName);
+ item.SetImagePath(ImageType.Banner, image.FullName);
}
}
@@ -351,7 +351,8 @@ namespace MediaBrowser.Providers
private void PopulateThumb(BaseItem item, ItemResolveArgs args)
{
// Thumbnail Image
- var image = GetImage(item, args, "thumb");
+ var image = GetImage(item, args, "thumb") ??
+ GetImage(item, args, "landscape");
if (image == null)
{
@@ -367,7 +368,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Thumb, image.FullName);
+ item.SetImagePath(ImageType.Thumb, image.FullName);
}
}
diff --git a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs
index b0bc1b875..8ee2553d0 100644
--- a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs
+++ b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs
@@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.LiveTv
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
- return item is Channel;
+ return item is LiveTvChannel;
}
/// <summary>
@@ -74,7 +74,7 @@ namespace MediaBrowser.Providers.LiveTv
try
{
- new BaseItemXmlParser<Channel>(Logger).Fetch((Channel)item, path, cancellationToken);
+ new BaseItemXmlParser<LiveTvChannel>(Logger).Fetch((LiveTvChannel)item, path, cancellationToken);
}
finally
{
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 2f35cb003..272274679 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -86,7 +86,6 @@
<Compile Include="Movies\MovieProviderFromXml.cs" />
<Compile Include="Movies\OpenMovieDatabaseProvider.cs" />
<Compile Include="Movies\PersonProviderFromXml.cs" />
- <Compile Include="Movies\PersonUpdatesPreScanTask.cs" />
<Compile Include="Movies\MovieDbPersonProvider.cs" />
<Compile Include="Music\AlbumInfoFromSongProvider.cs" />
<Compile Include="Music\AlbumProviderFromXml.cs" />
@@ -134,6 +133,7 @@
<Compile Include="TV\ManualTvdbPersonImageProvider.cs" />
<Compile Include="TV\ManualTvdbSeasonImageProvider.cs" />
<Compile Include="TV\ManualTvdbSeriesImageProvider.cs" />
+ <Compile Include="TV\SeasonIndexNumberProvider.cs" />
<Compile Include="TV\TvdbEpisodeProvider.cs" />
<Compile Include="TV\TvdbSeasonProvider.cs" />
<Compile Include="TV\TvdbSeriesProvider.cs" />
@@ -146,6 +146,7 @@
<Compile Include="TV\TvdbPrescanTask.cs" />
<Compile Include="TV\TvdbSeriesImageProvider.cs" />
<Compile Include="UserRootFolderNameProvider.cs" />
+ <Compile Include="VirtualItemImageValidator.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index 264b24b87..5782e3e63 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
@@ -1,6 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -23,12 +22,6 @@ namespace MediaBrowser.Providers.MediaInfo
public class AudioImageProvider : BaseMetadataProvider
{
/// <summary>
- /// Gets or sets the image cache.
- /// </summary>
- /// <value>The image cache.</value>
- public FileSystemRepository ImageCache { get; set; }
-
- /// <summary>
/// The _locks
/// </summary>
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
@@ -48,8 +41,6 @@ namespace MediaBrowser.Providers.MediaInfo
: base(logManager, configurationManager)
{
_mediaEncoder = mediaEncoder;
-
- ImageCache = new FileSystemRepository(Kernel.Instance.FFMpegManager.AudioImagesDataPath);
}
/// <summary>
@@ -113,7 +104,7 @@ namespace MediaBrowser.Providers.MediaInfo
return ItemUpdateType.ImageUpdate;
}
}
-
+
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
@@ -154,13 +145,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
cancellationToken.ThrowIfCancellationRequested();
- var album = item.Parent as MusicAlbum;
-
- var filename = item.Album ?? string.Empty;
- filename += item.Artists.FirstOrDefault() ?? string.Empty;
- filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks;
-
- var path = ImageCache.GetResourcePath(filename + "_primary", ".jpg");
+ var path = GetAudioImagePath(item);
if (!File.Exists(path))
{
@@ -196,6 +181,38 @@ namespace MediaBrowser.Providers.MediaInfo
}
/// <summary>
+ /// Gets the audio image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>System.String.</returns>
+ private string GetAudioImagePath(Audio item)
+ {
+ var album = item.Parent as MusicAlbum;
+
+ var filename = item.Album ?? string.Empty;
+ filename += item.Artists.FirstOrDefault() ?? string.Empty;
+ filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
+
+ filename = filename.GetMD5() + ".jpg";
+
+ var prefix = filename.Substring(0, 1);
+
+ return Path.Combine(AudioImagesPath, prefix, filename);
+ }
+
+ /// <summary>
+ /// Gets the audio images data path.
+ /// </summary>
+ /// <value>The audio images data path.</value>
+ public string AudioImagesPath
+ {
+ get
+ {
+ return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-audio-images");
+ }
+ }
+
+ /// <summary>
/// Gets the lock.
/// </summary>
/// <param name="filename">The filename.</param>
diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
index 5f285e6d8..0fdeddb49 100644
--- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
@@ -115,7 +115,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (video != null)
{
- inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
+ inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
}
return await MediaEncoder.GetMediaInfo(inputPath, type, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
index 673abea57..c38007288 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
@@ -151,8 +151,6 @@ namespace MediaBrowser.Providers.MediaInfo
// Disc number
audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
- audio.Language = GetDictionaryValue(tags, "language");
-
audio.ProductionYear = GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
index 7e3e3da3b..a2a4d5bbf 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
@@ -1,8 +1,8 @@
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -343,7 +343,7 @@ namespace MediaBrowser.Providers.MediaInfo
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
- await Kernel.Instance.FFMpegManager.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
+ await FFMpegManager.Instance.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
var videoFileChanged = CompareDate(video) > providerInfo.LastRefreshed;
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 2864983ce..d5815690f 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -1,6 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -12,7 +11,6 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
using System.IO;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -21,12 +19,6 @@ namespace MediaBrowser.Providers.MediaInfo
class VideoImageProvider : BaseMetadataProvider
{
/// <summary>
- /// Gets or sets the image cache.
- /// </summary>
- /// <value>The image cache.</value>
- public FileSystemRepository ImageCache { get; set; }
-
- /// <summary>
/// The _locks
/// </summary>
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
@@ -42,8 +34,6 @@ namespace MediaBrowser.Providers.MediaInfo
{
_mediaEncoder = mediaEncoder;
_isoManager = isoManager;
-
- ImageCache = new FileSystemRepository(Kernel.Instance.FFMpegManager.VideoImagesDataPath);
}
/// <summary>
@@ -206,9 +196,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
cancellationToken.ThrowIfCancellationRequested();
- var filename = item.Path + "_" + item.DateModified.Ticks + "_primary";
-
- var path = ImageCache.GetResourcePath(filename, ".jpg");
+ var path = GetVideoImagePath(item);
if (!File.Exists(path))
{
@@ -265,7 +253,7 @@ namespace MediaBrowser.Providers.MediaInfo
InputType type;
- var inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
await _mediaEncoder.ExtractImage(inputPath, type, video.Video3DFormat, imageOffset, path, cancellationToken).ConfigureAwait(false);
@@ -310,5 +298,33 @@ namespace MediaBrowser.Providers.MediaInfo
{
return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
}
+
+ /// <summary>
+ /// Gets the video images data path.
+ /// </summary>
+ /// <value>The video images data path.</value>
+ public string VideoImagesPath
+ {
+ get
+ {
+ return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-video-images");
+ }
+ }
+
+ /// <summary>
+ /// Gets the audio image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>System.String.</returns>
+ private string GetVideoImagePath(Video item)
+ {
+ var filename = item.Path + "_" + item.DateModified.Ticks + "_primary";
+
+ filename = filename.GetMD5() + ".jpg";
+
+ var prefix = filename.Substring(0, 1);
+
+ return Path.Combine(VideoImagesPath, prefix, filename);
+ }
}
}
diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
index 952d7e218..2682cf3c0 100644
--- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
+++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
@@ -112,6 +112,11 @@ namespace MediaBrowser.Providers.Movies
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
+ return SupportsItem(item);
+ }
+
+ internal static bool SupportsItem(IHasImages item)
+ {
var trailer = item as Trailer;
if (trailer != null)
@@ -295,7 +300,7 @@ namespace MediaBrowser.Providers.Movies
cancellationToken.ThrowIfCancellationRequested();
- var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
+ var backdropLimit = ConfigurationManager.Configuration.MovieOptions.MaxBackdrops;
if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops &&
item.BackdropImagePaths.Count < backdropLimit)
{
diff --git a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
index d714128ea..fae8cd591 100644
--- a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
@@ -36,23 +36,24 @@ namespace MediaBrowser.Providers.Movies
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
- return FanArtMovieProvider.Current.Supports(item);
+ return FanArtMovieProvider.SupportsItem(item);
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
+ var baseItem = (BaseItem)item;
var list = new List<RemoteImageInfo>();
- var movieId = item.GetProviderId(MetadataProviders.Tmdb);
+ var movieId = baseItem.GetProviderId(MetadataProviders.Tmdb);
if (!string.IsNullOrEmpty(movieId))
{
@@ -68,10 +69,10 @@ namespace MediaBrowser.Providers.Movies
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
+
// Sort first by width to prioritize HD versions
list = list.OrderByDescending(i => i.Width ?? 0)
.ThenByDescending(i =>
diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
index e5bd3bf47..b9cabded7 100644
--- a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
@@ -35,19 +35,19 @@ namespace MediaBrowser.Providers.Movies
get { return "TheMovieDb"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return MovieDbImagesProvider.SupportsItem(item);
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
@@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Movies
RatingType = RatingType.Score
}));
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
@@ -114,17 +114,15 @@ namespace MediaBrowser.Providers.Movies
.ThenByDescending(i => i.VoteCount ?? 0)
.ToList();
}
-
+
/// <summary>
/// Gets the posters.
/// </summary>
/// <param name="images">The images.</param>
/// <param name="item">The item.</param>
/// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
- private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images, BaseItem item)
+ private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images, IHasImages item)
{
- var language = _config.Configuration.PreferredMetadataLanguage;
-
return images.posters ?? new List<MovieDbProvider.Poster>();
}
@@ -134,7 +132,7 @@ namespace MediaBrowser.Providers.Movies
/// <param name="images">The images.</param>
/// <param name="item">The item.</param>
/// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
- private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images, BaseItem item)
+ private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images, IHasImages item)
{
var eligibleBackdrops = images.backdrops == null ? new List<MovieDbProvider.Backdrop>() :
images.backdrops
@@ -150,9 +148,9 @@ namespace MediaBrowser.Providers.Movies
/// <param name="item">The item.</param>
/// <param name="jsonSerializer">The json serializer.</param>
/// <returns>Task{MovieImages}.</returns>
- private MovieDbProvider.Images FetchImages(BaseItem item, IJsonSerializer jsonSerializer)
+ private MovieDbProvider.Images FetchImages(IHasImages item, IJsonSerializer jsonSerializer)
{
- var path = MovieDbProvider.Current.GetImagesDataFilePath(item);
+ var path = MovieDbProvider.Current.GetDataFilePath((BaseItem)item);
if (!string.IsNullOrEmpty(path))
{
diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs
index b381de332..453284751 100644
--- a/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -34,60 +33,44 @@ namespace MediaBrowser.Providers.Movies
get { return "TheMovieDb"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Person;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
- return GetAllImagesInternal(item, true, cancellationToken);
- }
-
- public async Task<IEnumerable<RemoteImageInfo>> GetAllImagesInternal(BaseItem item, bool retryOnMissingData, CancellationToken cancellationToken)
- {
- var id = item.GetProviderId(MetadataProviders.Tmdb);
+ var person = (Person)item;
+ var id = person.GetProviderId(MetadataProviders.Tmdb);
if (!string.IsNullOrEmpty(id))
{
- var dataFilePath = MovieDbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);
-
- try
- {
- var result = _jsonSerializer.DeserializeFromFile<MovieDbPersonProvider.PersonResult>(dataFilePath);
-
- var images = result.images ?? new MovieDbPersonProvider.Images();
+ await MovieDbPersonProvider.Current.DownloadPersonInfoIfNeeded(id, cancellationToken).ConfigureAwait(false);
- var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+ var dataFilePath = MovieDbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var result = _jsonSerializer.DeserializeFromFile<MovieDbPersonProvider.PersonResult>(dataFilePath);
- return GetImages(images, tmdbImageUrl);
- }
- catch (FileNotFoundException)
- {
+ var images = result.images ?? new MovieDbPersonProvider.Images();
- }
+ var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- if (retryOnMissingData)
- {
- await MovieDbPersonProvider.Current.DownloadPersonInfo(id, cancellationToken).ConfigureAwait(false);
+ var tmdbImageUrl = tmdbSettings.images.base_url + "original";
- return await GetAllImagesInternal(item, false, cancellationToken).ConfigureAwait(false);
- }
+ return GetImages(images, item.GetPreferredMetadataLanguage(), tmdbImageUrl);
}
return new List<RemoteImageInfo>();
}
-
- private IEnumerable<RemoteImageInfo> GetImages(MovieDbPersonProvider.Images images, string baseImageUrl)
+
+ private IEnumerable<RemoteImageInfo> GetImages(MovieDbPersonProvider.Images images, string preferredLanguage, string baseImageUrl)
{
var list = new List<RemoteImageInfo>();
@@ -104,7 +87,7 @@ namespace MediaBrowser.Providers.Movies
}));
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = preferredLanguage;
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
index d63fcec5c..7386f47f4 100644
--- a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
@@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.Movies
return SupportsItem(item);
}
- public static bool SupportsItem(BaseItem item)
+ internal static bool SupportsItem(IHasImages item)
{
var trailer = item as Trailer;
@@ -132,7 +132,9 @@ namespace MediaBrowser.Providers.Movies
}
// Don't refresh if we already have both poster and backdrop and we're not refreshing images
- if (item.HasImage(ImageType.Primary) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops)
+ if (item.HasImage(ImageType.Primary) &&
+ item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MovieOptions.MaxBackdrops &&
+ !item.LockedFields.Contains(MetadataFields.Images))
{
return false;
}
@@ -142,7 +144,7 @@ namespace MediaBrowser.Providers.Movies
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
- var path = MovieDbProvider.Current.GetImagesDataFilePath(item);
+ var path = MovieDbProvider.Current.GetDataFilePath(item);
if (!string.IsNullOrEmpty(path))
{
@@ -167,7 +169,6 @@ namespace MediaBrowser.Providers.Movies
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbImageProvider.ProviderName).ConfigureAwait(false);
-
await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
@@ -190,7 +191,7 @@ namespace MediaBrowser.Providers.Movies
.ToList();
// poster
- if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary))
+ if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var poster = eligiblePosters[0];
@@ -210,13 +211,16 @@ namespace MediaBrowser.Providers.Movies
cancellationToken.ThrowIfCancellationRequested();
var eligibleBackdrops = images
- .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= ConfigurationManager.Configuration.MinMovieBackdropDownloadWidth)
+ .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= ConfigurationManager.Configuration.MovieOptions.MinBackdropWidth)
.ToList();
- var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
+ var backdropLimit = ConfigurationManager.Configuration.MovieOptions.MaxBackdrops;
// backdrops - only download if earlier providers didn't find any (fanart)
- if (eligibleBackdrops.Count > 0 && ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit)
+ if (eligibleBackdrops.Count > 0 &&
+ ConfigurationManager.Configuration.DownloadMovieImages.Backdrops &&
+ item.BackdropImagePaths.Count < backdropLimit &&
+ !item.LockedFields.Contains(MetadataFields.Backdrops))
{
for (var i = 0; i < eligibleBackdrops.Count; i++)
{
diff --git a/MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs
index 8fa2ea249..f6c908a7c 100644
--- a/MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs
@@ -164,7 +164,6 @@ namespace MediaBrowser.Providers.Movies
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbPersonImageProvider.ProviderName).ConfigureAwait(false);
-
await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
@@ -187,7 +186,7 @@ namespace MediaBrowser.Providers.Movies
.ToList();
// poster
- if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary))
+ if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var poster = eligiblePosters[0];
diff --git a/MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs b/MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs
index 3efd8d7fe..c16c50412 100644
--- a/MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs
@@ -86,7 +86,7 @@ namespace MediaBrowser.Providers.Movies
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
- if (HasAltMeta(item) && !ConfigurationManager.Configuration.EnableTmdbUpdates)
+ if (HasAltMeta(item))
return false;
return base.NeedsRefreshInternal(item, providerInfo);
@@ -235,17 +235,12 @@ namespace MediaBrowser.Providers.Movies
/// <returns>Task.</returns>
private async Task FetchInfo(Person person, string id, bool isForcedRefresh, CancellationToken cancellationToken)
{
- var dataFilePath = GetPersonDataFilePath(ConfigurationManager.ApplicationPaths, id);
+ await DownloadPersonInfoIfNeeded(id, cancellationToken).ConfigureAwait(false);
- // Only download if not already there
- // The prescan task will take care of updates so we don't need to re-download here
- if (!File.Exists(dataFilePath))
+ if (isForcedRefresh || !HasAltMeta(person))
{
- await DownloadPersonInfo(id, cancellationToken).ConfigureAwait(false);
- }
+ var dataFilePath = GetPersonDataFilePath(ConfigurationManager.ApplicationPaths, id);
- if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(person))
- {
var info = JsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
cancellationToken.ThrowIfCancellationRequested();
@@ -254,10 +249,17 @@ namespace MediaBrowser.Providers.Movies
}
}
- internal async Task DownloadPersonInfo(string id, CancellationToken cancellationToken)
+ internal async Task DownloadPersonInfoIfNeeded(string id, CancellationToken cancellationToken)
{
var personDataPath = GetPersonDataPath(ConfigurationManager.ApplicationPaths, id);
+ var fileInfo = _fileSystem.GetFileSystemInfo(personDataPath);
+
+ if (fileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
+ {
+ return;
+ }
+
var url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images", MovieDbProvider.ApiKey, id);
using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
index a038d9b4c..c6c9df6f7 100644
--- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
@@ -205,13 +205,9 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(path))
{
- var imagesFilePath = GetImagesDataFilePath(item);
-
var fileInfo = new FileInfo(path);
- var imagesFileInfo = new FileInfo(imagesFilePath);
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed ||
- !imagesFileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(imagesFileInfo) > providerInfo.LastRefreshed;
+ return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
}
return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
@@ -318,7 +314,7 @@ namespace MediaBrowser.Providers.Movies
var year = item.ProductionYear ?? yearInName;
Logger.Info("MovieDbProvider: Finding id for item: " + name);
- string language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower();
+ var language = item.GetPreferredMetadataLanguage().ToLower();
//if we are a boxset - look at our first child
var boxset = item as BoxSet;
@@ -502,43 +498,35 @@ namespace MediaBrowser.Providers.Movies
{
// Id could be ImdbId or TmdbId
- var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
+ var country = item.GetPreferredMetadataCountryCode();
var dataFilePath = GetDataFilePath(item);
var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
- if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath) || !File.Exists(GetImagesDataFilePath(item)))
- {
- var isBoxSet = item is BoxSet;
+ var isBoxSet = item is BoxSet;
+ if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath))
+ {
var mainResult = await FetchMainResult(id, isBoxSet, language, cancellationToken).ConfigureAwait(false);
if (mainResult == null) return;
tmdbId = mainResult.id.ToString(_usCulture);
- var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxSet, tmdbId);
-
- dataFilePath = Path.Combine(movieDataPath, language + ".json");
+ dataFilePath = GetDataFilePath(isBoxSet, tmdbId, language);
var directory = Path.GetDirectoryName(dataFilePath);
Directory.CreateDirectory(directory);
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
-
- // Now get the language-less version
- mainResult = await FetchMainResult(id, isBoxSet, null, cancellationToken).ConfigureAwait(false);
-
- dataFilePath = Path.Combine(movieDataPath, "default.json");
-
- JsonSerializer.SerializeToFile(mainResult, dataFilePath);
}
if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item))
{
- dataFilePath = GetDataFilePath(item, tmdbId);
+ dataFilePath = GetDataFilePath(isBoxSet, tmdbId, language);
if (!string.IsNullOrEmpty(dataFilePath))
{
@@ -554,27 +542,18 @@ namespace MediaBrowser.Providers.Movies
/// </summary>
/// <param name="id">The id.</param>
/// <param name="isBoxSet">if set to <c>true</c> [is box set].</param>
- /// <param name="dataPath">The data path.</param>
+ /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- internal async Task DownloadMovieInfo(string id, bool isBoxSet, string dataPath, CancellationToken cancellationToken)
+ internal async Task DownloadMovieInfo(string id, bool isBoxSet, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
-
- var mainResult = await FetchMainResult(id, isBoxSet, language, cancellationToken).ConfigureAwait(false);
+ var mainResult = await FetchMainResult(id, isBoxSet, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
if (mainResult == null) return;
- var dataFilePath = Path.Combine(dataPath, language + ".json");
+ var dataFilePath = GetDataFilePath(isBoxSet, id, preferredMetadataLanguage);
- Directory.CreateDirectory(dataPath);
-
- JsonSerializer.SerializeToFile(mainResult, dataFilePath);
-
- // Now get the language-less version
- mainResult = await FetchMainResult(id, isBoxSet, null, cancellationToken).ConfigureAwait(false);
-
- dataFilePath = Path.Combine(dataPath, "default.json");
+ Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
}
@@ -593,30 +572,17 @@ namespace MediaBrowser.Providers.Movies
return null;
}
- return GetDataFilePath(item, id);
- }
-
- internal string GetDataFilePath(BaseItem item, string tmdbId)
- {
- var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
-
- var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, item is BoxSet, tmdbId);
-
- path = Path.Combine(path, language + ".json");
-
- return path;
+ return GetDataFilePath(item is BoxSet, id, item.GetPreferredMetadataLanguage());
}
- internal string GetImagesDataFilePath(BaseItem item)
+ internal string GetDataFilePath(bool isBoxset, string tmdbId, string preferredLanguage)
{
- var path = GetDataFilePath(item);
+ var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxset, tmdbId);
- if (!string.IsNullOrEmpty(path))
- {
- path = Path.Combine(Path.GetDirectoryName(path), "default.json");
- }
+ var filename = string.Format("all-{0}.json",
+ preferredLanguage ?? string.Empty);
- return path;
+ return Path.Combine(path, filename);
}
/// <summary>
@@ -633,9 +599,18 @@ namespace MediaBrowser.Providers.Movies
var url = string.Format(baseUrl, id, ApiKey);
+ // Get images in english and with no language
+ url += "&include_image_language=en,null";
+
if (!string.IsNullOrEmpty(language))
{
- url += "&language=" + language;
+ // If preferred language isn't english, get those images too
+ if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
+ {
+ url += string.Format(",{0}", language);
+ }
+
+ url += string.Format("&language={0}", language);
}
CompleteMovieData mainResult;
@@ -745,27 +720,29 @@ namespace MediaBrowser.Providers.Movies
// tmdb appears to have unified their numbers to always report "7.3" regardless of country
// so I removed the culture-specific processing here because it was not working for other countries -ebr
// Movies get this from imdb
- if (movie is BoxSet && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
+ if (!(movie is Movie) && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
{
movie.CommunityRating = rating;
}
// Movies get this from imdb
- if (movie is BoxSet)
+ if (!(movie is Movie))
{
movie.VoteCount = movieData.vote_count;
}
+ var preferredCountryCode = movie.GetPreferredMetadataCountryCode();
+
//release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match
if (movieData.releases != null && movieData.releases.countries != null)
{
- var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(ConfigurationManager.Configuration.MetadataCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country();
+ var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country();
var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country();
var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country();
if (!movie.LockedFields.Contains(MetadataFields.OfficialRating))
{
- var ratingPrefix = ConfigurationManager.Configuration.MetadataCountryCode.Equals("us", StringComparison.OrdinalIgnoreCase) ? "" : ConfigurationManager.Configuration.MetadataCountryCode + "-";
+ var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
? ratingPrefix + ourRelease.certification
: !string.IsNullOrEmpty(usRelease.certification)
@@ -811,7 +788,7 @@ namespace MediaBrowser.Providers.Movies
}
}
- //if that didn't find a rating and we are a boxset, use the one from our first child
+ // If that didn't find a rating and we are a boxset, use the one from our first child
if (movie.OfficialRating == null && movie is BoxSet && !movie.LockedFields.Contains(MetadataFields.OfficialRating))
{
var boxset = movie as BoxSet;
@@ -838,15 +815,17 @@ namespace MediaBrowser.Providers.Movies
// genres
// Movies get this from imdb
- if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres))
+ var genres = movieData.genres ?? new List<GenreItem>();
+ if (!movie.LockedFields.Contains(MetadataFields.Genres))
{
// Only grab them if a boxset or there are no genres.
// For movies and trailers we'll use imdb via omdb
- if (movie is BoxSet || movie.Genres.Count == 0)
+ // But omdb data is for english users only so fetch if language is not english
+ if (!(movie is Movie) || movie.Genres.Count == 0 || !string.Equals(movie.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase))
{
movie.Genres.Clear();
- foreach (var genre in movieData.genres.Select(g => g.name))
+ foreach (var genre in genres.Select(g => g.name))
{
movie.AddGenre(genre);
}
diff --git a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
index 46e947c70..291d2ff4d 100644
--- a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
+++ b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
@@ -2,7 +2,10 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
@@ -37,6 +40,7 @@ namespace MediaBrowser.Providers.Movies
private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _json;
private readonly IFileSystem _fileSystem;
+ private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="MovieUpdatesPreScanTask"/> class.
@@ -45,13 +49,14 @@ namespace MediaBrowser.Providers.Movies
/// <param name="httpClient">The HTTP client.</param>
/// <param name="config">The config.</param>
/// <param name="json">The json.</param>
- public MovieUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem)
+ public MovieUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_logger = logger;
_httpClient = httpClient;
_config = config;
_json = json;
_fileSystem = fileSystem;
+ _libraryManager = libraryManager;
}
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -101,7 +106,7 @@ namespace MediaBrowser.Providers.Movies
var timestampFileInfo = new FileInfo(timestampFile);
// Don't check for updates every single time
- if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3)
+ if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 7)
{
return;
}
@@ -196,15 +201,30 @@ namespace MediaBrowser.Providers.Movies
var list = ids.ToList();
var numComplete = 0;
+ // Gather all movies into a lookup by tmdb id
+ var allMovies = _libraryManager.RootFolder.RecursiveChildren
+ .Where(i => i is Movie || i is Trailer)
+ .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb)))
+ .ToLookup(i => i.GetProviderId(MetadataProviders.Tmdb));
+
foreach (var id in list)
{
- try
- {
- await UpdateMovie(id, isBoxSet, moviesDataPath, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
+ // Find the preferred language(s) for the movie in the library
+ var languages = allMovies[id]
+ .Select(i => i.GetPreferredMetadataLanguage())
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ foreach (var language in languages)
{
- _logger.ErrorException("Error updating tmdb movie id {0}", ex, id);
+ try
+ {
+ await UpdateMovie(id, isBoxSet, language, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error updating tmdb movie id {0}, language {1}", ex, id, language);
+ }
}
numComplete++;
@@ -221,18 +241,14 @@ namespace MediaBrowser.Providers.Movies
/// </summary>
/// <param name="id">The id.</param>
/// <param name="isBoxSet">if set to <c>true</c> [is box set].</param>
- /// <param name="dataPath">The data path.</param>
+ /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private Task UpdateMovie(string id, bool isBoxSet, string dataPath, CancellationToken cancellationToken)
+ private Task UpdateMovie(string id, bool isBoxSet, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- _logger.Info("Updating movie from tmdb " + id);
-
- var itemDataPath = Path.Combine(dataPath, id);
-
- Directory.CreateDirectory(dataPath);
+ _logger.Info("Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage);
- return MovieDbProvider.Current.DownloadMovieInfo(id, isBoxSet, itemDataPath, cancellationToken);
+ return MovieDbProvider.Current.DownloadMovieInfo(id, isBoxSet, preferredMetadataLanguage, cancellationToken);
}
class Result
diff --git a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
index 8370eecbb..8940f1d49 100644
--- a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
+++ b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
@@ -202,13 +202,21 @@ namespace MediaBrowser.Providers.Movies
private bool ShouldFetchGenres(BaseItem item)
{
+ var lang = item.GetPreferredMetadataLanguage();
+
+ // The data isn't localized and so can only be used for english users
+ if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
// Only fetch if other providers didn't get anything
if (item is Trailer)
{
return item.Genres.Count == 0;
}
- return item is Series;
+ return item is Series || item is Movie;
}
protected class RootObject
diff --git a/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs b/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs
deleted file mode 100644
index 489b0ad09..000000000
--- a/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs
+++ /dev/null
@@ -1,236 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Movies
-{
- public class PersonUpdatesPreScanTask : IPeoplePrescanTask
- {
- /// <summary>
- /// The updates URL
- /// </summary>
- private const string UpdatesUrl = "http://api.themoviedb.org/3/person/changes?start_date={0}&api_key={1}&page={2}";
-
- /// <summary>
- /// The _HTTP client
- /// </summary>
- private readonly IHttpClient _httpClient;
- /// <summary>
- /// The _logger
- /// </summary>
- private readonly ILogger _logger;
- /// <summary>
- /// The _config
- /// </summary>
- private readonly IServerConfigurationManager _config;
- private readonly IJsonSerializer _json;
- private readonly IFileSystem _fileSystem;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="PersonUpdatesPreScanTask"/> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="httpClient">The HTTP client.</param>
- /// <param name="config">The config.</param>
- public PersonUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem)
- {
- _logger = logger;
- _httpClient = httpClient;
- _config = config;
- _json = json;
- _fileSystem = fileSystem;
- }
-
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- /// <summary>
- /// Runs the specified progress.
- /// </summary>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
- {
- if (!_config.Configuration.EnableInternetProviders || !_config.Configuration.EnableTmdbUpdates)
- {
- progress.Report(100);
- return;
- }
-
- var path = MovieDbPersonProvider.GetPersonsDataPath(_config.CommonApplicationPaths);
-
- Directory.CreateDirectory(path);
-
- var timestampFile = Path.Combine(path, "time.txt");
-
- var timestampFileInfo = new FileInfo(timestampFile);
-
- // Don't check for updates every single time
- if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3)
- {
- return;
- }
-
- // Find out the last time we queried tvdb for updates
- var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
-
- var existingDirectories = GetExistingIds(path).ToList();
-
- if (!string.IsNullOrEmpty(lastUpdateTime))
- {
- long lastUpdateTicks;
-
- if (long.TryParse(lastUpdateTime, NumberStyles.Any, UsCulture, out lastUpdateTicks))
- {
- var lastUpdateDate = new DateTime(lastUpdateTicks, DateTimeKind.Utc);
-
- // They only allow up to 14 days of updates
- if ((DateTime.UtcNow - lastUpdateDate).TotalDays > 13)
- {
- lastUpdateDate = DateTime.UtcNow.AddDays(-13);
- }
-
- var updatedIds = await GetIdsToUpdate(lastUpdateDate, 1, cancellationToken).ConfigureAwait(false);
-
- var existingDictionary = existingDirectories.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- var idsToUpdate = updatedIds.Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i));
-
- await UpdatePeople(idsToUpdate, progress, cancellationToken).ConfigureAwait(false);
- }
- }
-
- File.WriteAllText(timestampFile, DateTime.UtcNow.Ticks.ToString(UsCulture), Encoding.UTF8);
- progress.Report(100);
- }
-
- /// <summary>
- /// Gets the existing ids.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>IEnumerable{System.String}.</returns>
- private IEnumerable<string> GetExistingIds(string path)
- {
- return Directory.EnumerateDirectories(path)
- .SelectMany(Directory.EnumerateDirectories)
- .Select(Path.GetFileNameWithoutExtension);
- }
-
- /// <summary>
- /// Gets the ids to update.
- /// </summary>
- /// <param name="lastUpdateTime">The last update time.</param>
- /// <param name="page">The page.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{System.String}}.</returns>
- private async Task<IEnumerable<string>> GetIdsToUpdate(DateTime lastUpdateTime, int page, CancellationToken cancellationToken)
- {
- var hasMorePages = false;
- var list = new List<string>();
-
- // First get last time
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = string.Format(UpdatesUrl, lastUpdateTime.ToString("yyyy-MM-dd"), MovieDbProvider.ApiKey, page),
- CancellationToken = cancellationToken,
- EnableHttpCompression = true,
- ResourcePool = MovieDbProvider.Current.MovieDbResourcePool,
- AcceptHeader = MovieDbProvider.AcceptHeader
-
- }).ConfigureAwait(false))
- {
- var obj = _json.DeserializeFromStream<RootObject>(stream);
-
- var data = obj.results.Select(i => i.id.ToString(UsCulture));
-
- list.AddRange(data);
-
- hasMorePages = page < obj.total_pages;
- }
-
- if (hasMorePages)
- {
- var more = await GetIdsToUpdate(lastUpdateTime, page + 1, cancellationToken).ConfigureAwait(false);
-
- list.AddRange(more);
- }
-
- return list;
- }
-
- /// <summary>
- /// Updates the people.
- /// </summary>
- /// <param name="ids">The ids.</param>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task UpdatePeople(IEnumerable<string> ids, IProgress<double> progress, CancellationToken cancellationToken)
- {
- var list = ids.ToList();
- var numComplete = 0;
-
- foreach (var id in list)
- {
- try
- {
- await UpdatePerson(id, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error updating tmdb person id {0}", ex, id);
- }
-
- numComplete++;
- double percent = numComplete;
- percent /= list.Count;
- percent *= 100;
-
- progress.Report(percent);
- }
- }
-
- /// <summary>
- /// Updates the person.
- /// </summary>
- /// <param name="id">The id.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private Task UpdatePerson(string id, CancellationToken cancellationToken)
- {
- _logger.Info("Updating person from tmdb " + id);
-
- return MovieDbPersonProvider.Current.DownloadPersonInfo(id, cancellationToken);
- }
-
- class Result
- {
- public int id { get; set; }
- public bool? adult { get; set; }
- }
-
- class RootObject
- {
- public List<Result> results { get; set; }
- public int page { get; set; }
- public int total_pages { get; set; }
- public int total_results { get; set; }
-
- public RootObject()
- {
- results = new List<Result>();
- }
- }
- }
-}
diff --git a/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs b/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs
index c4b4af97f..47799b8f3 100644
--- a/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs
+++ b/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs
@@ -140,7 +140,6 @@ namespace MediaBrowser.Providers.Music
}
}
-
providerInfo.FileStamp = GetComparisonData(songs);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
index 2e072b098..b6e0d61f7 100644
--- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
@@ -154,9 +154,11 @@ namespace MediaBrowser.Providers.Music
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
- var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartAlbumProvider.ProviderName).ConfigureAwait(false);
-
- await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
+ if (!item.LockedFields.Contains(MetadataFields.Images))
+ {
+ var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartAlbumProvider.ProviderName).ConfigureAwait(false);
+ await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
+ }
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
index 57a191ab1..b248fcb40 100644
--- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
@@ -213,13 +213,12 @@ namespace MediaBrowser.Providers.Music
}
if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art ||
- ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops ||
- ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner ||
- ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo ||
- ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary)
+ ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops ||
+ ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner ||
+ ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo ||
+ ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary)
{
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartArtistProvider.ProviderName).ConfigureAwait(false);
-
await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
}
@@ -268,46 +267,52 @@ namespace MediaBrowser.Providers.Music
/// <returns>Task.</returns>
private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images , CancellationToken cancellationToken)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && !item.HasImage(ImageType.Primary))
+ if (!item.LockedFields.Contains(MetadataFields.Images))
{
- await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && !item.HasImage(ImageType.Primary))
+ {
+ await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo && !item.HasImage(ImageType.Logo))
- {
- await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo && !item.HasImage(ImageType.Logo))
+ {
+ await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && !item.HasImage(ImageType.Art))
- {
- await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && !item.HasImage(ImageType.Art))
+ {
+ await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner && !item.HasImage(ImageType.Banner))
- {
- await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner && !item.HasImage(ImageType.Banner))
+ {
+ await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
+ }
+ }
- var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops &&
- item.BackdropImagePaths.Count < backdropLimit)
+ if (!item.LockedFields.Contains(MetadataFields.Backdrops))
{
- foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var backdropLimit = ConfigurationManager.Configuration.MusicOptions.MaxBackdrops;
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops &&
+ item.BackdropImagePaths.Count < backdropLimit)
{
- await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
- .ConfigureAwait(false);
+ foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ {
+ await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
+ .ConfigureAwait(false);
- if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ }
}
}
}
diff --git a/MediaBrowser.Providers/Music/LastFmImageProvider.cs b/MediaBrowser.Providers/Music/LastFmImageProvider.cs
index 2a30a3a2e..98ba58fa8 100644
--- a/MediaBrowser.Providers/Music/LastFmImageProvider.cs
+++ b/MediaBrowser.Providers/Music/LastFmImageProvider.cs
@@ -90,7 +90,7 @@ namespace MediaBrowser.Providers.Music
? ConfigurationManager.Configuration.DownloadMusicAlbumImages
: ConfigurationManager.Configuration.DownloadMusicArtistImages;
- if (configSetting.Primary && !item.HasImage(ImageType.Primary))
+ if (configSetting.Primary && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
diff --git a/MediaBrowser.Providers/Music/LastfmHelper.cs b/MediaBrowser.Providers/Music/LastfmHelper.cs
index 3301d5584..df02cee5b 100644
--- a/MediaBrowser.Providers/Music/LastfmHelper.cs
+++ b/MediaBrowser.Providers/Music/LastfmHelper.cs
@@ -81,16 +81,20 @@ namespace MediaBrowser.Providers.Music
}
// Only grab the date here if the album doesn't already have one, since id3 tags are preferred
- if (!item.PremiereDate.HasValue)
- {
- DateTime release;
+ DateTime release;
- if (DateTime.TryParse(data.releasedate, out release))
+ if (DateTime.TryParse(data.releasedate, out release))
+ {
+ // Lastfm sends back null as sometimes 1901, other times 0
+ if (release.Year > 1901)
{
- // Lastfm sends back null as sometimes 1901, other times 0
- if (release.Year > 1901)
+ if (!item.PremiereDate.HasValue)
{
item.PremiereDate = release;
+ }
+
+ if (!item.ProductionYear.HasValue)
+ {
item.ProductionYear = release.Year;
}
}
diff --git a/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs b/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
index d95365b02..5c923869f 100644
--- a/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
@@ -37,32 +37,34 @@ namespace MediaBrowser.Providers.Music
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is MusicAlbum;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
+ var album = (MusicAlbum)item;
+
var list = new List<RemoteImageInfo>();
- var artistMusicBrainzId = item.Parent.GetProviderId(MetadataProviders.Musicbrainz);
+ var artistMusicBrainzId = album.Parent.GetProviderId(MetadataProviders.Musicbrainz);
if (!string.IsNullOrEmpty(artistMusicBrainzId))
{
var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths, artistMusicBrainzId);
artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml");
- var musicBrainzReleaseGroupId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+ var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
- var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
+ var musicBrainzId = album.GetProviderId(MetadataProviders.Musicbrainz);
try
{
@@ -74,7 +76,7 @@ namespace MediaBrowser.Providers.Music
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs
index cdb07d3d7..ddf5064aa 100644
--- a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs
+++ b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs
@@ -37,23 +37,25 @@ namespace MediaBrowser.Providers.Music
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is MusicArtist;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
+ var artist = (MusicArtist)item;
+
var list = new List<RemoteImageInfo>();
- var artistMusicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
+ var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz);
if (!string.IsNullOrEmpty(artistMusicBrainzId))
{
@@ -70,7 +72,7 @@ namespace MediaBrowser.Providers.Music
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs
index 72e8c6f6b..6d6f1ec7b 100644
--- a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs
+++ b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs
@@ -23,19 +23,19 @@ namespace MediaBrowser.Providers.Music
get { return "last.fm"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is MusicAlbum || item is MusicArtist;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
diff --git a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
index 6880c9948..ad7f1287f 100644
--- a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
+++ b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
@@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Savers
// If new metadata has been downloaded or metadata was manually edited, proceed
if ((wasMetadataEdited || wasMetadataDownloaded))
{
- return item is Channel;
+ return item is LiveTvChannel;
}
return false;
diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
index ac8270dfe..dc2d5eddd 100644
--- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
+++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
@@ -326,12 +326,12 @@ namespace MediaBrowser.Providers.Savers
}
}
- var hasLanguage = item as IHasLanguage;
+ var hasLanguage = item as IHasPreferredMetadataLanguage;
if (hasLanguage != null)
{
- if (!string.IsNullOrEmpty(hasLanguage.Language))
+ if (!string.IsNullOrEmpty(hasLanguage.PreferredMetadataLanguage))
{
- builder.Append("<Language>" + SecurityElement.Escape(hasLanguage.Language) + "</Language>");
+ builder.Append("<Language>" + SecurityElement.Escape(hasLanguage.PreferredMetadataLanguage) + "</Language>");
}
}
@@ -426,8 +426,6 @@ namespace MediaBrowser.Providers.Savers
{
if (hasTagline.Taglines.Count > 0)
{
- builder.Append("<TagLine>" + SecurityElement.Escape(hasTagline.Taglines[0]) + "</TagLine>");
-
builder.Append("<Taglines>");
foreach (var tagline in hasTagline.Taglines)
@@ -449,8 +447,6 @@ namespace MediaBrowser.Providers.Savers
}
builder.Append("</Genres>");
-
- builder.Append("<Genre>" + SecurityElement.Escape(string.Join("|", item.Genres.ToArray())) + "</Genre>");
}
if (item.Studios.Count > 0)
diff --git a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs
index 4427e60e4..3e7597e0d 100644
--- a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs
+++ b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs
@@ -50,7 +50,12 @@ namespace MediaBrowser.Providers.TV
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
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;
}
/// <summary>
diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
index 713a95b26..50ce72a89 100644
--- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
@@ -101,11 +101,10 @@ namespace MediaBrowser.Providers.TV
{
cancellationToken.ThrowIfCancellationRequested();
- var season = (Season)item;
+ var season = (Season) item;
// Process images
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartSeasonImageProvider.ProviderName).ConfigureAwait(false);
-
await FetchImages(season, images.ToList(), cancellationToken).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
@@ -121,7 +120,7 @@ namespace MediaBrowser.Providers.TV
/// <returns>Task.</returns>
private async Task FetchImages(Season season, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
- if (ConfigurationManager.Configuration.DownloadSeasonImages.Thumb && !season.HasImage(ImageType.Thumb))
+ if (ConfigurationManager.Configuration.DownloadSeasonImages.Thumb && !season.HasImage(ImageType.Thumb) && !season.LockedFields.Contains(MetadataFields.Images))
{
await SaveImage(season, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs
index 90af81ec3..286702b8c 100644
--- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs
+++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs
@@ -196,56 +196,61 @@ namespace MediaBrowser.Providers.TV
/// <returns>Task.</returns>
private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Primary && !item.HasImage(ImageType.Primary))
+ if (!item.LockedFields.Contains(MetadataFields.Images))
{
- await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Primary && !item.HasImage(ImageType.Primary))
+ {
+ await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Logo && !item.HasImage(ImageType.Logo))
- {
- await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Logo && !item.HasImage(ImageType.Logo))
+ {
+ await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Art && !item.HasImage(ImageType.Art))
- {
- await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Art && !item.HasImage(ImageType.Art))
+ {
+ await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Thumb && !item.HasImage(ImageType.Thumb))
- {
- await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Thumb && !item.HasImage(ImageType.Thumb))
+ {
+ await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
- {
- await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
+ {
+ await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
+ }
+ }
- var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops &&
- item.BackdropImagePaths.Count < backdropLimit)
+ if (!item.LockedFields.Contains(MetadataFields.Backdrops))
{
- foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var backdropLimit = ConfigurationManager.Configuration.TvOptions.MaxBackdrops;
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops &&
+ item.BackdropImagePaths.Count < backdropLimit)
{
- await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
- .ConfigureAwait(false);
+ foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ {
+ await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
+ .ConfigureAwait(false);
- if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ }
}
}
-
}
private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
index f9b779011..503c56d0d 100644
--- a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
@@ -37,35 +37,36 @@ namespace MediaBrowser.Providers.TV
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Season;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
- var series = ((Season)item).Series;
+ var season = (Season)item;
+ var series = season.Series;
if (series != null)
{
var id = series.GetProviderId(MetadataProviders.Tvdb);
- if (!string.IsNullOrEmpty(id) && item.IndexNumber.HasValue)
+ if (!string.IsNullOrEmpty(id) && season.IndexNumber.HasValue)
{
var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id);
try
{
- AddImages(list, item.IndexNumber.Value, xmlPath, cancellationToken);
+ AddImages(list, season.IndexNumber.Value, xmlPath, cancellationToken);
}
catch (FileNotFoundException)
{
@@ -74,7 +75,7 @@ namespace MediaBrowser.Providers.TV
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs b/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
index cb7a4efd1..6a80f720a 100644
--- a/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
@@ -37,19 +37,19 @@ namespace MediaBrowser.Providers.TV
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Series;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
@@ -71,7 +71,7 @@ namespace MediaBrowser.Providers.TV
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs
index d63fb5091..6d38dee2e 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs
@@ -31,19 +31,19 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Episode;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var episode = (Episode)item;
diff --git a/MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs
index adeca12f2..456db1048 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs
@@ -37,19 +37,19 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Person;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var seriesWithPerson = _library.RootFolder
.RecursiveChildren
diff --git a/MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs
index 3efd0a3e3..d9a6f6507 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs
@@ -38,19 +38,19 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Season;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var season = (Season)item;
@@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.TV
try
{
- var result = GetImages(path, season.IndexNumber.Value, cancellationToken);
+ var result = GetImages(path, item.GetPreferredMetadataLanguage(), season.IndexNumber.Value, cancellationToken);
return Task.FromResult(result);
}
@@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.TV
return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
}
- private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, int seasonNumber, CancellationToken cancellationToken)
+ private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, int seasonNumber, CancellationToken cancellationToken)
{
var settings = new XmlReaderSettings
{
@@ -123,13 +123,11 @@ namespace MediaBrowser.Providers.TV
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+ var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
{
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
{
return 3;
}
diff --git a/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
index 5987215d1..644cad93b 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
@@ -38,21 +38,22 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Series;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
- var seriesId = item.GetProviderId(MetadataProviders.Tvdb);
+ var series = (Series)item;
+ var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
if (!string.IsNullOrEmpty(seriesId))
{
@@ -63,7 +64,7 @@ namespace MediaBrowser.Providers.TV
try
{
- var result = GetImages(path, cancellationToken);
+ var result = GetImages(path, item.GetPreferredMetadataLanguage(), cancellationToken);
return Task.FromResult(result);
}
@@ -76,7 +77,7 @@ namespace MediaBrowser.Providers.TV
return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
}
- private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, CancellationToken cancellationToken)
+ private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
{
var settings = new XmlReaderSettings
{
@@ -121,13 +122,11 @@ namespace MediaBrowser.Providers.TV
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+ var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
{
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
{
return 3;
}
diff --git a/MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs b/MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs
new file mode 100644
index 000000000..593784201
--- /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
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
+ /// </summary>
+ /// <param name="logManager">The log manager.</param>
+ /// <param name="configurationManager">The configuration manager.</param>
+ public SeasonIndexNumberProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
+ : base(logManager, configurationManager)
+ {
+ }
+
+ protected override bool RefreshOnVersionChange
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ protected override string ProviderVersion
+ {
+ get
+ {
+ return "2";
+ }
+ }
+
+ /// <summary>
+ /// Supportses the specified item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ public override bool Supports(BaseItem item)
+ {
+ if (item is Season)
+ {
+ var locationType = item.LocationType;
+ return locationType != LocationType.Virtual && locationType != LocationType.Remote;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="force">if set to <c>true</c> [force].</param>
+ /// <param name="providerInfo">The provider information.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{System.Boolean}.</returns>
+ public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ item.IndexNumber = TVUtils.GetSeasonNumberFromPath(item.Path);
+
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+
+ return TrueTaskResult;
+ }
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.First; }
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs b/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs
index 2127234dc..9fbcad7c0 100644
--- a/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs
+++ b/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs
@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
- get { return MetadataProviderPriority.First; }
+ get { return MetadataProviderPriority.Second; }
}
private const string XmlFileName = "season.xml";
diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
index 56b06f490..b889b991e 100644
--- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
+++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
@@ -39,8 +39,7 @@ namespace MediaBrowser.Providers.TV
private async Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
- if (!_config.Configuration.EnableInternetProviders ||
- _config.Configuration.InternetProviderExcludeTypes.Contains(typeof(Series).Name, StringComparer.OrdinalIgnoreCase))
+ if (!_config.Configuration.EnableInternetProviders)
{
progress.Report(100);
return;
diff --git a/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
index 3a503ea20..f2ce92efd 100644
--- a/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
@@ -75,9 +75,10 @@ namespace MediaBrowser.Providers.TV
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
+
private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
- if (!item.HasImage(ImageType.Primary))
+ if (!item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
diff --git a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
index df5c39b65..24d231192 100644
--- a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
+++ b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
@@ -45,6 +46,7 @@ namespace MediaBrowser.Providers.TV
/// </summary>
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
+ private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="TvdbPrescanTask"/> class.
@@ -52,12 +54,13 @@ namespace MediaBrowser.Providers.TV
/// <param name="logger">The logger.</param>
/// <param name="httpClient">The HTTP client.</param>
/// <param name="config">The config.</param>
- public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem)
+ public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_logger = logger;
_httpClient = httpClient;
_config = config;
_fileSystem = fileSystem;
+ _libraryManager = libraryManager;
}
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -70,8 +73,7 @@ namespace MediaBrowser.Providers.TV
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- if (!_config.Configuration.EnableInternetProviders ||
- _config.Configuration.InternetProviderExcludeTypes.Contains(typeof(Series).Name, StringComparer.OrdinalIgnoreCase))
+ if (!_config.Configuration.EnableInternetProviders)
{
progress.Report(100);
return;
@@ -274,19 +276,36 @@ namespace MediaBrowser.Providers.TV
var list = seriesIds.ToList();
var numComplete = 0;
+ // Gather all series into a lookup by tvdb id
+ var allSeries = _libraryManager.RootFolder.RecursiveChildren
+ .OfType<Series>()
+ .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
+ .ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb));
+
foreach (var seriesId in list)
{
- try
- {
- await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, cancellationToken).ConfigureAwait(false);
- }
- catch (HttpException ex)
+ // Find the preferred language(s) for the movie in the library
+ var languages = allSeries[seriesId]
+ .Select(i => i.GetPreferredMetadataLanguage())
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ foreach (var language in languages)
{
- // Already logged at lower levels, but don't fail the whole operation, unless timed out
- // We have to fail this to make it run again otherwise new episode data could potentially be missing
- if (ex.IsTimedOut)
+ try
{
- throw;
+ await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, language, cancellationToken).ConfigureAwait(false);
+ }
+ catch (HttpException ex)
+ {
+ _logger.ErrorException("Error updating tvdb series id {0}, language {1}", ex, seriesId, language);
+
+ // Already logged at lower levels, but don't fail the whole operation, unless timed out
+ // We have to fail this to make it run again otherwise new episode data could potentially be missing
+ if (ex.IsTimedOut)
+ {
+ throw;
+ }
}
}
@@ -305,17 +324,18 @@ namespace MediaBrowser.Providers.TV
/// <param name="id">The id.</param>
/// <param name="seriesDataPath">The series data path.</param>
/// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
+ /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, CancellationToken cancellationToken)
+ private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- _logger.Info("Updating series " + id);
+ _logger.Info("Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage);
seriesDataPath = Path.Combine(seriesDataPath, id);
Directory.CreateDirectory(seriesDataPath);
- return TvdbSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, lastTvDbUpdateTime, cancellationToken);
+ return TvdbSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs b/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs
index c5e5327c3..17ed6b5a2 100644
--- a/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs
@@ -159,17 +159,20 @@ namespace MediaBrowser.Providers.TV
private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
{
- if (!item.HasImage(ImageType.Primary))
+ if (!item.LockedFields.Contains(MetadataFields.Images))
{
- await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
- }
+ if (!item.HasImage(ImageType.Primary))
+ {
+ await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !item.HasImage(ImageType.Banner))
- {
- await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
+ if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !item.HasImage(ImageType.Banner))
+ {
+ await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
+ }
}
- if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit)
+ if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
{
foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop))
{
diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
index c1ac0e386..21a72dd57 100644
--- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
@@ -137,13 +137,13 @@ namespace MediaBrowser.Providers.TV
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
- if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops)
+ if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.TvOptions.MaxBackdrops)
{
return false;
}
return base.NeedsRefreshInternal(item, providerInfo);
}
-
+
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
@@ -167,33 +167,36 @@ namespace MediaBrowser.Providers.TV
private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
{
- if (!item.HasImage(ImageType.Primary))
+ if (!item.LockedFields.Contains(MetadataFields.Images))
{
- var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
-
- if (image != null)
+ if (!item.HasImage(ImageType.Primary))
{
- await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
- .ConfigureAwait(false);
- }
- }
+ var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
- {
- var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
+ if (image != null)
+ {
+ await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ }
- if (image != null)
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
{
- await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
- .ConfigureAwait(false);
+ var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
+
+ if (image != null)
+ {
+ await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+ .ConfigureAwait(false);
+ }
}
}
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit)
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
{
foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop &&
(!i.Width.HasValue ||
- i.Width.Value >= ConfigurationManager.Configuration.MinSeriesBackdropDownloadWidth)))
+ i.Width.Value >= ConfigurationManager.Configuration.TvOptions.MinBackdropWidth)))
{
var url = backdrop.Url;
diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
index 5691f885e..4df391e2a 100644
--- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
@@ -248,13 +248,13 @@ namespace MediaBrowser.Providers.TV
.Select(Path.GetFileName)
.ToList();
- var seriesXmlFilename = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower() + ".xml";
+ var seriesXmlFilename = series.GetPreferredMetadataLanguage().ToLower() + ".xml";
// Only download if not already there
// The prescan task will take care of updates so we don't need to re-download here
if (!files.Contains("banners.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains("actors.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains(seriesXmlFilename, StringComparer.OrdinalIgnoreCase))
{
- await DownloadSeriesZip(seriesId, seriesDataPath, null, cancellationToken).ConfigureAwait(false);
+ await DownloadSeriesZip(seriesId, seriesDataPath, null, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
}
// Have to check this here since we prevent the normal enforcement through ProviderManager
@@ -285,11 +285,13 @@ namespace MediaBrowser.Providers.TV
/// </summary>
/// <param name="seriesId">The series id.</param>
/// <param name="seriesDataPath">The series data path.</param>
+ /// <param name="lastTvDbUpdateTime">The last tv database update time.</param>
+ /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, long? lastTvDbUpdateTime, CancellationToken cancellationToken)
+ internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, ConfigurationManager.Configuration.PreferredMetadataLanguage);
+ var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, preferredMetadataLanguage);
using (var zipStream = await HttpClient.Get(new HttpRequestOptions
{
@@ -319,7 +321,7 @@ namespace MediaBrowser.Providers.TV
await SanitizeXmlFile(file).ConfigureAwait(false);
}
- await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, ConfigurationManager.Configuration.PreferredMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false);
+ await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, preferredMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false);
}
private void DeleteXmlFiles(string path)
@@ -852,7 +854,7 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrWhiteSpace(val))
{
// Only fill this in if there's no existing genres, because Imdb data from Omdb is preferred
- if (!item.LockedFields.Contains(MetadataFields.Genres) && (item.Genres.Count == 0 || !string.Equals(ConfigurationManager.Configuration.PreferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase)))
+ if (!item.LockedFields.Contains(MetadataFields.Genres) && (item.Genres.Count == 0 || !string.Equals(item.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase)))
{
var vals = val
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
diff --git a/MediaBrowser.Providers/VirtualItemImageValidator.cs b/MediaBrowser.Providers/VirtualItemImageValidator.cs
new file mode 100644
index 000000000..d4bbaf713
--- /dev/null
+++ b/MediaBrowser.Providers/VirtualItemImageValidator.cs
@@ -0,0 +1,48 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers
+{
+ public class VirtualItemImageValidator : BaseMetadataProvider
+ {
+ public VirtualItemImageValidator(ILogManager logManager, IServerConfigurationManager configurationManager)
+ : base(logManager, configurationManager)
+ {
+ }
+
+ public override bool Supports(BaseItem item)
+ {
+ var locationType = item.LocationType;
+
+ return locationType == LocationType.Virtual ||
+ locationType == LocationType.Remote;
+ }
+
+ public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ item.ValidateImages();
+ item.ValidateBackdrops();
+
+ var hasScreenshots = item as IHasScreenshots;
+
+ if (hasScreenshots != null)
+ {
+ hasScreenshots.ValidateScreenshots();
+ }
+
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ return TrueTaskResult;
+ }
+
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.First; }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
index 36b6e5a90..7ddf63cf8 100644
--- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
@@ -228,8 +228,12 @@ namespace MediaBrowser.Server.Implementations.Drawing
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
using (var thumbnail = new Bitmap(newWidth, newHeight, PixelFormat.Format32bppPArgb))
{
- // Preserve the original resolution
- thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
+ // Mono throw an exeception if assign 0 to SetResolution
+ if (originalImage.HorizontalResolution >= 0 && originalImage.VerticalResolution >= 0)
+ {
+ // Preserve the original resolution
+ thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
+ }
using (var thumbnailGraph = Graphics.FromImage(thumbnail))
{
@@ -594,7 +598,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imagePath">The image path.</param>
/// <returns>Guid.</returns>
/// <exception cref="System.ArgumentNullException">item</exception>
- public Guid GetImageCacheTag(BaseItem item, ImageType imageType, string imagePath)
+ public Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath)
{
if (item == null)
{
@@ -623,7 +627,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imageEnhancers">The image enhancers.</param>
/// <returns>Guid.</returns>
/// <exception cref="System.ArgumentNullException">item</exception>
- public Guid GetImageCacheTag(BaseItem item, ImageType imageType, string originalImagePath, DateTime dateModified, List<IImageEnhancer> imageEnhancers)
+ public Guid GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified, List<IImageEnhancer> imageEnhancers)
{
if (item == null)
{
@@ -660,7 +664,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{System.String}.</returns>
- public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
+ public async Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex)
{
var enhancers = GetSupportedEnhancers(item, imageType).ToList();
@@ -673,7 +677,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
return result.Item1;
}
- private async Task<Tuple<string, DateTime>> GetEnhancedImage(string originalImagePath, DateTime dateModified, BaseItem item,
+ private async Task<Tuple<string, DateTime>> GetEnhancedImage(string originalImagePath, DateTime dateModified, IHasImages item,
ImageType imageType, int imageIndex,
List<IImageEnhancer> enhancers)
{
@@ -709,7 +713,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="supportedEnhancers">The supported enhancers.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.ArgumentNullException">originalImagePath</exception>
- private async Task<string> GetEnhancedImageInternal(string originalImagePath, DateTime dateModified, BaseItem item, ImageType imageType, int imageIndex, List<IImageEnhancer> supportedEnhancers)
+ private async Task<string> GetEnhancedImageInternal(string originalImagePath, DateTime dateModified, IHasImages item, ImageType imageType, int imageIndex, List<IImageEnhancer> supportedEnhancers)
{
if (string.IsNullOrEmpty(originalImagePath))
{
@@ -782,7 +786,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{EnhancedImage}.</returns>
- private async Task<Image> ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, Image originalImage, BaseItem item, ImageType imageType, int imageIndex)
+ private async Task<Image> ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, Image originalImage, IHasImages item, ImageType imageType, int imageIndex)
{
var result = originalImage;
@@ -900,7 +904,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
return Path.Combine(path, filename);
}
- public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType)
+ public IEnumerable<IImageEnhancer> GetSupportedEnhancers(IHasImages item, ImageType imageType)
{
return ImageEnhancers.Where(i =>
{
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 14496d362..932c7ba3f 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -243,7 +243,8 @@ namespace MediaBrowser.Server.Implementations.Dto
NowViewingItemType = session.NowViewingItemType,
ApplicationVersion = session.ApplicationVersion,
CanSeek = session.CanSeek,
- QueueableMediaTypes = session.QueueableMediaTypes
+ QueueableMediaTypes = session.QueueableMediaTypes,
+ RemoteEndPoint = session.RemoteEndPoint
};
if (session.NowPlayingItem != null)
@@ -738,10 +739,12 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.MediaType = item.MediaType;
dto.LocationType = item.LocationType;
- var hasLanguage = item as IHasLanguage;
- if (hasLanguage != null)
+ var hasLang = item as IHasPreferredMetadataLanguage;
+
+ if (hasLang != null)
{
- dto.Language = hasLanguage.Language;
+ dto.PreferredMetadataCountryCode = hasLang.PreferredMetadataCountryCode;
+ dto.PreferredMetadataLanguage = hasLang.PreferredMetadataLanguage;
}
var hasCriticRating = item as IHasCriticRating;
@@ -818,7 +821,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.ParentLogoItemId = GetDtoId(parentWithLogo);
- dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImage(ImageType.Logo));
+ dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImagePath(ImageType.Logo));
}
}
@@ -831,7 +834,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.ParentArtItemId = GetDtoId(parentWithImage);
- dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImage(ImageType.Art));
+ dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImagePath(ImageType.Art));
}
}
@@ -844,7 +847,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.ParentThumbItemId = GetDtoId(parentWithImage);
- dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb, parentWithImage.GetImage(ImageType.Thumb));
+ dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb, parentWithImage.GetImagePath(ImageType.Thumb));
}
}
@@ -1037,7 +1040,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (series.HasImage(ImageType.Thumb))
{
- dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb, series.GetImage(ImageType.Thumb));
+ dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb, series.GetImagePath(ImageType.Thumb));
}
var imagePath = series.PrimaryImagePath;
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 791beb941..11c99a32c 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -899,6 +899,15 @@ namespace MediaBrowser.Server.Implementations.Library
}
/// <summary>
+ /// Queues the library scan.
+ /// </summary>
+ public void QueueLibraryScan()
+ {
+ // Just run the scheduled task so that the user can see it
+ _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
+ }
+
+ /// <summary>
/// Validates the media library internal.
/// </summary>
/// <param name="progress">The progress.</param>
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index e335f4ad5..e54c0af55 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -179,7 +179,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
private void SetProviderIdFromPath(Video item)
{
//we need to only look at the name of this actual item (not parents)
- var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(Path.GetDirectoryName(item.Path));
+ var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.MetaLocation);
var id = justName.GetAttributeValue("tmdbid");
@@ -268,7 +268,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
if (multiDiscFolders.Count > 0)
{
- return GetMultiDiscMovie<T>(multiDiscFolders);
+ var folders = fileSystemEntries.Where(child => (child.Attributes & FileAttributes.Directory) == FileAttributes.Directory);
+
+ return GetMultiDiscMovie<T>(multiDiscFolders, folders);
}
return null;
@@ -278,25 +280,26 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
/// Gets the multi disc movie.
/// </summary>
/// <typeparam name="T"></typeparam>
- /// <param name="folders">The folders.</param>
+ /// <param name="multiDiscFolders">The folders.</param>
+ /// <param name="allFolders">All folders.</param>
/// <returns>``0.</returns>
- private T GetMultiDiscMovie<T>(List<FileSystemInfo> folders)
+ private T GetMultiDiscMovie<T>(List<FileSystemInfo> multiDiscFolders, IEnumerable<FileSystemInfo> allFolders)
where T : Video, new()
{
- var videoType = VideoType.BluRay;
+ var videoTypes = new List<VideoType>();
- var folderPaths = folders.Select(i => i.FullName).Where(i =>
+ var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i =>
{
var subfolders = Directory.GetDirectories(i).Select(Path.GetFileName).ToList();
if (subfolders.Any(IsDvdDirectory))
{
- videoType = VideoType.Dvd;
+ videoTypes.Add(VideoType.Dvd);
return true;
}
if (subfolders.Any(IsBluRayDirectory))
{
- videoType = VideoType.BluRay;
+ videoTypes.Add(VideoType.BluRay);
return true;
}
@@ -304,18 +307,46 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
}).OrderBy(i => i).ToList();
+ // If different video types were found, don't allow this
+ if (videoTypes.Count > 0 && videoTypes.Any(i => i != videoTypes[0]))
+ {
+ return null;
+ }
+
if (folderPaths.Count == 0)
{
return null;
}
+ // If there are other folders side by side that are folder rips, don't allow it
+ // TODO: Improve this to return null if any folder is present aside from our regularly ignored folders
+ if (allFolders.Except(multiDiscFolders).Any(i =>
+ {
+ var subfolders = Directory.GetDirectories(i.FullName).Select(Path.GetFileName).ToList();
+
+ if (subfolders.Any(IsDvdDirectory))
+ {
+ return true;
+ }
+ if (subfolders.Any(IsBluRayDirectory))
+ {
+ return true;
+ }
+
+ return false;
+
+ }))
+ {
+ return null;
+ }
+
return new T
{
Path = folderPaths[0],
IsMultiPart = true,
- VideoType = videoType
+ VideoType = videoTypes[0]
};
}
diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
index 8d010aecc..79f126511 100644
--- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
@@ -49,7 +49,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// userId
/// or
/// key</exception>
- public async Task SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
+ public async Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
if (userData == null)
{
diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs
index 4243aecfe..d4a74f2b6 100644
--- a/MediaBrowser.Server.Implementations/Library/UserManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs
@@ -22,39 +22,10 @@ namespace MediaBrowser.Server.Implementations.Library
public class UserManager : IUserManager
{
/// <summary>
- /// The _users
- /// </summary>
- private IEnumerable<User> _users;
- /// <summary>
- /// The _user lock
- /// </summary>
- private object _usersSyncLock = new object();
- /// <summary>
- /// The _users initialized
- /// </summary>
- private bool _usersInitialized;
- /// <summary>
/// Gets the users.
/// </summary>
/// <value>The users.</value>
- public IEnumerable<User> Users
- {
- get
- {
- // Call ToList to exhaust the stream because we'll be iterating over this multiple times
- LazyInitializer.EnsureInitialized(ref _users, ref _usersInitialized, ref _usersSyncLock, LoadUsers);
- return _users;
- }
- internal set
- {
- _users = value;
-
- if (value == null)
- {
- _usersInitialized = false;
- }
- }
- }
+ public IEnumerable<User> Users { get; private set; }
/// <summary>
/// The _logger
@@ -78,11 +49,13 @@ namespace MediaBrowser.Server.Implementations.Library
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="configurationManager">The configuration manager.</param>
+ /// <param name="userRepository">The user repository.</param>
public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository)
{
_logger = logger;
UserRepository = userRepository;
ConfigurationManager = configurationManager;
+ Users = new List<User>();
}
#region UserUpdated Event
@@ -132,6 +105,11 @@ namespace MediaBrowser.Server.Implementations.Library
return Users.FirstOrDefault(u => u.Id == id);
}
+ public async Task Initialize()
+ {
+ Users = await LoadUsers().ConfigureAwait(false);
+ }
+
/// <summary>
/// Authenticates a User and returns a result indicating whether or not it succeeded
/// </summary>
@@ -185,7 +163,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// Loads the users from the repository
/// </summary>
/// <returns>IEnumerable{User}.</returns>
- private IEnumerable<User> LoadUsers()
+ private async Task<IEnumerable<User>> LoadUsers()
{
var users = UserRepository.RetrieveAllUsers().ToList();
@@ -198,10 +176,7 @@ namespace MediaBrowser.Server.Implementations.Library
user.DateLastSaved = DateTime.UtcNow;
- var task = UserRepository.SaveUser(user, CancellationToken.None);
-
- // Hate having to block threads
- Task.WaitAll(task);
+ await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
users.Add(user);
}
@@ -284,7 +259,7 @@ namespace MediaBrowser.Server.Implementations.Library
}
public event EventHandler<GenericEventArgs<User>> UserCreated;
-
+
/// <summary>
/// Creates the user.
/// </summary>
@@ -311,11 +286,11 @@ namespace MediaBrowser.Server.Implementations.Library
Users = list;
user.DateLastSaved = DateTime.UtcNow;
-
+
await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
-
+
return user;
}
@@ -377,10 +352,10 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- OnUserDeleted(user);
-
// Force this to be lazy loaded again
- Users = null;
+ Users = await LoadUsers().ConfigureAwait(false);
+
+ OnUserDeleted(user);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
index 4a8b2d638..d04ebe32d 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -21,18 +22,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly ILiveTvManager _liveTvManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
- public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem)
+ public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient)
: base(logManager, configurationManager)
{
_liveTvManager = liveTvManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
+ _httpClient = httpClient;
}
public override bool Supports(BaseItem item)
{
- return item is Channel;
+ return item is LiveTvChannel;
}
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
@@ -48,43 +51,93 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return true;
}
- var channel = (Channel)item;
+ var changed = true;
- if (channel.HasProviderImage ?? true)
+ try
{
- try
- {
- await DownloadImage(item, cancellationToken).ConfigureAwait(false);
- }
- catch (HttpException ex)
+ changed = await DownloadImage((LiveTvChannel)item, cancellationToken).ConfigureAwait(false);
+ }
+ catch (HttpException ex)
+ {
+ // Don't fail the provider on a 404
+ if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
{
- // Don't fail the provider on a 404
- if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
- {
- throw;
- }
+ throw;
}
}
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
- return true;
+ if (changed)
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ }
+
+ return changed;
}
- private async Task DownloadImage(BaseItem item, CancellationToken cancellationToken)
+ private async Task<bool> DownloadImage(LiveTvChannel item, CancellationToken cancellationToken)
{
- var channel = (Channel)item;
+ var channelInfo = item.ChannelInfo;
+
+ Stream imageStream = null;
+ string contentType = null;
+
+ if (!string.IsNullOrEmpty(channelInfo.ImagePath))
+ {
+ contentType = "image/" + Path.GetExtension(channelInfo.ImagePath).ToLower();
+ imageStream = _fileSystem.GetFileStream(channelInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+ }
+ else if (!string.IsNullOrEmpty(channelInfo.ImageUrl))
+ {
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = channelInfo.ImageUrl
+ };
- var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, channel.ServiceName, StringComparison.OrdinalIgnoreCase));
+ var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
- if (service != null)
+ if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Error("Provider did not return an image content type.");
+ return false;
+ }
+
+ imageStream = response.Content;
+ contentType = response.ContentType;
+ }
+ else if (channelInfo.HasImage ?? true)
{
- var response = await service.GetChannelImageAsync(channel.ChannelId, cancellationToken).ConfigureAwait(false);
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase));
+
+ if (service != null)
+ {
+ try
+ {
+ var response = await service.GetChannelImageAsync(channelInfo.Id, cancellationToken).ConfigureAwait(false);
+
+ if (response != null)
+ {
+ imageStream = response.Stream;
+ contentType = response.MimeType;
+ }
+ }
+ catch (NotImplementedException)
+ {
+ return false;
+ }
+ }
+ }
+ if (imageStream != null)
+ {
// Dummy up the original url
- var url = channel.ServiceName + channel.ChannelId;
+ var url = item.ServiceName + channelInfo.Id;
- await _providerManager.SaveImage(channel, response.Stream, response.MimeType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ return true;
}
+
+ return false;
}
public override MetadataProviderPriority Priority
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
index f93821cc9..6552c6892 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -8,6 +8,9 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.LiveTv
{
@@ -27,108 +30,151 @@ namespace MediaBrowser.Server.Implementations.LiveTv
_logger = logger;
}
- public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service)
+ public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service, LiveTvProgram program, LiveTvChannel channel)
{
var dto = new TimerInfoDto
{
Id = GetInternalTimerId(service.Name, info.Id).ToString("N"),
- ChannelName = info.ChannelName,
Overview = info.Overview,
EndDate = info.EndDate,
Name = info.Name,
StartDate = info.StartDate,
ExternalId = info.Id,
- ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
+ ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N"),
Status = info.Status,
SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N"),
- RequestedPostPaddingSeconds = info.RequestedPostPaddingSeconds,
- RequestedPrePaddingSeconds = info.RequestedPrePaddingSeconds,
- RequiredPostPaddingSeconds = info.RequiredPostPaddingSeconds,
- RequiredPrePaddingSeconds = info.RequiredPrePaddingSeconds,
+ PrePaddingSeconds = info.PrePaddingSeconds,
+ PostPaddingSeconds = info.PostPaddingSeconds,
+ IsPostPaddingRequired = info.IsPostPaddingRequired,
+ IsPrePaddingRequired = info.IsPrePaddingRequired,
ExternalChannelId = info.ChannelId,
- ExternalSeriesTimerId = info.SeriesTimerId
+ ExternalSeriesTimerId = info.SeriesTimerId,
+ ServiceName = service.Name,
+ ExternalProgramId = info.ProgramId,
+ Priority = info.Priority,
+ RunTimeTicks = (info.EndDate - info.StartDate).Ticks
};
- var duration = info.EndDate - info.StartDate;
- dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
-
if (!string.IsNullOrEmpty(info.ProgramId))
{
dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
}
+ if (program != null)
+ {
+ dto.ProgramInfo = GetProgramInfoDto(program, channel.ChannelInfo.Name);
+
+ dto.ProgramInfo.TimerId = dto.Id;
+ dto.ProgramInfo.SeriesTimerId = dto.SeriesTimerId;
+ }
+
+ if (channel != null)
+ {
+ dto.ChannelName = channel.ChannelInfo.Name;
+ }
+
return dto;
}
- public SeriesTimerInfoDto GetSeriesTimerInfoDto(SeriesTimerInfo info, ILiveTvService service)
+ public SeriesTimerInfoDto GetSeriesTimerInfoDto(SeriesTimerInfo info, ILiveTvService service, string channelName)
{
var dto = new SeriesTimerInfoDto
{
Id = GetInternalSeriesTimerId(service.Name, info.Id).ToString("N"),
- ChannelName = info.ChannelName,
Overview = info.Overview,
EndDate = info.EndDate,
Name = info.Name,
StartDate = info.StartDate,
ExternalId = info.Id,
- ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
- RequestedPostPaddingSeconds = info.RequestedPostPaddingSeconds,
- RequestedPrePaddingSeconds = info.RequestedPrePaddingSeconds,
- RequiredPostPaddingSeconds = info.RequiredPostPaddingSeconds,
- RequiredPrePaddingSeconds = info.RequiredPrePaddingSeconds,
+ PrePaddingSeconds = info.PrePaddingSeconds,
+ PostPaddingSeconds = info.PostPaddingSeconds,
+ IsPostPaddingRequired = info.IsPostPaddingRequired,
+ IsPrePaddingRequired = info.IsPrePaddingRequired,
Days = info.Days,
Priority = info.Priority,
- RecurrenceType = info.RecurrenceType,
+ RecordAnyChannel = info.RecordAnyChannel,
+ RecordAnyTime = info.RecordAnyTime,
+ RecordNewOnly = info.RecordNewOnly,
ExternalChannelId = info.ChannelId,
- ExternalProgramId = info.ProgramId
+ ExternalProgramId = info.ProgramId,
+ ServiceName = service.Name,
+ ChannelName = channelName
};
+ if (!string.IsNullOrEmpty(info.ChannelId))
+ {
+ dto.ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N");
+ }
+
if (!string.IsNullOrEmpty(info.ProgramId))
{
dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
}
+ dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days);
+
+ return dto;
+ }
+
+ public DayPattern? GetDayPattern(List<DayOfWeek> days)
+ {
DayPattern? pattern = null;
- if (info.Days != null && info.Days.Count > 0)
+ if (days.Count > 0)
{
- if (info.Days.Count == 7)
+ if (days.Count == 7)
{
pattern = DayPattern.Daily;
}
- else if (info.Days.Count == 2)
+ else if (days.Count == 2)
{
- if (info.Days.Contains(DayOfWeek.Saturday) && info.Days.Contains(DayOfWeek.Sunday))
+ if (days.Contains(DayOfWeek.Saturday) && days.Contains(DayOfWeek.Sunday))
{
pattern = DayPattern.Weekends;
}
}
- else if (info.Days.Count == 5)
+ else if (days.Count == 5)
{
- if (info.Days.Contains(DayOfWeek.Monday) && info.Days.Contains(DayOfWeek.Tuesday) && info.Days.Contains(DayOfWeek.Wednesday) && info.Days.Contains(DayOfWeek.Thursday) && info.Days.Contains(DayOfWeek.Friday))
+ if (days.Contains(DayOfWeek.Monday) && days.Contains(DayOfWeek.Tuesday) && days.Contains(DayOfWeek.Wednesday) && days.Contains(DayOfWeek.Thursday) && days.Contains(DayOfWeek.Friday))
{
pattern = DayPattern.Weekdays;
}
}
}
- dto.DayPattern = pattern;
+ return pattern;
+ }
- return dto;
+ /// <summary>
+ /// Convert the provider 0-5 scale to our 0-10 scale
+ /// </summary>
+ /// <param name="val"></param>
+ /// <returns></returns>
+ private float? GetClientCommunityRating(float? val)
+ {
+ if (!val.HasValue)
+ {
+ return null;
+ }
+
+ return val.Value * 2;
}
- public RecordingInfoDto GetRecordingInfoDto(RecordingInfo info, ILiveTvService service, User user = null)
+ public RecordingInfoDto GetRecordingInfoDto(LiveTvRecording recording, LiveTvChannel channel, ILiveTvService service, User user = null)
{
+ var info = recording.RecordingInfo;
+
var dto = new RecordingInfoDto
{
Id = GetInternalRecordingId(service.Name, info.Id).ToString("N"),
- ChannelName = info.ChannelName,
+ SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N"),
+ Type = recording.GetClientTypeName(),
Overview = info.Overview,
EndDate = info.EndDate,
Name = info.Name,
StartDate = info.StartDate,
ExternalId = info.Id,
- ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
+ ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N"),
Status = info.Status,
Path = info.Path,
Genres = info.Genres,
@@ -136,25 +182,44 @@ namespace MediaBrowser.Server.Implementations.LiveTv
EpisodeTitle = info.EpisodeTitle,
ChannelType = info.ChannelType,
MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video,
- CommunityRating = info.CommunityRating,
+ CommunityRating = GetClientCommunityRating(info.CommunityRating),
OfficialRating = info.OfficialRating,
Audio = info.Audio,
- IsHD = info.IsHD
+ IsHD = info.IsHD,
+ ServiceName = service.Name,
+ Url = info.Url,
+ IsMovie = info.IsMovie,
+ IsSeries = info.IsSeries,
+ IsSports = info.IsSports,
+ IsLive = info.IsLive,
+ IsNews = info.IsNews,
+ IsKids = info.IsKids,
+ IsPremiere = info.IsPremiere,
+ RunTimeTicks = (info.EndDate - info.StartDate).Ticks
};
- if (user != null)
+ var imageTag = GetImageTag(recording);
+
+ if (imageTag.HasValue)
{
- //dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
+ dto.ImageTags[ImageType.Primary] = imageTag.Value;
}
- var duration = info.EndDate - info.StartDate;
- dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
+ if (user != null)
+ {
+ dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, recording.GetUserDataKey()));
+ }
if (!string.IsNullOrEmpty(info.ProgramId))
{
dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
}
+ if (channel != null)
+ {
+ dto.ChannelName = channel.ChannelInfo.Name;
+ }
+
return dto;
}
@@ -164,17 +229,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv
/// <param name="info">The info.</param>
/// <param name="user">The user.</param>
/// <returns>ChannelInfoDto.</returns>
- public ChannelInfoDto GetChannelInfoDto(Channel info, User user = null)
+ public ChannelInfoDto GetChannelInfoDto(LiveTvChannel info, User user = null)
{
+ var channelInfo = info.ChannelInfo;
+
var dto = new ChannelInfoDto
{
Name = info.Name,
ServiceName = info.ServiceName,
- ChannelType = info.ChannelType,
- Number = info.ChannelNumber,
- Type = info.GetType().Name,
+ ChannelType = channelInfo.ChannelType,
+ Number = channelInfo.Number,
+ Type = info.GetClientTypeName(),
Id = info.Id.ToString("N"),
- MediaType = info.MediaType
+ MediaType = info.MediaType,
+ ExternalId = channelInfo.Id
};
if (user != null)
@@ -182,7 +250,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
}
- var imageTag = GetLogoImageTag(info);
+ var imageTag = GetImageTag(info);
if (imageTag.HasValue)
{
@@ -192,38 +260,49 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return dto;
}
- public ProgramInfoDto GetProgramInfoDto(ProgramInfo program, Channel channel, User user = null)
+ public ProgramInfoDto GetProgramInfoDto(LiveTvProgram item, string channelName, User user = null)
{
+ var program = item.ProgramInfo;
+
var dto = new ProgramInfoDto
{
- Id = GetInternalProgramId(channel.ServiceName, program.Id).ToString("N"),
- ChannelId = channel.Id.ToString("N"),
+ Id = GetInternalProgramId(item.ServiceName, program.Id).ToString("N"),
+ ChannelId = GetInternalChannelId(item.ServiceName, program.ChannelId).ToString("N"),
Overview = program.Overview,
EndDate = program.EndDate,
Genres = program.Genres,
ExternalId = program.Id,
Name = program.Name,
- ServiceName = channel.ServiceName,
+ ServiceName = item.ServiceName,
StartDate = program.StartDate,
OfficialRating = program.OfficialRating,
IsHD = program.IsHD,
OriginalAirDate = program.OriginalAirDate,
Audio = program.Audio,
- CommunityRating = program.CommunityRating,
+ CommunityRating = GetClientCommunityRating(program.CommunityRating),
AspectRatio = program.AspectRatio,
IsRepeat = program.IsRepeat,
- EpisodeTitle = program.EpisodeTitle
+ EpisodeTitle = program.EpisodeTitle,
+ ChannelName = channelName,
+ IsMovie = program.IsMovie,
+ IsSeries = program.IsSeries,
+ IsSports = program.IsSports,
+ IsLive = program.IsLive,
+ IsNews = program.IsNews,
+ IsKids = program.IsKids,
+ IsPremiere = program.IsPremiere,
+ RunTimeTicks = (program.EndDate - program.StartDate).Ticks
};
if (user != null)
{
- //dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
+ dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, item.GetUserDataKey()));
}
return dto;
}
- private Guid? GetLogoImageTag(Channel info)
+ private Guid? GetImageTag(BaseItem info)
{
var path = info.PrimaryImagePath;
@@ -238,17 +317,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
catch (Exception ex)
{
- _logger.ErrorException("Error getting channel image info for {0}", ex, info.Name);
+ _logger.ErrorException("Error getting image info for {0}", ex, info.Name);
}
return null;
}
- public Guid GetInternalChannelId(string serviceName, string externalId, string channelName)
+ public Guid GetInternalChannelId(string serviceName, string externalId)
{
- var name = serviceName + externalId + channelName;
+ var name = serviceName + externalId;
- return name.ToLower().GetMBId(typeof(Channel));
+ return name.ToLower().GetMBId(typeof(LiveTvChannel));
}
public Guid GetInternalTimerId(string serviceName, string externalId)
@@ -279,46 +358,118 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return name.ToLower().GetMD5();
}
- public TimerInfo GetTimerInfo(TimerInfoDto dto)
+ public async Task<TimerInfo> GetTimerInfo(TimerInfoDto dto, bool isNew, ILiveTvManager liveTv, CancellationToken cancellationToken)
{
- return new TimerInfo
+ var info = new TimerInfo
{
- Id = dto.ExternalId,
- ChannelName = dto.ChannelName,
Overview = dto.Overview,
EndDate = dto.EndDate,
Name = dto.Name,
StartDate = dto.StartDate,
- ChannelId = dto.ExternalChannelId,
Status = dto.Status,
+ PrePaddingSeconds = dto.PrePaddingSeconds,
+ PostPaddingSeconds = dto.PostPaddingSeconds,
+ IsPostPaddingRequired = dto.IsPostPaddingRequired,
+ IsPrePaddingRequired = dto.IsPrePaddingRequired,
+ Priority = dto.Priority,
SeriesTimerId = dto.ExternalSeriesTimerId,
- RequestedPostPaddingSeconds = dto.RequestedPostPaddingSeconds,
- RequestedPrePaddingSeconds = dto.RequestedPrePaddingSeconds,
- RequiredPostPaddingSeconds = dto.RequiredPostPaddingSeconds,
- RequiredPrePaddingSeconds = dto.RequiredPrePaddingSeconds
+ ProgramId = dto.ExternalProgramId,
+ ChannelId = dto.ExternalChannelId,
+ Id = dto.ExternalId
};
+
+ // Convert internal server id's to external tv provider id's
+ if (!isNew && !string.IsNullOrEmpty(dto.Id) && string.IsNullOrEmpty(info.Id))
+ {
+ var timer = await liveTv.GetSeriesTimer(dto.Id, cancellationToken).ConfigureAwait(false);
+
+ info.Id = timer.ExternalId;
+ }
+
+ if (!string.IsNullOrEmpty(dto.ChannelId) && string.IsNullOrEmpty(info.ChannelId))
+ {
+ var channel = await liveTv.GetChannel(dto.ChannelId, cancellationToken).ConfigureAwait(false);
+
+ if (channel != null)
+ {
+ info.ChannelId = channel.ExternalId;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(dto.ProgramId) && string.IsNullOrEmpty(info.ProgramId))
+ {
+ var program = await liveTv.GetProgram(dto.ProgramId, cancellationToken).ConfigureAwait(false);
+
+ if (program != null)
+ {
+ info.ProgramId = program.ExternalId;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(dto.SeriesTimerId) && string.IsNullOrEmpty(info.SeriesTimerId))
+ {
+ var timer = await liveTv.GetSeriesTimer(dto.SeriesTimerId, cancellationToken).ConfigureAwait(false);
+
+ if (timer != null)
+ {
+ info.SeriesTimerId = timer.ExternalId;
+ }
+ }
+
+ return info;
}
- public SeriesTimerInfo GetSeriesTimerInfo(SeriesTimerInfoDto dto)
+ public async Task<SeriesTimerInfo> GetSeriesTimerInfo(SeriesTimerInfoDto dto, bool isNew, ILiveTvManager liveTv, CancellationToken cancellationToken)
{
- return new SeriesTimerInfo
+ var info = new SeriesTimerInfo
{
- Id = dto.ExternalId,
- ChannelName = dto.ChannelName,
Overview = dto.Overview,
EndDate = dto.EndDate,
Name = dto.Name,
StartDate = dto.StartDate,
- ChannelId = dto.ExternalChannelId,
- RequestedPostPaddingSeconds = dto.RequestedPostPaddingSeconds,
- RequestedPrePaddingSeconds = dto.RequestedPrePaddingSeconds,
- RequiredPostPaddingSeconds = dto.RequiredPostPaddingSeconds,
- RequiredPrePaddingSeconds = dto.RequiredPrePaddingSeconds,
+ PrePaddingSeconds = dto.PrePaddingSeconds,
+ PostPaddingSeconds = dto.PostPaddingSeconds,
+ IsPostPaddingRequired = dto.IsPostPaddingRequired,
+ IsPrePaddingRequired = dto.IsPrePaddingRequired,
Days = dto.Days,
Priority = dto.Priority,
- RecurrenceType = dto.RecurrenceType,
- ProgramId = dto.ExternalProgramId
+ RecordAnyChannel = dto.RecordAnyChannel,
+ RecordAnyTime = dto.RecordAnyTime,
+ RecordNewOnly = dto.RecordNewOnly,
+ ProgramId = dto.ExternalProgramId,
+ ChannelId = dto.ExternalChannelId,
+ Id = dto.ExternalId
};
+
+ // Convert internal server id's to external tv provider id's
+ if (!isNew && !string.IsNullOrEmpty(dto.Id) && string.IsNullOrEmpty(info.Id))
+ {
+ var timer = await liveTv.GetSeriesTimer(dto.Id, cancellationToken).ConfigureAwait(false);
+
+ info.Id = timer.ExternalId;
+ }
+
+ if (!string.IsNullOrEmpty(dto.ChannelId) && string.IsNullOrEmpty(info.ChannelId))
+ {
+ var channel = await liveTv.GetChannel(dto.ChannelId, cancellationToken).ConfigureAwait(false);
+
+ if (channel != null)
+ {
+ info.ChannelId = channel.ExternalId;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(dto.ProgramId) && string.IsNullOrEmpty(info.ProgramId))
+ {
+ var program = await liveTv.GetProgram(dto.ProgramId, cancellationToken).ConfigureAwait(false);
+
+ if (program != null)
+ {
+ info.ProgramId = program.ExternalId;
+ }
+ }
+
+ return info;
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 318f450f1..1d12e2d45 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -36,8 +36,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
- private List<Channel> _channels = new List<Channel>();
- private List<ProgramInfoDto> _programs = new List<ProgramInfoDto>();
+ private Dictionary<Guid, LiveTvChannel> _channels = new Dictionary<Guid, LiveTvChannel>();
+ private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>();
public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager)
{
@@ -77,18 +77,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
- IEnumerable<Channel> channels = _channels;
+ IEnumerable<LiveTvChannel> channels = _channels.Values;
if (user != null)
{
- channels = channels.Where(i => i.IsParentalAllowed(user, _localization))
+ channels = channels
+ .Where(i => i.IsParentalAllowed(user, _localization))
.OrderBy(i =>
{
double number = 0;
- if (!string.IsNullOrEmpty(i.ChannelNumber))
+ if (!string.IsNullOrEmpty(i.ChannelInfo.Number))
{
- double.TryParse(i.ChannelNumber, out number);
+ double.TryParse(i.ChannelInfo.Number, out number);
}
return number;
@@ -100,9 +101,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
double number = 0;
- if (!string.IsNullOrEmpty(i.ChannelNumber))
+ if (!string.IsNullOrEmpty(i.ChannelInfo.Number))
{
- double.TryParse(i.ChannelNumber, out number);
+ double.TryParse(i.ChannelInfo.Number, out number);
}
return number;
@@ -120,14 +121,52 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return Task.FromResult(result);
}
- public Channel GetChannel(string id)
+ public LiveTvChannel GetInternalChannel(string id)
+ {
+ return GetInternalChannel(new Guid(id));
+ }
+
+ private LiveTvChannel GetInternalChannel(Guid id)
+ {
+ LiveTvChannel channel = null;
+
+ _channels.TryGetValue(id, out channel);
+ return channel;
+ }
+
+ public LiveTvProgram GetInternalProgram(string id)
{
var guid = new Guid(id);
- return _channels.FirstOrDefault(i => i.Id == guid);
+ LiveTvProgram obj = null;
+
+ _programs.TryGetValue(guid, out obj);
+ return obj;
+ }
+
+ public async Task<LiveTvRecording> GetInternalRecording(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 GetRecording(recording, service.Name, cancellationToken).ConfigureAwait(false);
}
- private async Task<Channel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
+ public async Task<StreamResponseInfo> 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<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
{
var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name));
@@ -148,28 +187,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv
isNew = true;
}
- var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id, channelInfo.Name);
+ var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
- var item = _itemRepo.RetrieveItem(id) as Channel;
+ var item = _itemRepo.RetrieveItem(id) as LiveTvChannel;
if (item == null)
{
- item = new Channel
+ item = new LiveTvChannel
{
Name = channelInfo.Name,
Id = id,
DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo),
DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo),
- Path = path,
- ChannelId = channelInfo.Id,
- ChannelNumber = channelInfo.Number,
- ServiceName = serviceName,
- HasProviderImage = channelInfo.HasImage
+ Path = path
};
isNew = true;
}
+ item.ChannelInfo = channelInfo;
+ item.ServiceName = serviceName;
+
// Set this now so we don't cause additional file system access during provider executions
item.ResetResolveArgs(fileInfo);
@@ -178,26 +216,160 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return item;
}
+ private async Task<LiveTvProgram> GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
+ {
+ var isNew = false;
+
+ var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
+
+ var item = _itemRepo.RetrieveItem(id) as LiveTvProgram;
+
+ if (item == null)
+ {
+ item = new LiveTvProgram
+ {
+ Name = info.Name,
+ Id = id,
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ };
+
+ isNew = true;
+ }
+
+ item.ChannelType = channelType;
+ item.ProgramInfo = info;
+ item.ServiceName = serviceName;
+
+ await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false);
+
+ return item;
+ }
+
+ private async Task<LiveTvRecording> GetRecording(RecordingInfo info, string serviceName, CancellationToken cancellationToken)
+ {
+ var isNew = false;
+
+ var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id);
+
+ var item = _itemRepo.RetrieveItem(id) as LiveTvRecording;
+
+ if (item == null)
+ {
+ item = new LiveTvRecording
+ {
+ Name = info.Name,
+ Id = id,
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ };
+
+ isNew = true;
+ }
+
+ item.RecordingInfo = info;
+ item.ServiceName = serviceName;
+
+ await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false);
+
+ return item;
+ }
+
+ private LiveTvChannel GetChannel(LiveTvProgram program)
+ {
+ var programChannelId = program.ProgramInfo.ChannelId;
+
+ var internalProgramChannelId = _tvDtoService.GetInternalChannelId(program.ServiceName, programChannelId);
+
+ return GetInternalChannel(internalProgramChannelId);
+ }
+
+ public async Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
+ {
+ var program = GetInternalProgram(id);
+
+ var channel = GetChannel(program);
+
+ var channelName = channel == null ? null : channel.ChannelInfo.Name;
+
+ var dto = _tvDtoService.GetProgramInfoDto(program, channelName, user);
+
+ await AddRecordingInfo(new[] { dto }, cancellationToken).ConfigureAwait(false);
+
+ return dto;
+ }
+
public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
{
- IEnumerable<ProgramInfoDto> programs = _programs
- .OrderBy(i => i.StartDate)
- .ThenBy(i => i.EndDate);
+ IEnumerable<LiveTvProgram> programs = _programs.Values;
if (query.ChannelIdList.Length > 0)
{
var guids = query.ChannelIdList.Select(i => new Guid(i)).ToList();
+ var serviceName = ActiveService.Name;
- programs = programs.Where(i => guids.Contains(new Guid(i.ChannelId)));
+ programs = programs.Where(i =>
+ {
+ var programChannelId = i.ProgramInfo.ChannelId;
+
+ var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId);
+
+ return guids.Contains(internalProgramChannelId);
+ });
}
- var returnArray = programs.ToArray();
+ var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
+
+ if (user != null)
+ {
+ programs = programs.Where(i => i.IsParentalAllowed(user, _localization));
+ }
+
+ var returnArray = programs
+ .OrderBy(i => i.ProgramInfo.StartDate)
+ .Select(i =>
+ {
+ var channel = GetChannel(i);
+
+ var channelName = channel == null ? null : channel.ChannelInfo.Name;
+
+ return _tvDtoService.GetProgramInfoDto(i, channelName, user);
+ })
+ .ToArray();
+
+ await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
- return new QueryResult<ProgramInfoDto>
+ var result = new QueryResult<ProgramInfoDto>
{
Items = returnArray,
TotalRecordCount = returnArray.Length
};
+
+ return result;
+ }
+
+ private async Task AddRecordingInfo(IEnumerable<ProgramInfoDto> programs, CancellationToken cancellationToken)
+ {
+ var timers = await ActiveService.GetTimersAsync(cancellationToken).ConfigureAwait(false);
+ var timerList = timers.ToList();
+
+ foreach (var program in programs)
+ {
+ var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, program.ExternalId, StringComparison.OrdinalIgnoreCase));
+
+ if (timer != null)
+ {
+ program.TimerId = _tvDtoService.GetInternalTimerId(program.ServiceName, timer.Id)
+ .ToString("N");
+
+ if (!string.IsNullOrEmpty(timer.SeriesTimerId))
+ {
+ program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(program.ServiceName, timer.SeriesTimerId)
+ .ToString("N");
+ }
+ }
+ }
+
}
internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
@@ -216,8 +388,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false);
var allChannelsList = allChannels.ToList();
- var list = new List<Channel>();
- var programs = new List<ProgramInfoDto>();
+ var list = new List<LiveTvChannel>();
+ var programs = new List<LiveTvProgram>();
var numComplete = 0;
@@ -229,7 +401,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false);
- programs.AddRange(channelPrograms.Select(program => _tvDtoService.GetProgramInfoDto(program, item)));
+ var programTasks = channelPrograms.Select(program => GetProgram(program, item.ChannelInfo.ChannelType, service.Name, cancellationToken));
+ var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
+
+ programs.AddRange(programEntities);
list.Add(item);
}
@@ -249,8 +424,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
progress.Report(90 * percent + 10);
}
- _programs = programs;
- _channels = list;
+ _programs = programs.ToDictionary(i => i.Id);
+ _channels = list.ToDictionary(i => i.Id);
}
private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
@@ -262,26 +437,43 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken)
{
- var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
+ var service = ActiveService;
- var list = new List<RecordingInfoDto>();
+ var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
- if (ActiveService != null)
- {
- var recordings = await ActiveService.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
+ var list = new List<RecordingInfo>();
- var dtos = recordings.Select(i => _tvDtoService.GetRecordingInfoDto(i, ActiveService, user));
+ var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
+ list.AddRange(recordings);
- list.AddRange(dtos);
+ if (!string.IsNullOrEmpty(query.ChannelId))
+ {
+ list = list
+ .Where(i => _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId) == new Guid(query.ChannelId))
+ .ToList();
}
- if (!string.IsNullOrEmpty(query.ChannelId))
+ if (!string.IsNullOrEmpty(query.Id))
{
- list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId))
+ list = list
+ .Where(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(query.Id))
.ToList();
}
- var returnArray = list.OrderByDescending(i => i.StartDate)
+ var entities = await GetEntities(list, service.Name, cancellationToken).ConfigureAwait(false);
+
+ if (user != null)
+ {
+ entities = entities.Where(i => i.IsParentalAllowed(user, _localization)).ToArray();
+ }
+
+ var returnArray = entities
+ .Select(i =>
+ {
+ var channel = string.IsNullOrEmpty(i.RecordingInfo.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.RecordingInfo.ChannelId));
+ return _tvDtoService.GetRecordingInfoDto(i, channel, service, user);
+ })
+ .OrderByDescending(i => i.StartDate)
.ToArray();
return new QueryResult<RecordingInfoDto>
@@ -291,17 +483,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv
};
}
+ private Task<LiveTvRecording[]> GetEntities(IEnumerable<RecordingInfo> recordings, string serviceName, CancellationToken cancellationToken)
+ {
+ var tasks = recordings.Select(i => GetRecording(i, serviceName, cancellationToken));
+
+ return Task.WhenAll(tasks);
+ }
+
private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId)
{
IEnumerable<ILiveTvService> services = _services;
if (string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(channelId))
{
- var channelIdGuid = new Guid(channelId);
+ var channel = GetInternalChannel(channelId);
- serviceName = _channels.Where(i => i.Id == channelIdGuid)
- .Select(i => i.ServiceName)
- .FirstOrDefault();
+ if (channel != null)
+ {
+ serviceName = channel.ServiceName;
+ }
}
if (!string.IsNullOrEmpty(serviceName))
@@ -312,31 +512,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return services;
}
- public Task ScheduleRecording(string programId)
- {
- throw new NotImplementedException();
- }
-
public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
{
- var list = new List<TimerInfoDto>();
-
- if (ActiveService != null)
- {
- var timers = await ActiveService.GetTimersAsync(cancellationToken).ConfigureAwait(false);
-
- var dtos = timers.Select(i => _tvDtoService.GetTimerInfoDto(i, ActiveService));
-
- list.AddRange(dtos);
- }
+ var service = ActiveService;
+ var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(query.ChannelId))
{
- list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId))
- .ToList();
+ var guid = new Guid(query.ChannelId);
+ timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
}
- var returnArray = list.OrderByDescending(i => i.StartDate)
+ var returnArray = timers
+ .Select(i =>
+ {
+ var program = string.IsNullOrEmpty(i.ProgramId) ? null : GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"));
+ var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
+
+ return _tvDtoService.GetTimerInfoDto(i, service, program, channel);
+ })
+ .OrderBy(i => i.StartDate)
.ToArray();
return new QueryResult<TimerInfoDto>
@@ -348,22 +543,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task DeleteRecording(string recordingId)
{
- var recordings = await GetRecordings(new RecordingQuery
- {
-
- }, CancellationToken.None).ConfigureAwait(false);
-
- var recording = recordings.Items
- .FirstOrDefault(i => string.Equals(recordingId, i.Id, StringComparison.OrdinalIgnoreCase));
+ var recording = await GetRecording(recordingId, CancellationToken.None).ConfigureAwait(false);
if (recording == null)
{
throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
}
- var channel = GetChannel(recording.ChannelId);
-
- var service = GetServices(channel.ServiceName, null)
+ var service = GetServices(recording.ServiceName, null)
.First();
await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
@@ -371,36 +558,44 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task CancelTimer(string id)
{
- var timers = await GetTimers(new TimerQuery
+ var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
+
+ if (timer == null)
{
+ throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
+ }
- }, CancellationToken.None).ConfigureAwait(false);
+ var service = GetServices(timer.ServiceName, null)
+ .First();
- var timer = timers.Items
- .FirstOrDefault(i => string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase));
+ await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public async Task CancelSeriesTimer(string id)
+ {
+ var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
if (timer == null)
{
throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
}
- var channel = GetChannel(timer.ChannelId);
-
- var service = GetServices(channel.ServiceName, null)
+ var service = GetServices(timer.ServiceName, null)
.First();
- await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
}
public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken, User user = null)
{
var results = await GetRecordings(new RecordingQuery
{
- UserId = user == null ? null : user.Id.ToString("N")
+ UserId = user == null ? null : user.Id.ToString("N"),
+ Id = id
}, cancellationToken).ConfigureAwait(false);
- return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+ return results.Items.FirstOrDefault();
}
public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
@@ -416,53 +611,128 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
}
-
- public Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
+
+ public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
{
- var info = _tvDtoService.GetTimerInfo(timer);
+ var service = ActiveService;
+
+ var timers = await service.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
+
+ var returnArray = timers
+ .Select(i =>
+ {
+ string channelName = null;
+
+ if (!string.IsNullOrEmpty(i.ChannelId))
+ {
+ var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId);
+ var channel = GetInternalChannel(internalChannelId);
+ channelName = channel == null ? null : channel.ChannelInfo.Name;
+ }
- return ActiveService.UpdateTimerAsync(info, cancellationToken);
+ return _tvDtoService.GetSeriesTimerInfoDto(i, service, channelName);
+
+ })
+ .OrderByDescending(i => i.StartDate)
+ .ToArray();
+
+ return new QueryResult<SeriesTimerInfoDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Length
+ };
}
- public Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
+ public Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null)
{
- var info = _tvDtoService.GetSeriesTimerInfo(timer);
+ var channel = GetInternalChannel(id);
- return ActiveService.UpdateSeriesTimerAsync(info, cancellationToken);
+ var dto = _tvDtoService.GetChannelInfoDto(channel, user);
+
+ return Task.FromResult(dto);
}
- public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
+ public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
{
- var list = new List<SeriesTimerInfoDto>();
+ var service = ActiveService;
- if (ActiveService != null)
- {
- var timers = await ActiveService.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
+ var info = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
- var dtos = timers.Select(i => _tvDtoService.GetSeriesTimerInfoDto(i, ActiveService));
+ var obj = _tvDtoService.GetSeriesTimerInfoDto(info, service, null);
- list.AddRange(dtos);
- }
+ obj.Id = obj.ExternalId = string.Empty;
- var returnArray = list.OrderByDescending(i => i.StartDate)
- .ToArray();
+ return obj;
+ }
- return new QueryResult<SeriesTimerInfoDto>
+ public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
+ {
+ var info = await GetNewTimerDefaults(cancellationToken).ConfigureAwait(false);
+
+ var program = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
+
+ info.Days = new List<DayOfWeek>
{
- Items = returnArray,
- TotalRecordCount = returnArray.Length
+ program.StartDate.ToLocalTime().DayOfWeek
};
+
+ info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
+
+ info.Name = program.Name;
+ info.ChannelId = program.ChannelId;
+ info.ChannelName = program.ChannelName;
+ info.EndDate = program.EndDate;
+ info.StartDate = program.StartDate;
+ info.Name = program.Name;
+ info.Overview = program.Overview;
+ info.ProgramId = program.Id;
+ info.ExternalProgramId = program.ExternalId;
+
+ return info;
}
- public async Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null)
+ public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
{
- var results = await GetChannels(new ChannelQuery
- {
- UserId = user == null ? null : user.Id.ToString("N")
+ var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
- }, cancellationToken).ConfigureAwait(false);
+ var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
- return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+ // Set priority from default values
+ var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
+ info.Priority = defaultValues.Priority;
+
+ await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
+ {
+ var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
+
+ var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
+
+ // Set priority from default values
+ var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
+ info.Priority = defaultValues.Priority;
+
+ await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
+ {
+ var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
+
+ var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
+
+ await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
+ {
+ var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
+
+ var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
+
+ await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
new file mode 100644
index 000000000..7c343f77c
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
@@ -0,0 +1,156 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+ public class ProgramImageProvider : BaseMetadataProvider
+ {
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly IProviderManager _providerManager;
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public ProgramImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient)
+ : base(logManager, configurationManager)
+ {
+ _liveTvManager = liveTvManager;
+ _providerManager = providerManager;
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ }
+
+ public override bool Supports(BaseItem item)
+ {
+ return item is LiveTvProgram;
+ }
+
+ protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+ {
+ return !item.HasImage(ImageType.Primary);
+ }
+
+ public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ if (item.HasImage(ImageType.Primary))
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ return true;
+ }
+
+ var changed = true;
+
+ try
+ {
+ changed = await DownloadImage((LiveTvProgram)item, cancellationToken).ConfigureAwait(false);
+ }
+ catch (HttpException ex)
+ {
+ // Don't fail the provider on a 404
+ if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
+ {
+ throw;
+ }
+ }
+
+ if (changed)
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ }
+
+ return changed;
+ }
+
+ private async Task<bool> DownloadImage(LiveTvProgram item, CancellationToken cancellationToken)
+ {
+ var programInfo = item.ProgramInfo;
+
+ Stream imageStream = null;
+ string contentType = null;
+
+ if (!string.IsNullOrEmpty(programInfo.ImagePath))
+ {
+ contentType = "image/" + Path.GetExtension(programInfo.ImagePath).ToLower();
+ imageStream = _fileSystem.GetFileStream(programInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+ }
+ else if (!string.IsNullOrEmpty(programInfo.ImageUrl))
+ {
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = programInfo.ImageUrl
+ };
+
+ var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
+
+ if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Error("Provider did not return an image content type.");
+ return false;
+ }
+
+ imageStream = response.Content;
+ contentType = response.ContentType;
+ }
+ else if (programInfo.HasImage ?? true)
+ {
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase));
+
+ if (service != null)
+ {
+ try
+ {
+ var response = await service.GetProgramImageAsync(programInfo.Id, programInfo.ChannelId, cancellationToken).ConfigureAwait(false);
+
+ if (response != null)
+ {
+ imageStream = response.Stream;
+ contentType = response.MimeType;
+ }
+ }
+ catch (NotImplementedException)
+ {
+ return false;
+ }
+ }
+ }
+
+ if (imageStream != null)
+ {
+ // Dummy up the original url
+ var url = item.ServiceName + programInfo.Id;
+
+ await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ return true;
+ }
+
+ return false;
+ }
+
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.Second; }
+ }
+
+ public override ItemUpdateType ItemUpdateType
+ {
+ get
+ {
+ return ItemUpdateType.ImageUpdate;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
new file mode 100644
index 000000000..0b5ec285e
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
@@ -0,0 +1,156 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+ public class RecordingImageProvider : BaseMetadataProvider
+ {
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly IProviderManager _providerManager;
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public RecordingImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient)
+ : base(logManager, configurationManager)
+ {
+ _liveTvManager = liveTvManager;
+ _providerManager = providerManager;
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ }
+
+ public override bool Supports(BaseItem item)
+ {
+ return item is LiveTvRecording;
+ }
+
+ protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+ {
+ return !item.HasImage(ImageType.Primary);
+ }
+
+ public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ if (item.HasImage(ImageType.Primary))
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ return true;
+ }
+
+ var changed = true;
+
+ try
+ {
+ changed = await DownloadImage((LiveTvRecording)item, cancellationToken).ConfigureAwait(false);
+ }
+ catch (HttpException ex)
+ {
+ // Don't fail the provider on a 404
+ if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
+ {
+ throw;
+ }
+ }
+
+ if (changed)
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ }
+
+ return changed;
+ }
+
+ private async Task<bool> DownloadImage(LiveTvRecording item, CancellationToken cancellationToken)
+ {
+ var recordingInfo = item.RecordingInfo;
+
+ Stream imageStream = null;
+ string contentType = null;
+
+ if (!string.IsNullOrEmpty(recordingInfo.ImagePath))
+ {
+ contentType = "image/" + Path.GetExtension(recordingInfo.ImagePath).ToLower();
+ imageStream = _fileSystem.GetFileStream(recordingInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+ }
+ else if (!string.IsNullOrEmpty(recordingInfo.ImageUrl))
+ {
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = recordingInfo.ImageUrl
+ };
+
+ var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
+
+ if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Error("Provider did not return an image content type.");
+ return false;
+ }
+
+ imageStream = response.Content;
+ contentType = response.ContentType;
+ }
+ else if (recordingInfo.HasImage ?? true)
+ {
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase));
+
+ if (service != null)
+ {
+ try
+ {
+ var response = await service.GetRecordingImageAsync(recordingInfo.Id, cancellationToken).ConfigureAwait(false);
+
+ if (response != null)
+ {
+ imageStream = response.Stream;
+ contentType = response.MimeType;
+ }
+ }
+ catch (NotImplementedException)
+ {
+ return false;
+ }
+ }
+ }
+
+ if (imageStream != null)
+ {
+ // Dummy up the original url
+ var url = item.ServiceName + recordingInfo.Id;
+
+ await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ return true;
+ }
+
+ return false;
+ }
+
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.Second; }
+ }
+
+ public override ItemUpdateType ItemUpdateType
+ {
+ get
+ {
+ return ItemUpdateType.ImageUpdate;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/au.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/au.txt
index a68a3f5f3..fa60f5305 100644
--- a/MediaBrowser.Server.Implementations/Localization/Ratings/au.txt
+++ b/MediaBrowser.Server.Implementations/Localization/Ratings/au.txt
@@ -1,6 +1,8 @@
AU-G,1
AU-PG,5
AU-M,6
+AU-MA15+,7
AU-M15+,8
AU-R18+,9
-AU-X18+,10 \ No newline at end of file
+AU-X18+,10
+AU-RC,11
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 2516a3ae3..86dd0bc75 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -48,14 +48,18 @@
<Reference Include="Alchemy">
<HintPath>..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll</HintPath>
</Reference>
- <Reference Include="Mono.Data.Sqlite">
- <HintPath>..\ThirdParty\Mono.Data.Sqlite\Mono.Data.Sqlite.dll</HintPath>
- </Reference>
<Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
+ <Reference Include="System.Data.SQLite, Version=1.0.90.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Data.SQLite.Linq">
+ <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
+ </Reference>
<Reference Include="System.Drawing" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
@@ -67,8 +71,9 @@
<Reference Include="BDInfo">
<HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.5\lib\net20\BDInfo.dll</HintPath>
</Reference>
- <Reference Include="System.Data.SQLite">
- <HintPath>..\packages\System.Data.SQLite.x86.1.0.89.0\lib\net45\System.Data.SQLite.dll</HintPath>
+ <Reference Include="System.Data.SQLite" Condition=" '$(ConfigurationName)' == 'Release Mono' ">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\ThirdParty\System.Data.SQLite.ManagedOnly\x86\1.0.90.0\net40\System.Data.SQLite.dll</HintPath>
</Reference>
<Reference Include="ServiceStack">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.dll</HintPath>
@@ -85,6 +90,7 @@
<Reference Include="ServiceStack.Text">
<HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
</Reference>
+ <Reference Include="Mono.Posix" Condition=" '$(ConfigurationName)' == 'Release Mono' " />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -153,6 +159,8 @@
<Compile Include="LiveTv\ChannelImageProvider.cs" />
<Compile Include="LiveTv\LiveTvDtoService.cs" />
<Compile Include="LiveTv\LiveTvManager.cs" />
+ <Compile Include="LiveTv\ProgramImageProvider.cs" />
+ <Compile Include="LiveTv\RecordingImageProvider.cs" />
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
<Compile Include="Localization\LocalizationManager.cs" />
<Compile Include="MediaEncoder\MediaEncoder.cs" />
@@ -166,12 +174,14 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\ImageSaver.cs" />
<Compile Include="Providers\ProviderManager.cs" />
+ <Compile Include="Roku\RokuControllerFactory.cs" />
<Compile Include="ScheduledTasks\PeopleValidationTask.cs" />
<Compile Include="ScheduledTasks\ChapterImagesTask.cs" />
<Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" />
<Compile Include="ServerApplicationPaths.cs" />
<Compile Include="ServerManager\ServerManager.cs" />
<Compile Include="ServerManager\WebSocketConnection.cs" />
+ <Compile Include="Roku\RokuSessionController.cs" />
<Compile Include="Session\SessionManager.cs">
<SubType>Code</SubType>
</Compile>
@@ -341,6 +351,16 @@
</Content>
<EmbeddedResource Include="Localization\Ratings\be.txt" />
</ItemGroup>
+ <ItemGroup>
+ <Content Include="..\ThirdParty\System.Data.SQLite.ManagedOnly\x86\1.0.90.0\net40\System.Data.SQLite.dll" Condition=" '$(ConfigurationName)' == 'Release Mono'">
+ <Link>System.Data.SQLite.dll</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\System.Data.SQLite.ManagedOnly\x86\1.0.90.0\net40\System.Data.SQLite.Linq.dll" Condition=" '$(ConfigurationName)' == 'Release Mono'">
+ <Link>System.Data.SQLite.Linq.dll</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
index 9836de735..0b3d5f784 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
@@ -1,11 +1,7 @@
using MediaBrowser.Model.Logging;
using System;
using System.Data;
-#if __MonoCS__
-using Mono.Data.Sqlite;
-#else
using System.Data.SQLite;
-#endif
using System.IO;
using System.Threading.Tasks;
@@ -140,18 +136,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
logger.Info("Opening {0}", dbPath);
- #if __MonoCS__
- var connectionstr = new SqliteConnectionStringBuilder
- {
- PageSize = 4096,
- CacheSize = 4096,
- SyncMode = SynchronizationModes.Normal,
- DataSource = dbPath,
- JournalMode = SQLiteJournalModeEnum.Off
- };
-
- var connection = new SqliteConnection(connectionstr.ConnectionString);
-#else
var connectionstr = new SQLiteConnectionStringBuilder
{
PageSize = 4096,
@@ -162,7 +146,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
};
var connection = new SQLiteConnection(connectionstr.ConnectionString);
-#endif
+
await connection.OpenAsync().ConfigureAwait(false);
return connection;
diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
index 49194ba8d..0346aba97 100644
--- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
+++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
@@ -118,6 +118,8 @@ namespace MediaBrowser.Server.Implementations.Providers
imageIndex = hasScreenshots.ScreenshotImagePaths.Count;
}
+ var index = imageIndex ?? 0;
+
var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally);
// If there are more than one output paths, the stream will need to be seekable
@@ -132,7 +134,7 @@ namespace MediaBrowser.Server.Implementations.Providers
source = memoryStream;
}
- var currentPath = GetCurrentImagePath(item, type, imageIndex);
+ var currentPath = GetCurrentImagePath(item, type, index);
using (source)
{
@@ -152,7 +154,7 @@ namespace MediaBrowser.Server.Implementations.Providers
}
}
- // Set the path into the BaseItem
+ // Set the path into the item
SetImagePath(item, type, imageIndex, paths[0], sourceUrl);
// Delete the current path
@@ -257,27 +259,9 @@ namespace MediaBrowser.Server.Implementations.Providers
/// or
/// imageIndex
/// </exception>
- private string GetCurrentImagePath(BaseItem item, ImageType type, int? imageIndex)
+ private string GetCurrentImagePath(IHasImages item, ImageType type, int imageIndex)
{
- switch (type)
- {
- case ImageType.Screenshot:
-
- var hasScreenshots = (IHasScreenshots)item;
- if (!imageIndex.HasValue)
- {
- throw new ArgumentNullException("imageIndex");
- }
- return hasScreenshots.ScreenshotImagePaths.Count > imageIndex.Value ? hasScreenshots.ScreenshotImagePaths[imageIndex.Value] : null;
- case ImageType.Backdrop:
- if (!imageIndex.HasValue)
- {
- throw new ArgumentNullException("imageIndex");
- }
- return item.BackdropImagePaths.Count > imageIndex.Value ? item.BackdropImagePaths[imageIndex.Value] : null;
- default:
- return item.GetImage(type);
- }
+ return item.GetImagePath(type, imageIndex);
}
/// <summary>
@@ -336,7 +320,7 @@ namespace MediaBrowser.Server.Implementations.Providers
}
break;
default:
- item.SetImage(type, path);
+ item.SetImagePath(type, path);
break;
}
}
@@ -502,7 +486,7 @@ namespace MediaBrowser.Server.Implementations.Providers
{
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) };
}
-
+
var extraFanartFilename = GetBackdropSaveFilename(item.BackdropImagePaths, "fanart", "fanart", outputIndex);
return new[]
@@ -579,6 +563,13 @@ namespace MediaBrowser.Server.Implementations.Providers
return new[] { Path.Combine(seriesFolder, imageFilename) };
}
+
+ if (item.IsInMixedFolder)
+ {
+ return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) };
+ }
+
+ return new[] { Path.Combine(item.MetaLocation, "landscape" + extension) };
}
// All other paths are the same
@@ -593,7 +584,7 @@ namespace MediaBrowser.Server.Implementations.Providers
/// <param name="imageFilename">The image filename.</param>
/// <param name="extension">The extension.</param>
/// <returns>System.String.</returns>
- private string GetSavePathForItemInMixedFolder(BaseItem item, ImageType type, string imageFilename, string extension)
+ private string GetSavePathForItemInMixedFolder(IHasImages item, ImageType type, string imageFilename, string extension)
{
if (type == ImageType.Primary)
{
diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
index cccabe2b8..e9c672151 100644
--- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
+++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
@@ -105,7 +105,6 @@ namespace MediaBrowser.Server.Implementations.Providers
cancellationToken.ThrowIfCancellationRequested();
var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders;
- var excludeTypes = ConfigurationManager.Configuration.InternetProviderExcludeTypes;
var providerHistories = item.DateLastSaved == default(DateTime) ?
new List<BaseProviderInfo>() :
@@ -133,15 +132,6 @@ namespace MediaBrowser.Server.Implementations.Providers
continue;
}
- // Skip if internet provider and this type is not allowed
- if (provider.RequiresInternet &&
- enableInternetProviders &&
- excludeTypes.Length > 0 &&
- excludeTypes.Contains(item.GetType().Name, StringComparer.OrdinalIgnoreCase))
- {
- continue;
- }
-
// Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running
// This is the case for the fan art provider which depends on the movie and tv providers having run before them
if (provider.RequiresInternet && item.DontFetchMeta && provider.EnforceDontFetchMetadata)
@@ -388,7 +378,7 @@ namespace MediaBrowser.Server.Implementations.Providers
providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
}
- var preferredLanguage = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+ var preferredLanguage = item.GetPreferredMetadataLanguage();
var tasks = providers.Select(i => Task.Run(async () =>
{
diff --git a/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs b/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs
new file mode 100644
index 000000000..71f70421a
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs
@@ -0,0 +1,32 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Serialization;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Roku
+{
+ public class RokuControllerFactory : ISessionControllerFactory
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly IJsonSerializer _json;
+ private readonly IServerApplicationHost _appHost;
+
+ public RokuControllerFactory(IHttpClient httpClient, IJsonSerializer json, IServerApplicationHost appHost)
+ {
+ _httpClient = httpClient;
+ _json = json;
+ _appHost = appHost;
+ }
+
+ public ISessionController GetSessionController(SessionInfo session)
+ {
+ if (string.Equals(session.Client, "roku", StringComparison.OrdinalIgnoreCase))
+ {
+ return new RokuSessionController(_httpClient, _json, _appHost, session);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs
new file mode 100644
index 000000000..b9e8b7950
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs
@@ -0,0 +1,149 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.System;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Roku
+{
+ public class RokuSessionController : ISessionController
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly IJsonSerializer _json;
+ private readonly IServerApplicationHost _appHost;
+
+ public SessionInfo Session { get; private set; }
+
+ public RokuSessionController(IHttpClient httpClient, IJsonSerializer json, IServerApplicationHost appHost, SessionInfo session)
+ {
+ _httpClient = httpClient;
+ _json = json;
+ _appHost = appHost;
+ Session = session;
+ }
+
+ public bool SupportsMediaRemoteControl
+ {
+ get { return true; }
+ }
+
+ public bool IsSessionActive
+ {
+ get
+ {
+ return (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 10;
+ }
+ }
+
+ public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<string>
+ {
+ MessageType = "SystemCommand",
+ Data = command.ToString()
+
+ }, cancellationToken);
+ }
+
+ public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<MessageCommand>
+ {
+ MessageType = "MessageCommand",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<PlayRequest>
+ {
+ MessageType = "Play",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<BrowseRequest>
+ {
+ MessageType = "Browse",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<PlaystateRequest>
+ {
+ MessageType = "Playstate",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
+ {
+ // Roku probably won't care about this
+ return Task.FromResult(true);
+ }
+
+ public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<SystemInfo>
+ {
+ MessageType = "RestartRequired",
+ Data = _appHost.GetSystemInfo()
+
+ }, cancellationToken);
+ }
+
+ public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
+ {
+ // Roku probably won't care about this
+ return Task.FromResult(true);
+ }
+
+ public Task SendServerShutdownNotification(CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<string>
+ {
+ MessageType = "ServerShuttingDown",
+ Data = string.Empty
+
+ }, cancellationToken);
+ }
+
+ public Task SendServerRestartNotification(CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<string>
+ {
+ MessageType = "ServerRestarting",
+ Data = string.Empty
+
+ }, cancellationToken);
+ }
+
+ private Task SendCommand(object obj, CancellationToken cancellationToken)
+ {
+ var json = _json.SerializeToString(obj);
+
+ return _httpClient.Post(new HttpRequestOptions
+ {
+ Url = "http://" + Session.RemoteEndPoint + "/mb/remotecontrol",
+ CancellationToken = cancellationToken,
+ RequestContent = json,
+ RequestContentType = "application/json"
+ });
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
index 9270b879a..2608ac172 100644
--- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
+++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
@@ -1,7 +1,7 @@
using MediaBrowser.Common.ScheduledTasks;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
@@ -21,10 +21,6 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
class ChapterImagesTask : IScheduledTask
{
/// <summary>
- /// The _kernel
- /// </summary>
- private readonly Kernel _kernel;
- /// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
@@ -48,13 +44,11 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
/// <summary>
/// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
/// </summary>
- /// <param name="kernel">The kernel.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="itemRepo">The item repo.</param>
- public ChapterImagesTask(Kernel kernel, ILogManager logManager, ILibraryManager libraryManager, IItemRepository itemRepo)
+ public ChapterImagesTask(ILogManager logManager, ILibraryManager libraryManager, IItemRepository itemRepo)
{
- _kernel = kernel;
_logger = logManager.GetLogger(GetType().Name);
_libraryManager = libraryManager;
_itemRepo = itemRepo;
@@ -102,13 +96,13 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
// Limit to video files to reduce changes of ffmpeg crash dialog
foreach (var item in newItems
.Where(i => i.LocationType == LocationType.FileSystem && i.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(i.PrimaryImagePath) && i.DefaultVideoStreamIndex.HasValue)
- .Take(2))
+ .Take(1))
{
try
{
var chapters = _itemRepo.GetChapters(item.Id).ToList();
- await _kernel.FFMpegManager.PopulateChapterImages(item, chapters, true, true, CancellationToken.None);
+ await FFMpegManager.Instance.PopulateChapterImages(item, chapters, true, true, CancellationToken.None);
}
catch (Exception ex)
{
@@ -123,8 +117,6 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
{
- // IMPORTANT: Make sure to update the dashboard "wizardsettings" page if this default ever changes
-
return new ITaskTrigger[]
{
new DailyTrigger { TimeOfDay = TimeSpan.FromHours(4) }
@@ -145,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
var numComplete = 0;
- var failHistoryPath = Path.Combine(_kernel.FFMpegManager.VideoImagesDataPath, "failures.txt");
+ var failHistoryPath = Path.Combine(FFMpegManager.Instance.ChapterImagesPath, "failures.txt");
List<string> previouslyFailedImages;
@@ -174,7 +166,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
var chapters = _itemRepo.GetChapters(video.Id).ToList();
- var success = await _kernel.FFMpegManager.PopulateChapterImages(video, chapters, extract, true, cancellationToken);
+ var success = await FFMpegManager.Instance.PopulateChapterImages(video, chapters, extract, true, cancellationToken);
if (!success)
{
@@ -203,7 +195,6 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
{
get
{
- // IMPORTANT: Make sure to update the dashboard "wizardsettings" page if this name ever changes
return "Chapter image extraction";
}
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index 3a07d33a6..1a94b9c79 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -39,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
-
+
/// <summary>
/// Gets or sets the configuration manager.
/// </summary>
@@ -65,6 +65,8 @@ namespace MediaBrowser.Server.Implementations.Session
/// </summary>
public event EventHandler<PlaybackProgressEventArgs> PlaybackStopped;
+ private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>();
+
/// <summary>
/// Initializes a new instance of the <see cref="SessionManager" /> class.
/// </summary>
@@ -83,6 +85,15 @@ namespace MediaBrowser.Server.Implementations.Session
}
/// <summary>
+ /// Adds the parts.
+ /// </summary>
+ /// <param name="sessionFactories">The session factories.</param>
+ public void AddParts(IEnumerable<ISessionControllerFactory> sessionFactories)
+ {
+ _sessionFactories = sessionFactories.ToList();
+ }
+
+ /// <summary>
/// Gets all connections.
/// </summary>
/// <value>All connections.</value>
@@ -98,11 +109,12 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="appVersion">The app version.</param>
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
+ /// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
- /// <exception cref="System.UnauthorizedAccessException"></exception>
/// <exception cref="System.ArgumentNullException">user</exception>
- public async Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, User user)
+ /// <exception cref="System.UnauthorizedAccessException"></exception>
+ public async Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
{
if (string.IsNullOrEmpty(clientType))
{
@@ -128,7 +140,7 @@ namespace MediaBrowser.Server.Implementations.Session
var activityDate = DateTime.UtcNow;
- var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, user);
+ var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, user);
session.LastActivityDate = activityDate;
@@ -196,9 +208,10 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="appVersion">The app version.</param>
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
+ /// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>SessionInfo.</returns>
- private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, User user)
+ private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
{
var key = clientType + deviceId + appVersion;
@@ -212,6 +225,14 @@ namespace MediaBrowser.Server.Implementations.Session
connection.DeviceName = deviceName;
connection.User = user;
+ connection.RemoteEndPoint = remoteEndPoint;
+
+ if (connection.SessionController == null)
+ {
+ connection.SessionController = _sessionFactories
+ .Select(i => i.GetSessionController(connection))
+ .FirstOrDefault(i => i != null);
+ }
return connection;
}
@@ -335,7 +356,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
throw new ArgumentException("PlaybackStopInfo.SessionId cannot be Guid.Empty");
}
-
+
if (info.PositionTicks.HasValue && info.PositionTicks.Value < 0)
{
throw new ArgumentOutOfRangeException("positionTicks");
@@ -497,7 +518,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
throw new ArgumentException("Virtual items are not playable.");
}
-
+
if (command.PlayCommand != PlayCommand.PlayNow)
{
if (items.Any(i => !session.QueueableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
@@ -505,7 +526,7 @@ namespace MediaBrowser.Server.Implementations.Session
throw new ArgumentException(string.Format("Session {0} is unable to queue the requested media type.", session.Id));
}
}
-
+
return session.SessionController.SendPlayCommand(command, cancellationToken);
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
index 41cb7eb6b..08481f09f 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -104,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
_logger.Debug("Logging session activity");
- await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, null).ConfigureAwait(false);
+ await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, message.Connection.RemoteEndPoint, null).ConfigureAwait(false);
session = _sessionManager.Sessions
.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) &&
@@ -114,7 +114,13 @@ namespace MediaBrowser.Server.Implementations.Session
if (session != null)
{
- var controller = new WebSocketController(session, _appHost);
+ var controller = session.SessionController as WebSocketController;
+
+ if (controller == null)
+ {
+ controller = new WebSocketController(session, _appHost);
+ }
+
controller.Sockets.Add(message.Connection);
session.SessionController = controller;
diff --git a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index d6b78bd84..8707f2e5b 100644
--- a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -80,25 +80,36 @@ namespace MediaBrowser.Server.Implementations.Sorting
return xSeason.CompareTo(ySeason);
}
- // Now we know they have the same season
+ // Special comes after episode
+ if (y.AirsAfterSeasonNumber.HasValue)
+ {
+ return -1;
+ }
- // Compare episode number
+ var yEpisode = y.AirsBeforeEpisodeNumber;
+
+ // Special comes before the season
+ if (!yEpisode.HasValue)
+ {
+ return 1;
+ }
- // Add 1 to to non-specials to account for AirsBeforeEpisodeNumber
- var xEpisode = x.IndexNumber ?? -1;
- xEpisode++;
+ // Compare episode number
+ var xEpisode = x.IndexNumber;
- var yEpisode = y.AirsBeforeEpisodeNumber ?? 10000;
+ if (!xEpisode.HasValue)
+ {
+ // Can't really compare if this happens
+ return 0;
+ }
- // Sometimes they'll both have a value.
- // For example AirsAfterSeasonNumber=1, AirsBeforeSeasonNumber=2, AirsBeforeEpisodeNumber=1
- // The episode should be displayed at the end of season 1
- if (y.AirsAfterSeasonNumber.HasValue && y.AirsBeforeSeasonNumber.HasValue && y.AirsBeforeSeasonNumber.Value > y.AirsAfterSeasonNumber.Value)
+ // Special comes before episode
+ if (xEpisode.Value == yEpisode.Value)
{
- yEpisode = 10000;
+ return 1;
}
- return xEpisode.CompareTo(yEpisode);
+ return xEpisode.Value.CompareTo(yEpisode.Value);
}
private int CompareSpecials(Episode x, Episode y)
diff --git a/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs b/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs
index 3fbb01f77..39a68b3f6 100644
--- a/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs
@@ -78,8 +78,14 @@ namespace MediaBrowser.Server.Implementations.Sorting
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
- thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
- thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
+ if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
+ {
+ return 0;
+ }
+ if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
+ {
+ return 0;
+ }
if (thisNumericChunk < thatNumericChunk)
{
diff --git a/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs b/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs
index 8be35071a..e46dab23e 100644
--- a/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs
+++ b/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs
@@ -4,6 +4,9 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Net;
+#if __MonoCS__
+using Mono.Unix.Native;
+#endif
namespace MediaBrowser.Server.Implementations.WebSocket
{
@@ -66,6 +69,20 @@ namespace MediaBrowser.Server.Implementations.WebSocket
TimeOut = TimeSpan.FromHours(24)
};
+ #if __MonoCS__
+ //Linux: port below 1024 require root or cap_net_bind_service
+ if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
+ {
+ if (Syscall.getuid() == 0)
+ {
+ WebSocketServer.FlashAccessPolicyEnabled = true;
+ }
+ else
+ {
+ WebSocketServer.FlashAccessPolicyEnabled = false;
+ }
+ }
+ #endif
WebSocketServer.Start();
}
catch (Exception ex)
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 54c8c9f9d..a504bc6ab 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -3,5 +3,5 @@
<package id="Alchemy" version="2.2.1" targetFramework="net45" />
<package id="MediaBrowser.BdInfo" version="1.0.0.5" targetFramework="net45" />
<package id="morelinq" version="1.0.16006" targetFramework="net45" />
- <package id="System.Data.SQLite.x86" version="1.0.89.0" targetFramework="net45" />
+ <package id="System.Data.SQLite.x86" version="1.0.90.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs
index 970e5a3e0..7cb7278dc 100644
--- a/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs
+++ b/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs
@@ -1,20 +1,57 @@
-
+using System;
+
namespace MediaBrowser.ServerApplication.FFMpeg
{
public static class FFMpegDownloadInfo
{
- public static string Version = "ffmpeg20130904";
+ public static string Version = ffmpegOsType("Version");
- public static string[] FfMpegUrls = new[]
- {
- "http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.2013-10-11.tar.gz",
+ public static string[] FfMpegUrls = ffmpegOsType("FfMpegUrls").Split(',');
- "https://www.dropbox.com/s/b9v17h105cps7p0/ffmpeg.static.32bit.2013-10-11.tar.gz?dl=1"
- };
+ public static string FFMpegFilename = ffmpegOsType("FFMpegFilename");
+ public static string FFProbeFilename = ffmpegOsType("FFProbeFilename");
- public static string FFMpegFilename = "ffmpeg";
- public static string FFProbeFilename = "ffprobe";
+ public static string ArchiveType = ffmpegOsType("ArchiveType");
- public static string ArchiveType = "gz";
+ private static string ffmpegOsType(string arg)
+ {
+ OperatingSystem os = Environment.OSVersion;
+ PlatformID pid = os.Platform;
+ switch (pid)
+ {
+ case PlatformID.Win32NT:
+ switch (arg)
+ {
+ case "Version":
+ return "ffmpeg20131221";
+ case "FfMpegUrls":
+ return "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20131221-git-70d6ce7-win32-static.7z,https://www.dropbox.com/s/d38uj7857trbw1g/ffmpeg-20131209-git-a12f679-win32-static.7z?dl=1";
+ case "FFMpegFilename":
+ return "ffmpeg.exe";
+ case "FFProbeFilename":
+ return "ffprobe.exe";
+ case "ArchiveType":
+ return "7z";
+ }
+ break;
+ case PlatformID.Unix:
+ case PlatformID.MacOSX:
+ switch (arg)
+ {
+ case "Version":
+ return "ffmpeg20131221";
+ case "FfMpegUrls":
+ return "http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.2013-12-21.tar.gz,https://www.dropbox.com/s/b9v17h105cps7p0/ffmpeg.static.32bit.2013-10-11.tar.gz?dl=1";
+ case "FFMpegFilename":
+ return "ffmpeg";
+ case "FFProbeFilename":
+ return "ffprobe";
+ case "ArchiveType":
+ return "gz";
+ }
+ break;
+ }
+ return "";
+ }
}
}
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index 39321876c..d37330821 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -50,6 +50,7 @@
<Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
+ <Reference Include="Mono.Posix" Condition=" '$(ConfigurationName)' == 'Release Mono' "/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -127,8 +128,11 @@
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
- <None Include="sqlite3.dll">
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="..\ThirdParty\SQLite3\x86\3.8.2\sqlite3.dll">
+ <Link>sqlite3.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
+ </Content>
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs
index cf0b4c6d7..59fc11c07 100644
--- a/MediaBrowser.Server.Mono/Program.cs
+++ b/MediaBrowser.Server.Mono/Program.cs
@@ -102,7 +102,9 @@ namespace MediaBrowser.Server.Mono
Console.WriteLine ("appHost.Init");
- var task = _appHost.Init();
+ var initProgress = new Progress<double>();
+
+ var task = _appHost.Init(initProgress);
Task.WaitAll (task);
Console.WriteLine ("Running startup tasks");
diff --git a/MediaBrowser.ServerApplication/App.xaml.cs b/MediaBrowser.ServerApplication/App.xaml.cs
index 50bf02deb..839841620 100644
--- a/MediaBrowser.ServerApplication/App.xaml.cs
+++ b/MediaBrowser.ServerApplication/App.xaml.cs
@@ -116,7 +116,8 @@ namespace MediaBrowser.ServerApplication
var win = new MainWindow(host.LogManager, host,
host.ServerConfigurationManager, host.UserManager,
host.LibraryManager, host.JsonSerializer,
- host.DisplayPreferencesRepository);
+ host.DisplayPreferencesRepository,
+ host.ItemRepository);
win.Show();
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 77b49fbf1..00db91f41 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -71,12 +71,6 @@ namespace MediaBrowser.ServerApplication
public class ApplicationHost : BaseApplicationHost<ServerApplicationPaths>, IServerApplicationHost
{
/// <summary>
- /// Gets the server kernel.
- /// </summary>
- /// <value>The server kernel.</value>
- protected Kernel ServerKernel { get; set; }
-
- /// <summary>
/// Gets the server configuration manager.
/// </summary>
/// <value>The server configuration manager.</value>
@@ -168,7 +162,7 @@ namespace MediaBrowser.ServerApplication
private IUserDataManager UserDataManager { get; set; }
private IUserRepository UserRepository { get; set; }
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
- private IItemRepository ItemRepository { get; set; }
+ internal IItemRepository ItemRepository { get; set; }
private INotificationsRepository NotificationsRepository { get; set; }
/// <summary>
@@ -220,8 +214,6 @@ namespace MediaBrowser.ServerApplication
/// <returns>Task.</returns>
protected override async Task RegisterResources(IProgress<double> progress)
{
- ServerKernel = new Kernel();
-
await base.RegisterResources(progress).ConfigureAwait(false);
RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory(LogManager, FileSystemManager));
@@ -229,7 +221,6 @@ namespace MediaBrowser.ServerApplication
RegisterSingleInstance<IServerApplicationHost>(this);
RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
- RegisterSingleInstance(ServerKernel);
RegisterSingleInstance(ServerConfigurationManager);
RegisterSingleInstance<IWebSocketServer>(() => new AlchemyServer(Logger));
@@ -301,6 +292,8 @@ namespace MediaBrowser.ServerApplication
await Task.WhenAll(itemsTask, displayPreferencesTask, userdataTask).ConfigureAwait(false);
progress.Report(100);
+ await ((UserManager) UserManager).Initialize().ConfigureAwait(false);
+
SetKernelProperties();
}
@@ -331,11 +324,11 @@ namespace MediaBrowser.ServerApplication
/// </summary>
private void SetKernelProperties()
{
- Parallel.Invoke(
- () => ServerKernel.FFMpegManager = new FFMpegManager(ApplicationPaths, MediaEncoder, Logger, ItemRepository, FileSystemManager),
- () => LocalizedStrings.StringFiles = GetExports<LocalizedStringData>(),
- SetStaticProperties
- );
+ new FFMpegManager(MediaEncoder, Logger, ItemRepository, FileSystemManager, ServerConfigurationManager);
+
+ LocalizedStrings.StringFiles = GetExports<LocalizedStringData>();
+
+ SetStaticProperties();
}
/// <summary>
@@ -451,6 +444,8 @@ namespace MediaBrowser.ServerApplication
ImageProcessor.AddParts(GetExports<IImageEnhancer>());
LiveTvManager.AddParts(GetExports<ILiveTvService>());
+
+ SessionManager.AddParts(GetExports<ISessionControllerFactory>());
}
/// <summary>
@@ -568,7 +563,7 @@ namespace MediaBrowser.ServerApplication
list.Add(typeof(IApplicationHost).Assembly);
// Include composable parts in the Controller assembly
- list.Add(typeof(Kernel).Assembly);
+ list.Add(typeof(IServerApplicationHost).Assembly);
// Include composable parts in the Providers assembly
list.Add(typeof(ImagesByNameProvider).Assembly);
diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs
index d257ff362..1e99c4eb0 100644
--- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs
+++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs
@@ -12,6 +12,9 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+#if __MonoCS__
+using Mono.Unix.Native;
+#endif
namespace MediaBrowser.ServerApplication.FFMpeg
{
@@ -147,6 +150,13 @@ namespace MediaBrowser.ServerApplication.FFMpeg
}))
{
File.Copy(file, Path.Combine(targetFolder, Path.GetFileName(file)), true);
+ #if __MonoCS__
+ //Linux: File permission to 666, and user's execute bit
+ if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
+ {
+ Syscall.chmod(Path.Combine(targetFolder, Path.GetFileName(file)), FilePermissions.DEFFILEMODE | FilePermissions.S_IXUSR);
+ }
+ #endif
}
}
finally
diff --git a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
index 6d6157fa7..1a5d73e6b 100644
--- a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
+++ b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
@@ -35,6 +35,8 @@ namespace MediaBrowser.ServerApplication
private readonly ILibraryManager _libraryManager;
private readonly IDisplayPreferencesRepository _displayPreferencesManager;
+ private readonly IItemRepository _itemRepository;
+
/// <summary>
/// The current user
/// </summary>
@@ -48,7 +50,7 @@ namespace MediaBrowser.ServerApplication
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="displayPreferencesManager">The display preferences manager.</param>
- public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesRepository displayPreferencesManager)
+ public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
@@ -62,7 +64,7 @@ namespace MediaBrowser.ServerApplication
ddlProfile.Items.Insert(0, new User { Name = "Physical" });
ddlProfile.SelectedIndex = 0;
ddlIndexBy.Visibility = ddlSortBy.Visibility = lblIndexBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;
-
+ _itemRepository = itemRepo;
}
/// <summary>
@@ -212,7 +214,24 @@ namespace MediaBrowser.ServerApplication
lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;
}
- txtData.Text = FormatJson(_jsonSerializer.SerializeToString(item));
+
+ var json = FormatJson(_jsonSerializer.SerializeToString(item));
+
+ if (item is IHasMediaStreams)
+ {
+ var mediaStreams = _itemRepository.GetMediaStreams(new MediaStreamQuery
+ {
+ ItemId = item.Id
+
+ }).ToList();
+
+ if (mediaStreams.Count > 0)
+ {
+ json += "\n\nMedia Streams:\n\n"+FormatJson(_jsonSerializer.SerializeToString(mediaStreams));
+ }
+ }
+
+ txtData.Text = json;
var previews = new List<PreviewItem>();
await Task.Run(() =>
@@ -223,19 +242,19 @@ namespace MediaBrowser.ServerApplication
}
if (item.HasImage(ImageType.Banner))
{
- previews.Add(new PreviewItem(item.GetImage(ImageType.Banner), "Banner"));
+ previews.Add(new PreviewItem(item.GetImagePath(ImageType.Banner), "Banner"));
}
if (item.HasImage(ImageType.Logo))
{
- previews.Add(new PreviewItem(item.GetImage(ImageType.Logo), "Logo"));
+ previews.Add(new PreviewItem(item.GetImagePath(ImageType.Logo), "Logo"));
}
if (item.HasImage(ImageType.Art))
{
- previews.Add(new PreviewItem(item.GetImage(ImageType.Art), "Art"));
+ previews.Add(new PreviewItem(item.GetImagePath(ImageType.Art), "Art"));
}
if (item.HasImage(ImageType.Thumb))
{
- previews.Add(new PreviewItem(item.GetImage(ImageType.Thumb), "Thumb"));
+ previews.Add(new PreviewItem(item.GetImagePath(ImageType.Thumb), "Thumb"));
}
previews.AddRange(
item.BackdropImagePaths.Select(
diff --git a/MediaBrowser.ServerApplication/MainWindow.xaml.cs b/MediaBrowser.ServerApplication/MainWindow.xaml.cs
index b1972fbdb..040d714cf 100644
--- a/MediaBrowser.ServerApplication/MainWindow.xaml.cs
+++ b/MediaBrowser.ServerApplication/MainWindow.xaml.cs
@@ -45,6 +45,7 @@ namespace MediaBrowser.ServerApplication
private readonly ILibraryManager _libraryManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IDisplayPreferencesRepository _displayPreferencesManager;
+ private readonly IItemRepository _itemRepository;
/// <summary>
/// Initializes a new instance of the <see cref="MainWindow" /> class.
@@ -57,7 +58,7 @@ namespace MediaBrowser.ServerApplication
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="displayPreferencesManager">The display preferences manager.</param>
/// <exception cref="System.ArgumentNullException">logger</exception>
- public MainWindow(ILogManager logManager, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, IDisplayPreferencesRepository displayPreferencesManager)
+ public MainWindow(ILogManager logManager, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo)
{
if (logManager == null)
{
@@ -73,6 +74,7 @@ namespace MediaBrowser.ServerApplication
}
_logger = logManager.GetLogger("MainWindow");
+ _itemRepository = itemRepo;
_appHost = appHost;
_logManager = logManager;
_configurationManager = configurationManager;
@@ -131,14 +133,10 @@ namespace MediaBrowser.ServerApplication
{
Dispatcher.InvokeAsync(() =>
{
- var developerToolsVisibility = _configurationManager.Configuration.EnableDeveloperTools
- ? Visibility.Visible
- : Visibility.Collapsed;
-
- separatorDeveloperTools.Visibility = developerToolsVisibility;
- cmdReloadServer.Visibility = developerToolsVisibility;
- cmOpenExplorer.Visibility = developerToolsVisibility;
- cmShowLogWindow.Visibility = developerToolsVisibility;
+ separatorDeveloperTools.Visibility = Visibility.Visible;
+ cmdReloadServer.Visibility = Visibility.Visible;
+ cmOpenExplorer.Visibility = Visibility.Visible;
+ cmShowLogWindow.Visibility = Visibility.Visible;
});
}
@@ -234,7 +232,7 @@ namespace MediaBrowser.ServerApplication
/// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
private void cmOpenExplorer_click(object sender, RoutedEventArgs e)
{
- new LibraryExplorer(_jsonSerializer, _logger, _appHost, _userManager, _libraryManager, _displayPreferencesManager).Show();
+ new LibraryExplorer(_jsonSerializer, _logger, _appHost, _userManager, _libraryManager, _displayPreferencesManager, _itemRepository).Show();
}
/// <summary>
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index 2188722b2..7743cc527 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -275,7 +275,9 @@ namespace MediaBrowser.WebDashboard.Api
// Don't cache if not configured to do so
// But always cache images to simulate production
- if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching &&
+ !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) &&
+ !contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
{
return ResultFactory.GetResult(GetResourceStream(path).Result, contentType);
}
@@ -284,7 +286,7 @@ namespace MediaBrowser.WebDashboard.Api
// Cache images unconditionally - updates to image files will require new filename
// If there's a version number in the query string we can cache this unconditionally
- if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(request.V))
+ if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(request.V))
{
cacheDuration = TimeSpan.FromDays(365);
}
@@ -414,8 +416,7 @@ namespace MediaBrowser.WebDashboard.Api
var files = new[]
{
- "http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css",
- "thirdparty/jqm-icon-pack-3.0/font-awesome/jqm-icon-pack-3.0.0-fa.css" + versionString,
+ "thirdparty/jquerymobile-1.4.0/jquery.mobile-1.4.0.min.css",
"css/all.css" + versionString
};
@@ -440,10 +441,8 @@ namespace MediaBrowser.WebDashboard.Api
var files = new[]
{
- "http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js",
- "http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js",
"scripts/all.js" + versionString,
- "thirdparty/jstree1.0/jquery.jstree.js"
+ "thirdparty/jstree1.0/jquery.jstree.min.js"
};
var tags = files.Select(s => string.Format("<script src=\"{0}\"></script>", s)).ToArray();
@@ -466,21 +465,22 @@ namespace MediaBrowser.WebDashboard.Api
"extensions.js",
"site.js",
"librarybrowser.js",
+ "librarymenu.js",
"ratingdialog.js",
"aboutpage.js",
"allusersettings.js",
"alphapicker.js",
"addpluginpage.js",
"advancedconfigurationpage.js",
- "advancedmetadataconfigurationpage.js",
+ "metadataadvanced.js",
"boxsets.js",
- "clientsettings.js",
+ "appsplayback.js",
+ "appsweather.js",
"dashboardpage.js",
"directorybrowser.js",
"edititemmetadata.js",
"edititempeople.js",
"edititemimages.js",
- "edituserpage.js",
"gamesrecommendedpage.js",
"gamesystemspage.js",
"gamespage.js",
@@ -495,6 +495,8 @@ namespace MediaBrowser.WebDashboard.Api
"livetvchannel.js",
"livetvchannels.js",
"livetvguide.js",
+ "livetvnewrecording.js",
+ "livetvprogram.js",
"livetvrecording.js",
"livetvrecordings.js",
"livetvtimer.js",
@@ -539,11 +541,14 @@ namespace MediaBrowser.WebDashboard.Api
"tvshows.js",
"tvstudios.js",
"tvupcoming.js",
- "updatepasswordpage.js",
+ "useredit.js",
+ "userpassword.js",
"userimagepage.js",
"userprofilespage.js",
"usersettings.js",
+ "userparentalcontrol.js",
"wizardfinishpage.js",
+ "wizardimagesettings.js",
"wizardservice.js",
"wizardstartpage.js",
"wizardsettings.js",
@@ -551,17 +556,20 @@ namespace MediaBrowser.WebDashboard.Api
};
var memoryStream = new MemoryStream();
-
var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
+ await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false);
+ await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.0/jquery.mobile-1.4.0.min.js", newLineBytes).ConfigureAwait(false);
+
+ //await AppendResource(memoryStream, "thirdparty/jquery.infinite-scroll-helper.min.js", newLineBytes).ConfigureAwait(false);
+
var versionString = string.Format("window.dashboardVersion='{0}';", _appHost.ApplicationVersion);
var versionBytes = Encoding.UTF8.GetBytes(versionString);
await memoryStream.WriteAsync(versionBytes, 0, versionBytes.Length).ConfigureAwait(false);
await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
- await AppendResource(memoryStream, "thirdparty/autoNumeric.js", newLineBytes).ConfigureAwait(false);
- await AppendResource(memoryStream, "thirdparty/html5slider.js", newLineBytes).ConfigureAwait(false);
+ await AppendResource(memoryStream, "thirdparty/autonumeric/autoNumeric.min.js", newLineBytes).ConfigureAwait(false);
await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false);
@@ -583,6 +591,7 @@ namespace MediaBrowser.WebDashboard.Api
var files = new[]
{
"site.css",
+ "mediaplayer.css",
"librarybrowser.css",
"detailtable.css",
"posteritem.css",
@@ -592,7 +601,9 @@ namespace MediaBrowser.WebDashboard.Api
"search.css",
"pluginupdates.css",
"remotecontrol.css",
- "userimage.css"
+ "userimage.css",
+ "livetv.css",
+ "icons.css"
};
var memoryStream = new MemoryStream();
diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js
index 53f4fde2a..e260a0a05 100644
--- a/MediaBrowser.WebDashboard/ApiClient.js
+++ b/MediaBrowser.WebDashboard/ApiClient.js
@@ -389,13 +389,21 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
});
};
- self.getLiveTvChannel = function (id) {
+ self.getLiveTvChannel = function (id, userId) {
if (!id) {
throw new Error("null id");
}
- var url = self.getUrl("LiveTv/Channels/" + id);
+ var options = {
+
+ };
+
+ if (userId) {
+ options.userId = userId;
+ }
+
+ var url = self.getUrl("LiveTv/Channels/" + id, options);
return self.ajax({
type: "GET",
@@ -437,13 +445,44 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
});
};
- self.getLiveTvRecording = function (id) {
+ self.getLiveTvRecording = function (id, userId) {
if (!id) {
throw new Error("null id");
}
- var url = self.getUrl("LiveTv/Recordings/" + id);
+ var options = {
+
+ };
+
+ if (userId) {
+ options.userId = userId;
+ }
+
+ var url = self.getUrl("LiveTv/Recordings/" + id, options);
+
+ return self.ajax({
+ type: "GET",
+ url: url,
+ dataType: "json"
+ });
+ };
+
+ self.getLiveTvProgram = function (id, userId) {
+
+ if (!id) {
+ throw new Error("null id");
+ }
+
+ var options = {
+
+ };
+
+ if (userId) {
+ options.userId = userId;
+ }
+
+ var url = self.getUrl("LiveTv/Programs/" + id, options);
return self.ajax({
type: "GET",
@@ -506,6 +545,19 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
});
};
+ self.getNewLiveTvTimerDefaults = function (options) {
+
+ options = options || {};
+
+ var url = self.getUrl("LiveTv/Timers/Defaults", options);
+
+ return self.ajax({
+ type: "GET",
+ url: url,
+ dataType: "json"
+ });
+ };
+
self.createLiveTvTimer = function (item) {
if (!item) {
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 09e68f59f..50d25de26 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -85,15 +85,75 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ApiClient.js" />
+ <Content Include="dashboard-ui\appsplayback.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\icons.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\clients\xbmc.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\css\images\editor.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\css\images\icons\audiocd.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\filter.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\mute.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\nexttrack.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\pause.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\play.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\previoustrack.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\remote.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\sort.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\stop.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\subtitles.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\volumedown.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\volumeup.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\css\images\items\detail\tv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\css\livetv.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\mediaplayer.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\livetvchannel.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\livetvnewrecording.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\livetvprogram.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\livetvrecording.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -172,19 +232,19 @@
<Content Include="dashboard-ui\css\images\fresh.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\film.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\film.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\game.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\game.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\music.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\music.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\person.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\person.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\tv.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\tv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\media\audioflyout.png">
@@ -355,12 +415,24 @@
<Content Include="dashboard-ui\livetvrecordings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\appsplayback.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\librarymenu.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\livetvchannel.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\livetvtimer.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\livetvnewrecording.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\livetvprogram.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\livetvrecording.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -397,9 +469,15 @@
<Content Include="dashboard-ui\scripts\tvupcoming.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\userparentalcontrol.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\usersettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\wizardimagesettings.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\wizardservice.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -409,12 +487,639 @@
<Content Include="dashboard-ui\livetvseriestimers.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquery-2.0.3.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquery.infinite-scroll-helper.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\ajax-loader.gif">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\action-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\action-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\alert-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\alert-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-l-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-l-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-r-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-r-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-l-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-l-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-r-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-r-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-l-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-l-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-r-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-r-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\audio-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\audio-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\back-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\back-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\bars-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\bars-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\bullets-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\bullets-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\calendar-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\calendar-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\camera-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\camera-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-d-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-d-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-l-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-l-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-r-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-r-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-u-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-u-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\check-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\check-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\clock-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\clock-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\cloud-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\cloud-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\comment-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\comment-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\delete-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\delete-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\edit-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\edit-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\eye-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\eye-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\forbidden-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\forbidden-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\forward-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\forward-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\gear-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\gear-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\grid-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\grid-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\heart-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\heart-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\home-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\home-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\info-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\info-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\location-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\location-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\lock-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\lock-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\mail-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\mail-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\minus-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\minus-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\navigation-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\navigation-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\phone-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\phone-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\plus-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\plus-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\power-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\power-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\recycle-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\recycle-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\refresh-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\refresh-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\search-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\search-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\shop-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\shop-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\star-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\star-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\tag-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\tag-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\user-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\user-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\video-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\video-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\action-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\action-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\alert-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\alert-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-l-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-l-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-r-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-r-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-l-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-l-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-r-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-r-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-l-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-l-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-r-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-r-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\audio-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\audio-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\back-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\back-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\bars-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\bars-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\bullets-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\bullets-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\calendar-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\calendar-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\camera-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\camera-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-d-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-d-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-l-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-l-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-r-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-r-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-u-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-u-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\check-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\check-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\clock-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\clock-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\cloud-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\cloud-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\comment-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\comment-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\delete-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\delete-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\edit-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\edit-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\eye-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\eye-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\forbidden-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\forbidden-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\forward-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\forward-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\gear-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\gear-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\grid-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\grid-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\heart-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\heart-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\home-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\home-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\info-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\info-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\location-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\location-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\lock-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\lock-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\mail-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\mail-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\minus-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\minus-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\navigation-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\navigation-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\phone-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\phone-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\plus-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\plus-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\power-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\power-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\recycle-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\recycle-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\refresh-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\refresh-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\search-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\search-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\shop-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\shop-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\star-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\star-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\tag-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\tag-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\user-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\user-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\video-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\video-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\jquery.mobile-1.4.0.min.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\jquery.mobile-1.4.0.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\tvupcoming.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\userparentalcontrol.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\usersettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\wizardimagesettings.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\wizardservice.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -618,10 +1323,7 @@
<Content Include="dashboard-ui\scripts\tvstudios.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\autoNumeric.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\html5slider.js">
+ <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.js">
@@ -734,12 +1436,12 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\clientsettings.html">
+ <Content Include="dashboard-ui\appsweather.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\edituser.html">
+ <Content Include="dashboard-ui\useredit.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -765,7 +1467,7 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\updatepassword.html">
+ <Content Include="dashboard-ui\userpassword.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -805,7 +1507,7 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\scripts\clientsettings.js">
+ <Content Include="dashboard-ui\scripts\appsweather.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -825,7 +1527,7 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\scripts\updatepasswordpage.js">
+ <Content Include="dashboard-ui\scripts\userpassword.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -833,7 +1535,7 @@
<Content Include="dashboard-ui\scripts\advancedconfigurationpage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\edituserpage.js">
+ <Content Include="dashboard-ui\scripts\useredit.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\userimagepage.js">
@@ -844,12 +1546,12 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\advancedmetadata.html">
+ <Content Include="dashboard-ui\metadataadvanced.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\scripts\advancedmetadataconfigurationpage.js">
+ <Content Include="dashboard-ui\scripts\metadataadvanced.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -1059,76 +1761,6 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\faicons-v2.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\faicons.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\ajax-loader.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\icons-18-black-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\icons-18-white-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\icons-36-black-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\icons-36-white-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\index.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\jqm-icon-pack-3.0.0-fa.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\ajax-loader.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\ajax-loader.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\icons-18-black-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\icons-18-white-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\icons-36-black-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\icons-36-white-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\index.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\jqm-icon-pack-2.0-original.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\font\fontawesome-webfont.eot">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\font\fontawesome-webfont.ttf">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\font\fontawesome-webfont.woff">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\font\FontAwesome.otf">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\jqm-icon-pack-3.0.0-fa.scss">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
<Content Include="dashboard-ui\css\images\supporter\registerpaypal.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1232,8 +1864,21 @@
</Content>
</ItemGroup>
<ItemGroup>
+ <None Include="dashboard-ui\css\fonts\OpenSans-ExtraBold.woff">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="dashboard-ui\css\fonts\OpenSans-Bold.woff">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="dashboard-ui\css\fonts\OpenSans.woff">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="dashboard-ui\css\fonts\OpenSans-Light.woff">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
<None Include="packages.config" />
</ItemGroup>
+ <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config
index 808183a67..659c5db1e 100644
--- a/MediaBrowser.WebDashboard/packages.config
+++ b/MediaBrowser.WebDashboard/packages.config
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="MediaBrowser.ApiClient.Javascript" version="3.0.206" targetFramework="net45" />
+ <package id="MediaBrowser.ApiClient.Javascript" version="3.0.210" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index 116671f44..1df7b17bf 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
- <version>3.0.271</version>
+ <version>3.0.292</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.271" />
+ <dependency id="MediaBrowser.Common" version="3.0.292" />
<dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.4.0" />
<dependency id="sharpcompress" version="0.10.2" />
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index 126ddf4ca..14e09d90c 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
- <version>3.0.271</version>
+ <version>3.0.292</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 7de6b9e00..d49dfb85d 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
- <version>3.0.271</version>
+ <version>3.0.292</version>
<title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.271" />
+ <dependency id="MediaBrowser.Common" version="3.0.292" />
</dependencies>
</metadata>
<files>
diff --git a/README.md b/README.md
index 43cf18bdc..c06ffec0e 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ We have several client apps released and in production:
- Html5
- [iOS](https://itunes.apple.com/us/app/media-browser-for-ios/id705058087 "iOS")
- [Media Portal](http://www.team-mediaportal.com/ "Media Portal")
-- Roku
+- [Roku](http://www.roku.com/channels/#!details/34503/media-browser "Roku")
- Windows 7/8 Desktop
- Windows Media Center
- [Windows Phone](http://www.windowsphone.com/s?appid=f4971ed9-f651-4bf6-84bb-94fd98613b86 "Windows Phone")
@@ -36,8 +36,8 @@ http://mediabrowser3.com/community
## Current Versions ##
-Release: 3.0.4999.38224<br/>
-Beta: 3.0.5028.39800<br/>
+Release: 3.0.5099.2102<br/>
+Beta: 3.0.5097.16641<br/>
## Images