pull/702/head
hatharry 8 years ago
commit cabf2cdc1b

@ -33,7 +33,9 @@ namespace Emby.Drawing.GDI
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceCopy;
// SourceCopy causes the image to be blank in OSX
//graphics.CompositingMode = CompositingMode.SourceCopy;
for (var row = 0; row < rows; row++)
{
@ -44,19 +46,9 @@ namespace Emby.Drawing.GDI
if (files.Count > index)
{
using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
using (var imgtemp = Image.FromFile(files[index]))
{
using (var memoryStream = new MemoryStream())
{
fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
using (var imgtemp = Image.FromStream(memoryStream, true, false))
{
graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
}
}
graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
}
}
@ -90,7 +82,9 @@ namespace Emby.Drawing.GDI
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceCopy;
// SourceCopy causes the image to be blank in OSX
//graphics.CompositingMode = CompositingMode.SourceCopy;
for (var row = 0; row < rows; row++)
{
@ -99,21 +93,10 @@ namespace Emby.Drawing.GDI
var x = col * singleSize;
var y = row * singleSize;
using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
using (var imgtemp = Image.FromFile(files[index]))
{
using (var memoryStream = new MemoryStream())
{
fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
using (var imgtemp = Image.FromStream(memoryStream, true, false))
{
graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
}
}
graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
}
index++;
}
}
@ -121,16 +104,5 @@ namespace Emby.Drawing.GDI
}
}
}
private static Stream GetStream(Image image)
{
var ms = new MemoryStream();
image.Save(ms, ImageFormat.Png);
ms.Position = 0;
return ms;
}
}
}

@ -119,9 +119,11 @@ namespace Emby.Drawing.GDI
thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
thumbnailGraph.CompositingMode = !hasPostProcessing ?
CompositingMode.SourceCopy :
CompositingMode.SourceOver;
// SourceCopy causes the image to be blank in OSX
//thumbnailGraph.CompositingMode = !hasPostProcessing ?
// CompositingMode.SourceCopy :
// CompositingMode.SourceOver;
SetBackgroundColor(thumbnailGraph, options);

@ -63,6 +63,15 @@ namespace MediaBrowser.Api
Instance = this;
_sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
_sessionManager.PlaybackStart += _sessionManager_PlaybackStart;
}
private void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e)
{
if (!string.IsNullOrWhiteSpace(e.PlaySessionId))
{
PingTranscodingJob(e.PlaySessionId, e.IsPaused);
}
}
void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
@ -126,9 +135,10 @@ namespace MediaBrowser.Api
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
var jobCount = _activeTranscodingJobs.Count;
var list = _activeTranscodingJobs.ToList();
var jobCount = list.Count;
Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, false, path => true));
Parallel.ForEach(list, j => KillTranscodingJob(j, false, path => true));
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
if (jobCount > 0)
@ -182,13 +192,13 @@ namespace MediaBrowser.Api
_activeTranscodingJobs.Add(job);
ReportTranscodingProgress(job, state, null, null, null, null);
ReportTranscodingProgress(job, state, null, null, null, null, null);
return job;
}
}
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded)
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
@ -198,6 +208,7 @@ namespace MediaBrowser.Api
job.CompletionPercentage = percentComplete;
job.TranscodingPositionTicks = ticks;
job.BytesTranscoded = bytesTranscoded;
job.BitRate = bitRate;
}
var deviceId = state.Request.DeviceId;
@ -209,7 +220,7 @@ namespace MediaBrowser.Api
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
{
Bitrate = state.TotalOutputBitrate,
Bitrate = bitRate ?? state.TotalOutputBitrate,
AudioCodec = audioCodec,
VideoCodec = videoCodec,
Container = state.OutputContainer,
@ -348,7 +359,7 @@ namespace MediaBrowser.Api
return;
}
var timerDuration = 1000;
var timerDuration = 10000;
if (job.Type != TranscodingJobType.Progressive)
{
@ -400,7 +411,7 @@ namespace MediaBrowser.Api
}
}
Logger.Debug("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
Logger.Info("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId);
KillTranscodingJob(job, true, path => true);
}
@ -558,13 +569,13 @@ namespace MediaBrowser.Api
{
}
catch (IOException ex)
catch (IOException)
{
//Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
DeletePartialStreamFiles(path, jobType, retryCount + 1, 500);
}
catch (Exception ex)
catch
{
//Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, path);
}
@ -684,6 +695,7 @@ namespace MediaBrowser.Api
public long? BytesDownloaded { get; set; }
public long? BytesTranscoded { get; set; }
public int? BitRate { get; set; }
public long? TranscodingPositionTicks { get; set; }
public long? DownloadPositionTicks { get; set; }

@ -139,6 +139,10 @@ namespace MediaBrowser.Api
{
options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value;
}
if (hasDtoOptions.EnableUserData.HasValue)
{
options.EnableUserData = hasDtoOptions.EnableUserData.Value;
}
if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes))
{

@ -4,6 +4,7 @@ namespace MediaBrowser.Api
public interface IHasDtoOptions : IHasItemFields
{
bool? EnableImages { get; set; }
bool? EnableUserData { get; set; }
int? ImageTypeLimit { get; set; }

@ -573,11 +573,9 @@ namespace MediaBrowser.Api.Images
var outputFormats = GetOutputFormats(request, imageInfo, cropwhitespace, supportedImageEnhancers);
var cacheGuid = new Guid(_imageProcessor.GetImageCacheTag(item, imageInfo, supportedImageEnhancers));
TimeSpan? cacheDuration = null;
if (!string.IsNullOrEmpty(request.Tag) && cacheGuid == new Guid(request.Tag))
if (!string.IsNullOrEmpty(request.Tag))
{
cacheDuration = TimeSpan.FromDays(365);
}

@ -5,6 +5,7 @@ using MediaBrowser.Controller.Providers;
using ServiceStack;
using System.Threading;
using CommonIO;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Api
{
@ -39,12 +40,14 @@ namespace MediaBrowser.Api
private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager;
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager, IFileSystem fileSystem)
public ItemRefreshService(ILibraryManager libraryManager, IProviderManager providerManager, IFileSystem fileSystem, ILogger logger)
{
_libraryManager = libraryManager;
_providerManager = providerManager;
_fileSystem = fileSystem;
_logger = logger;
}
/// <summary>
@ -69,7 +72,7 @@ namespace MediaBrowser.Api
private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request)
{
return new MetadataRefreshOptions(new DirectoryService(_fileSystem))
return new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))
{
MetadataRefreshMode = request.MetadataRefreshMode,
ImageRefreshMode = request.ImageRefreshMode,

@ -304,7 +304,7 @@ namespace MediaBrowser.Api
item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : (DateTime?)null;
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : (DateTime?)null;
item.ProductionYear = request.ProductionYear;
item.OfficialRating = request.OfficialRating;
item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
item.CustomRating = request.CustomRating;
SetProductionLocations(item, request);

@ -154,9 +154,12 @@ namespace MediaBrowser.Api.Library
public void Post(PerformOrganization request)
{
// Don't await this
var task = _iFileOrganizationService.PerformOrganization(request.Id);
Task.WaitAll(task);
// Async processing (close dialog early instead of waiting until the file has been copied)
// Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display
task.Wait(2000);
}
public void Post(OrganizeEpisode request)
@ -168,6 +171,7 @@ namespace MediaBrowser.Api.Library
dicNewProviderIds = request.NewSeriesProviderIds;
}
// Don't await this
var task = _iFileOrganizationService.PerformEpisodeOrganization(new EpisodeFileOrganizationRequest
{
EndingEpisodeNumber = request.EndingEpisodeNumber,
@ -182,11 +186,9 @@ namespace MediaBrowser.Api.Library
TargetFolder = request.TargetFolder
});
// For async processing (close dialog early instead of waiting until the file has been copied)
//var tasks = new Task[] { task };
//Task.WaitAll(tasks, 8000);
Task.WaitAll(task);
// Async processing (close dialog early instead of waiting until the file has been copied)
// Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display
task.Wait(2000);
}
public object Get(GetSmartMatchInfos request)

