aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations
diff options
context:
space:
mode:
authorEric Reed <ebr@mediabrowser3.com>2013-12-04 15:07:56 -0500
committerEric Reed <ebr@mediabrowser3.com>2013-12-04 15:07:56 -0500
commit6819be81601f6a95a60ce2735474ae0015d19bff (patch)
tree7e2743455e53d4a028fae789f2fc74a7c5ae87b9 /MediaBrowser.Server.Implementations
parent190be6311fbdf3a73f9c8e330f44edafe7764284 (diff)
parentcb882a4b48e9cf03cd363c54d93338ad62153e7e (diff)
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
Diffstat (limited to 'MediaBrowser.Server.Implementations')
-rw-r--r--MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs8
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs69
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs15
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs13
-rw-r--r--MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs110
-rw-r--r--MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs16
-rw-r--r--MediaBrowser.Server.Implementations/Library/ResolverHelper.cs5
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs15
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs9
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs75
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs95
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs553
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs59
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Ratings/ca.txt6
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj73
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs20
-rw-r--r--MediaBrowser.Server.Implementations/Providers/ImageSaver.cs50
-rw-r--r--MediaBrowser.Server.Implementations/ServerApplicationPaths.cs13
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs138
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/packages.config11
-rw-r--r--MediaBrowser.Server.Implementations/swagger-ui/index.html2
36 files changed, 1151 insertions, 245 deletions
diff --git a/MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs b/MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs
index 219b76cd53..06768f353b 100644
--- a/MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs
+++ b/MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs
@@ -127,7 +127,6 @@ namespace MediaBrowser.Server.Implementations.BdInfo
{
var stream = new MediaStream
{
- BitRate = Convert.ToInt32(audioStream.BitRate),
Codec = audioStream.CodecShortName,
Language = audioStream.LanguageCode,
Channels = audioStream.ChannelCount,
@@ -136,6 +135,13 @@ namespace MediaBrowser.Server.Implementations.BdInfo
Index = streams.Count
};
+ var bitrate = Convert.ToInt32(audioStream.BitRate);
+
+ if (bitrate > 0)
+ {
+ stream.BitRate = bitrate;
+ }
+
if (audioStream.LFE > 0)
{
stream.Channels = audioStream.ChannelCount + 1;
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 1f0e7d1e1e..f298c2d4d1 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -129,17 +129,13 @@ namespace MediaBrowser.Server.Implementations.Dto
/// <param name="user">The user.</param>
private void AttachItemByNameCounts(BaseItemDto dto, IItemByName item, User user)
{
- ItemByNameCounts counts;
-
if (user == null)
{
//counts = item.ItemCounts;
return;
}
- if (!item.UserItemCounts.TryGetValue(user.Id, out counts))
- {
- counts = new ItemByNameCounts();
- }
+
+ ItemByNameCounts counts = item.GetItemByNameCounts(user.Id) ?? new ItemByNameCounts();
dto.ChildCount = counts.TotalCount;
@@ -207,7 +203,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (!string.IsNullOrEmpty(image))
{
- dto.PrimaryImageTag = _imageProcessor.GetImageCacheTag(user, ImageType.Primary, image);
+ dto.PrimaryImageTag = GetImageCacheTag(user, ImageType.Primary, image);
try
{
@@ -277,7 +273,7 @@ namespace MediaBrowser.Server.Implementations.Dto
Id = GetDtoId(item),
Name = item.Name,
MediaType = item.MediaType,
- Type = item.GetType().Name,
+ Type = item.GetClientTypeName(),
RunTimeTicks = item.RunTimeTicks
};
@@ -285,13 +281,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (!string.IsNullOrEmpty(imagePath))
{
- try
- {
- info.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, imagePath);
- }
- catch (IOException)
- {
- }
+ info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary, imagePath);
}
return info;
@@ -433,7 +423,7 @@ namespace MediaBrowser.Server.Implementations.Dto
// Ordering by person type to ensure actors and artists are at the front.
// This is taking advantage of the fact that they both begin with A
// This should be improved in the future
- var people = item.People.OrderBy(i => i.Type).ToList();
+ var people = item.People.OrderBy(i => i.SortOrder ?? int.MaxValue).ThenBy(i => i.Type).ToList();
// Attach People by transforming them into BaseItemPerson (DTO)
dto.People = new BaseItemPerson[people.Count];
@@ -733,14 +723,18 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.EnableInternetProviders = !item.DontFetchMeta;
}
- if (fields.Contains(ItemFields.Budget))
+ var hasBudget = item as IHasBudget;
+ if (hasBudget != null)
{
- dto.Budget = item.Budget;
- }
+ if (fields.Contains(ItemFields.Budget))
+ {
+ dto.Budget = hasBudget.Budget;
+ }
- if (fields.Contains(ItemFields.Revenue))
- {
- dto.Revenue = item.Revenue;
+ if (fields.Contains(ItemFields.Revenue))
+ {
+ dto.Revenue = hasBudget.Revenue;
+ }
}
dto.EndDate = item.EndDate;
@@ -760,7 +754,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.ProductionLocations = item.ProductionLocations;
}
- dto.AspectRatio = item.AspectRatio;
+ var hasAspectRatio = item as IHasAspectRatio;
+ if (hasAspectRatio != null)
+ {
+ dto.AspectRatio = hasAspectRatio.AspectRatio;
+ }
dto.BackdropImageTags = GetBackdropImageTags(item);
@@ -806,11 +804,17 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
- var localTrailerCount = item.LocalTrailerIds.Count;
+ var hasTrailers = item as IHasTrailers;
+ if (hasTrailers != null)
+ {
+ dto.LocalTrailerCount = hasTrailers.LocalTrailerIds.Count;
+ }
- if (localTrailerCount > 0)
+ if (fields.Contains(ItemFields.RemoteTrailers))
{
- dto.LocalTrailerCount = localTrailerCount;
+ dto.RemoteTrailers = hasTrailers != null ?
+ hasTrailers.RemoteTrailers :
+ new List<MediaUrl>();
}
dto.Name = item.Name;
@@ -923,12 +927,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Taglines = item.Taglines;
}
- if (fields.Contains(ItemFields.RemoteTrailers))
- {
- dto.RemoteTrailers = item.RemoteTrailers;
- }
-
- dto.Type = item.GetType().Name;
+ dto.Type = item.GetClientTypeName();
dto.CommunityRating = item.CommunityRating;
dto.VoteCount = item.VoteCount;
@@ -1030,6 +1029,12 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.IndexNumberEnd = episode.IndexNumberEnd;
dto.SpecialSeasonNumber = episode.AirsAfterSeasonNumber ?? episode.AirsBeforeSeasonNumber;
+
+ var seasonId = episode.SeasonId;
+ if (seasonId.HasValue)
+ {
+ dto.SeasonId = seasonId.Value.ToString("N");
+ }
}
// Add SeriesInfo
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
index e6942fae6a..5a5a2dd040 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -99,6 +99,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return result;
}
+ private bool SupportsCompression
+ {
+ get
+ {
+ return true;
+ }
+ }
+
/// <summary>
/// Gets the optimized result.
/// </summary>
@@ -116,7 +124,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
throw new ArgumentNullException("result");
}
- var optimizedResult = requestContext.ToOptimizedResult(result);
+ var optimizedResult = SupportsCompression ? requestContext.ToOptimizedResult(result) : result;
if (responseHeaders != null)
{
@@ -458,6 +466,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
+ if (!SupportsCompression)
+ {
+ return new HttpResult(content, contentType);
+ }
+
var contents = content.Compress(requestContext.CompressionType);
return new CompressedResult(contents, requestContext.CompressionType, contentType);
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
index 20728a30ca..904b6799b4 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
@@ -1,6 +1,6 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using ServiceStack.ServiceHost;
-using System.Diagnostics;
using System.IO;
namespace MediaBrowser.Server.Implementations.HttpServer
@@ -20,6 +20,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
public class SwaggerService : IHasResultFactory, IRestfulService
{
+ private readonly IApplicationPaths _appPaths;
+
+ public SwaggerService(IApplicationPaths appPaths)
+ {
+ _appPaths = appPaths;
+ }
+
/// <summary>
/// Gets the specified request.
/// </summary>
@@ -27,7 +34,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <returns>System.Object.</returns>
public object Get(GetSwaggerResource request)
{
- var runningDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
+ var runningDirectory = Path.GetDirectoryName(_appPaths.ApplicationPath);
var swaggerDirectory = Path.Combine(runningDirectory, "swagger-ui");
diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs
index ffb3512220..870a14bd80 100644
--- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs
+++ b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs
@@ -68,7 +68,8 @@ namespace MediaBrowser.Server.Implementations.IO
{
// This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called.
// Seeing long delays in some situations, especially over the network.
- await Task.Delay(40000).ConfigureAwait(false);
+ // Seeing delays up to 40 seconds, but not going to ignore changes for that long.
+ await Task.Delay(1500).ConfigureAwait(false);
string val;
_tempIgnoredPaths.TryRemove(path, out val);
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 6a2df70b17..3b6a5ea25d 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -391,10 +391,23 @@ namespace MediaBrowser.Server.Implementations.Library
/// <param name="item">The item.</param>
private void UpdateItemInLibraryCache(BaseItem item)
{
- if (!(item is IItemByName))
+ if (item is IItemByName)
{
- LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
+ var hasDualAccess = item as IHasDualAccess;
+ if (hasDualAccess != null)
+ {
+ if (hasDualAccess.IsAccessedByName)
+ {
+ return;
+ }
+ }
+ else
+ {
+ return;
+ }
}
+
+ LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
}
/// <summary>
@@ -657,16 +670,6 @@ namespace MediaBrowser.Server.Implementations.Library
}
/// <summary>
- /// Gets a Genre
- /// </summary>
- /// <param name="name">The name.</param>
- /// <returns>Task{Genre}.</returns>
- public Artist GetArtist(string name)
- {
- return GetItemByName<Artist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name);
- }
-
- /// <summary>
/// The us culture
/// </summary>
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -688,6 +691,16 @@ namespace MediaBrowser.Server.Implementations.Library
}
/// <summary>
+ /// Gets a Genre
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <returns>Task{Genre}.</returns>
+ public MusicArtist GetArtist(string name)
+ {
+ return GetItemByName<MusicArtist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name);
+ }
+
+ /// <summary>
/// The images by name item cache
/// </summary>
private readonly ConcurrentDictionary<string, BaseItem> _itemsByName = new ConcurrentDictionary<string, BaseItem>(StringComparer.OrdinalIgnoreCase);
@@ -697,12 +710,12 @@ namespace MediaBrowser.Server.Implementations.Library
{
if (string.IsNullOrEmpty(path))
{
- throw new ArgumentNullException();
+ throw new ArgumentNullException("path");
}
if (string.IsNullOrEmpty(name))
{
- throw new ArgumentNullException();
+ throw new ArgumentNullException("name");
}
var validFilename = _fileSystem.GetValidFilename(name).Trim();
@@ -743,6 +756,20 @@ namespace MediaBrowser.Server.Implementations.Library
private Tuple<bool, T> CreateItemByName<T>(string path, string name)
where T : BaseItem, new()
{
+ var isArtist = typeof(T) == typeof(MusicArtist);
+
+ if (isArtist)
+ {
+ var existing = RootFolder.RecursiveChildren
+ .OfType<T>()
+ .FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
+
+ if (existing != null)
+ {
+ return new Tuple<bool, T>(false, existing);
+ }
+ }
+
var fileInfo = new DirectoryInfo(path);
var isNew = false;
@@ -779,6 +806,11 @@ namespace MediaBrowser.Server.Implementations.Library
isNew = true;
}
+ if (isArtist)
+ {
+ (item as MusicArtist).IsAccessedByName = true;
+ }
+
// Set this now so we don't cause additional file system access during provider executions
item.ResetResolveArgs(fileInfo);
@@ -874,6 +906,20 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>Task.</returns>
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
+ _directoryWatchersFactory().Stop();
+
+ try
+ {
+ await PerformLibraryValidation(progress, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ _directoryWatchersFactory().Start();
+ }
+ }
+
+ private async Task PerformLibraryValidation(IProgress<double> progress, CancellationToken cancellationToken)
+ {
_logger.Info("Validating media library");
await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
@@ -1361,16 +1407,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>BaseItem.</returns>
public BaseItem RetrieveItem(Guid id)
{
- var item = ItemRepository.RetrieveItem(id);
-
- var folder = item as Folder;
-
- if (folder != null)
- {
- folder.LoadSavedChildren();
- }
-
- return item;
+ return ItemRepository.RetrieveItem(id);
}
private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
@@ -1470,5 +1507,30 @@ namespace MediaBrowser.Server.Implementations.Library
return collectionTypes.Count == 1 ? collectionTypes[0] : null;
}
+
+
+ public IEnumerable<string> GetAllArtists()
+ {
+ return GetAllArtists(RootFolder.RecursiveChildren);
+ }
+
+ public IEnumerable<string> GetAllArtists(IEnumerable<BaseItem> items)
+ {
+ return items
+ .OfType<Audio>()
+ .SelectMany(i =>
+ {
+ var list = new List<string>();
+
+ if (!string.IsNullOrEmpty(i.AlbumArtist))
+ {
+ list.Add(i.AlbumArtist);
+ }
+ list.AddRange(i.Artists);
+
+ return list;
+ })
+ .Distinct(StringComparer.OrdinalIgnoreCase);
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs b/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs
index d5fccc6fd1..b194b2e946 100644
--- a/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs
+++ b/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs
@@ -74,21 +74,7 @@ namespace MediaBrowser.Server.Implementations.Library
}));
// Find artists
- var artists = items.OfType<Audio>()
- .SelectMany(i =>
- {
- var list = new List<string>();
-
- if (!string.IsNullOrEmpty(i.AlbumArtist))
- {
- list.Add(i.AlbumArtist);
- }
- list.AddRange(i.Artists);
-
- return list;
- })
- .Where(i => !string.IsNullOrEmpty(i))
- .Distinct(StringComparer.OrdinalIgnoreCase)
+ var artists = _libraryManager.GetAllArtists(items)
.ToList();
foreach (var item in artists)
diff --git a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
index 620bcaee4a..e32fcd627b 100644
--- a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
+++ b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
@@ -1,11 +1,11 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using System;
using System.IO;
+using System.Linq;
using System.Text.RegularExpressions;
namespace MediaBrowser.Server.Implementations.Library
@@ -48,7 +48,8 @@ namespace MediaBrowser.Server.Implementations.Library
// Make sure the item has a name
EnsureName(item);
- item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1;
+ item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
+ item.Parents.Any(i => i.DontFetchMeta);
// Make sure DateCreated and DateModified have values
EntityResolutionHelper.EnsureDates(fileSystem, item, args, true);
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 0f87b9d337..03e29dd38f 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -172,7 +172,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 = Path.GetFileName(item.Path);
+ var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(Path.GetDirectoryName(item.Path));
var id = justName.GetAttributeValue("tmdbid");
@@ -345,26 +345,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
/// <returns><c>true</c> if [is DVD directory] [the specified directory name]; otherwise, <c>false</c>.</returns>
private bool IsDvdDirectory(string directoryName)
{
- return directoryName.Equals("video_ts", StringComparison.OrdinalIgnoreCase);
+ return string.Equals(directoryName, "video_ts", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
- /// Determines whether [is hd DVD directory] [the specified directory name].
- /// </summary>
- /// <param name="directoryName">Name of the directory.</param>
- /// <returns><c>true</c> if [is hd DVD directory] [the specified directory name]; otherwise, <c>false</c>.</returns>
- private bool IsHdDvdDirectory(string directoryName)
- {
- return directoryName.Equals("hvdvd_ts", StringComparison.OrdinalIgnoreCase);
- }
- /// <summary>
/// Determines whether [is blu ray directory] [the specified directory name].
/// </summary>
/// <param name="directoryName">Name of the directory.</param>
/// <returns><c>true</c> if [is blu ray directory] [the specified directory name]; otherwise, <c>false</c>.</returns>
private bool IsBluRayDirectory(string directoryName)
{
- return directoryName.Equals("bdmv", StringComparison.OrdinalIgnoreCase);
+ return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index 0a6a72fc10..693594a20b 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
var season = args.Parent as Season;
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
- if (season != null)
+ if (season != null || args.Parent is Series)
{
Episode episode = null;
@@ -51,8 +51,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
if (episode != null)
{
- episode.ParentIndexNumber = season.IndexNumber;
-
+ if (season != null)
+ {
+ episode.ParentIndexNumber = season.IndexNumber;
+ }
+
if (episode.ParentIndexNumber == null)
{
episode.ParentIndexNumber = TVUtils.GetSeasonNumberFromEpisodeFile(args.Path);
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs
index 7d46d7060d..40ef5304c6 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs
@@ -57,7 +57,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
var allItems = _libraryManager.RootFolder.GetRecursiveChildren();
- var allMusicArtists = allItems.OfType<MusicArtist>().ToList();
var allSongs = allItems.OfType<Audio>().ToList();
var innerProgress = new ActionableProgress<double>();
@@ -80,36 +79,8 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
cancellationToken.ThrowIfCancellationRequested();
- artist.ValidateImages();
- artist.ValidateBackdrops();
-
- var musicArtist = Artist.FindMusicArtist(artist, allMusicArtists);
-
- if (musicArtist != null)
- {
- MergeImages(musicArtist.Images, artist.Images);
-
- // Merge backdrops
- var additionalBackdrops = musicArtist
- .BackdropImagePaths
- .Except(artist.BackdropImagePaths)
- .ToList();
-
- var sources = additionalBackdrops
- .Select(musicArtist.GetImageSourceInfo)
- .Where(i => i != null)
- .ToList();
-
- foreach (var path in additionalBackdrops)
- {
- artist.RemoveImageSourceForPath(path);
- }
-
- artist.BackdropImagePaths.AddRange(additionalBackdrops);
- artist.ImageSources.AddRange(sources);
- }
-
- if (!artist.LockedFields.Contains(MetadataFields.Genres))
+ // Only do this for artists accessed by name. Folder-based artists use ArtistInfoFromSongsProvider
+ if (artist.IsAccessedByName && !artist.LockedFields.Contains(MetadataFields.Genres))
{
// Avoid implicitly captured closure
var artist1 = artist;
@@ -145,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <param name="artist">The artist.</param>
/// <param name="userId">The user id.</param>
/// <param name="allItems">All items.</param>
- private void SetItemCounts(Artist artist, Guid? userId, IEnumerable<IHasArtist> allItems)
+ private void SetItemCounts(MusicArtist artist, Guid? userId, IEnumerable<IHasArtist> allItems)
{
var name = artist.Name;
@@ -166,26 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
if (userId.HasValue)
{
- artist.UserItemCounts[userId.Value] = counts;
- }
- }
-
- /// <summary>
- /// Merges the images.
- /// </summary>
- /// <param name="source">The source.</param>
- /// <param name="target">The target.</param>
- private void MergeImages(Dictionary<ImageType, string> source, Dictionary<ImageType, string> target)
- {
- foreach (var key in source.Keys
- .Where(k => !target.ContainsKey(k)))
- {
- string path;
-
- if (source.TryGetValue(key, out path))
- {
- target[key] = path;
- }
+ artist.SetItemByNameCounts(userId.Value, counts);
}
}
@@ -196,25 +148,12 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <returns>Task{Artist[]}.</returns>
- private async Task<List<Artist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress)
+ private async Task<List<MusicArtist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress)
{
- var allArtists = allSongs
- .SelectMany(i =>
- {
- var list = new List<string>();
-
- if (!string.IsNullOrEmpty(i.AlbumArtist))
- {
- list.Add(i.AlbumArtist);
- }
- list.AddRange(i.Artists);
-
- return list;
- })
- .Distinct(StringComparer.OrdinalIgnoreCase)
+ var allArtists = _libraryManager.GetAllArtists(allSongs)
.ToList();
- var returnArtists = new List<Artist>(allArtists.Count);
+ var returnArtists = new List<MusicArtist>(allArtists.Count);
var numComplete = 0;
var numArtists = allArtists.Count;
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs
index d21a123c07..c7af7a238f 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs
@@ -106,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
var itemCounts = CountHelpers.GetCounts(counts[libraryId]);
- itemByName.UserItemCounts[libraryId] = itemCounts;
+ itemByName.SetItemByNameCounts(libraryId, itemCounts);
}
await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs
index 0670e1a851..cb1253df07 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs
@@ -107,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
var itemCounts = CountHelpers.GetCounts(counts[libraryId]);
- itemByName.UserItemCounts[libraryId] = itemCounts;
+ itemByName.SetItemByNameCounts(libraryId, itemCounts);
}
await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs
index 166f557cf0..57a6a612bc 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs
@@ -107,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
var itemCounts = CountHelpers.GetCounts(counts[libraryId]);
- itemByName.UserItemCounts[libraryId] = itemCounts;
+ itemByName.SetItemByNameCounts(libraryId, itemCounts);
}
await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs
index cfc7f4310d..0104b2b7ec 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs
@@ -94,7 +94,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
var itemCounts = CountHelpers.GetCounts(counts[libraryId]);
- itemByName.UserItemCounts[libraryId] = itemCounts;
+ itemByName.SetItemByNameCounts(libraryId, itemCounts);
}
}
catch (Exception ex)
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
index 02c7a94b4b..0f4ff562ef 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -106,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
{
var itemCounts = CountHelpers.GetCounts(counts[libraryId]);
- itemByName.UserItemCounts[libraryId] = itemCounts;
+ itemByName.SetItemByNameCounts(libraryId, itemCounts);
}
await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
new file mode 100644
index 0000000000..322948bade
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
@@ -0,0 +1,95 @@
+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.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+ public class ChannelImageProvider : BaseMetadataProvider
+ {
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly IProviderManager _providerManager;
+
+ public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager)
+ : base(logManager, configurationManager)
+ {
+ _liveTvManager = liveTvManager;
+ _providerManager = providerManager;
+ }
+
+ public override bool Supports(BaseItem item)
+ {
+ return item is Channel;
+ }
+
+ protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+ {
+ return !item.HasImage(ImageType.Primary);
+ }
+
+ public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
+ {
+ if (item.HasImage(ImageType.Primary))
+ {
+ SetLastRefreshed(item, DateTime.UtcNow);
+ return true;
+ }
+
+ try
+ {
+ await DownloadImage(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;
+ }
+ }
+
+
+ SetLastRefreshed(item, DateTime.UtcNow);
+ return true;
+ }
+
+ private async Task DownloadImage(BaseItem item, CancellationToken cancellationToken)
+ {
+ var channel = (Channel)item;
+
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, channel.ServiceName, StringComparison.OrdinalIgnoreCase));
+
+ if (service != null)
+ {
+ var response = await service.GetChannelImageAsync(channel.ChannelId, cancellationToken).ConfigureAwait(false);
+
+ // Dummy up the original url
+ var url = channel.ServiceName + channel.ChannelId;
+
+ await _providerManager.SaveImage(channel, response.Stream, response.MimeType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.Second; }
+ }
+
+ public override ItemUpdateType ItemUpdateType
+ {
+ get
+ {
+ return ItemUpdateType.ImageUpdate;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 05bac17c3b..4fd1f0e43b 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -1,6 +1,23 @@
-using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Querying;
+using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.LiveTv
{
@@ -9,7 +26,35 @@ namespace MediaBrowser.Server.Implementations.LiveTv
/// </summary>
public class LiveTvManager : ILiveTvManager
{
+ private readonly IServerApplicationPaths _appPaths;
+ private readonly IFileSystem _fileSystem;
+ private readonly ILogger _logger;
+ private readonly IItemRepository _itemRepo;
+ private readonly IImageProcessor _imageProcessor;
+
+ private readonly IUserManager _userManager;
+ private readonly ILocalizationManager _localization;
+ private readonly IUserDataManager _userDataManager;
+ private readonly IDtoService _dtoService;
+
private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
+
+ private List<Channel> _channels = new List<Channel>();
+ private List<ProgramInfoDto> _programs = new List<ProgramInfoDto>();
+
+ public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserManager userManager, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService)
+ {
+ _appPaths = appPaths;
+ _fileSystem = fileSystem;
+ _logger = logger;
+ _itemRepo = itemRepo;
+ _imageProcessor = imageProcessor;
+ _userManager = userManager;
+ _localization = localization;
+ _userDataManager = userDataManager;
+ _dtoService = dtoService;
+ }
+
/// <summary>
/// Gets the services.
/// </summary>
@@ -32,17 +77,515 @@ namespace MediaBrowser.Server.Implementations.LiveTv
/// Gets the channel info dto.
/// </summary>
/// <param name="info">The info.</param>
+ /// <param name="user">The user.</param>
/// <returns>ChannelInfoDto.</returns>
- public ChannelInfoDto GetChannelInfoDto(ChannelInfo info)
+ public ChannelInfoDto GetChannelInfoDto(Channel info, User user)
{
- return new ChannelInfoDto
+ var dto = new ChannelInfoDto
{
Name = info.Name,
ServiceName = info.ServiceName,
ChannelType = info.ChannelType,
- Id = info.Id,
- Number = info.Number
+ Number = info.ChannelNumber,
+ Type = info.GetType().Name,
+ Id = info.Id.ToString("N"),
+ MediaType = info.MediaType
+ };
+
+ if (user != null)
+ {
+ dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
+ }
+
+ var imageTag = GetLogoImageTag(info);
+
+ if (imageTag.HasValue)
+ {
+ dto.ImageTags[ImageType.Primary] = imageTag.Value;
+ }
+
+ return dto;
+ }
+
+ private Guid? GetLogoImageTag(Channel info)
+ {
+ var path = info.PrimaryImagePath;
+
+ if (string.IsNullOrEmpty(path))
+ {
+ return null;
+ }
+
+ try
+ {
+ return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting channel image info for {0}", ex, info.Name);
+ }
+
+ return null;
+ }
+
+ public QueryResult<ChannelInfoDto> GetChannels(ChannelQuery query)
+ {
+ var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
+
+ IEnumerable<Channel> channels = _channels;
+
+ if (user != null)
+ {
+ channels = channels.Where(i => i.IsParentalAllowed(user, _localization))
+ .OrderBy(i =>
+ {
+ double number = 0;
+
+ if (!string.IsNullOrEmpty(i.ChannelNumber))
+ {
+ double.TryParse(i.ChannelNumber, out number);
+ }
+
+ return number;
+
+ });
+ }
+
+ var returnChannels = channels.OrderBy(i =>
+ {
+ double number = 0;
+
+ if (!string.IsNullOrEmpty(i.ChannelNumber))
+ {
+ double.TryParse(i.ChannelNumber, out number);
+ }
+
+ return number;
+
+ }).ThenBy(i => i.Name)
+ .Select(i => GetChannelInfoDto(i, user))
+ .ToArray();
+
+ return new QueryResult<ChannelInfoDto>
+ {
+ Items = returnChannels,
+ TotalRecordCount = returnChannels.Length
+ };
+ }
+
+ public Channel GetChannel(string id)
+ {
+ var guid = new Guid(id);
+
+ return _channels.FirstOrDefault(i => i.Id == guid);
+ }
+
+ public ChannelInfoDto GetChannelInfoDto(string id, string userId)
+ {
+ var channel = GetChannel(id);
+
+ var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(new Guid(userId));
+
+ return channel == null ? null : GetChannelInfoDto(channel, user);
+ }
+
+ private ProgramInfoDto GetProgramInfoDto(ProgramInfo program, Channel channel)
+ {
+ var id = GetInternalProgramIdId(channel.ServiceName, program.Id).ToString("N");
+
+ return new ProgramInfoDto
+ {
+ ChannelId = channel.Id.ToString("N"),
+ Description = program.Description,
+ EndDate = program.EndDate,
+ Genres = program.Genres,
+ ExternalId = program.Id,
+ Id = id,
+ Name = program.Name,
+ ServiceName = channel.ServiceName,
+ StartDate = program.StartDate,
+ OfficialRating = program.OfficialRating,
+ Quality = program.Quality,
+ OriginalAirDate = program.OriginalAirDate,
+ Audio = program.Audio,
+ CommunityRating = program.CommunityRating,
+ AspectRatio = program.AspectRatio,
+ IsRepeat = program.IsRepeat,
+ EpisodeTitle = program.EpisodeTitle
+ };
+ }
+
+ private Guid GetInternalChannelId(string serviceName, string externalChannelId, string channelName)
+ {
+ var name = serviceName + externalChannelId + channelName;
+
+ return name.ToLower().GetMBId(typeof(Channel));
+ }
+
+ private Guid GetInternalProgramIdId(string serviceName, string externalProgramId)
+ {
+ var name = serviceName + externalProgramId;
+
+ return name.ToLower().GetMD5();
+ }
+
+ private async Task<Channel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
+ {
+ var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name));
+
+ var fileInfo = new DirectoryInfo(path);
+
+ var isNew = false;
+
+ if (!fileInfo.Exists)
+ {
+ Directory.CreateDirectory(path);
+ fileInfo = new DirectoryInfo(path);
+
+ if (!fileInfo.Exists)
+ {
+ throw new IOException("Path not created: " + path);
+ }
+
+ isNew = true;
+ }
+
+ var id = GetInternalChannelId(serviceName, channelInfo.Id, channelInfo.Name);
+
+ var item = _itemRepo.RetrieveItem(id) as Channel;
+
+ if (item == null)
+ {
+ item = new Channel
+ {
+ Name = channelInfo.Name,
+ Id = id,
+ DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo),
+ DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo),
+ Path = path,
+ ChannelId = channelInfo.Id,
+ ChannelNumber = channelInfo.Number,
+ ServiceName = serviceName
+ };
+
+ isNew = true;
+ }
+
+ // Set this now so we don't cause additional file system access during provider executions
+ item.ResetResolveArgs(fileInfo);
+
+ await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false);
+
+ return item;
+ }
+
+ public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
+ {
+ IEnumerable<ProgramInfoDto> programs = _programs
+ .OrderBy(i => i.StartDate)
+ .ThenBy(i => i.EndDate);
+
+ if (!string.IsNullOrEmpty(query.ServiceName))
+ {
+ programs = programs.Where(i => string.Equals(i.ServiceName, query.ServiceName, StringComparison.OrdinalIgnoreCase));
+ }
+
+ if (query.ChannelIdList.Length > 0)
+ {
+ var guids = query.ChannelIdList.Select(i => new Guid(i)).ToList();
+
+ programs = programs.Where(i => guids.Contains(new Guid(i.ChannelId)));
+ }
+
+ var returnArray = programs.ToArray();
+
+ return new QueryResult<ProgramInfoDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Length
+ };
+ }
+
+ internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ // Avoid implicitly captured closure
+ var currentCancellationToken = cancellationToken;
+
+ var channelTasks = _services.Select(i => GetChannels(i, currentCancellationToken));
+
+ progress.Report(10);
+
+ var results = await Task.WhenAll(channelTasks).ConfigureAwait(false);
+
+ var allChannels = results.SelectMany(i => i).ToList();
+
+ var list = new List<Channel>();
+ var programs = new List<ProgramInfoDto>();
+
+ var numComplete = 0;
+
+ foreach (var channelInfo in allChannels)
+ {
+ try
+ {
+ var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false);
+
+ var service = _services.First(i => string.Equals(channelInfo.Item1, i.Name, StringComparison.OrdinalIgnoreCase));
+
+ var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false);
+
+ programs.AddRange(channelPrograms.Select(program => GetProgramInfoDto(program, item)));
+
+ list.Add(item);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Item2.Name);
+ }
+
+ numComplete++;
+ double percent = numComplete;
+ percent /= allChannels.Count;
+
+ progress.Report(90 * percent + 10);
+ }
+
+ _programs = programs;
+ _channels = list;
+ }
+
+ private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
+ {
+ var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
+
+ return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
+ }
+
+ private async Task<IEnumerable<RecordingInfoDto>> GetRecordings(ILiveTvService service, CancellationToken cancellationToken)
+ {
+ var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
+
+ return recordings.Select(i => GetRecordingInfoDto(i, service));
+ }
+
+ private RecordingInfoDto GetRecordingInfoDto(RecordingInfo info, ILiveTvService service)
+ {
+ var id = service.Name + info.ChannelId + info.Id;
+ id = id.GetMD5().ToString("N");
+
+ var dto = new RecordingInfoDto
+ {
+ ChannelName = info.ChannelName,
+ Description = info.Description,
+ EndDate = info.EndDate,
+ Name = info.Name,
+ StartDate = info.StartDate,
+ Id = id,
+ ExternalId = info.Id,
+ ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
+ Status = info.Status,
+ Path = info.Path,
+ Genres = info.Genres,
+ IsRepeat = info.IsRepeat,
+ EpisodeTitle = info.EpisodeTitle,
+ ChannelType = info.ChannelType,
+ MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video,
+ CommunityRating = info.CommunityRating,
+ OfficialRating = info.OfficialRating
};
+
+ var duration = info.EndDate - info.StartDate;
+ dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
+
+ if (!string.IsNullOrEmpty(info.ProgramId))
+ {
+ dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N");
+ }
+
+ return dto;
+ }
+
+ public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken)
+ {
+ var list = new List<RecordingInfoDto>();
+
+ foreach (var service in GetServices(query.ServiceName, query.ChannelId))
+ {
+ var recordings = await GetRecordings(service, cancellationToken).ConfigureAwait(false);
+
+ list.AddRange(recordings);
+ }
+
+ if (!string.IsNullOrEmpty(query.ChannelId))
+ {
+ list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId))
+ .ToList();
+ }
+
+ var returnArray = list.OrderByDescending(i => i.StartDate)
+ .ToArray();
+
+ return new QueryResult<RecordingInfoDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Length
+ };
+ }
+
+ private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId)
+ {
+ IEnumerable<ILiveTvService> services = _services;
+
+ if (string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(channelId))
+ {
+ var channelIdGuid = new Guid(channelId);
+
+ serviceName = _channels.Where(i => i.Id == channelIdGuid)
+ .Select(i => i.ServiceName)
+ .FirstOrDefault();
+ }
+
+ if (!string.IsNullOrEmpty(serviceName))
+ {
+ services = services.Where(i => string.Equals(i.Name, serviceName, StringComparison.OrdinalIgnoreCase));
+ }
+
+ 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>();
+
+ foreach (var service in GetServices(query.ServiceName, query.ChannelId))
+ {
+ var timers = await GetTimers(service, cancellationToken).ConfigureAwait(false);
+
+ list.AddRange(timers);
+ }
+
+ if (!string.IsNullOrEmpty(query.ChannelId))
+ {
+ list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId))
+ .ToList();
+ }
+
+ var returnArray = list.OrderByDescending(i => i.StartDate)
+ .ToArray();
+
+ return new QueryResult<TimerInfoDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Length
+ };
+ }
+
+ private async Task<IEnumerable<TimerInfoDto>> GetTimers(ILiveTvService service, CancellationToken cancellationToken)
+ {
+ var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false);
+
+ return timers.Select(i => GetTimerInfoDto(i, service));
+ }
+
+ private TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service)
+ {
+ var id = service.Name + info.ChannelId + info.Id;
+ id = id.GetMD5().ToString("N");
+
+ var dto = new TimerInfoDto
+ {
+ ChannelName = info.ChannelName,
+ Description = info.Description,
+ EndDate = info.EndDate,
+ Name = info.Name,
+ StartDate = info.StartDate,
+ Id = id,
+ ExternalId = info.Id,
+ ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
+ Status = info.Status,
+ SeriesTimerId = info.SeriesTimerId,
+ PrePaddingSeconds = info.PrePaddingSeconds,
+ PostPaddingSeconds = info.PostPaddingSeconds
+ };
+
+ var duration = info.EndDate - info.StartDate;
+ dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
+
+ if (!string.IsNullOrEmpty(info.ProgramId))
+ {
+ dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N");
+ }
+
+ return dto;
+ }
+
+ 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));
+
+ 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)
+ .First();
+
+ await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public async Task CancelTimer(string id)
+ {
+ var timers = await GetTimers(new TimerQuery
+ {
+
+ }, CancellationToken.None).ConfigureAwait(false);
+
+ var timer = timers.Items
+ .FirstOrDefault(i => string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase));
+
+ 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)
+ .First();
+
+ await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken)
+ {
+ var results = await GetRecordings(new RecordingQuery(), cancellationToken).ConfigureAwait(false);
+
+ return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+ }
+
+ public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
+ {
+ var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false);
+
+ return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
new file mode 100644
index 0000000000..00bf9e55ba
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
@@ -0,0 +1,59 @@
+using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Tasks;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+ class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
+ {
+ private readonly ILiveTvManager _liveTvManager;
+
+ public RefreshChannelsScheduledTask(ILiveTvManager liveTvManager)
+ {
+ _liveTvManager = liveTvManager;
+ }
+
+ public string Name
+ {
+ get { return "Refresh Guide"; }
+ }
+
+ public string Description
+ {
+ get { return "Downloads channel information from live tv services."; }
+ }
+
+ public string Category
+ {
+ get { return "Live TV"; }
+ }
+
+ public Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress)
+ {
+ var manager = (LiveTvManager)_liveTvManager;
+
+ return manager.RefreshChannels(progress, cancellationToken);
+ }
+
+ public IEnumerable<ITaskTrigger> GetDefaultTriggers()
+ {
+ return new ITaskTrigger[]
+ {
+
+ new StartupTrigger(),
+
+ new SystemEventTrigger{ SystemEvent = SystemEvent.WakeFromSleep},
+
+ new IntervalTrigger{ Interval = TimeSpan.FromHours(2)}
+ };
+ }
+
+ public bool IsHidden
+ {
+ get { return _liveTvManager.Services.Count == 0; }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/ca.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/ca.txt
new file mode 100644
index 0000000000..5a110648cd
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/Ratings/ca.txt
@@ -0,0 +1,6 @@
+CA-G,1
+CA-PG,5
+CA-14A,7
+CA-A,8
+CA-18A,9
+CA-R,10 \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index ac451e1ebd..3bfbdea3ea 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -41,8 +41,39 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.5\lib\net20\BDInfo.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.OrmLite.SqliteNET">
- <HintPath>..\packages\ServiceStack.OrmLite.Sqlite32.3.9.63\lib\net40\ServiceStack.OrmLite.SqliteNET.dll</HintPath>
+ <Reference Include="Mono.Data.Sqlite">
+ <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.70\lib\net35\Mono.Data.Sqlite.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.3.9.70\lib\net35\ServiceStack.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Api.Swagger, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.Api.Swagger.3.9.70\lib\net35\ServiceStack.Api.Swagger.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Common, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Common.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Interfaces.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.OrmLite, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.70\lib\net35\ServiceStack.OrmLite.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.OrmLite.Sqlite">
+ <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.70\lib\net35\ServiceStack.OrmLite.Sqlite.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.ServiceInterface, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.3.9.70\lib\net35\ServiceStack.ServiceInterface.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.Text.3.9.70\lib\net35\ServiceStack.Text.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -72,39 +103,12 @@
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack">
- <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Api.Swagger">
- <HintPath>..\packages\ServiceStack.Api.Swagger.3.9.59\lib\net35\ServiceStack.Api.Swagger.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Common">
- <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Interfaces">
- <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath>
- </Reference>
<Reference Include="ServiceStack.OrmLite.SqlServer">
<HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.43\lib\ServiceStack.OrmLite.SqlServer.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Redis">
<HintPath>..\packages\ServiceStack.Redis.3.9.43\lib\net35\ServiceStack.Redis.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.ServiceInterface">
- <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.ServiceInterface.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Text">
- <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
- </Reference>
- <Reference Include="Mono.Data.Sqlite">
- <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.64\lib\net35\Mono.Data.Sqlite.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.OrmLite">
- <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.64\lib\net35\ServiceStack.OrmLite.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.OrmLite.Sqlite">
- <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.64\lib\net35\ServiceStack.OrmLite.Sqlite.dll</HintPath>
- </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -167,7 +171,9 @@
<Compile Include="Library\Validators\StudiosPostScanTask.cs" />
<Compile Include="Library\Validators\StudiosValidator.cs" />
<Compile Include="Library\Validators\YearsPostScanTask.cs" />
+ <Compile Include="LiveTv\ChannelImageProvider.cs" />
<Compile Include="LiveTv\LiveTvManager.cs" />
+ <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
<Compile Include="Localization\LocalizationManager.cs" />
<Compile Include="MediaEncoder\MediaEncoder.cs" />
<Compile Include="Persistence\SqliteChapterRepository.cs" />
@@ -188,6 +194,7 @@
</Compile>
<Compile Include="Session\SessionWebSocketListener.cs" />
<Compile Include="Session\WebSocketController.cs" />
+ <Compile Include="Sorting\AiredEpisodeOrderComparer.cs" />
<Compile Include="Sorting\AirTimeComparer.cs" />
<Compile Include="Sorting\AlbumArtistComparer.cs" />
<Compile Include="Sorting\AlbumComparer.cs" />
@@ -265,6 +272,7 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
+ <Content Include="sqlite3.dll" />
<Content Include="swagger-ui\css\hightlight.default.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -283,9 +291,6 @@
<Content Include="swagger-ui\images\wordnik_api.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="swagger-ui\index.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="swagger-ui\lib\backbone-min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -319,6 +324,10 @@
<Content Include="swagger-ui\swagger-ui.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <EmbeddedResource Include="Localization\Ratings\ca.txt" />
+ <Content Include="swagger-ui\index.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
index b8874fb548..2224c657ff 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
@@ -864,25 +864,33 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
throw new ArgumentNullException("outputPath");
}
- var vf = "scale=iw*sar:ih, scale=600:-1";
-
+ var vf = "crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
+ // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
if (threedFormat.HasValue)
{
switch (threedFormat.Value)
{
case Video3DFormat.HalfSideBySide:
+ vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
+ // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
+ break;
case Video3DFormat.FullSideBySide:
- vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,scale=600:-1";
+ vf = "crop=iw/2:ih:0:0,setdar=dar=a,,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
+ //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600.
break;
case Video3DFormat.HalfTopAndBottom:
+ vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
+ //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
+ break;
case Video3DFormat.FullTopAndBottom:
- vf = "crop=iw:ih/2:0:0,scale=iw:(ih*2),scale=600:-1";
+ vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar";
+ // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600
break;
}
}
- var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -filter:v select=\"eq(pict_type\\,I)\" -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf) :
- string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf);
+ var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"thumbnail,{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf) :
+ string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf); //use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back jic
var probeSize = GetProbeSizeArgument(type);
diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
index ff11b9a2be..e2192535c8 100644
--- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
+++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -13,7 +14,6 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.Providers
{
@@ -89,6 +89,23 @@ namespace MediaBrowser.Server.Implementations.Providers
if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
{
saveLocally = false;
+
+ var season = item as Season;
+
+ // If season is virtual under a physical series, save locally if using compatible convention
+ if (season != null && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible)
+ {
+ var series = season.Series;
+
+ if (series != null)
+ {
+ var seriesLocationType = series.LocationType;
+ if (seriesLocationType == LocationType.FileSystem || seriesLocationType == LocationType.Offline)
+ {
+ saveLocally = true;
+ }
+ }
+ }
}
if (type == ImageType.Backdrop && imageIndex == null)
@@ -344,6 +361,9 @@ namespace MediaBrowser.Server.Implementations.Providers
case ImageType.Art:
filename = "clearart";
break;
+ case ImageType.Disc:
+ filename = item is MusicAlbum ? "cdart" : "disc";
+ break;
case ImageType.Primary:
filename = item is Episode ? Path.GetFileNameWithoutExtension(item.Path) : "folder";
break;
@@ -399,7 +419,7 @@ namespace MediaBrowser.Server.Implementations.Providers
return path;
}
- private string GetBackdropSaveFilename(List<string> images, string zeroIndexFilename, string numberedIndexPrefix, int index)
+ private string GetBackdropSaveFilename(IEnumerable<string> images, string zeroIndexFilename, string numberedIndexPrefix, int index)
{
if (index == 0)
{
@@ -428,6 +448,8 @@ namespace MediaBrowser.Server.Implementations.Providers
/// <exception cref="System.ArgumentNullException">imageIndex</exception>
private string[] GetCompatibleSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType)
{
+ var season = item as Season;
+
var extension = mimeType.Split('/').Last();
if (string.Equals(extension, "jpeg", StringComparison.OrdinalIgnoreCase))
@@ -446,9 +468,9 @@ namespace MediaBrowser.Server.Implementations.Providers
if (imageIndex.Value == 0)
{
- if (item is Season && item.IndexNumber.HasValue)
+ if (season != null && item.IndexNumber.HasValue)
{
- var seriesFolder = Path.GetDirectoryName(item.Path);
+ var seriesFolder = season.SeriesPath;
var seasonMarker = item.IndexNumber.Value == 0
? "-specials"
@@ -478,9 +500,9 @@ namespace MediaBrowser.Server.Implementations.Providers
if (type == ImageType.Primary)
{
- if (item is Season && item.IndexNumber.HasValue)
+ if (season != null && item.IndexNumber.HasValue)
{
- var seriesFolder = Path.GetDirectoryName(item.Path);
+ var seriesFolder = season.SeriesPath;
var seasonMarker = item.IndexNumber.Value == 0
? "-specials"
@@ -505,15 +527,19 @@ namespace MediaBrowser.Server.Implementations.Providers
return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) };
}
- var filename = "poster" + extension;
- return new[] { Path.Combine(item.MetaLocation, filename) };
+ if (item is MusicAlbum || item is MusicArtist)
+ {
+ return new[] { Path.Combine(item.MetaLocation, "folder" + extension) };
+ }
+
+ return new[] { Path.Combine(item.MetaLocation, "poster" + extension) };
}
if (type == ImageType.Banner)
{
- if (item is Season && item.IndexNumber.HasValue)
+ if (season != null && item.IndexNumber.HasValue)
{
- var seriesFolder = Path.GetDirectoryName(item.Path);
+ var seriesFolder = season.SeriesPath;
var seasonMarker = item.IndexNumber.Value == 0
? "-specials"
@@ -527,9 +553,9 @@ namespace MediaBrowser.Server.Implementations.Providers
if (type == ImageType.Thumb)
{
- if (item is Season && item.IndexNumber.HasValue)
+ if (season != null && item.IndexNumber.HasValue)
{
- var seriesFolder = Path.GetDirectoryName(item.Path);
+ var seriesFolder = season.SeriesPath;
var seasonMarker = item.IndexNumber.Value == 0
? "-specials"
diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs
index fb3c5aec86..db538f8dd0 100644
--- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs
+++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs
@@ -13,16 +13,16 @@ namespace MediaBrowser.Server.Implementations
/// <summary>
/// Initializes a new instance of the <see cref="ServerApplicationPaths" /> class.
/// </summary>
- public ServerApplicationPaths()
- : base(true)
+ public ServerApplicationPaths(string applicationPath)
+ : base(true, applicationPath)
{
}
#else
/// <summary>
/// Initializes a new instance of the <see cref="ServerApplicationPaths"/> class.
/// </summary>
- public ServerApplicationPaths()
- : base(false)
+ public ServerApplicationPaths(string applicationPath)
+ : base(false, applicationPath)
{
}
#endif
@@ -30,9 +30,8 @@ namespace MediaBrowser.Server.Implementations
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class.
/// </summary>
- /// <param name="programDataPath">The program data path.</param>
- public ServerApplicationPaths(string programDataPath)
- : base(programDataPath)
+ public ServerApplicationPaths(string programDataPath, string applicationPath)
+ : base(programDataPath, applicationPath)
{
}
diff --git a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
new file mode 100644
index 0000000000..76971342a0
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -0,0 +1,138 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Sorting;
+using MediaBrowser.Model.Querying;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+ class AiredEpisodeOrderComparer : IBaseItemComparer
+ {
+ /// <summary>
+ /// Compares the specified x.
+ /// </summary>
+ /// <param name="x">The x.</param>
+ /// <param name="y">The y.</param>
+ /// <returns>System.Int32.</returns>
+ public int Compare(BaseItem x, BaseItem y)
+ {
+ if (x.PremiereDate.HasValue && y.PremiereDate.HasValue)
+ {
+ var val = DateTime.Compare(x.PremiereDate.Value, y.PremiereDate.Value);
+
+ if (val != 0)
+ {
+ //return val;
+ }
+ }
+
+ var episode1 = x as Episode;
+ var episode2 = y as Episode;
+
+ if (episode1 == null)
+ {
+ if (episode2 == null)
+ {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ if (episode2 == null)
+ {
+ return -1;
+ }
+
+ return Compare(episode1, episode2);
+ }
+
+ private int Compare(Episode x, Episode y)
+ {
+ var isXSpecial = (x.PhysicalSeasonNumber ?? -1) == 0;
+ var isYSpecial = (y.PhysicalSeasonNumber ?? -1) == 0;
+
+ if (isXSpecial && isYSpecial)
+ {
+ return CompareSpecials(x, y);
+ }
+
+ if (!isXSpecial && !isYSpecial)
+ {
+ return CompareEpisodes(x, y);
+ }
+
+ if (!isXSpecial && isYSpecial)
+ {
+ return CompareEpisodeToSpecial(x, y);
+ }
+
+ return CompareEpisodeToSpecial(y, x) * -1;
+ }
+
+ private int CompareEpisodeToSpecial(Episode x, Episode y)
+ {
+ var xSeason = x.PhysicalSeasonNumber ?? -1;
+ var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1;
+
+ if (xSeason != ySeason)
+ {
+ return xSeason.CompareTo(ySeason);
+ }
+
+ // Now we know they have the same season
+
+ // Compare episode number
+
+ // Add 1 to to non-specials to account for AirsBeforeEpisodeNumber
+ var xEpisode = x.IndexNumber ?? -1;
+ xEpisode++;
+ var yEpisode = y.AirsBeforeEpisodeNumber ?? 10000;
+
+ return xEpisode.CompareTo(yEpisode);
+ }
+
+ private int CompareSpecials(Episode x, Episode y)
+ {
+ return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y));
+ }
+
+ private int GetSpecialCompareValue(Episode item)
+ {
+ // First sort by season number
+ // Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough)
+ var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000;
+
+ // Second sort order is if it airs after the season
+ if (item.AirsAfterSeasonNumber.HasValue)
+ {
+ val += 1000000;
+ }
+
+ // Third level is the episode number
+ val += (item.AirsBeforeEpisodeNumber ?? 0) * 1000;
+
+ // Finally, if that's still the same, last resort is the special number itself
+ val += item.IndexNumber ?? 0;
+
+ return val;
+ }
+
+ private int CompareEpisodes(Episode x, Episode y)
+ {
+ var xValue = ((x.PhysicalSeasonNumber ?? -1) * 1000) + (x.IndexNumber ?? -1);
+ var yValue = ((y.PhysicalSeasonNumber ?? -1) * 1000) + (y.IndexNumber ?? -1);
+
+ return xValue.CompareTo(yValue);
+ }
+
+ /// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name
+ {
+ get { return ItemSortBy.AiredEpisodeOrder; }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs
index 8e24bc52d6..e35ba00f2e 100644
--- a/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (itemByName != null)
{
- var counts = itemByName.GetItemByNameCounts(User);
+ var counts = itemByName.GetItemByNameCounts(User.Id);
if (counts != null)
{
diff --git a/MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs b/MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs
index d2dac65499..87a7325c63 100644
--- a/MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs
@@ -19,7 +19,12 @@ namespace MediaBrowser.Server.Implementations.Sorting
private double GetValue(BaseItem x)
{
- return x.Budget ?? 0;
+ var hasBudget = x as IHasBudget;
+ if (hasBudget != null)
+ {
+ return hasBudget.Budget ?? 0;
+ }
+ return 0;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs
index 7731e59d2b..b3fd8a023d 100644
--- a/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (itemByName != null)
{
- var counts = itemByName.GetItemByNameCounts(User);
+ var counts = itemByName.GetItemByNameCounts(User.Id);
if (counts != null)
{
diff --git a/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs
index 51f39a02f2..605f4d1af4 100644
--- a/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (itemByName != null)
{
- var counts = itemByName.GetItemByNameCounts(User);
+ var counts = itemByName.GetItemByNameCounts(User.Id);
if (counts != null)
{
diff --git a/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs
index 889658459c..6c9c5534d6 100644
--- a/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (itemByName != null)
{
- var counts = itemByName.GetItemByNameCounts(User);
+ var counts = itemByName.GetItemByNameCounts(User.Id);
if (counts != null)
{
diff --git a/MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs b/MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs
index e9d7912a16..6caa27ac39 100644
--- a/MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs
@@ -19,7 +19,12 @@ namespace MediaBrowser.Server.Implementations.Sorting
private double GetValue(BaseItem x)
{
- return x.Revenue ?? 0;
+ var hasBudget = x as IHasBudget;
+ if (hasBudget != null)
+ {
+ return hasBudget.Revenue ?? 0;
+ }
+ return 0;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs
index 13d2932cbc..8567e400cd 100644
--- a/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (itemByName != null)
{
- var counts = itemByName.GetItemByNameCounts(User);
+ var counts = itemByName.GetItemByNameCounts(User.Id);
if (counts != null)
{
diff --git a/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs
index b12e1322a0..85b849a217 100644
--- a/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (itemByName != null)
{
- var counts = itemByName.GetItemByNameCounts(User);
+ var counts = itemByName.GetItemByNameCounts(User.Id);
if (counts != null)
{
diff --git a/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs
index b6f67410a0..a13875674d 100644
--- a/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs
@@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (itemByName != null)
{
- var counts = itemByName.GetItemByNameCounts(User);
+ var counts = itemByName.GetItemByNameCounts(User.Id);
if (counts != null)
{
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index eeeedfe362..488dbc1ae8 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -6,13 +6,12 @@
<package id="Rx-Core" version="2.1.30214.0" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.1.30214.0" targetFramework="net45" />
<package id="Rx-Linq" version="2.1.30214.0" targetFramework="net45" />
- <package id="ServiceStack" version="3.9.62" targetFramework="net45" />
- <package id="ServiceStack.Api.Swagger" version="3.9.59" targetFramework="net45" />
- <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" />
- <package id="ServiceStack.OrmLite.Sqlite.Mono" version="3.9.64" targetFramework="net45" />
- <package id="ServiceStack.OrmLite.Sqlite32" version="3.9.63" targetFramework="net45" />
+ <package id="ServiceStack" version="3.9.70" targetFramework="net45" />
+ <package id="ServiceStack.Api.Swagger" version="3.9.70" targetFramework="net45" />
+ <package id="ServiceStack.Common" version="3.9.70" targetFramework="net45" />
+ <package id="ServiceStack.OrmLite.Sqlite.Mono" version="3.9.70" targetFramework="net45" />
<package id="ServiceStack.OrmLite.SqlServer" version="3.9.43" targetFramework="net45" />
<package id="ServiceStack.Redis" version="3.9.43" targetFramework="net45" />
- <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" />
+ <package id="ServiceStack.Text" version="3.9.70" targetFramework="net45" />
<package id="System.Data.SQLite.x86" version="1.0.89.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/swagger-ui/index.html b/MediaBrowser.Server.Implementations/swagger-ui/index.html
index 0fcc069596..49f983a723 100644
--- a/MediaBrowser.Server.Implementations/swagger-ui/index.html
+++ b/MediaBrowser.Server.Implementations/swagger-ui/index.html
@@ -20,7 +20,7 @@
$(function () {
window.swaggerUi = new SwaggerUi({
discoveryUrl: "../resources",
- apiKey:"special-key",
+ apiKey: "special-key",
dom_id:"swagger-ui-container",
supportHeaderParams: false,
supportedSubmitMethods: ['get', 'post', 'put'],