@ -10,6 +10,9 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Api.Library
{
@ -52,6 +55,8 @@ namespace MediaBrowser.Api.Library
/// </summary>
/// <value>The path.</value>
public string[] Paths { get; set; }
public LibraryOptions LibraryOptions { get; set; }
}
[Route("/Library/VirtualFolders", "DELETE")]
@ -136,6 +141,14 @@ namespace MediaBrowser.Api.Library
public bool RefreshLibrary { get; set; }
}
[Route("/Library/VirtualFolders/LibraryOptions", "POST")]
public class UpdateLibraryOptions : IReturnVoid
{
public string Id { get; set; }
public LibraryOptions LibraryOptions { get; set; }
}
/// <summary>
/// Class LibraryStructureService
/// </summary>
@ -184,13 +197,22 @@ namespace MediaBrowser.Api.Library
return ToOptimizedSerializedResultUsingCache(result);
}
public void Post(UpdateLibraryOptions request)
{
var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(request.Id);
collectionFolder.UpdateLibraryOptions(request.LibraryOptions);
}
/// <summary>
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
public void Post(AddVirtualFolder request)
{
_libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, request.RefreshLibrary);
var libraryOptions = request.LibraryOptions ?? new LibraryOptions();
_libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, libraryOptions, request.RefreshLibrary);
}
/// <summary>
@ -214,12 +236,12 @@ namespace MediaBrowser.Api.Library
var currentPath = Path.Combine(rootFolderPath, request.Name);
var newPath = Path.Combine(rootFolderPath, request.NewName);
if (!_fileSystem.DirectoryExists(currentPath))
if (!_fileSystem.DirectoryExists(currentPath))
{
throw new DirectoryNotFoundException("The media collection does not exist");
}
if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && _fileSystem.DirectoryExists(newPath))
if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && _fileSystem.DirectoryExists(newPath))
{
throw new ArgumentException("There is already a media collection with the name " + newPath + ".");
}
@ -234,11 +256,11 @@ namespace MediaBrowser.Api.Library
//Create an unique name
var temporaryName = Guid.NewGuid().ToString();
var temporaryPath = Path.Combine(rootFolderPath, temporaryName);
_fileSystem.MoveDirectory(currentPath, temporaryPath);
_fileSystem.MoveDirectory(currentPath, temporaryPath);
currentPath = temporaryPath;
}
_fileSystem.MoveDirectory(currentPath, newPath);
_fileSystem.MoveDirectory(currentPath, newPath);
}
finally
{

@ -82,6 +82,9 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "AddCurrentProgram", Description = "Optional. Adds current program info to each channel", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public bool AddCurrentProgram { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
public GetChannels()
{
AddCurrentProgram = true;
@ -149,6 +152,9 @@ namespace MediaBrowser.Api.LiveTv
public bool EnableTotalRecordCount { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
public GetRecordings()
{
EnableTotalRecordCount = true;
@ -271,6 +277,9 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
/// <summary>
/// Fields to return within the items, in addition to basic information
/// </summary>
@ -331,6 +340,9 @@ namespace MediaBrowser.Api.LiveTv
/// <value>The fields.</value>
[ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Fields { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
}
[Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")]
@ -726,7 +738,12 @@ namespace MediaBrowser.Api.LiveTv
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId);
var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, GetDtoOptions(Request), user).ConfigureAwait(false)).ToArray();
var options = GetDtoOptions(request);
RemoveFields(options);
options.AddCurrentProgram = request.AddCurrentProgram;
var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, options, user).ConfigureAwait(false)).ToArray();
var result = new QueryResult<BaseItemDto>
{
@ -737,6 +754,14 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedSerializedResultUsingCache(result);
}
private void RemoveFields(DtoOptions options)
{
options.Fields.Remove(ItemFields.CanDelete);
options.Fields.Remove(ItemFields.CanDownload);
options.Fields.Remove(ItemFields.DisplayPreferencesId);
options.Fields.Remove(ItemFields.Etag);
}
public object Get(GetChannel request)
{
var user = string.IsNullOrWhiteSpace(request.UserId) ? null : _userManager.GetUserById(request.UserId);
@ -997,10 +1022,7 @@ namespace MediaBrowser.Api.LiveTv
public async Task<object> Get(GetRecordingGroup request)
{
var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery
{
}, CancellationToken.None).ConfigureAwait(false);
var result = await _liveTvManager.GetRecordingGroups(new RecordingGroupQuery(), CancellationToken.None).ConfigureAwait(false);
var group = result.Items.FirstOrDefault(i => string.Equals(i.Id, request.Id, StringComparison.OrdinalIgnoreCase));

@ -192,7 +192,8 @@ namespace MediaBrowser.Api.Movies
SortOrder = SortOrder.Descending,
Limit = 7,
ParentId = parentIdGuid,
Recursive = true
Recursive = true,
IsPlayed = true
};
var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();

@ -22,6 +22,8 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
namespace MediaBrowser.Api.Playback
{
@ -69,6 +71,9 @@ namespace MediaBrowser.Api.Playback
protected IZipClient ZipClient { get; private set; }
protected IJsonSerializer JsonSerializer { get; private set; }
public static IServerApplicationHost AppHost;
public static IHttpClient HttpClient;
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
@ -286,28 +291,46 @@ namespace MediaBrowser.Api.Playback
protected string GetH264Encoder(StreamState state)
{
var defaultEncoder = "libx264";
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
if (state.VideoType == VideoType.VideoFile)
{
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
var hwType = encodingOptions.HardwareAccelerationType;
if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
return "h264_qsv";
return GetAvailableEncoder("h264_qsv", defaultEncoder);
}
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
{
return GetAvailableEncoder("h264_nvenc", defaultEncoder);
}
if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
return "h264_nvenc";
return GetAvailableEncoder("h264_omx", defaultEncoder);
}
if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice))
{
return "h264_omx";
return GetAvailableEncoder("h264_vaapi", defaultEncoder);
}
}
return "libx264";
return defaultEncoder;
}
private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder)
{
if (MediaEncoder.SupportsEncoder(preferredEncoder))
{
return preferredEncoder;
}
return defaultEncoder;
}
/// <summary>
@ -409,7 +432,8 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
{
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
// not supported by h264_omx
param += " -profile:v " + state.VideoRequest.Profile;
@ -464,7 +488,8 @@ namespace MediaBrowser.Api.Playback
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
!string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt yuv420p " + param;
}
@ -530,59 +555,97 @@ namespace MediaBrowser.Api.Playback
var filters = new List<string>();
if (state.DeInterlace)
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
filters.Add("yadif=0:-1:0");
}
// If fixed dimensions were supplied
if (request.Width.HasValue && request.Height.HasValue)
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
var widthParam = request.Width.Value.ToString(UsCulture);
var heightParam = request.Height.Value.ToString(UsCulture);
// Work around vaapi's reduced scaling features
var scaler = "scale_vaapi";
filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
}
// Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
// (outputWidth, outputHeight). The user may request precise output dimensions or maximum
// output dimensions. Output dimensions are guaranteed to be even.
decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
{
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
if (outputWidth > maximumWidth || outputHeight > maximumHeight)
{
var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
}
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
}
outputWidth = 2 * Math.Truncate(outputWidth / 2);
outputHeight = 2 * Math.Truncate(outputHeight / 2);
// If a fixed width was requested
else if (request.Width.HasValue)
if (outputWidth != inputWidth || outputHeight != inputHeight)
{
filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
}
}
else
{
var widthParam = request.Width.Value.ToString(UsCulture);
// If fixed dimensions were supplied
if (request.Width.HasValue && request.Height.HasValue)
{
var widthParam = request.Width.Value.ToString(UsCulture);
var heightParam = request.Height.Value.ToString(UsCulture);
filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
}
filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
}
// If a fixed height was requested
else if (request.Height.HasValue)
{
var heightParam = request.Height.Value.ToString(UsCulture);
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
{
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
}
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
}
// If a max width was requested
else if (request.MaxWidth.HasValue)
{
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
// If a fixed width was requested
else if (request.Width.HasValue)
{
var widthParam = request.Width.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
}
filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
}
// If a max height was requested
else if (request.MaxHeight.HasValue)
{
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
// If a fixed height was requested
else if (request.Height.HasValue)
{
var heightParam = request.Height.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
}
// If a max width was requested
else if (request.MaxWidth.HasValue)
{
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
}
// If a max height was requested
else if (request.MaxHeight.HasValue)
{
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
}
}
var output = string.Empty;
@ -917,6 +980,15 @@ namespace MediaBrowser.Api.Playback
}
}
if (state.VideoRequest != null)
{
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
{
arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
}
}
return arg.Trim();
}
@ -1042,14 +1114,14 @@ namespace MediaBrowser.Api.Playback
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
Logger.Info(commandLineLogMessage);
var logFilePrefix = "transcode";
var logFilePrefix = "ffmpeg-transcode";
if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
logFilePrefix = "directstream";
logFilePrefix = "ffmpeg-directstream";
}
else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
logFilePrefix = "remux";
logFilePrefix = "ffmpeg-remux";
}
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
@ -1080,7 +1152,7 @@ namespace MediaBrowser.Api.Playback
//process.BeginOutputReadLine();
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
// Wait for the file to exist before proceeeding
while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
@ -1099,28 +1171,30 @@ namespace MediaBrowser.Api.Playback
}
StartThrottler(state, transcodingJob);
ReportUsage(state);
return transcodingJob;
}
private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
{
if (EnableThrottling(state) && state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.VideoType == VideoType.VideoFile &&
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
{
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
state.TranscodingThrottler.Start();
}
transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
state.TranscodingThrottler.Start();
}
}
protected virtual bool EnableThrottling(StreamState state)
{
return true;
// do not use throttling with hardware encoders
return state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
state.IsInputVideo &&
state.VideoType == VideoType.VideoFile &&
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) &&
string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
}
private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
@ -1158,6 +1232,7 @@ namespace MediaBrowser.Api.Playback
double? percent = null;
TimeSpan? transcodingPosition = null;
long? bytesTranscoded = null;
int? bitRate = null;
var parts = line.Split(' ');
@ -1221,11 +1296,32 @@ namespace MediaBrowser.Api.Playback
}
}
}
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
{
var rate = part.Split(new[] { '=' }, 2).Last();
int? scale = null;
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
{
scale = 1024;
rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
}
if (scale.HasValue)
{
float val;
if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
{
bitRate = (int)Math.Ceiling(val * scale.Value);
}
}
}
}
if (framerate.HasValue || percent.HasValue)
{
ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded);
ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
}
}
@ -1547,13 +1643,6 @@ namespace MediaBrowser.Api.Playback
}
}
else if (i == 25)
{
if (videoRequest != null)
{
videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
}
else if (i == 26)
{
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
{
@ -1564,17 +1653,21 @@ namespace MediaBrowser.Api.Playback
}
}
}
else if (i == 27)
else if (i == 26)
{
request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
}
else if (i == 28)
else if (i == 27)
{
if (videoRequest != null)
{
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
}
else if (i == 28)
{
request.Tag = val;
}
}
}
@ -1788,6 +1881,19 @@ namespace MediaBrowser.Api.Playback
{
state.OutputAudioCodec = "copy";
}
else
{
// If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
if (!string.IsNullOrWhiteSpace(auth.UserId))
{
var user = UserManager.GetUserById(auth.UserId);
if (!user.Policy.EnableAudioPlaybackTranscoding)
{
state.OutputAudioCodec = "copy";
}
}
}
}
private void AttachMediaSourceInfo(StreamState state,
@ -2159,13 +2265,127 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null)
{
state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream;
state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
}
}
}
}
private async void ReportUsage(StreamState state)
{
try
{
await ReportUsageInternal(state).ConfigureAwait(false);
}
catch
{
}
}
private Task ReportUsageInternal(StreamState state)
{
if (!ServerConfigurationManager.Configuration.EnableAnonymousUsageReporting)
{
return Task.FromResult(true);
}
if (!MediaEncoder.IsDefaultEncoderPath)
{
return Task.FromResult(true);
}
var dict = new Dictionary<string, string>();
var outputAudio = GetAudioEncoder(state);
if (!string.IsNullOrWhiteSpace(outputAudio))
{
dict["outputAudio"] = outputAudio;
}
var outputVideo = GetVideoEncoder(state);
if (!string.IsNullOrWhiteSpace(outputVideo))
{
dict["outputVideo"] = outputVideo;
}
if (ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
return Task.FromResult(true);
}
dict["id"] = AppHost.SystemId;
dict["type"] = state.VideoRequest == null ? "Audio" : "Video";
var audioStream = state.AudioStream;
if (audioStream != null && !string.IsNullOrWhiteSpace(audioStream.Codec))
{
dict["inputAudio"] = audioStream.Codec;
}
var videoStream = state.VideoStream;
if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
{
dict["inputVideo"] = videoStream.Codec;
}
var cert = GetType().Assembly.GetModules().First().GetSignerCertificate();
if (cert != null)
{
dict["assemblySig"] = cert.GetCertHashString();
dict["certSubject"] = cert.Subject ?? string.Empty;
dict["certIssuer"] = cert.Issuer ?? string.Empty;
}
else
{
return Task.FromResult(true);
}
if (state.SupportedAudioCodecs.Count > 0)
{
dict["supportedAudioCodecs"] = string.Join(",", state.SupportedAudioCodecs.ToArray());
}
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
dict["appName"] = auth.Client ?? string.Empty;
dict["appVersion"] = auth.Version ?? string.Empty;
dict["device"] = auth.Device ?? string.Empty;
dict["deviceId"] = auth.DeviceId ?? string.Empty;
dict["context"] = "streaming";
//Logger.Info(JsonSerializer.SerializeToString(dict));
if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputAudio ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
list.Add(outputAudio);
ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
}
if (!ServerConfigurationManager.Configuration.CodecsUsed.Contains(outputVideo ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
var list = ServerConfigurationManager.Configuration.CodecsUsed.ToList();
list.Add(outputVideo);
ServerConfigurationManager.Configuration.CodecsUsed = list.ToArray();
}
ServerConfigurationManager.SaveConfiguration();
//Logger.Info(JsonSerializer.SerializeToString(dict));
var options = new HttpRequestOptions()
{
Url = "https://mb3admin.com/admin/service/transcoding/report",
CancellationToken = CancellationToken.None,
LogRequest = false,
LogErrors = false
};
options.RequestContent = JsonSerializer.SerializeToString(dict);
options.RequestContentType = "application/json";
return HttpClient.Post(options);
}
/// <summary>
/// Adds the dlna headers.
/// </summary>

@ -3,7 +3,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
@ -282,11 +281,6 @@ namespace MediaBrowser.Api.Playback.Hls
{
var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
if (state.VideoRequest.ForceLiveStream)
{
return true;
}
return isLiveStream;
}
}

@ -257,8 +257,7 @@ namespace MediaBrowser.Api.Playback.Hls
return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
// 256k
private const int BufferSize = 262144;
private const int BufferSize = 81920;
private long GetStartPositionTicks(StreamState state, int requestedIndex)
{

@ -284,6 +284,13 @@ namespace MediaBrowser.Api.Playback
options.ForceDirectPlay = true;
}
}
else if (item is Video)
{
if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
{
options.ForceDirectPlay = true;
}
}
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
@ -315,6 +322,13 @@ namespace MediaBrowser.Api.Playback
options.ForceDirectStream = true;
}
}
else if (item is Video)
{
if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
{
options.ForceDirectStream = true;
}
}
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?

@ -142,7 +142,8 @@ namespace MediaBrowser.Api.Playback.Progressive
var outputPath = state.OutputFilePath;
var outputPathExists = FileSystem.FileExists(outputPath);
var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
var transcodingJob = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
var isTranscodeCached = outputPathExists && transcodingJob != null;
AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached);
@ -153,42 +154,64 @@ namespace MediaBrowser.Api.Playback.Progressive
using (state)
{
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
ResponseHeaders = responseHeaders,
ContentType = contentType,
IsHeadRequest = isHeadRequest,
Path = state.MediaPath
}).ConfigureAwait(false);
}
}
TimeSpan? cacheDuration = null;
// Not static but transcode cache file exists
if (isTranscodeCached)
{
var contentType = state.GetMimeType(outputPath);
if (!string.IsNullOrEmpty(request.Tag))
{
cacheDuration = TimeSpan.FromDays(365);
}
try
{
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
ResponseHeaders = responseHeaders,
ContentType = contentType,
IsHeadRequest = isHeadRequest,
Path = outputPath
Path = state.MediaPath,
CacheDuration = cacheDuration
}).ConfigureAwait(false);
}
finally
{
state.Dispose();
}
}
//// Not static but transcode cache file exists
//if (isTranscodeCached && state.VideoRequest == null)
//{
// var contentType = state.GetMimeType(outputPath);
// try
// {
// if (transcodingJob != null)
// {
// ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob);
// }
// return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
// {
// ResponseHeaders = responseHeaders,
// ContentType = contentType,
// IsHeadRequest = isHeadRequest,
// Path = outputPath,
// FileShare = FileShare.ReadWrite,
// OnComplete = () =>
// {
// if (transcodingJob != null)
// {
// ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
// }
// }
// }).ConfigureAwait(false);
// }
// finally
// {
// state.Dispose();
// }
//}
// Need to start ffmpeg
try
{
return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
.ConfigureAwait(false);
return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
}
catch
{
@ -347,9 +370,9 @@ namespace MediaBrowser.Api.Playback.Progressive
outputHeaders[item.Key] = item.Value;
}
Func<Stream,Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream, CancellationToken.None);
var streamSource = new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None);
return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders);
return ResultFactory.GetAsyncStreamWriter(streamSource);
}
finally
{
@ -368,7 +391,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (totalBitrate > 0 && state.RunTimeTicks.HasValue)
{
return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds);
return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8);
}
return null;

@ -1,59 +1,84 @@
using MediaBrowser.Model.Logging;
using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Net;
using System.Collections.Generic;
using ServiceStack.Web;
namespace MediaBrowser.Api.Playback.Progressive
{
public class ProgressiveFileCopier
public class ProgressiveFileCopier : IAsyncStreamSource, IHasOptions
{
private readonly IFileSystem _fileSystem;
private readonly TranscodingJob _job;
private readonly ILogger _logger;
private readonly string _path;
private readonly CancellationToken _cancellationToken;
private readonly Dictionary<string, string> _outputHeaders;
// 256k
private const int BufferSize = 262144;
private const int BufferSize = 81920;
private long _bytesWritten = 0;
public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job, ILogger logger)
public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
{
_fileSystem = fileSystem;
_path = path;
_outputHeaders = outputHeaders;
_job = job;
_logger = logger;
_cancellationToken = cancellationToken;
}
public async Task StreamFile(string path, Stream outputStream, CancellationToken cancellationToken)
public IDictionary<string, string> Options
{
var eofCount = 0;
get
{
return _outputHeaders;
}
}
using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
public async Task WriteToAsync(Stream outputStream)
{
try
{
while (eofCount < 15)
var eofCount = 0;
using (var fs = _fileSystem.GetFileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{
var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, cancellationToken).ConfigureAwait(false);
while (eofCount < 15)
{
var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, _cancellationToken).ConfigureAwait(false);
//var position = fs.Position;
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
//var position = fs.Position;
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
if (bytesRead == 0)
{
if (_job == null || _job.HasExited)
if (bytesRead == 0)
{
eofCount++;
if (_job == null || _job.HasExited)
{
eofCount++;
}
await Task.Delay(100, _cancellationToken).ConfigureAwait(false);
}
else
{
eofCount = 0;
}
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
else
{
eofCount = 0;
}
}
}
finally
{
if (_job != null)
{
ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
}
}
}
private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)

@ -149,11 +149,11 @@ namespace MediaBrowser.Api.Playback.Progressive
{
args += " -copyts -avoid_negative_ts disabled -start_at_zero";
}
return args;
}
var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
5.ToString(UsCulture));
args += keyFrameArg;
@ -237,4 +237,4 @@ namespace MediaBrowser.Api.Playback.Progressive
return args;
}
}
}
}

@ -74,6 +74,7 @@ namespace MediaBrowser.Api.Playback
public string Params { get; set; }
public string PlaySessionId { get; set; }
public string LiveStreamId { get; set; }
public string Tag { get; set; }
}
public class VideoStreamRequest : StreamRequest
@ -192,8 +193,6 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool CopyTimestamps { get; set; }
public bool ForceLiveStream { get; set; }
public bool EnableSubtitlesInManifest { get; set; }
public VideoStreamRequest()

@ -80,7 +80,10 @@ namespace MediaBrowser.Api.Playback
{
return 10;
}
if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1)
if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 ||
userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
{
return 10;
}
@ -208,7 +211,7 @@ namespace MediaBrowser.Api.Playback
private async void DisposeLiveStream()
{
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId))
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
{
try
{

@ -72,7 +72,7 @@ namespace MediaBrowser.Api
}
[Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")]
public class GetPlaylistItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields
public class GetPlaylistItems : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
{
[ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
public string Id { get; set; }
@ -104,6 +104,18 @@ namespace MediaBrowser.Api
/// <value>The fields.</value>
[ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Fields { get; set; }
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
[ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? ImageTypeLimit { get; set; }
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
}
[Authenticated]

@ -227,7 +227,7 @@ namespace MediaBrowser.Api
.ToList();
}
}
catch (Exception ex)
catch
{
//Logger.ErrorException("Error getting plugin list", ex);
// Play it safe here

@ -275,8 +275,6 @@ namespace MediaBrowser.Api.Reports
case ItemFilter.IsPlayed:
query.IsPlayed = true;
break;
case ItemFilter.IsRecentlyAdded:
break;
case ItemFilter.IsResumable:
query.IsResumable = true;
break;

@ -29,8 +29,20 @@ namespace MediaBrowser.Api
public string ExcludeArtistIds { get; set; }
}
public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasItemFields
public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasDtoOptions
{
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
[ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? ImageTypeLimit { get; set; }
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>

@ -117,7 +117,7 @@ namespace MediaBrowser.Api
config.EnableStandaloneMusicKeys = true;
config.EnableCaseSensitiveItemIds = true;
//config.EnableFolderView = true;
config.SchemaVersion = 108;
config.SchemaVersion = 109;
}
public void Post(UpdateStartupConfiguration request)

@ -24,7 +24,20 @@ namespace MediaBrowser.Api.Sync
}
break;
}
if (item.IsFolder && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
if (item.IsAudio)
{
options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile);
break;
}
if (item.IsMusicGenre || item.IsArtist|| item.IsType("musicalbum"))
{
options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile);
options.Add(SyncJobOption.ItemLimit);
break;
}
if (item.IsFolderItem && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
{
options.Add(SyncJobOption.Quality);
options.Add(SyncJobOption.Profile);
@ -44,7 +57,7 @@ namespace MediaBrowser.Api.Sync
{
if (item.SupportsSync ?? false)
{
if (item.IsFolder || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson)
if (item.IsFolderItem || item.IsGameGenre || item.IsMusicGenre || item.IsGenre || item.IsArtist || item.IsStudio || item.IsPerson)
{
options.Add(SyncJobOption.SyncNewContent);
options.Add(SyncJobOption.ItemLimit);

@ -66,6 +66,7 @@ namespace MediaBrowser.Api.Sync
public string Id { get; set; }
}
[Route("/Sync/Items/Cancel", "POST", Summary = "Cancels items from a sync target")]
[Route("/Sync/{TargetId}/Items", "DELETE", Summary = "Cancels items from a sync target")]
public class CancelItems : IReturnVoid
{
@ -211,7 +212,7 @@ namespace MediaBrowser.Api.Sync
return ToOptimizedResult(result);
}
public void Delete(CancelItems request)
public void Any(CancelItems request)
{
var itemIds = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
@ -290,7 +291,8 @@ namespace MediaBrowser.Api.Sync
{
Fields = new List<ItemFields>
{
ItemFields.SyncInfo
ItemFields.SyncInfo,
ItemFields.BasicSyncInfo
}
};

@ -69,6 +69,9 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
}
[Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")]
@ -117,6 +120,9 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
}
[Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")]
@ -184,6 +190,10 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
}
[Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")]
@ -226,6 +236,10 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
}
/// <summary>
@ -409,23 +423,14 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException("No series exists with Id " + request.Id);
}
var seasons = series.GetSeasons(user);
if (request.IsSpecialSeason.HasValue)
var seasons = (await series.GetItems(new InternalItemsQuery(user)
{
var val = request.IsSpecialSeason.Value;
IsMissing = request.IsMissing,
IsVirtualUnaired = request.IsVirtualUnaired,
IsSpecialSeason = request.IsSpecialSeason,
AdjacentTo = request.AdjacentTo
seasons = seasons.Where(i => i.IsSpecialSeason == val);
}
seasons = FilterVirtualSeasons(request, seasons);
// This must be the last filter
if (!string.IsNullOrEmpty(request.AdjacentTo))
{
seasons = UserViewBuilder.FilterForAdjacency(seasons, request.AdjacentTo)
.Cast<Season>();
}
}).ConfigureAwait(false)).Items.OfType<Season>();
var dtoOptions = GetDtoOptions(request);
@ -439,23 +444,6 @@ namespace MediaBrowser.Api
};
}
private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items)
{
if (request.IsMissing.HasValue)
{
var val = request.IsMissing.Value;
items = items.Where(i => (i.IsMissingSeason) == val);
}
if (request.IsVirtualUnaired.HasValue)
{
var val = request.IsVirtualUnaired.Value;
items = items.Where(i => i.IsVirtualUnaired == val);
}
return items;
}
public async Task<object> Get(GetEpisodes request)
{
var user = _userManager.GetUserById(request.UserId);
@ -490,7 +478,7 @@ namespace MediaBrowser.Api
}
else
{
episodes = series.GetEpisodes(user, season);
episodes = series.GetSeasonEpisodes(user, season);
}
}
else

@ -8,8 +8,6 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using ServiceStack;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary

@ -164,8 +164,6 @@ namespace MediaBrowser.Api.UserLibrary
case ItemFilter.IsPlayed:
query.IsPlayed = true;
break;
case ItemFilter.IsRecentlyAdded:
break;
case ItemFilter.IsResumable:
query.IsResumable = true;
break;
@ -180,9 +178,10 @@ namespace MediaBrowser.Api.UserLibrary
var result = GetItems(request, query);
var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions);
var dtos = result.Items.Select(i =>
{
var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, user);
var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, syncProgess, user);
if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes))
{
@ -213,6 +212,7 @@ namespace MediaBrowser.Api.UserLibrary
dto.AlbumCount = counts.AlbumCount;
dto.SongCount = counts.SongCount;
dto.GameCount = counts.GameCount;
dto.ArtistCount = counts.ArtistCount;
}
/// <summary>
@ -325,7 +325,8 @@ namespace MediaBrowser.Api.UserLibrary
tuples = ibnItems.Select(i => new Tuple<BaseItem, List<BaseItem>>(i, new List<BaseItem>()));
}
var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user));
var syncProgess = DtoService.GetSyncedItemProgress(dtoOptions);
var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, syncProgess, user));
result.Items = dtos.Where(i => i != null).ToArray();

@ -226,6 +226,9 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
[ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? ImageTypeLimit { get; set; }

@ -4,11 +4,9 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary

@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities;
using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
@ -158,33 +157,11 @@ namespace MediaBrowser.Api.UserLibrary
folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
}
if (!string.IsNullOrEmpty(request.Ids))
{
request.Recursive = true;
var query = GetItemsQuery(request, user);
var result = await folder.GetItems(query).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(request.SortBy))
{
var ids = query.ItemIds.ToList();
// Try to preserve order
result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
}
return result;
}
if (request.Recursive)
if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || user == null)
{
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
}
if (user == null)
{
return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
}
var userRoot = item as UserRootFolder;
if (userRoot == null)
@ -294,8 +271,6 @@ namespace MediaBrowser.Api.UserLibrary
case ItemFilter.IsPlayed:
query.IsPlayed = true;
break;
case ItemFilter.IsRecentlyAdded:
break;
case ItemFilter.IsResumable:
query.IsResumable = true;
break;

@ -8,8 +8,6 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using ServiceStack;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary

@ -12,6 +12,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Api.UserLibrary
{
@ -244,6 +246,9 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string EnableImageTypes { get; set; }
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
public GetLatestMedia()
{
Limit = 20;
@ -262,14 +267,16 @@ namespace MediaBrowser.Api.UserLibrary
private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
private readonly IUserViewManager _userViewManager;
private readonly IFileSystem _fileSystem;
public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager)
public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, IUserViewManager userViewManager, IFileSystem fileSystem)
{
_userManager = userManager;
_libraryManager = libraryManager;
_userDataRepository = userDataRepository;
_dtoService = dtoService;
_userViewManager = userViewManager;
_fileSystem = fileSystem;
}
/// <summary>
@ -426,12 +433,14 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetItem request)
public async Task<object> Get(GetItem request)
{
var user = _userManager.GetUserById(request.UserId);
var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
var dtoOptions = GetDtoOptions(request);
var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
@ -439,6 +448,27 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedSerializedResultUsingCache(result);
}
private async Task RefreshItemOnDemandIfNeeded(BaseItem item)
{
if (item is Person)
{
var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview) && item.HasImage(ImageType.Primary);
var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 3;
if (!hasMetdata)
{
var options = new MetadataRefreshOptions(_fileSystem)
{
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
ImageRefreshMode = ImageRefreshMode.FullRefresh,
ForceSave = performFullRefresh
};
await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false);
}
}
}
/// <summary>
/// Gets the specified request.
/// </summary>

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Api
{
@ -81,11 +82,18 @@ namespace MediaBrowser.Api
var dtoOptions = GetDtoOptions(request);
var video = (Video)item;
var items = video.GetAdditionalParts()
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video))
.ToArray();
var video = item as Video;
BaseItemDto[] items;
if (video != null)
{
items = video.GetAdditionalParts()
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video))
.ToArray();
}
else
{
items = new BaseItemDto[] { };
}
var result = new ItemsResult
{

@ -55,7 +55,7 @@
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath>
<HintPath>..\packages\NLog.4.3.6\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Patterns.Logging">

@ -429,17 +429,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
GC.Collect(2, GCCollectionMode.Forced, true);
}
/// <summary>
/// Executes the task.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <returns>Task.</returns>
private Task ExecuteTask(CancellationToken cancellationToken, IProgress<double> progress)
{
return Task.Run(async () => await ScheduledTask.Execute(cancellationToken, progress).ConfigureAwait(false), cancellationToken);
}
/// <summary>
/// Progress_s the progress changed.
/// </summary>

@ -1,6 +1,7 @@
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using CommonIO;
@ -24,13 +25,22 @@ namespace MediaBrowser.Common.Implementations.Serialization
// Need to cache these
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
private readonly ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
new ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer>();
private readonly Dictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
new Dictionary<string, System.Xml.Serialization.XmlSerializer>();
private System.Xml.Serialization.XmlSerializer GetSerializer(Type type)
{
var key = type.FullName;
return _serializers.GetOrAdd(key, k => new System.Xml.Serialization.XmlSerializer(type));
lock (_serializers)
{
System.Xml.Serialization.XmlSerializer serializer;
if (!_serializers.TryGetValue(key, out serializer))
{
serializer = new System.Xml.Serialization.XmlSerializer(type);
_serializers[key] = serializer;
}
return serializer;
}
}
/// <summary>

@ -33,7 +33,6 @@ namespace MediaBrowser.Common.Implementations.Updates
EnableKeepAlive = false,
CancellationToken = cancellationToken,
UserAgent = "Emby/3.0"
};
if (_cacheLength.Ticks > 0)
@ -79,6 +78,69 @@ namespace MediaBrowser.Common.Implementations.Updates
};
}
private bool MatchesUpdateLevel(RootObject i, PackageVersionClass updateLevel)
{
if (updateLevel == PackageVersionClass.Beta)
{
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase);
}
if (updateLevel == PackageVersionClass.Dev)
{
return !i.prerelease || i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) ||
i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
}
// Technically all we need to do is check that it's not pre-release
// But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
return !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) &&
!i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase);
}
public async Task<List<RootObject>> GetLatestReleases(string organzation, string repository, string assetFilename, CancellationToken cancellationToken)
{
var list = new List<RootObject>();
var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
var options = new HttpRequestOptions
{
Url = url,
EnableKeepAlive = false,
CancellationToken = cancellationToken,
UserAgent = "Emby/3.0"
};
if (_cacheLength.Ticks > 0)
{
options.CacheMode = CacheMode.Unconditional;
options.CacheLength = _cacheLength;
}
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
{
var obj = _jsonSerializer.DeserializeFromStream<RootObject[]>(stream);
obj = obj.Where(i => (i.assets ?? new List<Asset>()).Any(a => IsAsset(a, assetFilename))).ToArray();
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Release)).OrderByDescending(GetVersion).Take(1));
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Beta)).OrderByDescending(GetVersion).Take(1));
list.AddRange(obj.Where(i => MatchesUpdateLevel(i, PackageVersionClass.Dev)).OrderByDescending(GetVersion).Take(1));
return list;
}
}
public Version GetVersion(RootObject obj)
{
Version version;
if (!Version.TryParse(obj.tag_name, out version))
{
return new Version(1, 0);
}
return version;
}
private CheckForUpdateResult CheckForUpdateResult(RootObject obj, Version minVersion, string assetFilename, string packageName, string targetFilename)
{
Version version;

@ -2,7 +2,7 @@
<packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="NLog" version="4.3.5" targetFramework="net45" />
<package id="NLog" version="4.3.6" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
</packages>

@ -9,11 +9,11 @@ namespace MediaBrowser.Common.IO
/// <summary>
/// The default copy to buffer size
/// </summary>
public const int DefaultCopyToBufferSize = 262144;
public const int DefaultCopyToBufferSize = 81920;
/// <summary>
/// The default file stream buffer size
/// </summary>
public const int DefaultFileStreamBufferSize = 262144;
public const int DefaultFileStreamBufferSize = 81920;
}
}

@ -211,7 +211,7 @@ namespace MediaBrowser.Common.Plugins
{
return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
}
catch (Exception ex)
catch
{
return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
}

@ -57,10 +57,7 @@ namespace MediaBrowser.Controller.Channels
catch
{
// Already logged at lower levels
return new QueryResult<BaseItem>
{
};
return new QueryResult<BaseItem>();
}
}

@ -83,8 +83,7 @@ namespace MediaBrowser.Controller.Channels
{
var list = new List<MediaStream>();
if (!string.IsNullOrWhiteSpace(info.VideoCodec) &&
!string.IsNullOrWhiteSpace(info.AudioCodec))
if (!string.IsNullOrWhiteSpace(info.VideoCodec))
{
list.Add(new MediaStream
{
@ -99,7 +98,10 @@ namespace MediaBrowser.Controller.Channels
BitRate = info.VideoBitrate,
AverageFrameRate = info.Framerate
});
}
if (!string.IsNullOrWhiteSpace(info.AudioCodec))
{
list.Add(new MediaStream
{
Type = MediaStreamType.Audio,

@ -19,12 +19,16 @@ namespace MediaBrowser.Controller.Dto
public bool EnableImages { get; set; }
public bool AddProgramRecordingInfo { get; set; }
public string DeviceId { get; set; }
public bool EnableUserData { get; set; }
public bool AddCurrentProgram { get; set; }
public DtoOptions()
{
Fields = new List<ItemFields>();
ImageTypeLimit = int.MaxValue;
EnableImages = true;
EnableUserData = true;
AddCurrentProgram = true;
Fields = Enum.GetNames(typeof (ItemFields))
.Select(i => (ItemFields) Enum.Parse(typeof (ItemFields), i, true))

@ -1,9 +1,9 @@
using System;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using System.Collections.Generic;
using System.Threading.Tasks;
using MediaBrowser.Controller.Sync;
namespace MediaBrowser.Controller.Dto
{
@ -43,14 +43,6 @@ namespace MediaBrowser.Controller.Dto
/// <returns>Task{BaseItemDto}.</returns>
BaseItemDto GetBaseItemDto(BaseItem item, List<ItemFields> fields, User user = null, BaseItem owner = null);
/// <summary>
/// Fills the synchronize information.
/// </summary>
/// <param name="tuples">The tuples.</param>
/// <param name="options">The options.</param>
/// <param name="user">The user.</param>
void FillSyncInfo(IEnumerable<Tuple<BaseItem, BaseItemDto>> tuples, DtoOptions options, User user);
/// <summary>
/// Gets the base item dto.
/// </summary>
@ -89,11 +81,8 @@ namespace MediaBrowser.Controller.Dto
/// <summary>
/// Gets the item by name dto.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="options">The options.</param>
/// <param name="taggedItems">The tagged items.</param>
/// <param name="user">The user.</param>
/// <returns>BaseItemDto.</returns>
BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null);
BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Dictionary<string, SyncedItemProgress> syncProgress, User user = null);
Dictionary<string, SyncedItemProgress> GetSyncedItemProgress(DtoOptions options);
}
}

@ -5,6 +5,8 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Providers;
@ -67,6 +69,31 @@ namespace MediaBrowser.Controller.Entities
return CreateResolveArgs(directoryService, true).FileSystemChildren;
}
private List<Guid> _childrenIds = null;
private readonly object _childIdsLock = new object();
protected override IEnumerable<BaseItem> LoadChildren()
{
lock (_childIdsLock)
{
if (_childrenIds == null || _childrenIds.Count == 0)
{
var list = base.LoadChildren().ToList();
_childrenIds = list.Select(i => i.Id).ToList();
return list;
}
return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
}
}
private void ClearCache()
{
lock (_childIdsLock)
{
_childrenIds = null;
}
}
private bool _requiresRefresh;
public override bool RequiresRefresh()
{
@ -76,7 +103,7 @@ namespace MediaBrowser.Controller.Entities
{
var locations = PhysicalLocations.ToList();
var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
var newLocations = CreateResolveArgs(new DirectoryService(Logger, FileSystem), false).PhysicalLocations.ToList();
if (!locations.SequenceEqual(newLocations))
{
@ -89,6 +116,8 @@ namespace MediaBrowser.Controller.Entities
public override bool BeforeMetadataRefresh()
{
ClearCache();
var changed = base.BeforeMetadataRefresh() || _requiresRefresh;
_requiresRefresh = false;
return changed;
@ -96,9 +125,11 @@ namespace MediaBrowser.Controller.Entities
private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
{
ClearCache();
var path = ContainingFolderPath;
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths , directoryService)
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
{
FileInfo = FileSystem.GetDirectoryInfo(path),
Path = path,
@ -135,7 +166,22 @@ namespace MediaBrowser.Controller.Entities
return args;
}
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren);
}
protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
ClearCache();
await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService)
.ConfigureAwait(false);
ClearCache();
}
/// <summary>
/// Adds the virtual child.
/// </summary>
@ -151,15 +197,6 @@ namespace MediaBrowser.Controller.Entities
_virtualChildren.Add(child);
}
/// <summary>
/// Get the children of this folder from the actual file system
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren);
}
/// <summary>
/// Finds the virtual child.
/// </summary>

@ -5,9 +5,11 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
namespace MediaBrowser.Controller.Entities.Audio
@ -47,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Audio
}
[IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange
public override bool EnableRefreshOnDateModifiedChange
{
get { return true; }
}
@ -266,6 +268,11 @@ namespace MediaBrowser.Controller.Entities.Audio
Size = i.Size
};
if (info.Protocol == MediaProtocol.File)
{
info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N");
}
if (string.IsNullOrEmpty(info.Container))
{
if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)

@ -169,13 +169,9 @@ namespace MediaBrowser.Controller.Entities.Audio
list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics());
return list;
}
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
{
return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
}
return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
}
protected override bool GetBlockUnratedValue(UserPolicy config)
{
@ -274,5 +270,54 @@ namespace MediaBrowser.Controller.Entities.Audio
return false;
}
}
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.ArtistsPath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
if (IsAccessedByName)
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
if (IsAccessedByName)
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
}
return hasChanges;
}
}
}

@ -18,13 +18,9 @@ namespace MediaBrowser.Controller.Entities.Audio
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
{
return GetUserDataKeys()[0];
}
return GetUserDataKeys()[0];
}
[IgnoreDataMember]
@ -96,5 +92,48 @@ namespace MediaBrowser.Controller.Entities.Audio
return LibraryManager.GetItemList(query);
}
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.MusicGenrePath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
}
}

@ -281,6 +281,20 @@ namespace MediaBrowser.Controller.Entities
}
}
public Task UpdateIsOffline(bool newValue)
{
var item = this;
if (item.IsOffline != newValue)
{
item.IsOffline = newValue;
// this is creating too many repeated db updates
//return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
}
return Task.FromResult(true);
}
/// <summary>
/// Gets or sets the type of the location.
/// </summary>
@ -290,10 +304,10 @@ namespace MediaBrowser.Controller.Entities
{
get
{
if (IsOffline)
{
return LocationType.Offline;
}
//if (IsOffline)
//{
// return LocationType.Offline;
//}
if (string.IsNullOrWhiteSpace(Path))
{
@ -455,7 +469,7 @@ namespace MediaBrowser.Controller.Entities
public DateTime DateLastRefreshed { get; set; }
[IgnoreDataMember]
public virtual bool EnableForceSaveOnDateModifiedChange
public virtual bool EnableRefreshOnDateModifiedChange
{
get { return false; }
}
@ -767,6 +781,9 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public string OfficialRating { get; set; }
[IgnoreDataMember]
public int InheritedParentalRatingValue { get; set; }
/// <summary>
/// Gets or sets the critic rating.
/// </summary>
@ -951,7 +968,7 @@ namespace MediaBrowser.Controller.Entities
.Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
);
return LibraryManager.ResolvePaths(files, directoryService, null)
return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
.OfType<Audio.Audio>()
.Select(audio =>
{
@ -981,7 +998,7 @@ namespace MediaBrowser.Controller.Entities
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => directoryService.GetFiles(i.FullName));
return LibraryManager.ResolvePaths(files, directoryService, null)
return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
.OfType<Video>()
.Select(item =>
{
@ -1003,7 +1020,7 @@ namespace MediaBrowser.Controller.Entities
public Task RefreshMetadata(CancellationToken cancellationToken)
{
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken);
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken);
}
/// <summary>
@ -1194,10 +1211,17 @@ namespace MediaBrowser.Controller.Entities
get { return null; }
}
public virtual string CreatePresentationUniqueKey()
{
return Id.ToString("N");
}
[IgnoreDataMember]
public virtual string PresentationUniqueKey
public string PresentationUniqueKey { get; set; }
public string GetPresentationUniqueKey()
{
get { return Id.ToString("N"); }
return PresentationUniqueKey ?? CreatePresentationUniqueKey();
}
public virtual bool RequiresRefresh()
@ -2206,6 +2230,15 @@ namespace MediaBrowser.Controller.Entities
}
}
[IgnoreDataMember]
public virtual bool StopRefreshIfLocalMetadataFound
{
get
{
return true;
}
}
public virtual IEnumerable<Guid> GetIdsForAncestorQuery()
{
return new[] { Id };

@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange
public override bool EnableRefreshOnDateModifiedChange
{
get { return true; }
}

@ -3,11 +3,15 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Serialization;
using MoreLinq;
namespace MediaBrowser.Controller.Entities
@ -18,6 +22,8 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class CollectionFolder : Folder, ICollectionFolder
{
public static IXmlSerializer XmlSerializer { get; set; }
public CollectionFolder()
{
PhysicalLocationsList = new List<string>();
@ -39,6 +45,72 @@ namespace MediaBrowser.Controller.Entities
public string CollectionType { get; set; }
private static readonly Dictionary<string, LibraryOptions> LibraryOptions = new Dictionary<string, LibraryOptions>();
public LibraryOptions GetLibraryOptions()
{
lock (LibraryOptions)
{
LibraryOptions options;
if (!LibraryOptions.TryGetValue(Path, out options))
{
options = LoadLibraryOptions();
LibraryOptions[Path] = options;
}
return options;
}
}
private LibraryOptions LoadLibraryOptions()
{
try
{
var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(Path)) as LibraryOptions;
if (result == null)
{
return new LibraryOptions();
}
return result;
}
catch (FileNotFoundException)
{
return new LibraryOptions();
}
catch (DirectoryNotFoundException)
{
return new LibraryOptions();
}
catch (Exception ex)
{
Logger.ErrorException("Error loading library options", ex);
return new LibraryOptions();
}
}
private static string GetLibraryOptionsPath(string path)
{
return System.IO.Path.Combine(path, "options.xml");
}
public void UpdateLibraryOptions(LibraryOptions options)
{
SaveLibraryOptions(Path, options);
}
public static void SaveLibraryOptions(string path, LibraryOptions options)
{
lock (LibraryOptions)
{
LibraryOptions[path] = options;
options.SchemaVersion = 1;
XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path));
}
}
/// <summary>
/// Allow different display preferences for each collection folder
/// </summary>
@ -82,7 +154,7 @@ namespace MediaBrowser.Controller.Entities
{
var locations = PhysicalLocations.ToList();
var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
var newLocations = CreateResolveArgs(new DirectoryService(Logger, FileSystem), false).PhysicalLocations.ToList();
if (!locations.SequenceEqual(newLocations))
{

@ -1,5 +1,4 @@
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
@ -14,6 +13,8 @@ using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Channels;
namespace MediaBrowser.Controller.Entities
@ -273,13 +274,14 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
protected virtual IEnumerable<BaseItem> LoadChildren()
{
//Logger.Debug("Loading children from {0} {1} {2}", GetType().Name, Id, Path);
//just load our children from the repo - the library will be validated and maintained in other processes
return GetCachedChildren();
}
public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken)
{
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(FileSystem)));
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)));
}
/// <summary>
@ -373,7 +375,7 @@ namespace MediaBrowser.Controller.Entities
if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
{
await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
await currentChild.UpdateIsOffline(false).ConfigureAwait(false);
validChildren.Add(currentChild);
continue;
@ -402,7 +404,7 @@ namespace MediaBrowser.Controller.Entities
else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
{
await UpdateIsOffline(item, true).ConfigureAwait(false);
await item.UpdateIsOffline(true).ConfigureAwait(false);
}
else
{
@ -459,17 +461,6 @@ namespace MediaBrowser.Controller.Entities
progress.Report(100);
}
private Task UpdateIsOffline(BaseItem item, bool newValue)
{
if (item.IsOffline != newValue)
{
item.IsOffline = newValue;
return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
}
return Task.FromResult(true);
}
private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{
var children = ActualChildren.ToList();
@ -643,8 +634,9 @@ namespace MediaBrowser.Controller.Entities
protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
var collectionType = LibraryManager.GetContentType(this);
var libraryOptions = LibraryManager.GetLibraryOptions(this);
return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, collectionType);
return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, libraryOptions, collectionType);
}
/// <summary>
@ -699,7 +691,7 @@ namespace MediaBrowser.Controller.Entities
items = GetRecursiveChildren(user, query);
}
return PostFilterAndSort(items, query);
return PostFilterAndSort(items, query, true, true);
}
if (!(this is UserRootFolder) && !(this is AggregateFolder))
@ -883,6 +875,15 @@ namespace MediaBrowser.Controller.Entities
return true;
}
if (query.IsPlayed.HasValue)
{
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(typeof(Series).Name))
{
Logger.Debug("Query requires post-filtering due to IsPlayed");
return true;
}
}
return false;
}
@ -890,8 +891,16 @@ namespace MediaBrowser.Controller.Entities
{
if (query.ItemIds.Length > 0)
{
var specificItems = query.ItemIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
return Task.FromResult(PostFilterAndSort(specificItems, query));
var result = LibraryManager.GetItemsResult(query);
if (query.SortBy.Length == 0)
{
var ids = query.ItemIds.ToList();
// Try to preserve order
result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
}
return Task.FromResult(result);
}
return GetItemsInternal(query);
@ -919,10 +928,7 @@ namespace MediaBrowser.Controller.Entities
catch
{
// Already logged at lower levels
return new QueryResult<BaseItem>
{
};
return new QueryResult<BaseItem>();
}
}
@ -950,12 +956,12 @@ namespace MediaBrowser.Controller.Entities
: GetChildren(user, true).Where(filter);
}
return PostFilterAndSort(items, query);
return PostFilterAndSort(items, query, true, true);
}
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query, bool collapseBoxSetItems, bool enableSorting)
{
return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager);
return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager, collapseBoxSetItems, enableSorting);
}
public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
@ -1419,7 +1425,7 @@ namespace MediaBrowser.Controller.Entities
itemDto.RecursiveItemCount = allItemsQueryResult.TotalRecordCount;
}
double recursiveItemCount = allItemsQueryResult.TotalRecordCount;
var recursiveItemCount = allItemsQueryResult.TotalRecordCount;
double unplayedCount = unplayedQueryResult.TotalRecordCount;
if (recursiveItemCount > 0)
@ -1429,6 +1435,14 @@ namespace MediaBrowser.Controller.Entities
dto.Played = dto.PlayedPercentage.Value >= 100;
dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
}
if (itemDto != null)
{
if (this is Season || this is MusicAlbum)
{
itemDto.ChildCount = recursiveItemCount;
}
}
}
}
}

@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange
public override bool EnableRefreshOnDateModifiedChange
{
get { return true; }
}

@ -16,12 +16,9 @@ namespace MediaBrowser.Controller.Entities
return list;
}
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
{
return GetUserDataKeys()[0];
}
return GetUserDataKeys()[0];
}
/// <summary>
@ -87,5 +84,48 @@ namespace MediaBrowser.Controller.Entities
return false;
}
}
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GameGenrePath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
}
}

@ -19,13 +19,9 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
{
return GetUserDataKeys()[0];
}
return GetUserDataKeys()[0];
}
/// <summary>
@ -91,5 +87,48 @@ namespace MediaBrowser.Controller.Entities
return false;
}
}
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GenrePath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
}
}

@ -1,5 +1,4 @@
using System;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using System.Collections.Generic;
using System.Threading;

@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value>The date last refreshed.</value>
DateTime DateLastRefreshed { get; set; }
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
@ -52,6 +52,15 @@ namespace MediaBrowser.Controller.Entities
bool RequiresRefresh();
bool EnableForceSaveOnDateModifiedChange { get; }
bool EnableRefreshOnDateModifiedChange { get; }
string PresentationUniqueKey { get; set; }
string GetPresentationUniqueKey();
string CreatePresentationUniqueKey();
bool StopRefreshIfLocalMetadataFound { get; }
int? GetInheritedParentalRatingValue();
int InheritedParentalRatingValue { get; set; }
}
}

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{

@ -37,6 +37,7 @@ namespace MediaBrowser.Controller.Entities
public string[] Genres { get; set; }
public string[] Keywords { get; set; }
public bool? IsSpecialSeason { get; set; }
public bool? IsMissing { get; set; }
public bool? IsUnaired { get; set; }
public bool? IsVirtualUnaired { get; set; }
@ -50,6 +51,7 @@ namespace MediaBrowser.Controller.Entities
public string PresentationUniqueKey { get; set; }
public string Path { get; set; }
public string PathNotStartsWith { get; set; }
public string Name { get; set; }
public string SlugName { get; set; }

@ -11,11 +11,13 @@ namespace MediaBrowser.Controller.Entities
public int? MaxListOrder { get; set; }
public Guid AppearsInItemId { get; set; }
public string NameContains { get; set; }
public SourceType[] SourceTypes { get; set; }
public InternalPeopleQuery()
{
PersonTypes = new List<string>();
ExcludePersonTypes = new List<string>();
SourceTypes = new SourceType[] { };
}
}
}

@ -62,6 +62,26 @@ namespace MediaBrowser.Controller.Entities.Movies
return UnratedItem.Movie;
}
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
if (IsLegacyBoxSet)
{
return base.GetNonCachedChildren(directoryService);
}
return new List<BaseItem>();
}
protected override IEnumerable<BaseItem> LoadChildren()
{
if (IsLegacyBoxSet)
{
return base.LoadChildren();
}
// Save a trip to the database
return new List<BaseItem>();
}
[IgnoreDataMember]
public override bool IsPreSorted
{
@ -76,7 +96,21 @@ namespace MediaBrowser.Controller.Entities.Movies
{
get
{
return true;
if (IsLegacyBoxSet)
{
return true;
}
return false;
}
}
[IgnoreDataMember]
private bool IsLegacyBoxSet
{
get
{
return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
}
}

@ -179,5 +179,15 @@ namespace MediaBrowser.Controller.Entities.Movies
return list;
}
[IgnoreDataMember]
public override bool StopRefreshIfLocalMetadataFound
{
get
{
// Need people id's from internet metadata
return false;
}
}
}
}

@ -1,7 +1,6 @@
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System.Collections.Generic;
using System.Runtime.Serialization;

@ -26,13 +26,9 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
{
return GetUserDataKeys()[0];
}
return GetUserDataKeys()[0];
}
public PersonLookupInfo GetLookupInfo()
@ -126,6 +122,64 @@ namespace MediaBrowser.Controller.Entities
return false;
}
}
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validFilename = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
string subFolderPrefix = null;
foreach (char c in validFilename)
{
if (char.IsLetterOrDigit(c))
{
subFolderPrefix = c.ToString();
break;
}
}
var path = ConfigurationManager.ApplicationPaths.PeoplePath;
return string.IsNullOrEmpty(subFolderPrefix) ?
System.IO.Path.Combine(path, validFilename) :
System.IO.Path.Combine(path, subFolderPrefix, validFilename);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
}
/// <summary>

@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange
public override bool EnableRefreshOnDateModifiedChange
{
get { return true; }
}

@ -18,13 +18,9 @@ namespace MediaBrowser.Controller.Entities
list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
{
return GetUserDataKeys()[0];
}
return GetUserDataKeys()[0];
}
/// <summary>
@ -89,5 +85,48 @@ namespace MediaBrowser.Controller.Entities
return false;
}
}
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.StudioPath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
}
}

@ -135,7 +135,11 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public Series Series
{
get { return FindParent<Series>(); }
get
{
var seriesId = SeriesId ?? FindSeriesId();
return seriesId.HasValue ? (LibraryManager.GetItemById(seriesId.Value) as Series) : null;
}
}
[IgnoreDataMember]
@ -143,24 +147,8 @@ namespace MediaBrowser.Controller.Entities.TV
{
get
{
var season = FindParent<Season>();
// Episodes directly in series folder
if (season == null)
{
var series = Series;
if (series != null && ParentIndexNumber.HasValue)
{
var findNumber = ParentIndexNumber.Value;
season = series.Children
.OfType<Season>()
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
}
}
return season;
var seasonId = SeasonId ?? FindSeasonId();
return seasonId.HasValue ? (LibraryManager.GetItemById(seasonId.Value) as Season) : null;
}
}
@ -193,7 +181,23 @@ namespace MediaBrowser.Controller.Entities.TV
public Guid? FindSeasonId()
{
var season = Season;
var season = FindParent<Season>();
// Episodes directly in series folder
if (season == null)
{
var series = Series;
if (series != null && ParentIndexNumber.HasValue)
{
var findNumber = ParentIndexNumber.Value;
season = series.Children
.OfType<Season>()
.FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
}
}
return season == null ? (Guid?)null : season.Id;
}
@ -263,7 +267,7 @@ namespace MediaBrowser.Controller.Entities.TV
public Guid? FindSeriesId()
{
var series = Series;
var series = FindParent<Series>();
return series == null ? (Guid?)null : series.Id;
}

@ -1,6 +1,5 @@
using System;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Users;
using MoreLinq;
@ -86,7 +85,11 @@ namespace MediaBrowser.Controller.Entities.TV
public override int GetChildCount(User user)
{
return GetChildren(user, true).Count();
Logger.Debug("Season {0} getting child cound", (Path ?? Name));
var result = GetChildren(user, true).Count();
Logger.Debug("Season {0} child cound: ", result);
return result;
}
/// <summary>
@ -96,7 +99,11 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public Series Series
{
get { return FindParent<Series>(); }
get
{
var seriesId = SeriesId ?? FindSeriesId();
return seriesId.HasValue ? (LibraryManager.GetItemById(seriesId.Value) as Series) : null;
}
}
[IgnoreDataMember]
@ -115,22 +122,18 @@ namespace MediaBrowser.Controller.Entities.TV
}
}
[IgnoreDataMember]
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
if (IndexNumber.HasValue)
{
if (IndexNumber.HasValue)
var series = Series;
if (series != null)
{
var series = Series;
if (series != null)
{
return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000");
}
return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000");
}
return base.PresentationUniqueKey;
}
return base.CreatePresentationUniqueKey();
}
/// <summary>
@ -142,24 +145,6 @@ namespace MediaBrowser.Controller.Entities.TV
return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name;
}
[IgnoreDataMember]
public bool IsMissingSeason
{
get { return (IsVirtualItem) && !IsUnaired; }
}
[IgnoreDataMember]
public bool IsVirtualUnaired
{
get { return (IsVirtualItem) && IsUnaired; }
}
[IgnoreDataMember]
public bool IsSpecialSeason
{
get { return (IndexNumber ?? -1) == 0; }
}
protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
if (query.User == null)
@ -171,10 +156,15 @@ namespace MediaBrowser.Controller.Entities.TV
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
var id = Guid.NewGuid().ToString("N");
Logger.Debug("Season.GetItemsInternal entering GetEpisodes. Request id: " + id);
var items = GetEpisodes(user).Where(filter);
var result = PostFilterAndSort(items, query);
Logger.Debug("Season.GetItemsInternal entering PostFilterAndSort. Request id: " + id);
var result = PostFilterAndSort(items, query, false, false);
Logger.Debug("Season.GetItemsInternal complete. Request id: " + id);
return Task.FromResult(result);
}
@ -185,19 +175,17 @@ namespace MediaBrowser.Controller.Entities.TV
/// <returns>IEnumerable{Episode}.</returns>
public IEnumerable<Episode> GetEpisodes(User user)
{
var config = user.Configuration;
return GetEpisodes(Series, user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
return GetEpisodes(Series, user);
}
public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
public IEnumerable<Episode> GetEpisodes(Series series, User user)
{
return GetEpisodes(series, user, includeMissingEpisodes, includeVirtualUnairedEpisodes, null);
return GetEpisodes(series, user, null);
}
public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
public IEnumerable<Episode> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes)
{
return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes, allSeriesEpisodes);
return series.GetSeasonEpisodes(user, this, allSeriesEpisodes);
}
public IEnumerable<Episode> GetEpisodes()
@ -257,7 +245,7 @@ namespace MediaBrowser.Controller.Entities.TV
public Guid? FindSeriesId()
{
var series = Series;
var series = FindParent<Series>();
return series == null ? (Guid?)null : series.Id;
}

@ -96,19 +96,29 @@ namespace MediaBrowser.Controller.Entities.TV
}
}
[IgnoreDataMember]
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
var userdatakeys = GetUserDataKeys();
if (userdatakeys.Count > 1)
{
var userdatakeys = GetUserDataKeys();
return AddLibrariesToPresentationUniqueKey(userdatakeys[0]);
}
return base.CreatePresentationUniqueKey();
}
if (userdatakeys.Count > 1)
{
return userdatakeys[0];
}
return base.PresentationUniqueKey;
private string AddLibrariesToPresentationUniqueKey(string key)
{
var folders = LibraryManager.GetCollectionFolders(this)
.Select(i => i.Id.ToString("N"))
.ToArray();
if (folders.Length == 0)
{
return key;
}
return key + "-" + string.Join("-", folders);
}
private static string GetUniqueSeriesKey(BaseItem series)
@ -117,7 +127,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
return series.Id.ToString("N");
}
return series.PresentationUniqueKey;
return series.GetPresentationUniqueKey();
}
public override int GetChildCount(User user)
@ -197,7 +207,30 @@ namespace MediaBrowser.Controller.Entities.TV
{
var config = user.Configuration;
return GetSeasons(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
var seriesKey = GetUniqueSeriesKey(this);
Logger.Debug("GetSeasons SeriesKey: {0}", seriesKey);
var query = new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] {typeof (Season).Name},
SortBy = new[] {ItemSortBy.SortName}
};
if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
{
query.IsVirtualItem = false;
}
else if (!config.DisplayMissingEpisodes)
{
query.IsMissing = false;
}
else if (!config.DisplayUnairedEpisodes)
{
query.IsVirtualUnaired = false;
}
return LibraryManager.GetItemList(query).Cast<Season>();
}
protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
@ -227,55 +260,43 @@ namespace MediaBrowser.Controller.Entities.TV
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
var items = GetSeasons(user).Where(filter);
var result = PostFilterAndSort(items, query);
var result = PostFilterAndSort(items, query, false, true);
return Task.FromResult(result);
}
public IEnumerable<Season> GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired)
public IEnumerable<Episode> GetEpisodes(User user)
{
IEnumerable<Season> seasons;
var seriesKey = GetUniqueSeriesKey(this);
Logger.Debug("GetEpisodes seriesKey: {0}", seriesKey);
seasons = LibraryManager.GetItemList(new InternalItemsQuery(user)
var query = new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
IncludeItemTypes = new[] { typeof(Season).Name },
SortBy = new[] { ItemSortBy.SortName }
}).Cast<Season>();
if (!includeMissingSeasons)
AncestorWithPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] {typeof (Episode).Name, typeof (Season).Name},
SortBy = new[] {ItemSortBy.SortName}
};
var config = user.Configuration;
if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
{
seasons = seasons.Where(i => !(i.IsMissingSeason));
query.IsVirtualItem = false;
}
if (!includeVirtualUnaired)
else if (!config.DisplayMissingEpisodes)
{
seasons = seasons.Where(i => !i.IsVirtualUnaired);
query.IsMissing = false;
}
return seasons;
}
public IEnumerable<Episode> GetEpisodes(User user)
{
var config = user.Configuration;
return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
}
public IEnumerable<Episode> GetEpisodes(User user, bool includeMissing, bool includeVirtualUnaired)
{
var allItems = LibraryManager.GetItemList(new InternalItemsQuery(user)
else if (!config.DisplayUnairedEpisodes)
{
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
SortBy = new[] { ItemSortBy.SortName }
query.IsVirtualUnaired = false;
}
}).ToList();
var allItems = LibraryManager.GetItemList(query).ToList();
Logger.Debug("GetEpisodes return {0} items from database", allItems.Count);
var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
var allEpisodes = allItems.OfType<Season>()
.SelectMany(i => i.GetEpisodes(this, user, includeMissing, includeVirtualUnaired, allSeriesEpisodes))
.SelectMany(i => i.GetEpisodes(this, user, allSeriesEpisodes))
.Reverse()
.ToList();
@ -352,78 +373,68 @@ namespace MediaBrowser.Controller.Entities.TV
progress.Report(100);
}
public IEnumerable<Episode> GetEpisodes(User user, Season season)
{
var config = user.Configuration;
return GetEpisodes(user, season, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
}
private IEnumerable<Episode> GetAllEpisodes(User user)
{
return LibraryManager.GetItemList(new InternalItemsQuery(user)
Logger.Debug("Series.GetAllEpisodes entering GetItemList");
var result = LibraryManager.GetItemList(new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
IncludeItemTypes = new[] { typeof(Episode).Name },
SortBy = new[] { ItemSortBy.SortName }
}).Cast<Episode>();
}
}).Cast<Episode>().ToList();
public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
{
IEnumerable<Episode> episodes = GetAllEpisodes(user);
Logger.Debug("Series.GetAllEpisodes returning {0} episodes", result.Count);
return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
return result;
}
public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason)
{
if (allSeriesEpisodes == null)
var seriesKey = GetUniqueSeriesKey(this);
Logger.Debug("GetSeasonEpisodes seriesKey: {0}", seriesKey);
var query = new InternalItemsQuery(user)
{
return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes);
AncestorWithPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { typeof(Episode).Name },
SortBy = new[] { ItemSortBy.SortName }
};
var config = user.Configuration;
if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
{
query.IsVirtualItem = false;
}
var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
if (!includeMissingEpisodes)
else if (!config.DisplayMissingEpisodes)
{
episodes = episodes.Where(i => !i.IsMissingEpisode);
query.IsMissing = false;
}
if (!includeVirtualUnairedEpisodes)
else if (!config.DisplayUnairedEpisodes)
{
episodes = episodes.Where(i => !i.IsVirtualUnaired);
query.IsVirtualUnaired = false;
}
var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
var allItems = LibraryManager.GetItemList(query).OfType<Episode>();
return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
.Cast<Episode>();
return GetSeasonEpisodes(user, parentSeason, allItems);
}
/// <summary>
/// Filters the episodes by season.
/// </summary>
public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason, IEnumerable<Episode> allSeriesEpisodes)
{
if (!includeSpecials || seasonNumber < 1)
if (allSeriesEpisodes == null)
{
return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber);
Logger.Debug("GetSeasonEpisodes allSeriesEpisodes is null");
return GetSeasonEpisodes(user, parentSeason);
}
return episodes.Where(i =>
{
var episode = i;
if (episode != null)
{
var currentSeasonNumber = episode.AiredSeasonNumber;
Logger.Debug("GetSeasonEpisodes FilterEpisodesBySeason");
var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
}
var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
return false;
});
return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
.Cast<Episode>();
}
/// <summary>
@ -454,6 +465,32 @@ namespace MediaBrowser.Controller.Entities.TV
});
}
/// <summary>
/// Filters the episodes by season.
/// </summary>
public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
{
if (!includeSpecials || seasonNumber < 1)
{
return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber);
}
return episodes.Where(i =>
{
var episode = i;
if (episode != null)
{
var currentSeasonNumber = episode.AiredSeasonNumber;
return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
}
return false;
});
}
protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Series);
@ -509,5 +546,15 @@ namespace MediaBrowser.Controller.Entities.TV
return list;
}
[IgnoreDataMember]
public override bool StopRefreshIfLocalMetadataFound
{
get
{
// Need people id's from internet metadata
return false;
}
}
}
}

@ -2,9 +2,7 @@
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.Serialization;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Entities
@ -126,5 +124,15 @@ namespace MediaBrowser.Controller.Entities
return list;
}
[IgnoreDataMember]
public override bool StopRefreshIfLocalMetadataFound
{
get
{
// Need people id's from internet metadata
return false;
}
}
}
}

@ -213,7 +213,7 @@ namespace MediaBrowser.Controller.Entities
Name = newName;
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem))
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem))
{
ReplaceAllMetadata = true,
ImageRefreshMode = ImageRefreshMode.FullRefresh,

@ -1,6 +1,5 @@
using System.Runtime.Serialization;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using System;
@ -8,7 +7,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Entities
{
@ -18,6 +16,31 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class UserRootFolder : Folder
{
private List<Guid> _childrenIds = null;
private readonly object _childIdsLock = new object();
protected override IEnumerable<BaseItem> LoadChildren()
{
lock (_childIdsLock)
{
if (_childrenIds == null)
{
var list = base.LoadChildren().ToList();
_childrenIds = list.Select(i => i.Id).ToList();
return list;
}
return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
}
}
private void ClearCache()
{
lock (_childIdsLock)
{
_childrenIds = null;
}
}
protected override async Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
if (query.Recursive)
@ -35,7 +58,7 @@ namespace MediaBrowser.Controller.Entities
var user = query.User;
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
return PostFilterAndSort(result.Where(filter), query);
return PostFilterAndSort(result.Where(filter), query, true, true);
}
public override int GetChildCount(User user)
@ -71,6 +94,8 @@ namespace MediaBrowser.Controller.Entities
public override bool BeforeMetadataRefresh()
{
ClearCache();
var hasChanges = base.BeforeMetadataRefresh();
if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase))
@ -82,11 +107,22 @@ namespace MediaBrowser.Controller.Entities
return hasChanges;
}
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
ClearCache();
return base.GetNonCachedChildren(directoryService);
}
protected override async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
ClearCache();
await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService)
.ConfigureAwait(false);
ClearCache();
// Not the best way to handle this, but it solves an issue
// CollectionFolders aren't always getting saved after changes
// This means that grabbing the item by Id may end up returning the old one

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
@ -14,12 +13,10 @@ 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;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
using MoreLinq;
namespace MediaBrowser.Controller.Entities
@ -427,7 +424,7 @@ namespace MediaBrowser.Controller.Entities
query.SortBy = new string[] { };
return PostFilterAndSort(items, parent, null, query);
return PostFilterAndSort(items, parent, null, query, false, true);
}
private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query)
@ -783,7 +780,7 @@ namespace MediaBrowser.Controller.Entities
{
items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager));
return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config);
return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config, true, true);
}
public static bool FilterItem(BaseItem item, InternalItemsQuery query)
@ -794,9 +791,11 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
BaseItem queryParent,
int? totalRecordLimit,
InternalItemsQuery query)
InternalItemsQuery query,
bool collapseBoxSetItems,
bool enableSorting)
{
return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config);
return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config, collapseBoxSetItems, enableSorting);
}
public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
@ -804,7 +803,9 @@ namespace MediaBrowser.Controller.Entities
int? totalRecordLimit,
InternalItemsQuery query,
ILibraryManager libraryManager,
IServerConfigurationManager configurationManager)
IServerConfigurationManager configurationManager,
bool collapseBoxSetItems,
bool enableSorting)
{
var user = query.User;
@ -813,7 +814,10 @@ namespace MediaBrowser.Controller.Entities
query.IsVirtualUnaired,
query.IsUnaired);
items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
if (collapseBoxSetItems)
{
items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
}
// This must be the last filter
if (!string.IsNullOrEmpty(query.AdjacentTo))
@ -821,7 +825,7 @@ namespace MediaBrowser.Controller.Entities
items = FilterForAdjacency(items, query.AdjacentTo);
}
return Sort(items, totalRecordLimit, query, libraryManager);
return SortAndPage(items, totalRecordLimit, query, libraryManager, enableSorting);
}
public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items,
@ -1096,8 +1100,6 @@ namespace MediaBrowser.Controller.Entities
bool? isVirtualUnaired,
bool? isUnaired)
{
items = FilterVirtualSeasons(items, isMissing, isVirtualUnaired, isUnaired);
if (isMissing.HasValue)
{
var val = isMissing.Value;
@ -1143,65 +1145,14 @@ namespace MediaBrowser.Controller.Entities
return items;
}
private static IEnumerable<BaseItem> FilterVirtualSeasons(
IEnumerable<BaseItem> items,
bool? isMissing,
bool? isVirtualUnaired,
bool? isUnaired)
{
if (isMissing.HasValue)
{
var val = isMissing.Value;
items = items.Where(i =>
{
var e = i as Season;
if (e != null)
{
return (e.IsMissingSeason) == val;
}
return true;
});
}
if (isUnaired.HasValue)
{
var val = isUnaired.Value;
items = items.Where(i =>
{
var e = i as Season;
if (e != null)
{
return e.IsUnaired == val;
}
return true;
});
}
if (isVirtualUnaired.HasValue)
{
var val = isVirtualUnaired.Value;
items = items.Where(i =>
{
var e = i as Season;
if (e != null)
{
return e.IsVirtualUnaired == val;
}
return true;
});
}
return items;
}
public static QueryResult<BaseItem> Sort(IEnumerable<BaseItem> items,
public static QueryResult<BaseItem> SortAndPage(IEnumerable<BaseItem> items,
int? totalRecordLimit,
InternalItemsQuery query,
ILibraryManager libraryManager)
ILibraryManager libraryManager, bool enableSorting)
{
var user = query.User;
items = items.DistinctBy(i => i.PresentationUniqueKey, StringComparer.OrdinalIgnoreCase);
items = items.DistinctBy(i => i.GetPresentationUniqueKey(), StringComparer.OrdinalIgnoreCase);
if (query.SortBy.Length > 0)
{

@ -12,6 +12,7 @@ using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
namespace MediaBrowser.Controller.Entities
@ -44,24 +45,23 @@ namespace MediaBrowser.Controller.Entities
}
}
[IgnoreDataMember]
public override string PresentationUniqueKey
public override string CreatePresentationUniqueKey()
{
get
if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
{
if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
{
return PrimaryVersionId;
}
return base.PresentationUniqueKey;
return PrimaryVersionId;
}
return base.CreatePresentationUniqueKey();
}
[IgnoreDataMember]
public override bool EnableForceSaveOnDateModifiedChange
public override bool EnableRefreshOnDateModifiedChange
{
get { return true; }
get
{
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso;
}
}
public int? TotalBitrate { get; set; }
@ -612,6 +612,11 @@ namespace MediaBrowser.Controller.Entities
SupportsDirectStream = i.VideoType == VideoType.VideoFile
};
if (info.Protocol == MediaProtocol.File)
{
info.ETag = i.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N");
}
if (i.IsShortcut)
{
info.Path = i.ShortcutPath;

@ -112,5 +112,48 @@ namespace MediaBrowser.Controller.Entities
return false;
}
}
public static string GetPath(string name, bool normalizeName = true)
{
// Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ?
FileSystem.GetValidFilename(name).Trim().TrimEnd('.') :
name;
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.YearPath, validName);
}
private string GetRebasedPath()
{
return GetPath(System.IO.Path.GetFileName(Path), false);
}
public override bool RequiresRefresh()
{
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Logger.Debug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath);
return true;
}
return base.RequiresRefresh();
}
/// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made
/// </summary>
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal))
{
Path = newPath;
hasChanges = true;
}
return hasChanges;
}
}
}

@ -1,5 +1,7 @@
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Querying;
using System;
using System.Threading;
using System.Threading.Tasks;
@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.FileOrganization
{
public interface IFileOrganizationService
{
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemAdded;
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemUpdated;
event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemRemoved;
event EventHandler LogReset;
/// <summary>
/// Processes the new files.
/// </summary>
@ -81,5 +88,20 @@ namespace MediaBrowser.Controller.FileOrganization
/// <param name="ItemName">Item name.</param>
/// <param name="matchString">The match string to delete.</param>
void DeleteSmartMatchEntry(string ItemName, string matchString);
/// <summary>
/// Attempts to add a an item to the list of currently processed items.
/// </summary>
/// <param name="result">The result item.</param>
/// <param name="fullClientRefresh">Passing true will notify the client to reload all items, otherwise only a single item will be refreshed.</param>
/// <returns>True if the item was added, False if the item is already contained in the list.</returns>
bool AddToInProgressList(FileOrganizationResult result, bool fullClientRefresh);
/// <summary>
/// Removes an item from the list of currently processed items.
/// </summary>
/// <param name="result">The result item.</param>
/// <returns>True if the item was removed, False if the item was not contained in the list.</returns>
bool RemoveFromInprogressList(FileOrganizationResult result);
}
}

@ -106,5 +106,7 @@ namespace MediaBrowser.Controller
/// </summary>
/// <value>The internal metadata path.</value>
string InternalMetadataPath { get; }
string ArtistsPath { get; }
}
}

@ -11,6 +11,8 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Library
@ -32,15 +34,11 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Resolves a set of files into a list of BaseItem
/// </summary>
/// <param name="files">The files.</param>
/// <param name="directoryService">The directory service.</param>
/// <param name="parent">The parent.</param>
/// <param name="collectionType">Type of the collection.</param>
/// <returns>List{``0}.</returns>
IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
IDirectoryService directoryService,
Folder parent, string
collectionType = null);
Folder parent,
LibraryOptions libraryOptions,
string collectionType = null);
/// <summary>
/// Gets the root folder.
@ -397,6 +395,9 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns>
bool IsAudioFile(string path);
bool IsAudioFile(string path, LibraryOptions libraryOptions);
bool IsVideoFile(string path, LibraryOptions libraryOptions);
/// <summary>
/// Gets the season number from path.
/// </summary>
@ -453,6 +454,8 @@ namespace MediaBrowser.Controller.Library
/// <returns>IEnumerable&lt;Folder&gt;.</returns>
IEnumerable<Folder> GetCollectionFolders(BaseItem item);
LibraryOptions GetLibraryOptions(BaseItem item);
/// <summary>
/// Gets the people.
/// </summary>
@ -474,12 +477,6 @@ namespace MediaBrowser.Controller.Library
/// <returns>List&lt;Person&gt;.</returns>
List<Person> GetPeopleItems(InternalPeopleQuery query);
/// <summary>
/// Gets all people names.
/// </summary>
/// <returns>List&lt;System.String&gt;.</returns>
List<PersonInfo> GetAllPeople();
/// <summary>
/// Updates the people.
/// </summary>
@ -557,7 +554,7 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary);
void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, LibraryOptions options, bool refreshLibrary);
void RemoveVirtualFolder(string name, bool refreshLibrary);
void AddMediaPath(string virtualFolderName, string path);
void RemoveMediaPath(string virtualFolderName, string path);
@ -568,5 +565,6 @@ namespace MediaBrowser.Controller.Library
QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
}
}

@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using CommonIO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Library
{
@ -51,6 +53,13 @@ namespace MediaBrowser.Controller.Library
}
}
public LibraryOptions LibraryOptions { get; set; }
public LibraryOptions GetLibraryOptions()
{
return LibraryOptions ?? (LibraryOptions = (Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent)));
}
/// <summary>
/// Gets or sets the file system dictionary.
/// </summary>

@ -331,12 +331,11 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
Task AddInfoToProgramDto(List<Tuple<BaseItem,BaseItemDto>> programs, List<ItemFields> fields, User user = null);
/// <summary>
/// Saves the tuner host.
/// </summary>
/// <param name="info">The information.</param>
/// <returns>Task.</returns>
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info);
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
/// <summary>
/// Saves the listing provider.
/// </summary>

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.LiveTv;

@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.LiveTv
namespace MediaBrowser.Controller.LiveTv
{
public class TimerEventInfo
{

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.LiveTv
namespace MediaBrowser.Controller.LiveTv
{
public class TunerChannelMapping
{

@ -236,6 +236,7 @@
<Compile Include="Net\IAuthorizationContext.cs" />
<Compile Include="Net\IAuthService.cs" />
<Compile Include="Net\IHasAuthorization.cs" />
<Compile Include="Net\IAsyncStreamSource.cs" />
<Compile Include="Net\IHasResultFactory.cs" />
<Compile Include="Net\IHasSession.cs" />
<Compile Include="Net\IHttpResultFactory.cs" />

@ -1,5 +1,4 @@
using System.Linq;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Controller.MediaEncoding
{

@ -1,7 +1,6 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Dlna;
@ -134,5 +133,7 @@ namespace MediaBrowser.Controller.MediaEncoding
Task Init();
Task UpdateEncoderPath(string path, string pathType);
bool SupportsEncoder(string encoder);
bool IsDefaultEncoderPath { get; }
}
}

@ -36,8 +36,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return new[] {videoPath};
}
public static List<string> GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, IEnumerable<string> filenames)
private static List<string> GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, List<string> filenames)
{
if (filenames.Count == 0)
{
return new List<string>();
}
var allFiles = fileSystem
.GetFilePaths(rootPath, true)
.ToList();

@ -0,0 +1,18 @@
using ServiceStack.Web;
using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Net
{
/// <summary>
/// Interface IAsyncStreamSource
/// Enables asynchronous writing to http resonse streams
/// </summary>
public interface IAsyncStreamSource
{
/// <summary>
/// Asynchronously write to the response stream.
/// </summary>
Task WriteToAsync(Stream responseStream);
}
}

@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Net
/// <returns>System.Object.</returns>
object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null);
object GetAsyncStreamWriter(Func<Stream,Task> streamWriter, IDictionary<string, string> responseHeaders = null);
object GetAsyncStreamWriter(IAsyncStreamSource streamSource);
/// <summary>
/// Gets the optimized result.

@ -169,6 +169,13 @@ namespace MediaBrowser.Controller.Persistence
QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
List<string> GetGameGenreNames();
List<string> GetMusicGenreNames();
List<string> GetStudioNames();
List<string> GetGenreNames();
List<string> GetAllArtistNames();
}
}

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.Playlists
{
@ -58,11 +59,22 @@ namespace MediaBrowser.Controller.Playlists
return true;
}
protected override IEnumerable<BaseItem> LoadChildren()
{
// Save a trip to the database
return new List<BaseItem>();
}
public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{
return GetPlayableItems(user).Result;
}
protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
return new List<BaseItem>();
}
public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
{
var items = GetPlayableItems(user).Result;

@ -724,6 +724,15 @@ namespace MediaBrowser.Controller.Providers
}
break;
}
case "TvMazeId":
{
var id = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(id))
{
item.SetProviderId(MetadataProviders.TvMaze, id);
}
break;
}
case "AudioDbArtistId":
{
var id = reader.ReadElementContentAsString();

@ -16,13 +16,16 @@ namespace MediaBrowser.Controller.Providers
private readonly ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>> _cache =
new ConcurrentDictionary<string, Dictionary<string, FileSystemMetadata>>(StringComparer.OrdinalIgnoreCase);
public DirectoryService(ILogger logger, IFileSystem fileSystem)
private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache =
new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
public DirectoryService(ILogger logger, IFileSystem fileSystem)
{
_logger = logger;
_fileSystem = fileSystem;
}
public DirectoryService(IFileSystem fileSystem)
public DirectoryService(IFileSystem fileSystem)
: this(new NullLogger(), fileSystem)
{
}
@ -100,20 +103,19 @@ namespace MediaBrowser.Controller.Providers
public FileSystemMetadata GetFile(string path)
{
var directory = Path.GetDirectoryName(path);
if (string.IsNullOrWhiteSpace(directory))
FileSystemMetadata file;
if (!_fileCache.TryGetValue(path, out file))
{
_logger.Debug("Parent path is null for {0}", path);
return null;
}
var dict = GetFileSystemDictionary(directory, false);
file = _fileSystem.GetFileInfo(path);
FileSystemMetadata entry;
dict.TryGetValue(path, out entry);
if (file != null)
{
_fileCache.TryAdd(path, file);
}
}
return entry;
return file;
//return _fileSystem.GetFileInfo(path);
}
public IEnumerable<FileSystemMetadata> GetDirectories(string path)

@ -1,5 +1,6 @@
using System.Linq;
using CommonIO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers
@ -19,7 +20,7 @@ namespace MediaBrowser.Controller.Providers
public bool ForceSave { get; set; }
public MetadataRefreshOptions(IFileSystem fileSystem)
: this(new DirectoryService(fileSystem))
: this(new DirectoryService(new NullLogger(), fileSystem))
{
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save