diff options
Diffstat (limited to 'MediaBrowser.Api')
80 files changed, 0 insertions, 22198 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs deleted file mode 100644 index 6691080bc..000000000 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ /dev/null @@ -1,678 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Api.Playback; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Session; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class ServerEntryPoint. - /// </summary> - public class ApiEntryPoint : IServerEntryPoint - { - /// <summary> - /// The instance. - /// </summary> - public static ApiEntryPoint Instance; - - /// <summary> - /// The logger. - /// </summary> - private ILogger _logger; - - /// <summary> - /// The configuration manager. - /// </summary> - private IServerConfigurationManager _serverConfigurationManager; - - private readonly ISessionManager _sessionManager; - private readonly IFileSystem _fileSystem; - private readonly IMediaSourceManager _mediaSourceManager; - - /// <summary> - /// The active transcoding jobs - /// </summary> - private readonly List<TranscodingJob> _activeTranscodingJobs = new List<TranscodingJob>(); - - private readonly Dictionary<string, SemaphoreSlim> _transcodingLocks = - new Dictionary<string, SemaphoreSlim>(); - - private bool _disposed = false; - - /// <summary> - /// Initializes a new instance of the <see cref="ApiEntryPoint" /> class. - /// </summary> - /// <param name="logger">The logger.</param> - /// <param name="sessionManager">The session manager.</param> - /// <param name="config">The configuration.</param> - /// <param name="fileSystem">The file system.</param> - /// <param name="mediaSourceManager">The media source manager.</param> - public ApiEntryPoint( - ILogger<ApiEntryPoint> logger, - ISessionManager sessionManager, - IServerConfigurationManager config, - IFileSystem fileSystem, - IMediaSourceManager mediaSourceManager) - { - _logger = logger; - _sessionManager = sessionManager; - _serverConfigurationManager = config; - _fileSystem = fileSystem; - _mediaSourceManager = mediaSourceManager; - - _sessionManager.PlaybackProgress += OnPlaybackProgress; - _sessionManager.PlaybackStart += OnPlaybackStart; - - Instance = this; - } - - public static string[] Split(string value, char separator, bool removeEmpty) - { - if (string.IsNullOrWhiteSpace(value)) - { - return Array.Empty<string>(); - } - - return removeEmpty - ? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries) - : value.Split(separator); - } - - public SemaphoreSlim GetTranscodingLock(string outputPath) - { - lock (_transcodingLocks) - { - if (!_transcodingLocks.TryGetValue(outputPath, out SemaphoreSlim result)) - { - result = new SemaphoreSlim(1, 1); - _transcodingLocks[outputPath] = result; - } - - return result; - } - } - - private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e) - { - if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) - { - PingTranscodingJob(e.PlaySessionId, e.IsPaused); - } - } - - private void OnPlaybackProgress(object sender, PlaybackProgressEventArgs e) - { - if (!string.IsNullOrWhiteSpace(e.PlaySessionId)) - { - PingTranscodingJob(e.PlaySessionId, e.IsPaused); - } - } - - /// <summary> - /// Runs this instance. - /// </summary> - public Task RunAsync() - { - try - { - DeleteEncodedMediaCache(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting encoded media cache"); - } - - return Task.CompletedTask; - } - - /// <summary> - /// Deletes the encoded media cache. - /// </summary> - private void DeleteEncodedMediaCache() - { - var path = _serverConfigurationManager.GetTranscodePath(); - if (!Directory.Exists(path)) - { - return; - } - - foreach (var file in _fileSystem.GetFilePaths(path, true)) - { - _fileSystem.DeleteFile(file); - } - } - - /// <inheritdoc /> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases unmanaged and - optionally - managed resources. - /// </summary> - /// <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) - { - if (_disposed) - { - return; - } - - if (dispose) - { - // TODO: dispose - } - - var jobs = _activeTranscodingJobs.ToList(); - var jobCount = jobs.Count; - - IEnumerable<Task> GetKillJobs() - { - foreach (var job in jobs) - { - yield return KillTranscodingJob(job, false, path => true); - } - } - - // Wait for all processes to be killed - if (jobCount > 0) - { - Task.WaitAll(GetKillJobs().ToArray()); - } - - _activeTranscodingJobs.Clear(); - _transcodingLocks.Clear(); - - _sessionManager.PlaybackProgress -= OnPlaybackProgress; - _sessionManager.PlaybackStart -= OnPlaybackStart; - - _disposed = true; - } - - - /// <summary> - /// Called when [transcode beginning]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="playSessionId">The play session identifier.</param> - /// <param name="liveStreamId">The live stream identifier.</param> - /// <param name="transcodingJobId">The transcoding job identifier.</param> - /// <param name="type">The type.</param> - /// <param name="process">The process.</param> - /// <param name="deviceId">The device id.</param> - /// <param name="state">The state.</param> - /// <param name="cancellationTokenSource">The cancellation token source.</param> - /// <returns>TranscodingJob.</returns> - public TranscodingJob OnTranscodeBeginning( - string path, - string playSessionId, - string liveStreamId, - string transcodingJobId, - TranscodingJobType type, - Process process, - string deviceId, - StreamState state, - CancellationTokenSource cancellationTokenSource) - { - lock (_activeTranscodingJobs) - { - var job = new TranscodingJob(_logger) - { - Type = type, - Path = path, - Process = process, - ActiveRequestCount = 1, - DeviceId = deviceId, - CancellationTokenSource = cancellationTokenSource, - Id = transcodingJobId, - PlaySessionId = playSessionId, - LiveStreamId = liveStreamId, - MediaSource = state.MediaSource - }; - - _activeTranscodingJobs.Add(job); - - 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, int? bitRate) - { - var ticks = transcodingPosition?.Ticks; - - if (job != null) - { - job.Framerate = framerate; - job.CompletionPercentage = percentComplete; - job.TranscodingPositionTicks = ticks; - job.BytesTranscoded = bytesTranscoded; - job.BitRate = bitRate; - } - - var deviceId = state.Request.DeviceId; - - if (!string.IsNullOrWhiteSpace(deviceId)) - { - var audioCodec = state.ActualOutputAudioCodec; - var videoCodec = state.ActualOutputVideoCodec; - - _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo - { - Bitrate = bitRate ?? state.TotalOutputBitrate, - AudioCodec = audioCodec, - VideoCodec = videoCodec, - Container = state.OutputContainer, - Framerate = framerate, - CompletionPercentage = percentComplete, - Width = state.OutputWidth, - Height = state.OutputHeight, - AudioChannels = state.OutputAudioChannels, - IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase), - IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase), - TranscodeReasons = state.TranscodeReasons - }); - } - } - - /// <summary> - /// <summary> - /// The progressive - /// </summary> - /// Called when [transcode failed to start]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="type">The type.</param> - /// <param name="state">The state.</param> - public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state) - { - lock (_activeTranscodingJobs) - { - var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - - if (job != null) - { - _activeTranscodingJobs.Remove(job); - } - } - - lock (_transcodingLocks) - { - _transcodingLocks.Remove(path); - } - - if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) - { - _sessionManager.ClearTranscodingInfo(state.Request.DeviceId); - } - } - - /// <summary> - /// Determines whether [has active transcoding job] [the specified path]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="type">The type.</param> - /// <returns><c>true</c> if [has active transcoding job] [the specified path]; otherwise, <c>false</c>.</returns> - public bool HasActiveTranscodingJob(string path, TranscodingJobType type) - { - return GetTranscodingJob(path, type) != null; - } - - public TranscodingJob GetTranscodingJob(string path, TranscodingJobType type) - { - lock (_activeTranscodingJobs) - { - return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - } - } - - public TranscodingJob GetTranscodingJob(string playSessionId) - { - lock (_activeTranscodingJobs) - { - return _activeTranscodingJobs.FirstOrDefault(j => string.Equals(j.PlaySessionId, playSessionId, StringComparison.OrdinalIgnoreCase)); - } - } - - /// <summary> - /// Called when [transcode begin request]. - /// </summary> - /// <param name="path">The path.</param> - /// <param name="type">The type.</param> - public TranscodingJob OnTranscodeBeginRequest(string path, TranscodingJobType type) - { - lock (_activeTranscodingJobs) - { - var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase)); - - if (job == null) - { - return null; - } - - OnTranscodeBeginRequest(job); - - return job; - } - } - - public void OnTranscodeBeginRequest(TranscodingJob job) - { - job.ActiveRequestCount++; - - if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive) - { - job.StopKillTimer(); - } - } - - public void OnTranscodeEndRequest(TranscodingJob job) - { - job.ActiveRequestCount--; - _logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount); - if (job.ActiveRequestCount <= 0) - { - PingTimer(job, false); - } - } - - internal void PingTranscodingJob(string playSessionId, bool? isUserPaused) - { - if (string.IsNullOrEmpty(playSessionId)) - { - throw new ArgumentNullException(nameof(playSessionId)); - } - - _logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused); - - List<TranscodingJob> jobs; - - lock (_activeTranscodingJobs) - { - // This is really only needed for HLS. - // Progressive streams can stop on their own reliably - jobs = _activeTranscodingJobs.Where(j => string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)).ToList(); - } - - foreach (var job in jobs) - { - if (isUserPaused.HasValue) - { - _logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id); - job.IsUserPaused = isUserPaused.Value; - } - - PingTimer(job, true); - } - } - - private void PingTimer(TranscodingJob job, bool isProgressCheckIn) - { - if (job.HasExited) - { - job.StopKillTimer(); - return; - } - - var timerDuration = 10000; - - if (job.Type != TranscodingJobType.Progressive) - { - timerDuration = 60000; - } - - job.PingTimeout = timerDuration; - job.LastPingDate = DateTime.UtcNow; - - // Don't start the timer for playback checkins with progressive streaming - if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn) - { - job.StartKillTimer(OnTranscodeKillTimerStopped); - } - else - { - job.ChangeKillTimerIfStarted(); - } - } - - /// <summary> - /// Called when [transcode kill timer stopped]. - /// </summary> - /// <param name="state">The state.</param> - private async void OnTranscodeKillTimerStopped(object state) - { - var job = (TranscodingJob)state; - - if (!job.HasExited && job.Type != TranscodingJobType.Progressive) - { - var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds; - - if (timeSinceLastPing < job.PingTimeout) - { - job.StartKillTimer(OnTranscodeKillTimerStopped, job.PingTimeout); - return; - } - } - - _logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - - await KillTranscodingJob(job, true, path => true); - } - - /// <summary> - /// Kills the single transcoding job. - /// </summary> - /// <param name="deviceId">The device id.</param> - /// <param name="playSessionId">The play session identifier.</param> - /// <param name="deleteFiles">The delete files.</param> - /// <returns>Task.</returns> - internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles) - { - return KillTranscodingJobs(j => string.IsNullOrWhiteSpace(playSessionId) - ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase) - : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles); - } - - /// <summary> - /// Kills the transcoding jobs. - /// </summary> - /// <param name="killJob">The kill job.</param> - /// <param name="deleteFiles">The delete files.</param> - /// <returns>Task.</returns> - private Task KillTranscodingJobs(Func<TranscodingJob, bool> killJob, Func<string, bool> deleteFiles) - { - var jobs = new List<TranscodingJob>(); - - lock (_activeTranscodingJobs) - { - // This is really only needed for HLS. - // Progressive streams can stop on their own reliably - jobs.AddRange(_activeTranscodingJobs.Where(killJob)); - } - - if (jobs.Count == 0) - { - return Task.CompletedTask; - } - - IEnumerable<Task> GetKillJobs() - { - foreach (var job in jobs) - { - yield return KillTranscodingJob(job, false, deleteFiles); - } - } - - return Task.WhenAll(GetKillJobs()); - } - - /// <summary> - /// Kills the transcoding job. - /// </summary> - /// <param name="job">The job.</param> - /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param> - /// <param name="delete">The delete.</param> - private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete) - { - job.DisposeKillTimer(); - - _logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - - lock (_activeTranscodingJobs) - { - _activeTranscodingJobs.Remove(job); - - if (!job.CancellationTokenSource.IsCancellationRequested) - { - job.CancellationTokenSource.Cancel(); - } - } - - lock (_transcodingLocks) - { - _transcodingLocks.Remove(job.Path); - } - - lock (job.ProcessLock) - { - job.TranscodingThrottler?.Stop().GetAwaiter().GetResult(); - - var process = job.Process; - - var hasExited = job.HasExited; - - if (!hasExited) - { - try - { - _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path); - - process.StandardInput.WriteLine("q"); - - // Need to wait because killing is asynchronous - if (!process.WaitForExit(5000)) - { - _logger.LogInformation("Killing ffmpeg process for {Path}", job.Path); - process.Kill(); - } - } - catch (InvalidOperationException) - { - } - } - } - - if (delete(job.Path)) - { - await DeletePartialStreamFiles(job.Path, job.Type, 0, 1500).ConfigureAwait(false); - } - - if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) - { - try - { - await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing live stream for {Path}", job.Path); - } - } - } - - private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) - { - if (retryCount >= 10) - { - return; - } - - _logger.LogInformation("Deleting partial stream file(s) {Path}", path); - - await Task.Delay(delayMs).ConfigureAwait(false); - - try - { - if (jobType == TranscodingJobType.Progressive) - { - DeleteProgressivePartialStreamFiles(path); - } - else - { - DeleteHlsPartialStreamFiles(path); - } - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); - - await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); - } - } - - /// <summary> - /// Deletes the progressive partial stream files. - /// </summary> - /// <param name="outputFilePath">The output file path.</param> - private void DeleteProgressivePartialStreamFiles(string outputFilePath) - { - if (File.Exists(outputFilePath)) - { - _fileSystem.DeleteFile(outputFilePath); - } - } - - /// <summary> - /// Deletes the HLS partial stream files. - /// </summary> - /// <param name="outputFilePath">The output file path.</param> - private void DeleteHlsPartialStreamFiles(string outputFilePath) - { - var directory = Path.GetDirectoryName(outputFilePath); - var name = Path.GetFileNameWithoutExtension(outputFilePath); - - var filesToDelete = _fileSystem.GetFilePaths(directory) - .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1); - - List<Exception> exs = null; - foreach (var file in filesToDelete) - { - try - { - _logger.LogDebug("Deleting HLS file {0}", file); - _fileSystem.DeleteFile(file); - } - catch (IOException ex) - { - (exs ??= new List<Exception>(4)).Add(ex); - _logger.LogError(ex, "Error deleting HLS file {Path}", file); - } - } - - if (exs != null) - { - throw new AggregateException("Error deleting HLS files", exs); - } - } - } -} diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs deleted file mode 100644 index 1632ca1b0..000000000 --- a/MediaBrowser.Api/Attachments/AttachmentService.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Attachments -{ - [Route("/Videos/{Id}/{MediaSourceId}/Attachments/{Index}", "GET", Summary = "Gets specified attachment.")] - public class GetAttachment - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "Index", Description = "The attachment stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - } - - public class AttachmentService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly IAttachmentExtractor _attachmentExtractor; - - public AttachmentService( - ILogger<AttachmentService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILibraryManager libraryManager, - IAttachmentExtractor attachmentExtractor) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _libraryManager = libraryManager; - _attachmentExtractor = attachmentExtractor; - } - - public async Task<object> Get(GetAttachment request) - { - var (attachment, attachmentStream) = await GetAttachment(request).ConfigureAwait(false); - var mime = string.IsNullOrWhiteSpace(attachment.MimeType) ? "application/octet-stream" : attachment.MimeType; - - return ResultFactory.GetResult(Request, attachmentStream, mime); - } - - private Task<(MediaAttachment, Stream)> GetAttachment(GetAttachment request) - { - var item = _libraryManager.GetItemById(request.Id); - - return _attachmentExtractor.GetAttachment(item, - request.MediaSourceId, - request.Index, - CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs deleted file mode 100644 index 2cd68ac1b..000000000 --- a/MediaBrowser.Api/BaseApiService.cs +++ /dev/null @@ -1,418 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class BaseApiService - /// </summary> - public abstract class BaseApiService : IService, IRequiresRequest - { - public BaseApiService( - ILogger<BaseApiService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory) - { - Logger = logger; - ServerConfigurationManager = serverConfigurationManager; - ResultFactory = httpResultFactory; - } - - /// <summary> - /// Gets the logger. - /// </summary> - /// <value>The logger.</value> - protected ILogger<BaseApiService> Logger { get; } - - /// <summary> - /// Gets or sets the server configuration manager. - /// </summary> - /// <value>The server configuration manager.</value> - protected IServerConfigurationManager ServerConfigurationManager { get; } - - /// <summary> - /// Gets the HTTP result factory. - /// </summary> - /// <value>The HTTP result factory.</value> - protected IHttpResultFactory ResultFactory { get; } - - /// <summary> - /// Gets or sets the request context. - /// </summary> - /// <value>The request context.</value> - public IRequest Request { get; set; } - - public string GetHeader(string name) => Request.Headers[name]; - - public static string[] SplitValue(string value, char delim) - { - return value == null - ? Array.Empty<string>() - : value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries); - } - - public static Guid[] GetGuids(string value) - { - if (value == null) - { - return Array.Empty<Guid>(); - } - - return value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(i => new Guid(i)) - .ToArray(); - } - - /// <summary> - /// To the optimized result. - /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="result">The result.</param> - /// <returns>System.Object.</returns> - protected object ToOptimizedResult<T>(T result) - where T : class - { - return ResultFactory.GetResult(Request, result); - } - - protected void AssertCanUpdateUser(IAuthorizationContext authContext, IUserManager userManager, Guid userId, bool restrictUserPreferences) - { - var auth = authContext.GetAuthorizationInfo(Request); - - var authenticatedUser = auth.User; - - // If they're going to update the record of another user, they must be an administrator - if ((!userId.Equals(auth.UserId) && !authenticatedUser.Policy.IsAdministrator) - || (restrictUserPreferences && !authenticatedUser.Policy.EnableUserPreferenceAccess)) - { - throw new SecurityException("Unauthorized access."); - } - } - - /// <summary> - /// Gets the session. - /// </summary> - /// <returns>SessionInfo.</returns> - protected SessionInfo GetSession(ISessionContext sessionContext) - { - var session = sessionContext.GetSession(Request); - - if (session == null) - { - throw new ArgumentException("Session not found."); - } - - return session; - } - - protected DtoOptions GetDtoOptions(IAuthorizationContext authContext, object request) - { - var options = new DtoOptions(); - - if (request is IHasItemFields hasFields) - { - options.Fields = hasFields.GetItemFields(); - } - - if (!options.ContainsField(ItemFields.RecursiveItemCount) - || !options.ContainsField(ItemFields.ChildCount)) - { - var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty; - if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1) - { - int oldLen = options.Fields.Length; - var arr = new ItemFields[oldLen + 1]; - options.Fields.CopyTo(arr, 0); - arr[oldLen] = ItemFields.RecursiveItemCount; - options.Fields = arr; - } - - if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("wmc", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("media center", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("classic", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("samsung", StringComparison.OrdinalIgnoreCase) != -1 || - client.IndexOf("androidtv", StringComparison.OrdinalIgnoreCase) != -1) - { - - int oldLen = options.Fields.Length; - var arr = new ItemFields[oldLen + 1]; - options.Fields.CopyTo(arr, 0); - arr[oldLen] = ItemFields.ChildCount; - options.Fields = arr; - } - } - - if (request is IHasDtoOptions hasDtoOptions) - { - options.EnableImages = hasDtoOptions.EnableImages ?? true; - - if (hasDtoOptions.ImageTypeLimit.HasValue) - { - options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value; - } - - if (hasDtoOptions.EnableUserData.HasValue) - { - options.EnableUserData = hasDtoOptions.EnableUserData.Value; - } - - if (!string.IsNullOrWhiteSpace(hasDtoOptions.EnableImageTypes)) - { - options.ImageTypes = hasDtoOptions.EnableImageTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)) - .ToArray(); - } - } - - return options; - } - - protected MusicArtist GetArtist(string name, ILibraryManager libraryManager, DtoOptions dtoOptions) - { - if (name.IndexOf(BaseItem.SlugChar) != -1) - { - var result = GetItemFromSlugName<MusicArtist>(libraryManager, name, dtoOptions); - - if (result != null) - { - return result; - } - } - - return libraryManager.GetArtist(name, dtoOptions); - } - - protected Studio GetStudio(string name, ILibraryManager libraryManager, DtoOptions dtoOptions) - { - if (name.IndexOf(BaseItem.SlugChar) != -1) - { - var result = GetItemFromSlugName<Studio>(libraryManager, name, dtoOptions); - - if (result != null) - { - return result; - } - } - - return libraryManager.GetStudio(name); - } - - protected Genre GetGenre(string name, ILibraryManager libraryManager, DtoOptions dtoOptions) - { - if (name.IndexOf(BaseItem.SlugChar) != -1) - { - var result = GetItemFromSlugName<Genre>(libraryManager, name, dtoOptions); - - if (result != null) - { - return result; - } - } - - return libraryManager.GetGenre(name); - } - - protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager, DtoOptions dtoOptions) - { - if (name.IndexOf(BaseItem.SlugChar) != -1) - { - var result = GetItemFromSlugName<MusicGenre>(libraryManager, name, dtoOptions); - - if (result != null) - { - return result; - } - } - - return libraryManager.GetMusicGenre(name); - } - - protected Person GetPerson(string name, ILibraryManager libraryManager, DtoOptions dtoOptions) - { - if (name.IndexOf(BaseItem.SlugChar) != -1) - { - var result = GetItemFromSlugName<Person>(libraryManager, name, dtoOptions); - - if (result != null) - { - return result; - } - } - - return libraryManager.GetPerson(name); - } - - private T GetItemFromSlugName<T>(ILibraryManager libraryManager, string name, DtoOptions dtoOptions) - where T : BaseItem, new() - { - var result = libraryManager.GetItemList(new InternalItemsQuery - { - Name = name.Replace(BaseItem.SlugChar, '&'), - IncludeItemTypes = new[] { typeof(T).Name }, - DtoOptions = dtoOptions - - }).OfType<T>().FirstOrDefault(); - - result ??= libraryManager.GetItemList(new InternalItemsQuery - { - Name = name.Replace(BaseItem.SlugChar, '/'), - IncludeItemTypes = new[] { typeof(T).Name }, - DtoOptions = dtoOptions - - }).OfType<T>().FirstOrDefault(); - - result ??= libraryManager.GetItemList(new InternalItemsQuery - { - Name = name.Replace(BaseItem.SlugChar, '?'), - IncludeItemTypes = new[] { typeof(T).Name }, - DtoOptions = dtoOptions - - }).OfType<T>().FirstOrDefault(); - - return result; - } - - /// <summary> - /// Gets the path segment at the specified index. - /// </summary> - /// <param name="index">The index of the path segment.</param> - /// <returns>The path segment at the specified index.</returns> - /// <exception cref="IndexOutOfRangeException" >Path doesn't contain enough segments.</exception> - /// <exception cref="InvalidDataException" >Path doesn't start with the base url.</exception> - protected internal ReadOnlySpan<char> GetPathValue(int index) - { - static void ThrowIndexOutOfRangeException() - => throw new IndexOutOfRangeException("Path doesn't contain enough segments."); - - static void ThrowInvalidDataException() - => throw new InvalidDataException("Path doesn't start with the base url."); - - ReadOnlySpan<char> path = Request.PathInfo; - - // Remove the protocol part from the url - int pos = path.LastIndexOf("://"); - if (pos != -1) - { - path = path.Slice(pos + 3); - } - - // Remove the query string - pos = path.LastIndexOf('?'); - if (pos != -1) - { - path = path.Slice(0, pos); - } - - // Remove the domain - pos = path.IndexOf('/'); - if (pos != -1) - { - path = path.Slice(pos); - } - - // Remove base url - string baseUrl = ServerConfigurationManager.Configuration.BaseUrl; - int baseUrlLen = baseUrl.Length; - if (baseUrlLen != 0) - { - if (path.StartsWith(baseUrl, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(baseUrlLen); - } - else - { - // The path doesn't start with the base url, - // how did we get here? - ThrowInvalidDataException(); - } - } - - // Remove leading / - path = path.Slice(1); - - // Backwards compatibility - const string Emby = "emby/"; - if (path.StartsWith(Emby, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(Emby.Length); - } - - const string MediaBrowser = "mediabrowser/"; - if (path.StartsWith(MediaBrowser, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(MediaBrowser.Length); - } - - // Skip segments until we are at the right index - for (int i = 0; i < index; i++) - { - pos = path.IndexOf('/'); - if (pos == -1) - { - ThrowIndexOutOfRangeException(); - } - - path = path.Slice(pos + 1); - } - - // Remove the rest - pos = path.IndexOf('/'); - if (pos != -1) - { - path = path.Slice(0, pos); - } - - return path; - } - - /// <summary> - /// Gets the name of the item by. - /// </summary> - protected BaseItem GetItemByName(string name, string type, ILibraryManager libraryManager, DtoOptions dtoOptions) - { - if (type.Equals("Person", StringComparison.OrdinalIgnoreCase)) - { - return GetPerson(name, libraryManager, dtoOptions); - } - else if (type.Equals("Artist", StringComparison.OrdinalIgnoreCase)) - { - return GetArtist(name, libraryManager, dtoOptions); - } - else if (type.Equals("Genre", StringComparison.OrdinalIgnoreCase)) - { - return GetGenre(name, libraryManager, dtoOptions); - } - else if (type.Equals("MusicGenre", StringComparison.OrdinalIgnoreCase)) - { - return GetMusicGenre(name, libraryManager, dtoOptions); - } - else if (type.Equals("Studio", StringComparison.OrdinalIgnoreCase)) - { - return GetStudio(name, libraryManager, dtoOptions); - } - else if (type.Equals("Year", StringComparison.OrdinalIgnoreCase)) - { - return libraryManager.GetYear(int.Parse(name)); - } - - throw new ArgumentException("Invalid type", nameof(type)); - } - } -} diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs deleted file mode 100644 index f4724e774..000000000 --- a/MediaBrowser.Api/BrandingService.cs +++ /dev/null @@ -1,44 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Branding; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Branding/Configuration", "GET", Summary = "Gets branding configuration")] - public class GetBrandingOptions : IReturn<BrandingOptions> - { - } - - [Route("/Branding/Css", "GET", Summary = "Gets custom css")] - [Route("/Branding/Css.css", "GET", Summary = "Gets custom css")] - public class GetBrandingCss - { - } - - public class BrandingService : BaseApiService - { - public BrandingService( - ILogger<BrandingService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory) - : base(logger, serverConfigurationManager, httpResultFactory) - { - } - - public object Get(GetBrandingOptions request) - { - return ServerConfigurationManager.GetConfiguration<BrandingOptions>("branding"); - } - - public object Get(GetBrandingCss request) - { - var result = ServerConfigurationManager.GetConfiguration<BrandingOptions>("branding"); - - // When null this throws a 405 error under Mono OSX, so default to empty string - return ResultFactory.GetResult(Request, result.CustomCss ?? string.Empty, "text/css"); - } - } -} diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs deleted file mode 100644 index fd9b8c396..000000000 --- a/MediaBrowser.Api/ChannelService.cs +++ /dev/null @@ -1,341 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Api.UserLibrary; -using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Channels; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Channels", "GET", Summary = "Gets available channels")] - public class GetChannels : IReturn<QueryResult<BaseItemDto>> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "SupportsLatestItems", Description = "Optional. Filter by channels that support getting latest items.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? SupportsLatestItems { get; set; } - - public bool? SupportsMediaDeletion { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is favorite. - /// </summary> - /// <value><c>null</c> if [is favorite] contains no value, <c>true</c> if [is favorite]; otherwise, <c>false</c>.</value> - public bool? IsFavorite { get; set; } - } - - [Route("/Channels/{Id}/Features", "GET", Summary = "Gets features for a channel")] - public class GetChannelFeatures : IReturn<ChannelFeatures> - { - [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Channels/Features", "GET", Summary = "Gets features for a channel")] - public class GetAllChannelFeatures : IReturn<ChannelFeatures[]> - { - } - - [Route("/Channels/{Id}/Items", "GET", Summary = "Gets channel items")] - public class GetChannelItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields - { - [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "FolderId", Description = "Folder Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string FolderId { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SortOrder { get; set; } - - [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Filters { get; set; } - - [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string SortBy { get; set; } - - [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, 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; } - - /// <summary> - /// Gets the filters. - /// </summary> - /// <returns>IEnumerable{ItemFilter}.</returns> - public IEnumerable<ItemFilter> GetFilters() - { - var val = Filters; - - return string.IsNullOrEmpty(val) - ? Array.Empty<ItemFilter>() - : val.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true)); - } - - /// <summary> - /// Gets the order by. - /// </summary> - /// <returns>IEnumerable{ItemSortBy}.</returns> - public ValueTuple<string, SortOrder>[] GetOrderBy() - { - return BaseItemsRequest.GetOrderBy(SortBy, SortOrder); - } - } - - [Route("/Channels/Items/Latest", "GET", Summary = "Gets channel items")] - public class GetLatestChannelItems : IReturn<QueryResult<BaseItemDto>>, IHasItemFields - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Filters { get; set; } - - [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, 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 = "ChannelIds", Description = "Optional. Specify one or more channel id's, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ChannelIds { get; set; } - - /// <summary> - /// Gets the filters. - /// </summary> - /// <returns>IEnumerable{ItemFilter}.</returns> - public IEnumerable<ItemFilter> GetFilters() - { - return string.IsNullOrEmpty(Filters) - ? Array.Empty<ItemFilter>() - : Filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true)); - } - } - - [Authenticated] - public class ChannelService : BaseApiService - { - private readonly IChannelManager _channelManager; - private IUserManager _userManager; - - public ChannelService( - ILogger<ChannelService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IChannelManager channelManager, - IUserManager userManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _channelManager = channelManager; - _userManager = userManager; - } - - public object Get(GetAllChannelFeatures request) - { - var result = _channelManager.GetAllChannelFeatures(); - - return ToOptimizedResult(result); - } - - public object Get(GetChannelFeatures request) - { - var result = _channelManager.GetChannelFeatures(request.Id); - - return ToOptimizedResult(result); - } - - public object Get(GetChannels request) - { - var result = _channelManager.GetChannels(new ChannelQuery - { - Limit = request.Limit, - StartIndex = request.StartIndex, - UserId = request.UserId, - SupportsLatestItems = request.SupportsLatestItems, - SupportsMediaDeletion = request.SupportsMediaDeletion, - IsFavorite = request.IsFavorite - }); - - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetChannelItems request) - { - var user = request.UserId.Equals(Guid.Empty) - ? null - : _userManager.GetUserById(request.UserId); - - var query = new InternalItemsQuery(user) - { - Limit = request.Limit, - StartIndex = request.StartIndex, - ChannelIds = new[] { new Guid(request.Id) }, - ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId), - OrderBy = request.GetOrderBy(), - DtoOptions = new Controller.Dto.DtoOptions - { - Fields = request.GetItemFields() - } - - }; - - foreach (var filter in request.GetFilters()) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - var result = await _channelManager.GetChannelItems(query, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetLatestChannelItems request) - { - var user = request.UserId.Equals(Guid.Empty) - ? null - : _userManager.GetUserById(request.UserId); - - var query = new InternalItemsQuery(user) - { - Limit = request.Limit, - StartIndex = request.StartIndex, - ChannelIds = (request.ChannelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToArray(), - DtoOptions = new Controller.Dto.DtoOptions - { - Fields = request.GetItemFields() - } - }; - - foreach (var filter in request.GetFilters()) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - var result = await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - } -} diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs deleted file mode 100644 index 316be04a0..000000000 --- a/MediaBrowser.Api/ConfigurationService.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetConfiguration - /// </summary> - [Route("/System/Configuration", "GET", Summary = "Gets application configuration")] - [Authenticated] - public class GetConfiguration : IReturn<ServerConfiguration> - { - - } - - [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")] - [Authenticated(AllowBeforeStartupWizard = true)] - public class GetNamedConfiguration - { - [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Key { get; set; } - } - - /// <summary> - /// Class UpdateConfiguration - /// </summary> - [Route("/System/Configuration", "POST", Summary = "Updates application configuration")] - [Authenticated(Roles = "Admin")] - public class UpdateConfiguration : ServerConfiguration, IReturnVoid - { - } - - [Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")] - [Authenticated(Roles = "Admin")] - public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream - { - [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Key { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")] - [Authenticated(Roles = "Admin")] - public class GetDefaultMetadataOptions : IReturn<MetadataOptions> - { - - } - - [Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")] - [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] - public class UpdateMediaEncoderPath : IReturnVoid - { - [ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Path { get; set; } - [ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string PathType { get; set; } - } - - public class ConfigurationService : BaseApiService - { - /// <summary> - /// The _json serializer - /// </summary> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// The _configuration manager - /// </summary> - private readonly IServerConfigurationManager _configurationManager; - - private readonly IMediaEncoder _mediaEncoder; - - public ConfigurationService( - ILogger<ConfigurationService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IJsonSerializer jsonSerializer, - IServerConfigurationManager configurationManager, - IMediaEncoder mediaEncoder) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _jsonSerializer = jsonSerializer; - _configurationManager = configurationManager; - _mediaEncoder = mediaEncoder; - } - - public void Post(UpdateMediaEncoderPath request) - { - _mediaEncoder.UpdateEncoderPath(request.Path, request.PathType); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetConfiguration request) - { - return ToOptimizedResult(_configurationManager.Configuration); - } - - public object Get(GetNamedConfiguration request) - { - var result = _configurationManager.GetConfiguration(request.Key); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Posts the specified configuraiton. - /// </summary> - /// <param name="request">The request.</param> - public void Post(UpdateConfiguration request) - { - // Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration - var json = _jsonSerializer.SerializeToString(request); - - var config = _jsonSerializer.DeserializeFromString<ServerConfiguration>(json); - - _configurationManager.ReplaceConfiguration(config); - } - - public async Task Post(UpdateNamedConfiguration request) - { - var key = GetPathValue(2).ToString(); - - var configurationType = _configurationManager.GetConfigurationType(key); - var configuration = await _jsonSerializer.DeserializeFromStreamAsync(request.RequestStream, configurationType).ConfigureAwait(false); - - _configurationManager.SaveConfiguration(key, configuration); - } - - public object Get(GetDefaultMetadataOptions request) - { - return ToOptimizedResult(new MetadataOptions()); - } - } -} diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs deleted file mode 100644 index 53eb9667d..000000000 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.IO; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Devices -{ - [Route("/Devices", "GET", Summary = "Gets all devices")] - [Authenticated(Roles = "Admin")] - public class GetDevices : DeviceQuery, IReturn<QueryResult<DeviceInfo>> - { - } - - [Route("/Devices/Info", "GET", Summary = "Gets info for a device")] - [Authenticated(Roles = "Admin")] - public class GetDeviceInfo : IReturn<DeviceInfo> - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Devices/Options", "GET", Summary = "Gets options for a device")] - [Authenticated(Roles = "Admin")] - public class GetDeviceOptions : IReturn<DeviceOptions> - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Devices", "DELETE", Summary = "Deletes a device")] - public class DeleteDevice - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Devices/CameraUploads", "GET", Summary = "Gets camera upload history for a device")] - [Authenticated] - public class GetCameraUploads : IReturn<ContentUploadHistory> - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - } - - [Route("/Devices/CameraUploads", "POST", Summary = "Uploads content")] - [Authenticated] - public class PostCameraUpload : IRequiresRequestStream, IReturnVoid - { - [ApiMember(Name = "DeviceId", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string DeviceId { get; set; } - - [ApiMember(Name = "Album", Description = "Album", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Album { get; set; } - - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } - - [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Id { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/Devices/Options", "POST", Summary = "Updates device options")] - [Authenticated(Roles = "Admin")] - public class PostDeviceOptions : DeviceOptions, IReturnVoid - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - public class DeviceService : BaseApiService - { - private readonly IDeviceManager _deviceManager; - private readonly IAuthenticationRepository _authRepo; - private readonly ISessionManager _sessionManager; - - public DeviceService( - ILogger<DeviceService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IDeviceManager deviceManager, - IAuthenticationRepository authRepo, - ISessionManager sessionManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _deviceManager = deviceManager; - _authRepo = authRepo; - _sessionManager = sessionManager; - } - - public void Post(PostDeviceOptions request) - { - _deviceManager.UpdateDeviceOptions(request.Id, request); - } - - public object Get(GetDevices request) - { - return ToOptimizedResult(_deviceManager.GetDevices(request)); - } - - public object Get(GetDeviceInfo request) - { - return _deviceManager.GetDevice(request.Id); - } - - public object Get(GetDeviceOptions request) - { - return _deviceManager.GetDeviceOptions(request.Id); - } - - public void Delete(DeleteDevice request) - { - var sessions = _authRepo.Get(new AuthenticationInfoQuery - { - DeviceId = request.Id - - }).Items; - - foreach (var session in sessions) - { - _sessionManager.Logout(session); - } - } - } -} diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs deleted file mode 100644 index 62c4ff43f..000000000 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Threading; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class UpdateDisplayPreferences - /// </summary> - [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST", Summary = "Updates a user's display preferences for an item")] - public class UpdateDisplayPreferences : DisplayPreferences, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "DisplayPreferencesId", Description = "DisplayPreferences Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string DisplayPreferencesId { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string UserId { get; set; } - } - - [Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")] - public class GetDisplayPreferences : IReturn<DisplayPreferences> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - - [ApiMember(Name = "Client", Description = "Client", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Client { get; set; } - } - - /// <summary> - /// Class DisplayPreferencesService - /// </summary> - [Authenticated] - public class DisplayPreferencesService : BaseApiService - { - /// <summary> - /// The _display preferences manager - /// </summary> - private readonly IDisplayPreferencesRepository _displayPreferencesManager; - /// <summary> - /// The _json serializer - /// </summary> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// Initializes a new instance of the <see cref="DisplayPreferencesService" /> class. - /// </summary> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="displayPreferencesManager">The display preferences manager.</param> - public DisplayPreferencesService( - ILogger<DisplayPreferencesService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IJsonSerializer jsonSerializer, - IDisplayPreferencesRepository displayPreferencesManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _jsonSerializer = jsonSerializer; - _displayPreferencesManager = displayPreferencesManager; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Get(GetDisplayPreferences request) - { - var result = _displayPreferencesManager.GetDisplayPreferences(request.Id, request.UserId, request.Client); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(UpdateDisplayPreferences request) - { - // Serialize to json and then back so that the core doesn't see the request dto type - var displayPreferences = _jsonSerializer.DeserializeFromString<DisplayPreferences>(_jsonSerializer.SerializeToString(request)); - - _displayPreferencesManager.SaveDisplayPreferences(displayPreferences, request.UserId, request.Client, CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs deleted file mode 100644 index d199ce154..000000000 --- a/MediaBrowser.Api/EnvironmentService.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetDirectoryContents - /// </summary> - [Route("/Environment/DirectoryContents", "GET", Summary = "Gets the contents of a given directory in the file system")] - public class GetDirectoryContents : IReturn<List<FileSystemEntryInfo>> - { - /// <summary> - /// Gets or sets the path. - /// </summary> - /// <value>The path.</value> - [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Path { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [include files]. - /// </summary> - /// <value><c>true</c> if [include files]; otherwise, <c>false</c>.</value> - [ApiMember(Name = "IncludeFiles", Description = "An optional filter to include or exclude files from the results. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool IncludeFiles { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [include directories]. - /// </summary> - /// <value><c>true</c> if [include directories]; otherwise, <c>false</c>.</value> - [ApiMember(Name = "IncludeDirectories", Description = "An optional filter to include or exclude folders from the results. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool IncludeDirectories { get; set; } - } - - [Route("/Environment/ValidatePath", "POST", Summary = "Gets the contents of a given directory in the file system")] - public class ValidatePath - { - /// <summary> - /// Gets or sets the path. - /// </summary> - /// <value>The path.</value> - [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Path { get; set; } - - public bool ValidateWriteable { get; set; } - public bool? IsFile { get; set; } - } - - [Obsolete] - [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")] - public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>> - { - /// <summary> - /// Gets or sets the path. - /// </summary> - /// <value>The path.</value> - [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Path { get; set; } - } - - /// <summary> - /// Class GetDrives - /// </summary> - [Route("/Environment/Drives", "GET", Summary = "Gets available drives from the server's file system")] - public class GetDrives : IReturn<List<FileSystemEntryInfo>> - { - } - - /// <summary> - /// Class GetNetworkComputers - /// </summary> - [Route("/Environment/NetworkDevices", "GET", Summary = "Gets a list of devices on the network")] - public class GetNetworkDevices : IReturn<List<FileSystemEntryInfo>> - { - } - - [Route("/Environment/ParentPath", "GET", Summary = "Gets the parent path of a given path")] - public class GetParentPath : IReturn<string> - { - /// <summary> - /// Gets or sets the path. - /// </summary> - /// <value>The path.</value> - [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Path { get; set; } - } - - public class DefaultDirectoryBrowserInfo - { - public string Path { get; set; } - } - - [Route("/Environment/DefaultDirectoryBrowser", "GET", Summary = "Gets the parent path of a given path")] - public class GetDefaultDirectoryBrowser : IReturn<DefaultDirectoryBrowserInfo> - { - - } - - /// <summary> - /// Class EnvironmentService - /// </summary> - [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] - public class EnvironmentService : BaseApiService - { - private const char UncSeparator = '\\'; - private const string UncSeparatorString = "\\"; - - /// <summary> - /// The _network manager - /// </summary> - private readonly INetworkManager _networkManager; - private readonly IFileSystem _fileSystem; - - /// <summary> - /// Initializes a new instance of the <see cref="EnvironmentService" /> class. - /// </summary> - /// <param name="networkManager">The network manager.</param> - public EnvironmentService( - ILogger<EnvironmentService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - INetworkManager networkManager, - IFileSystem fileSystem) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _networkManager = networkManager; - _fileSystem = fileSystem; - } - - public void Post(ValidatePath request) - { - if (request.IsFile.HasValue) - { - if (request.IsFile.Value) - { - if (!File.Exists(request.Path)) - { - throw new FileNotFoundException("File not found", request.Path); - } - } - else - { - if (!Directory.Exists(request.Path)) - { - throw new FileNotFoundException("File not found", request.Path); - } - } - } - - else - { - if (!File.Exists(request.Path) && !Directory.Exists(request.Path)) - { - throw new FileNotFoundException("Path not found", request.Path); - } - - if (request.ValidateWriteable) - { - EnsureWriteAccess(request.Path); - } - } - } - - protected void EnsureWriteAccess(string path) - { - var file = Path.Combine(path, Guid.NewGuid().ToString()); - - File.WriteAllText(file, string.Empty); - _fileSystem.DeleteFile(file); - } - - public object Get(GetDefaultDirectoryBrowser request) => - ToOptimizedResult(new DefaultDirectoryBrowserInfo { Path = null }); - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetDirectoryContents request) - { - var path = request.Path; - - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(Path)); - } - - var networkPrefix = UncSeparatorString + UncSeparatorString; - - if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) - && path.LastIndexOf(UncSeparator) == 1) - { - return ToOptimizedResult(Array.Empty<FileSystemEntryInfo>()); - } - - return ToOptimizedResult(GetFileSystemEntries(request).ToList()); - } - - [Obsolete] - public object Get(GetNetworkShares request) - => ToOptimizedResult(Array.Empty<FileSystemEntryInfo>()); - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetDrives request) - { - var result = GetDrives().ToList(); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the list that is returned when an empty path is supplied - /// </summary> - /// <returns>IEnumerable{FileSystemEntryInfo}.</returns> - private IEnumerable<FileSystemEntryInfo> GetDrives() - { - return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo - { - Name = d.Name, - Path = d.FullName, - Type = FileSystemEntryType.Directory - }); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetNetworkDevices request) - => ToOptimizedResult(Array.Empty<FileSystemEntryInfo>()); - - /// <summary> - /// Gets the file system entries. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>IEnumerable{FileSystemEntryInfo}.</returns> - private IEnumerable<FileSystemEntryInfo> GetFileSystemEntries(GetDirectoryContents request) - { - var entries = _fileSystem.GetFileSystemEntries(request.Path).OrderBy(i => i.FullName).Where(i => - { - var isDirectory = i.IsDirectory; - - if (!request.IncludeFiles && !isDirectory) - { - return false; - } - - return request.IncludeDirectories || !isDirectory; - }); - - return entries.Select(f => new FileSystemEntryInfo - { - Name = f.Name, - Path = f.FullName, - Type = f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File - - }); - } - - public object Get(GetParentPath request) - { - var parent = Path.GetDirectoryName(request.Path); - - if (string.IsNullOrEmpty(parent)) - { - // Check if unc share - var index = request.Path.LastIndexOf(UncSeparator); - - if (index != -1 && request.Path.IndexOf(UncSeparator) == 0) - { - parent = request.Path.Substring(0, index); - - if (string.IsNullOrWhiteSpace(parent.TrimStart(UncSeparator))) - { - parent = null; - } - } - } - - return parent; - } - } -} diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs deleted file mode 100644 index 5eb72cdb1..000000000 --- a/MediaBrowser.Api/FilterService.cs +++ /dev/null @@ -1,243 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Items/Filters", "GET", Summary = "Gets branding configuration")] - public class GetQueryFiltersLegacy : IReturn<QueryFiltersLegacy> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ParentId { get; set; } - - [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string IncludeItemTypes { get; set; } - - [ApiMember(Name = "MediaTypes", Description = "Optional filter by MediaType. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string MediaTypes { get; set; } - - public string[] GetMediaTypes() - { - return (MediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetIncludeItemTypes() - { - return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - } - - [Route("/Items/Filters2", "GET", Summary = "Gets branding configuration")] - public class GetQueryFilters : IReturn<QueryFilters> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ParentId { get; set; } - - [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string IncludeItemTypes { get; set; } - - [ApiMember(Name = "MediaTypes", Description = "Optional filter by MediaType. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string MediaTypes { get; set; } - - public string[] GetMediaTypes() - { - return (MediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetIncludeItemTypes() - { - return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public bool? IsAiring { get; set; } - public bool? IsMovie { get; set; } - public bool? IsSports { get; set; } - public bool? IsKids { get; set; } - public bool? IsNews { get; set; } - public bool? IsSeries { get; set; } - public bool? Recursive { get; set; } - } - - [Authenticated] - public class FilterService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; - - public FilterService( - ILogger<FilterService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILibraryManager libraryManager, - IUserManager userManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _libraryManager = libraryManager; - _userManager = userManager; - } - - public object Get(GetQueryFilters request) - { - var parentItem = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId); - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, typeof(Trailer).Name, StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, "Program", StringComparison.OrdinalIgnoreCase)) - { - parentItem = null; - } - - var filters = new QueryFilters(); - - var genreQuery = new InternalItemsQuery(user) - { - IncludeItemTypes = request.GetIncludeItemTypes(), - DtoOptions = new Controller.Dto.DtoOptions - { - Fields = new ItemFields[] { }, - EnableImages = false, - EnableUserData = false - }, - IsAiring = request.IsAiring, - IsMovie = request.IsMovie, - IsSports = request.IsSports, - IsKids = request.IsKids, - IsNews = request.IsNews, - IsSeries = request.IsSeries - }; - - // Non recursive not yet supported for library folders - if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder) - { - genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new[] { parentItem.Id }; - } - else - { - genreQuery.Parent = parentItem; - } - - if (string.Equals(request.IncludeItemTypes, "MusicAlbum", StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, "MusicVideo", StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, "MusicArtist", StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, "Audio", StringComparison.OrdinalIgnoreCase)) - { - filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair - { - Name = i.Item1.Name, - Id = i.Item1.Id - - }).ToArray(); - } - else - { - filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair - { - Name = i.Item1.Name, - Id = i.Item1.Id - - }).ToArray(); - } - - return ToOptimizedResult(filters); - } - - public object Get(GetQueryFiltersLegacy request) - { - var parentItem = string.IsNullOrEmpty(request.ParentId) ? null : _libraryManager.GetItemById(request.ParentId); - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, typeof(Trailer).Name, StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, "Program", StringComparison.OrdinalIgnoreCase)) - { - parentItem = null; - } - - var item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder() : - parentItem; - - var result = ((Folder)item).GetItemList(GetItemsQuery(request, user)); - - var filters = GetFilters(result); - - return ToOptimizedResult(filters); - } - - private QueryFiltersLegacy GetFilters(IReadOnlyCollection<BaseItem> items) - { - var result = new QueryFiltersLegacy(); - - result.Years = items.Select(i => i.ProductionYear ?? -1) - .Where(i => i > 0) - .Distinct() - .OrderBy(i => i) - .ToArray(); - - result.Genres = items.SelectMany(i => i.Genres) - .DistinctNames() - .OrderBy(i => i) - .ToArray(); - - result.Tags = items - .SelectMany(i => i.Tags) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(i => i) - .ToArray(); - - result.OfficialRatings = items - .Select(i => i.OfficialRating) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(i => i) - .ToArray(); - - return result; - } - - private InternalItemsQuery GetItemsQuery(GetQueryFiltersLegacy request, User user) - { - var query = new InternalItemsQuery - { - User = user, - MediaTypes = request.GetMediaTypes(), - IncludeItemTypes = request.GetIncludeItemTypes(), - Recursive = true, - EnableTotalRecordCount = false, - DtoOptions = new Controller.Dto.DtoOptions - { - Fields = new[] { ItemFields.Genres, ItemFields.Tags }, - EnableImages = false, - EnableUserData = false - } - }; - - return query; - } - } -} diff --git a/MediaBrowser.Api/IHasDtoOptions.cs b/MediaBrowser.Api/IHasDtoOptions.cs deleted file mode 100644 index 03d3b3692..000000000 --- a/MediaBrowser.Api/IHasDtoOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MediaBrowser.Api -{ - public interface IHasDtoOptions : IHasItemFields - { - bool? EnableImages { get; set; } - bool? EnableUserData { get; set; } - - int? ImageTypeLimit { get; set; } - - string EnableImageTypes { get; set; } - } -} diff --git a/MediaBrowser.Api/IHasItemFields.cs b/MediaBrowser.Api/IHasItemFields.cs deleted file mode 100644 index 85b4a7e2d..000000000 --- a/MediaBrowser.Api/IHasItemFields.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Linq; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Interface IHasItemFields - /// </summary> - public interface IHasItemFields - { - /// <summary> - /// Gets or sets the fields. - /// </summary> - /// <value>The fields.</value> - string Fields { get; set; } - } - - /// <summary> - /// Class ItemFieldsExtensions. - /// </summary> - public static class ItemFieldsExtensions - { - /// <summary> - /// Gets the item fields. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>IEnumerable{ItemFields}.</returns> - public static ItemFields[] GetItemFields(this IHasItemFields request) - { - var val = request.Fields; - - if (string.IsNullOrEmpty(val)) - { - return Array.Empty<ItemFields>(); - } - - return val.Split(',').Select(v => - { - if (Enum.TryParse(v, true, out ItemFields value)) - { - return (ItemFields?)value; - } - - return null; - - }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); - } - } -} diff --git a/MediaBrowser.Api/Images/ImageByNameService.cs b/MediaBrowser.Api/Images/ImageByNameService.cs deleted file mode 100644 index 45b7d0c10..000000000 --- a/MediaBrowser.Api/Images/ImageByNameService.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Images -{ - /// <summary> - /// Class GetGeneralImage - /// </summary> - [Route("/Images/General/{Name}/{Type}", "GET", Summary = "Gets a general image by name")] - public class GetGeneralImage - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The name of the image", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - [ApiMember(Name = "Type", Description = "Image Type (primary, backdrop, logo, etc).", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Type { get; set; } - } - - /// <summary> - /// Class GetRatingImage - /// </summary> - [Route("/Images/Ratings/{Theme}/{Name}", "GET", Summary = "Gets a rating image by name")] - public class GetRatingImage - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The name of the image", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the theme. - /// </summary> - /// <value>The theme.</value> - [ApiMember(Name = "Theme", Description = "The theme to get the image from", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Theme { get; set; } - } - - /// <summary> - /// Class GetMediaInfoImage - /// </summary> - [Route("/Images/MediaInfo/{Theme}/{Name}", "GET", Summary = "Gets a media info image by name")] - public class GetMediaInfoImage - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The name of the image", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the theme. - /// </summary> - /// <value>The theme.</value> - [ApiMember(Name = "Theme", Description = "The theme to get the image from", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Theme { get; set; } - } - - [Route("/Images/MediaInfo", "GET", Summary = "Gets all media info image by name")] - [Authenticated] - public class GetMediaInfoImages : IReturn<List<ImageByNameInfo>> - { - } - - [Route("/Images/Ratings", "GET", Summary = "Gets all rating images by name")] - [Authenticated] - public class GetRatingImages : IReturn<List<ImageByNameInfo>> - { - } - - [Route("/Images/General", "GET", Summary = "Gets all general images by name")] - [Authenticated] - public class GetGeneralImages : IReturn<List<ImageByNameInfo>> - { - } - - /// <summary> - /// Class ImageByNameService - /// </summary> - public class ImageByNameService : BaseApiService - { - /// <summary> - /// The _app paths - /// </summary> - private readonly IServerApplicationPaths _appPaths; - - private readonly IFileSystem _fileSystem; - - /// <summary> - /// Initializes a new instance of the <see cref="ImageByNameService" /> class. - /// </summary> - public ImageByNameService( - ILogger<ImageByNameService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory resultFactory, - IFileSystem fileSystem) - : base(logger, serverConfigurationManager, resultFactory) - { - _appPaths = serverConfigurationManager.ApplicationPaths; - _fileSystem = fileSystem; - } - - public object Get(GetMediaInfoImages request) - { - return ToOptimizedResult(GetImageList(_appPaths.MediaInfoImagesPath, true)); - } - - public object Get(GetRatingImages request) - { - return ToOptimizedResult(GetImageList(_appPaths.RatingsPath, true)); - } - - public object Get(GetGeneralImages request) - { - return ToOptimizedResult(GetImageList(_appPaths.GeneralPath, false)); - } - - private List<ImageByNameInfo> GetImageList(string path, bool supportsThemes) - { - try - { - return _fileSystem.GetFiles(path, BaseItem.SupportedImageExtensions, false, true) - .Select(i => new ImageByNameInfo - { - Name = _fileSystem.GetFileNameWithoutExtension(i), - FileLength = i.Length, - - // For themeable images, use the Theme property - // For general images, the same object structure is fine, - // but it's not owned by a theme, so call it Context - Theme = supportsThemes ? GetThemeName(i.FullName, path) : null, - Context = supportsThemes ? null : GetThemeName(i.FullName, path), - - Format = i.Extension.ToLowerInvariant().TrimStart('.') - }) - .OrderBy(i => i.Name) - .ToList(); - } - catch (IOException) - { - return new List<ImageByNameInfo>(); - } - } - - private string GetThemeName(string path, string rootImagePath) - { - var parentName = Path.GetDirectoryName(path); - - if (string.Equals(parentName, rootImagePath, StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - parentName = Path.GetFileName(parentName); - - return string.Equals(parentName, "all", StringComparison.OrdinalIgnoreCase) ? - null : - parentName; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetGeneralImage request) - { - var filename = string.Equals(request.Type, "primary", StringComparison.OrdinalIgnoreCase) - ? "folder" - : request.Type; - - var paths = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(_appPaths.GeneralPath, request.Name, filename + i)).ToList(); - - var path = paths.FirstOrDefault(File.Exists) ?? paths.FirstOrDefault(); - - return ResultFactory.GetStaticFileResult(Request, path); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetRatingImage request) - { - var themeFolder = Path.Combine(_appPaths.RatingsPath, request.Theme); - - if (Directory.Exists(themeFolder)) - { - var path = BaseItem.SupportedImageExtensions - .Select(i => Path.Combine(themeFolder, request.Name + i)) - .FirstOrDefault(File.Exists); - - if (!string.IsNullOrEmpty(path)) - { - return ResultFactory.GetStaticFileResult(Request, path); - } - } - - var allFolder = Path.Combine(_appPaths.RatingsPath, "all"); - - if (Directory.Exists(allFolder)) - { - // Avoid implicitly captured closure - var currentRequest = request; - - var path = BaseItem.SupportedImageExtensions - .Select(i => Path.Combine(allFolder, currentRequest.Name + i)) - .FirstOrDefault(File.Exists); - - if (!string.IsNullOrEmpty(path)) - { - return ResultFactory.GetStaticFileResult(Request, path); - } - } - - throw new ResourceNotFoundException("MediaInfo image not found: " + request.Name); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetMediaInfoImage request) - { - var themeFolder = Path.Combine(_appPaths.MediaInfoImagesPath, request.Theme); - - if (Directory.Exists(themeFolder)) - { - var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, request.Name + i)) - .FirstOrDefault(File.Exists); - - if (!string.IsNullOrEmpty(path)) - { - return ResultFactory.GetStaticFileResult(Request, path); - } - } - - var allFolder = Path.Combine(_appPaths.MediaInfoImagesPath, "all"); - - if (Directory.Exists(allFolder)) - { - // Avoid implicitly captured closure - var currentRequest = request; - - var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, currentRequest.Name + i)) - .FirstOrDefault(File.Exists); - - if (!string.IsNullOrEmpty(path)) - { - return ResultFactory.GetStaticFileResult(Request, path); - } - } - - throw new ResourceNotFoundException("MediaInfo image not found: " + request.Name); - } - } -} diff --git a/MediaBrowser.Api/Images/ImageRequest.cs b/MediaBrowser.Api/Images/ImageRequest.cs deleted file mode 100644 index 71ff09b63..000000000 --- a/MediaBrowser.Api/Images/ImageRequest.cs +++ /dev/null @@ -1,100 +0,0 @@ -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Images -{ - /// <summary> - /// Class ImageRequest - /// </summary> - public class ImageRequest : DeleteImageRequest - { - /// <summary> - /// The max width - /// </summary> - [ApiMember(Name = "MaxWidth", Description = "The maximum image width to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MaxWidth { get; set; } - - /// <summary> - /// The max height - /// </summary> - [ApiMember(Name = "MaxHeight", Description = "The maximum image height to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MaxHeight { get; set; } - - /// <summary> - /// The width - /// </summary> - [ApiMember(Name = "Width", Description = "The fixed image width to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Width { get; set; } - - /// <summary> - /// The height - /// </summary> - [ApiMember(Name = "Height", Description = "The fixed image height to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Height { get; set; } - - /// <summary> - /// Gets or sets the quality. - /// </summary> - /// <value>The quality.</value> - [ApiMember(Name = "Quality", Description = "Optional quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Quality { get; set; } - - /// <summary> - /// Gets or sets the tag. - /// </summary> - /// <value>The tag.</value> - [ApiMember(Name = "Tag", Description = "Optional. Supply the cache tag from the item object to receive strong caching headers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Tag { get; set; } - - [ApiMember(Name = "CropWhitespace", Description = "Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? CropWhitespace { get; set; } - - [ApiMember(Name = "EnableImageEnhancers", Description = "Enable or disable image enhancers such as cover art.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool EnableImageEnhancers { get; set; } - - [ApiMember(Name = "Format", Description = "Determines the output foramt of the image - original,gif,jpg,png", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public string Format { get; set; } - - [ApiMember(Name = "AddPlayedIndicator", Description = "Optional. Add a played indicator", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool AddPlayedIndicator { get; set; } - - [ApiMember(Name = "PercentPlayed", Description = "Optional percent to render for the percent played overlay", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double? PercentPlayed { get; set; } - - [ApiMember(Name = "UnplayedCount", Description = "Optional unplayed count overlay to render", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? UnplayedCount { get; set; } - - public int? Blur { get; set; } - - [ApiMember(Name = "BackgroundColor", Description = "Optional. Apply a background color for transparent images.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string BackgroundColor { get; set; } - - [ApiMember(Name = "ForegroundLayer", Description = "Optional. Apply a foreground layer on top of the image.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ForegroundLayer { get; set; } - - public ImageRequest() - { - EnableImageEnhancers = true; - } - } - - /// <summary> - /// Class DeleteImageRequest - /// </summary> - public class DeleteImageRequest - { - /// <summary> - /// Gets or sets the type of the image. - /// </summary> - /// <value>The type of the image.</value> - [ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET,POST,DELETE")] - public ImageType Type { get; set; } - - /// <summary> - /// Gets or sets the index. - /// </summary> - /// <value>The index.</value> - [ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET,POST,DELETE")] - public int? Index { get; set; } - } -} diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs deleted file mode 100644 index 2e9b3e6cb..000000000 --- a/MediaBrowser.Api/Images/ImageService.cs +++ /dev/null @@ -1,771 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; - -namespace MediaBrowser.Api.Images -{ - /// <summary> - /// Class GetItemImage. - /// </summary> - [Route("/Items/{Id}/Images", "GET", Summary = "Gets information about an item's images")] - [Authenticated] - public class GetItemImageInfos : IReturn<List<ImageInfo>> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/{Id}/Images/{Type}", "GET")] - [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")] - [Route("/Items/{Id}/Images/{Type}", "HEAD")] - [Route("/Items/{Id}/Images/{Type}/{Index}", "HEAD")] - [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}/{UnplayedCount}", "GET")] - [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}/{UnplayedCount}", "HEAD")] - public class GetItemImage : ImageRequest - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class UpdateItemImageIndex - /// </summary> - [Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST", Summary = "Updates the index for an item image")] - [Authenticated(Roles = "admin")] - public class UpdateItemImageIndex : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// Gets or sets the type of the image. - /// </summary> - /// <value>The type of the image.</value> - [ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public ImageType Type { get; set; } - - /// <summary> - /// Gets or sets the index. - /// </summary> - /// <value>The index.</value> - [ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int Index { get; set; } - - /// <summary> - /// Gets or sets the new index. - /// </summary> - /// <value>The new index.</value> - [ApiMember(Name = "NewIndex", Description = "The new image index", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public int NewIndex { get; set; } - } - - /// <summary> - /// Class GetPersonImage - /// </summary> - [Route("/Artists/{Name}/Images/{Type}", "GET")] - [Route("/Artists/{Name}/Images/{Type}/{Index}", "GET")] - [Route("/Genres/{Name}/Images/{Type}", "GET")] - [Route("/Genres/{Name}/Images/{Type}/{Index}", "GET")] - [Route("/MusicGenres/{Name}/Images/{Type}", "GET")] - [Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "GET")] - [Route("/Persons/{Name}/Images/{Type}", "GET")] - [Route("/Persons/{Name}/Images/{Type}/{Index}", "GET")] - [Route("/Studios/{Name}/Images/{Type}", "GET")] - [Route("/Studios/{Name}/Images/{Type}/{Index}", "GET")] - ////[Route("/Years/{Year}/Images/{Type}", "GET")] - ////[Route("/Years/{Year}/Images/{Type}/{Index}", "GET")] - [Route("/Artists/{Name}/Images/{Type}", "HEAD")] - [Route("/Artists/{Name}/Images/{Type}/{Index}", "HEAD")] - [Route("/Genres/{Name}/Images/{Type}", "HEAD")] - [Route("/Genres/{Name}/Images/{Type}/{Index}", "HEAD")] - [Route("/MusicGenres/{Name}/Images/{Type}", "HEAD")] - [Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "HEAD")] - [Route("/Persons/{Name}/Images/{Type}", "HEAD")] - [Route("/Persons/{Name}/Images/{Type}/{Index}", "HEAD")] - [Route("/Studios/{Name}/Images/{Type}", "HEAD")] - [Route("/Studios/{Name}/Images/{Type}/{Index}", "HEAD")] - ////[Route("/Years/{Year}/Images/{Type}", "HEAD")] - ////[Route("/Years/{Year}/Images/{Type}/{Index}", "HEAD")] - public class GetItemByNameImage : ImageRequest - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "Item name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - /// <summary> - /// Class GetUserImage - /// </summary> - [Route("/Users/{Id}/Images/{Type}", "GET")] - [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")] - [Route("/Users/{Id}/Images/{Type}", "HEAD")] - [Route("/Users/{Id}/Images/{Type}/{Index}", "HEAD")] - public class GetUserImage : ImageRequest - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class DeleteItemImage - /// </summary> - [Route("/Items/{Id}/Images/{Type}", "DELETE")] - [Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")] - [Authenticated(Roles = "admin")] - public class DeleteItemImage : DeleteImageRequest, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - /// <summary> - /// Class DeleteUserImage - /// </summary> - [Route("/Users/{Id}/Images/{Type}", "DELETE")] - [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")] - [Authenticated] - public class DeleteUserImage : DeleteImageRequest, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class PostUserImage - /// </summary> - [Route("/Users/{Id}/Images/{Type}", "POST")] - [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")] - [Authenticated] - public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// The raw Http Request Input Stream - /// </summary> - /// <value>The request stream.</value> - public Stream RequestStream { get; set; } - } - - /// <summary> - /// Class PostItemImage - /// </summary> - [Route("/Items/{Id}/Images/{Type}", "POST")] - [Route("/Items/{Id}/Images/{Type}/{Index}", "POST")] - [Authenticated(Roles = "admin")] - public class PostItemImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// The raw Http Request Input Stream - /// </summary> - /// <value>The request stream.</value> - public Stream RequestStream { get; set; } - } - - /// <summary> - /// Class ImageService - /// </summary> - public class ImageService : BaseApiService - { - private readonly IUserManager _userManager; - - private readonly ILibraryManager _libraryManager; - - private readonly IProviderManager _providerManager; - - private readonly IImageProcessor _imageProcessor; - private readonly IFileSystem _fileSystem; - private readonly IAuthorizationContext _authContext; - - /// <summary> - /// Initializes a new instance of the <see cref="ImageService" /> class. - /// </summary> - public ImageService( - ILogger<ImageService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IProviderManager providerManager, - IImageProcessor imageProcessor, - IFileSystem fileSystem, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _libraryManager = libraryManager; - _providerManager = providerManager; - _imageProcessor = imageProcessor; - _fileSystem = fileSystem; - _authContext = authContext; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetItemImageInfos request) - { - var item = _libraryManager.GetItemById(request.Id); - - var result = GetItemImageInfos(item); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the item image infos. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>Task{List{ImageInfo}}.</returns> - public List<ImageInfo> GetItemImageInfos(BaseItem item) - { - var list = new List<ImageInfo>(); - - var itemImages = item.ImageInfos; - - foreach (var image in itemImages) - { - if (!item.AllowsMultipleImages(image.Type)) - { - var info = GetImageInfo(item, image, null); - - if (info != null) - { - list.Add(info); - } - } - } - - foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages)) - { - var index = 0; - - // Prevent implicitly captured closure - var currentImageType = imageType; - - foreach (var image in itemImages.Where(i => i.Type == currentImageType)) - { - var info = GetImageInfo(item, image, index); - - if (info != null) - { - list.Add(info); - } - - index++; - } - } - - return list; - } - - private ImageInfo GetImageInfo(BaseItem item, ItemImageInfo info, int? imageIndex) - { - int? width = null; - int? height = null; - long length = 0; - - try - { - if (info.IsLocalFile) - { - var fileInfo = _fileSystem.GetFileInfo(info.Path); - length = fileInfo.Length; - - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info); - _libraryManager.UpdateImages(item); - width = size.Width; - height = size.Height; - - if (width <= 0 || height <= 0) - { - width = null; - height = null; - } - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting image information for {Item}", item.Name); - } - - try - { - return new ImageInfo - { - Path = info.Path, - ImageIndex = imageIndex, - ImageType = info.Type, - ImageTag = _imageProcessor.GetImageCacheTag(item, info), - Size = length, - Width = width, - Height = height - }; - } - catch (Exception ex) - { - Logger.LogError(ex, "Error getting image information for {Path}", info.Path); - - return null; - } - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetItemImage request) - { - return GetImage(request, request.Id, null, false); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Head(GetItemImage request) - { - return GetImage(request, request.Id, null, true); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetUserImage request) - { - var item = _userManager.GetUserById(request.Id); - - return GetImage(request, Guid.Empty, item, false); - } - - public object Head(GetUserImage request) - { - var item = _userManager.GetUserById(request.Id); - - return GetImage(request, Guid.Empty, item, true); - } - - public object Get(GetItemByNameImage request) - { - var type = GetPathValue(0).ToString(); - - var item = GetItemByName(request.Name, type, _libraryManager, new DtoOptions(false)); - - return GetImage(request, item.Id, item, false); - } - - public object Head(GetItemByNameImage request) - { - var type = GetPathValue(0).ToString(); - - var item = GetItemByName(request.Name, type, _libraryManager, new DtoOptions(false)); - - return GetImage(request, item.Id, item, true); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(PostUserImage request) - { - var id = Guid.Parse(GetPathValue(1)); - - AssertCanUpdateUser(_authContext, _userManager, id, true); - - request.Type = Enum.Parse<ImageType>(GetPathValue(3).ToString(), true); - - var item = _userManager.GetUserById(id); - - return PostImage(item, request.RequestStream, request.Type, Request.ContentType); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(PostItemImage request) - { - var id = Guid.Parse(GetPathValue(1)); - - request.Type = Enum.Parse<ImageType>(GetPathValue(3).ToString(), true); - - var item = _libraryManager.GetItemById(id); - - return PostImage(item, request.RequestStream, request.Type, Request.ContentType); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(DeleteUserImage request) - { - var userId = request.Id; - AssertCanUpdateUser(_authContext, _userManager, userId, true); - - var item = _userManager.GetUserById(userId); - - item.DeleteImage(request.Type, request.Index ?? 0); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(DeleteItemImage request) - { - var item = _libraryManager.GetItemById(request.Id); - - item.DeleteImage(request.Type, request.Index ?? 0); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(UpdateItemImageIndex request) - { - var item = _libraryManager.GetItemById(request.Id); - - UpdateItemIndex(item, request.Type, request.Index, request.NewIndex); - } - - /// <summary> - /// Updates the index of the item. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="type">The type.</param> - /// <param name="currentIndex">Index of the current.</param> - /// <param name="newIndex">The new index.</param> - /// <returns>Task.</returns> - private void UpdateItemIndex(BaseItem item, ImageType type, int currentIndex, int newIndex) - { - item.SwapImages(type, currentIndex, newIndex); - } - - /// <summary> - /// Gets the image. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="item">The item.</param> - /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> - /// <returns>System.Object.</returns> - /// <exception cref="ResourceNotFoundException"></exception> - public Task<object> GetImage(ImageRequest request, Guid itemId, BaseItem item, bool isHeadRequest) - { - if (request.PercentPlayed.HasValue) - { - if (request.PercentPlayed.Value <= 0) - { - request.PercentPlayed = null; - } - else if (request.PercentPlayed.Value >= 100) - { - request.PercentPlayed = null; - request.AddPlayedIndicator = true; - } - } - - if (request.PercentPlayed.HasValue) - { - request.UnplayedCount = null; - } - - if (request.UnplayedCount.HasValue - && request.UnplayedCount.Value <= 0) - { - request.UnplayedCount = null; - } - - if (item == null) - { - item = _libraryManager.GetItemById(itemId); - - if (item == null) - { - throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N", CultureInfo.InvariantCulture))); - } - } - - var imageInfo = GetImageInfo(request, item); - if (imageInfo == null) - { - var displayText = item == null ? itemId.ToString() : item.Name; - throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type)); - } - - bool cropwhitespace; - if (request.CropWhitespace.HasValue) - { - cropwhitespace = request.CropWhitespace.Value; - } - else - { - cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art; - } - - var outputFormats = GetOutputFormats(request); - - TimeSpan? cacheDuration = null; - - if (!string.IsNullOrEmpty(request.Tag)) - { - cacheDuration = TimeSpan.FromDays(365); - } - - var responseHeaders = new Dictionary<string, string> - { - {"transferMode.dlna.org", "Interactive"}, - {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"} - }; - - return GetImageResult( - item, - itemId, - request, - imageInfo, - cropwhitespace, - outputFormats, - cacheDuration, - responseHeaders, - isHeadRequest); - } - - private async Task<object> GetImageResult( - BaseItem item, - Guid itemId, - ImageRequest request, - ItemImageInfo image, - bool cropwhitespace, - IReadOnlyCollection<ImageFormat> supportedFormats, - TimeSpan? cacheDuration, - IDictionary<string, string> headers, - bool isHeadRequest) - { - if (!image.IsLocalFile) - { - item ??= _libraryManager.GetItemById(itemId); - image = await _libraryManager.ConvertImageToLocal(item, image, request.Index ?? 0).ConfigureAwait(false); - } - - var options = new ImageProcessingOptions - { - CropWhiteSpace = cropwhitespace, - Height = request.Height, - ImageIndex = request.Index ?? 0, - Image = image, - Item = item, - ItemId = itemId, - MaxHeight = request.MaxHeight, - MaxWidth = request.MaxWidth, - Quality = request.Quality ?? 100, - Width = request.Width, - AddPlayedIndicator = request.AddPlayedIndicator, - PercentPlayed = request.PercentPlayed ?? 0, - UnplayedCount = request.UnplayedCount, - Blur = request.Blur, - BackgroundColor = request.BackgroundColor, - ForegroundLayer = request.ForegroundLayer, - SupportedOutputFormats = supportedFormats - }; - - var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); - - headers[HeaderNames.Vary] = HeaderNames.Accept; - - return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - CacheDuration = cacheDuration, - ResponseHeaders = headers, - ContentType = imageResult.Item2, - DateLastModified = imageResult.Item3, - IsHeadRequest = isHeadRequest, - Path = imageResult.Item1, - - FileShare = FileShare.Read - - }).ConfigureAwait(false); - } - - private ImageFormat[] GetOutputFormats(ImageRequest request) - { - if (!string.IsNullOrWhiteSpace(request.Format) - && Enum.TryParse(request.Format, true, out ImageFormat format)) - { - return new[] { format }; - } - - return GetClientSupportedFormats(); - } - - private ImageFormat[] GetClientSupportedFormats() - { - var supportedFormats = Request.AcceptTypes ?? Array.Empty<string>(); - if (supportedFormats.Length > 0) - { - for (int i = 0; i < supportedFormats.Length; i++) - { - int index = supportedFormats[i].IndexOf(';'); - if (index != -1) - { - supportedFormats[i] = supportedFormats[i].Substring(0, index); - } - } - } - - var acceptParam = Request.QueryString["accept"]; - - var supportsWebP = SupportsFormat(supportedFormats, acceptParam, "webp", false); - - if (!supportsWebP) - { - var userAgent = Request.UserAgent ?? string.Empty; - if (userAgent.IndexOf("crosswalk", StringComparison.OrdinalIgnoreCase) != -1 && - userAgent.IndexOf("android", StringComparison.OrdinalIgnoreCase) != -1) - { - supportsWebP = true; - } - } - - var formats = new List<ImageFormat>(4); - - if (supportsWebP) - { - formats.Add(ImageFormat.Webp); - } - - formats.Add(ImageFormat.Jpg); - formats.Add(ImageFormat.Png); - - if (SupportsFormat(supportedFormats, acceptParam, "gif", true)) - { - formats.Add(ImageFormat.Gif); - } - - return formats.ToArray(); - } - - private bool SupportsFormat(IEnumerable<string> requestAcceptTypes, string acceptParam, string format, bool acceptAll) - { - var mimeType = "image/" + format; - - if (requestAcceptTypes.Contains(mimeType)) - { - return true; - } - - if (acceptAll && requestAcceptTypes.Contains("*/*")) - { - return true; - } - - return string.Equals(Request.QueryString["accept"], format, StringComparison.OrdinalIgnoreCase); - } - - /// <summary> - /// Gets the image path. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="item">The item.</param> - /// <returns>System.String.</returns> - private ItemImageInfo GetImageInfo(ImageRequest request, BaseItem item) - { - var index = request.Index ?? 0; - - return item.GetImageInfo(request.Type, index); - } - - /// <summary> - /// Posts the image. - /// </summary> - /// <param name="entity">The entity.</param> - /// <param name="inputStream">The input stream.</param> - /// <param name="imageType">Type of the image.</param> - /// <param name="mimeType">Type of the MIME.</param> - /// <returns>Task.</returns> - public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType) - { - using var reader = new StreamReader(inputStream); - var text = await reader.ReadToEndAsync().ConfigureAwait(false); - - var bytes = Convert.FromBase64String(text); - - var memoryStream = new MemoryStream(bytes) - { - Position = 0 - }; - - // Handle image/png; charset=utf-8 - mimeType = mimeType.Split(';').FirstOrDefault(); - - await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false); - - entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs deleted file mode 100644 index 23bf54712..000000000 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ /dev/null @@ -1,298 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Images -{ - public class BaseRemoteImageRequest : IReturn<RemoteImageResult> - { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public ImageType? Type { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "ProviderName", Description = "Optional. The image provider to use", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ProviderName { get; set; } - - [ApiMember(Name = "IncludeAllLanguages", Description = "Optional.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeAllLanguages { get; set; } - } - - [Route("/Items/{Id}/RemoteImages", "GET", Summary = "Gets available remote images for an item")] - [Authenticated] - public class GetRemoteImages : BaseRemoteImageRequest - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/{Id}/RemoteImages/Providers", "GET", Summary = "Gets available remote image providers for an item")] - [Authenticated] - public class GetRemoteImageProviders : IReturn<List<ImageProviderInfo>> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - public class BaseDownloadRemoteImage : IReturnVoid - { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public ImageType Type { get; set; } - - [ApiMember(Name = "ProviderName", Description = "The image provider", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ProviderName { get; set; } - - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ImageUrl { get; set; } - } - - [Route("/Items/{Id}/RemoteImages/Download", "POST", Summary = "Downloads a remote image for an item")] - [Authenticated(Roles = "Admin")] - public class DownloadRemoteImage : BaseDownloadRemoteImage - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Images/Remote", "GET", Summary = "Gets a remote image")] - public class GetRemoteImage - { - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ImageUrl { get; set; } - } - - public class RemoteImageService : BaseApiService - { - private readonly IProviderManager _providerManager; - - private readonly IServerApplicationPaths _appPaths; - private readonly IHttpClient _httpClient; - private readonly IFileSystem _fileSystem; - - private readonly ILibraryManager _libraryManager; - - public RemoteImageService( - ILogger<RemoteImageService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IProviderManager providerManager, - IServerApplicationPaths appPaths, - IHttpClient httpClient, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _providerManager = providerManager; - _appPaths = appPaths; - _httpClient = httpClient; - _fileSystem = fileSystem; - _libraryManager = libraryManager; - } - - public object Get(GetRemoteImageProviders request) - { - var item = _libraryManager.GetItemById(request.Id); - - var result = GetImageProviders(item); - - return ToOptimizedResult(result); - } - - private List<ImageProviderInfo> GetImageProviders(BaseItem item) - { - return _providerManager.GetRemoteImageProviderInfo(item).ToList(); - } - - public async Task<object> Get(GetRemoteImages request) - { - var item = _libraryManager.GetItemById(request.Id); - - var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery - { - ProviderName = request.ProviderName, - IncludeAllLanguages = request.IncludeAllLanguages, - IncludeDisabledProviders = true, - ImageType = request.Type - - }, CancellationToken.None).ConfigureAwait(false); - - var imagesList = images.ToArray(); - - var allProviders = _providerManager.GetRemoteImageProviderInfo(item); - - if (request.Type.HasValue) - { - allProviders = allProviders.Where(i => i.SupportedImages.Contains(request.Type.Value)); - } - - var result = new RemoteImageResult - { - TotalRecordCount = imagesList.Length, - Providers = allProviders.Select(i => i.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray() - }; - - if (request.StartIndex.HasValue) - { - imagesList = imagesList.Skip(request.StartIndex.Value) - .ToArray(); - } - - if (request.Limit.HasValue) - { - imagesList = imagesList.Take(request.Limit.Value) - .ToArray(); - } - - result.Images = imagesList; - - return result; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(DownloadRemoteImage request) - { - var item = _libraryManager.GetItemById(request.Id); - - return DownloadRemoteImage(item, request); - } - - /// <summary> - /// Downloads the remote image. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="request">The request.</param> - /// <returns>Task.</returns> - private async Task DownloadRemoteImage(BaseItem item, BaseDownloadRemoteImage request) - { - await _providerManager.SaveImage(item, request.ImageUrl, request.Type, null, CancellationToken.None).ConfigureAwait(false); - - item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public async Task<object> Get(GetRemoteImage request) - { - var urlHash = request.ImageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - string contentPath; - - try - { - contentPath = File.ReadAllText(pointerCachePath); - - if (File.Exists(contentPath)) - { - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - await DownloadImage(request.ImageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - - // Read the pointer file again - contentPath = File.ReadAllText(pointerCachePath); - - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - - /// <summary> - /// Downloads the image. - /// </summary> - /// <param name="url">The URL.</param> - /// <param name="urlHash">The URL hash.</param> - /// <param name="pointerCachePath">The pointer cache path.</param> - /// <returns>Task.</returns> - private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath) - { - using var result = await _httpClient.GetResponse(new HttpRequestOptions - { - Url = url, - BufferContent = false - }).ConfigureAwait(false); - var ext = result.ContentType.Split('/')[^1]; - - var fullCachePath = GetFullCachePath(urlHash + "." + ext); - - Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - var stream = result.Content; - await using (stream.ConfigureAwait(false)) - { - var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - await using (filestream.ConfigureAwait(false)) - { - await stream.CopyToAsync(filestream).ConfigureAwait(false); - } - } - - Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); - File.WriteAllText(pointerCachePath, fullCachePath); - } - - /// <summary> - /// Gets the full cache path. - /// </summary> - /// <param name="filename">The filename.</param> - /// <returns>System.String.</returns> - private string GetFullCachePath(string filename) - { - return Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); - } - } -} diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs deleted file mode 100644 index 68e3dfa59..000000000 --- a/MediaBrowser.Api/ItemLookupService.cs +++ /dev/null @@ -1,336 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Items/{Id}/ExternalIdInfos", "GET", Summary = "Gets external id infos for an item")] - [Authenticated(Roles = "Admin")] - public class GetExternalIdInfos : IReturn<List<ExternalIdInfo>> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - } - - [Route("/Items/RemoteSearch/Movie", "POST")] - [Authenticated] - public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/Trailer", "POST")] - [Authenticated] - public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/MusicVideo", "POST")] - [Authenticated] - public class GetMusicVideoRemoteSearchResults : RemoteSearchQuery<MusicVideoInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/Series", "POST")] - [Authenticated] - public class GetSeriesRemoteSearchResults : RemoteSearchQuery<SeriesInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/BoxSet", "POST")] - [Authenticated] - public class GetBoxSetRemoteSearchResults : RemoteSearchQuery<BoxSetInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/MusicArtist", "POST")] - [Authenticated] - public class GetMusicArtistRemoteSearchResults : RemoteSearchQuery<ArtistInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/MusicAlbum", "POST")] - [Authenticated] - public class GetMusicAlbumRemoteSearchResults : RemoteSearchQuery<AlbumInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/Person", "POST")] - [Authenticated(Roles = "Admin")] - public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/Book", "POST")] - [Authenticated] - public class GetBookRemoteSearchResults : RemoteSearchQuery<BookInfo>, IReturn<List<RemoteSearchResult>> - { - } - - [Route("/Items/RemoteSearch/Image", "GET", Summary = "Gets a remote image")] - public class GetRemoteSearchImage - { - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ImageUrl { get; set; } - - [ApiMember(Name = "ProviderName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ProviderName { get; set; } - } - - [Route("/Items/RemoteSearch/Apply/{Id}", "POST", Summary = "Applies search criteria to an item and refreshes metadata")] - [Authenticated(Roles = "Admin")] - public class ApplySearchCriteria : RemoteSearchResult, IReturnVoid - { - [ApiMember(Name = "Id", Description = "The item id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "ReplaceAllImages", Description = "Whether or not to replace all images", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool ReplaceAllImages { get; set; } - - public ApplySearchCriteria() - { - ReplaceAllImages = true; - } - } - - public class ItemLookupService : BaseApiService - { - private readonly IProviderManager _providerManager; - private readonly IServerApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly ILibraryManager _libraryManager; - private readonly IJsonSerializer _json; - - public ItemLookupService( - ILogger<ItemLookupService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager, - IJsonSerializer json) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _providerManager = providerManager; - _appPaths = serverConfigurationManager.ApplicationPaths; - _fileSystem = fileSystem; - _libraryManager = libraryManager; - _json = json; - } - - public object Get(GetExternalIdInfos request) - { - var item = _libraryManager.GetItemById(request.Id); - - var infos = _providerManager.GetExternalIdInfos(item).ToList(); - - return ToOptimizedResult(infos); - } - - public async Task<object> Post(GetTrailerRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(GetBookRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(GetMovieRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(GetSeriesRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(GetBoxSetRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(GetMusicVideoRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(GetPersonRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(GetMusicAlbumRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(GetMusicArtistRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public Task<object> Get(GetRemoteSearchImage request) - { - return GetRemoteImage(request); - } - - public Task Post(ApplySearchCriteria request) - { - var item = _libraryManager.GetItemById(new Guid(request.Id)); - - //foreach (var key in request.ProviderIds) - //{ - // var value = key.Value; - - // if (!string.IsNullOrWhiteSpace(value)) - // { - // item.SetProviderId(key.Key, value); - // } - //} - Logger.LogInformation("Setting provider id's to item {0}-{1}: {2}", item.Id, item.Name, _json.SerializeToString(request.ProviderIds)); - - // Since the refresh process won't erase provider Ids, we need to set this explicitly now. - item.ProviderIds = request.ProviderIds; - //item.ProductionYear = request.ProductionYear; - //item.Name = request.Name; - - return _providerManager.RefreshFullItem( - item, - new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - ReplaceAllMetadata = true, - ReplaceAllImages = request.ReplaceAllImages, - SearchResult = request - }, - CancellationToken.None); - } - - /// <summary> - /// Gets the remote image. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{System.Object}.</returns> - private async Task<object> GetRemoteImage(GetRemoteSearchImage request) - { - var urlHash = request.ImageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - string contentPath; - - try - { - contentPath = File.ReadAllText(pointerCachePath); - - if (File.Exists(contentPath)) - { - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - await DownloadImage(request.ProviderName, request.ImageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - - // Read the pointer file again - contentPath = File.ReadAllText(pointerCachePath); - - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - - /// <summary> - /// Downloads the image. - /// </summary> - /// <param name="providerName">Name of the provider.</param> - /// <param name="url">The URL.</param> - /// <param name="urlHash">The URL hash.</param> - /// <param name="pointerCachePath">The pointer cache path.</param> - /// <returns>Task.</returns> - private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath) - { - var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false); - - var ext = result.ContentType.Split('/')[^1]; - - var fullCachePath = GetFullCachePath(urlHash + "." + ext); - - Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - var stream = result.Content; - - await using (stream.ConfigureAwait(false)) - { - var fileStream = new FileStream( - fullCachePath, - FileMode.Create, - FileAccess.Write, - FileShare.Read, - IODefaults.FileStreamBufferSize, - true); - await using (fileStream.ConfigureAwait(false)) - { - await stream.CopyToAsync(fileStream).ConfigureAwait(false); - } - } - - Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); - File.WriteAllText(pointerCachePath, fullCachePath); - } - - /// <summary> - /// Gets the full cache path. - /// </summary> - /// <param name="filename">The filename.</param> - /// <returns>System.String.</returns> - private string GetFullCachePath(string filename) - => Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); - } -} diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs deleted file mode 100644 index 5e86f04a8..000000000 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ /dev/null @@ -1,83 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - public class BaseRefreshRequest : IReturnVoid - { - [ApiMember(Name = "MetadataRefreshMode", Description = "Specifies the metadata refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public MetadataRefreshMode MetadataRefreshMode { get; set; } - - [ApiMember(Name = "ImageRefreshMode", Description = "Specifies the image refresh mode", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public MetadataRefreshMode ImageRefreshMode { get; set; } - - [ApiMember(Name = "ReplaceAllMetadata", Description = "Determines if metadata should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool ReplaceAllMetadata { get; set; } - - [ApiMember(Name = "ReplaceAllImages", Description = "Determines if images should be replaced. Only applicable if mode is FullRefresh", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool ReplaceAllImages { get; set; } - } - - [Route("/Items/{Id}/Refresh", "POST", Summary = "Refreshes metadata for an item")] - public class RefreshItem : BaseRefreshRequest - { - [ApiMember(Name = "Recursive", Description = "Indicates if the refresh should occur recursively.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Recursive { get; set; } - - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Authenticated] - public class ItemRefreshService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly IProviderManager _providerManager; - private readonly IFileSystem _fileSystem; - - public ItemRefreshService( - ILogger<ItemRefreshService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILibraryManager libraryManager, - IProviderManager providerManager, - IFileSystem fileSystem) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _libraryManager = libraryManager; - _providerManager = providerManager; - _fileSystem = fileSystem; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(RefreshItem request) - { - var item = _libraryManager.GetItemById(request.Id); - - var options = GetRefreshOptions(request); - - _providerManager.QueueRefresh(item.Id, options, RefreshPriority.High); - } - - private MetadataRefreshOptions GetRefreshOptions(RefreshItem request) - { - return new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - MetadataRefreshMode = request.MetadataRefreshMode, - ImageRefreshMode = request.ImageRefreshMode, - ReplaceAllImages = request.ReplaceAllImages, - ReplaceAllMetadata = request.ReplaceAllMetadata, - ForceSave = request.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || request.ImageRefreshMode == MetadataRefreshMode.FullRefresh || request.ReplaceAllImages || request.ReplaceAllMetadata, - IsAutomated = false - }; - } - } -} diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs deleted file mode 100644 index 2db6d717a..000000000 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ /dev/null @@ -1,396 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Items/{ItemId}", "POST", Summary = "Updates an item")] - public class UpdateItem : BaseItemDto, IReturnVoid - { - [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string ItemId { get; set; } - } - - [Route("/Items/{ItemId}/MetadataEditor", "GET", Summary = "Gets metadata editor info for an item")] - public class GetMetadataEditorInfo : IReturn<MetadataEditorInfo> - { - [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string ItemId { get; set; } - } - - [Route("/Items/{ItemId}/ContentType", "POST", Summary = "Updates an item's content type")] - public class UpdateItemContentType : IReturnVoid - { - [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid ItemId { get; set; } - - [ApiMember(Name = "ContentType", Description = "The content type of the item", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ContentType { get; set; } - } - - [Authenticated(Roles = "admin")] - public class ItemUpdateService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly IProviderManager _providerManager; - private readonly ILocalizationManager _localizationManager; - private readonly IFileSystem _fileSystem; - - public ItemUpdateService( - ILogger<ItemUpdateService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IFileSystem fileSystem, - ILibraryManager libraryManager, - IProviderManager providerManager, - ILocalizationManager localizationManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _libraryManager = libraryManager; - _providerManager = providerManager; - _localizationManager = localizationManager; - _fileSystem = fileSystem; - } - - public object Get(GetMetadataEditorInfo request) - { - var item = _libraryManager.GetItemById(request.ItemId); - - var info = new MetadataEditorInfo - { - ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(), - ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(), - Countries = _localizationManager.GetCountries().ToArray(), - Cultures = _localizationManager.GetCultures().ToArray() - }; - - if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName) && - item.SourceType == SourceType.Library) - { - var inheritedContentType = _libraryManager.GetInheritedContentType(item); - var configuredContentType = _libraryManager.GetConfiguredContentType(item); - - if (string.IsNullOrWhiteSpace(inheritedContentType) || !string.IsNullOrWhiteSpace(configuredContentType)) - { - info.ContentTypeOptions = GetContentTypeOptions(true).ToArray(); - info.ContentType = configuredContentType; - - if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) - { - info.ContentTypeOptions = info.ContentTypeOptions - .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - } - } - } - - return ToOptimizedResult(info); - } - - public void Post(UpdateItemContentType request) - { - var item = _libraryManager.GetItemById(request.ItemId); - var path = item.ContainingFolderPath; - - var types = ServerConfigurationManager.Configuration.ContentTypes - .Where(i => !string.IsNullOrWhiteSpace(i.Name)) - .Where(i => !string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (!string.IsNullOrWhiteSpace(request.ContentType)) - { - types.Add(new NameValuePair - { - Name = path, - Value = request.ContentType - }); - } - - ServerConfigurationManager.Configuration.ContentTypes = types.ToArray(); - ServerConfigurationManager.SaveConfiguration(); - } - - private List<NameValuePair> GetContentTypeOptions(bool isForItem) - { - var list = new List<NameValuePair>(); - - if (isForItem) - { - list.Add(new NameValuePair - { - Name = "Inherit", - Value = "" - }); - } - - list.Add(new NameValuePair - { - Name = "Movies", - Value = "movies" - }); - list.Add(new NameValuePair - { - Name = "Music", - Value = "music" - }); - list.Add(new NameValuePair - { - Name = "Shows", - Value = "tvshows" - }); - - if (!isForItem) - { - list.Add(new NameValuePair - { - Name = "Books", - Value = "books" - }); - } - - list.Add(new NameValuePair - { - Name = "HomeVideos", - Value = "homevideos" - }); - list.Add(new NameValuePair - { - Name = "MusicVideos", - Value = "musicvideos" - }); - list.Add(new NameValuePair - { - Name = "Photos", - Value = "photos" - }); - - if (!isForItem) - { - list.Add(new NameValuePair - { - Name = "MixedContent", - Value = "" - }); - } - - foreach (var val in list) - { - val.Name = _localizationManager.GetLocalizedString(val.Name); - } - - return list; - } - - public void Post(UpdateItem request) - { - var item = _libraryManager.GetItemById(request.ItemId); - - var newLockData = request.LockData ?? false; - var isLockedChanged = item.IsLocked != newLockData; - - var series = item as Series; - var displayOrderChanged = series != null && !string.Equals(series.DisplayOrder ?? string.Empty, request.DisplayOrder ?? string.Empty, StringComparison.OrdinalIgnoreCase); - - // Do this first so that metadata savers can pull the updates from the database. - if (request.People != null) - { - _libraryManager.UpdatePeople(item, request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList()); - } - - UpdateItem(request, item); - - item.OnMetadataChanged(); - - item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - - if (isLockedChanged && item.IsFolder) - { - var folder = (Folder)item; - - foreach (var child in folder.GetRecursiveChildren()) - { - child.IsLocked = newLockData; - child.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - } - } - - if (displayOrderChanged) - { - _providerManager.QueueRefresh( - series.Id, - new MetadataRefreshOptions(new DirectoryService(_fileSystem)) - { - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - ReplaceAllMetadata = true - }, - RefreshPriority.High); - } - } - - private DateTime NormalizeDateTime(DateTime val) - { - return DateTime.SpecifyKind(val, DateTimeKind.Utc); - } - - private void UpdateItem(BaseItemDto request, BaseItem item) - { - item.Name = request.Name; - item.ForcedSortName = request.ForcedSortName; - - item.OriginalTitle = string.IsNullOrWhiteSpace(request.OriginalTitle) ? null : request.OriginalTitle; - - item.CriticRating = request.CriticRating; - - item.CommunityRating = request.CommunityRating; - item.IndexNumber = request.IndexNumber; - item.ParentIndexNumber = request.ParentIndexNumber; - item.Overview = request.Overview; - item.Genres = request.Genres; - - if (item is Episode episode) - { - episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber; - episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber; - episode.AirsBeforeSeasonNumber = request.AirsBeforeSeasonNumber; - } - - item.Tags = request.Tags; - - if (request.Taglines != null) - { - item.Tagline = request.Taglines.FirstOrDefault(); - } - - if (request.Studios != null) - { - item.Studios = request.Studios.Select(x => x.Name).ToArray(); - } - - if (request.DateCreated.HasValue) - { - item.DateCreated = NormalizeDateTime(request.DateCreated.Value); - } - - 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 = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating; - item.CustomRating = request.CustomRating; - - if (request.ProductionLocations != null) - { - item.ProductionLocations = request.ProductionLocations; - } - - item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode; - item.PreferredMetadataLanguage = request.PreferredMetadataLanguage; - - if (item is IHasDisplayOrder hasDisplayOrder) - { - hasDisplayOrder.DisplayOrder = request.DisplayOrder; - } - - if (item is IHasAspectRatio hasAspectRatio) - { - hasAspectRatio.AspectRatio = request.AspectRatio; - } - - item.IsLocked = request.LockData ?? false; - - if (request.LockedFields != null) - { - item.LockedFields = request.LockedFields; - } - - // Only allow this for series. Runtimes for media comes from ffprobe. - if (item is Series) - { - item.RunTimeTicks = request.RunTimeTicks; - } - - foreach (var pair in request.ProviderIds.ToList()) - { - if (string.IsNullOrEmpty(pair.Value)) - { - request.ProviderIds.Remove(pair.Key); - } - } - - item.ProviderIds = request.ProviderIds; - - if (item is Video video) - { - video.Video3DFormat = request.Video3DFormat; - } - - if (request.AlbumArtists != null) - { - if (item is IHasAlbumArtist hasAlbumArtists) - { - hasAlbumArtists.AlbumArtists = request - .AlbumArtists - .Select(i => i.Name) - .ToArray(); - } - } - - if (request.ArtistItems != null) - { - if (item is IHasArtist hasArtists) - { - hasArtists.Artists = request - .ArtistItems - .Select(i => i.Name) - .ToArray(); - } - } - - if (item is Audio song) - { - song.Album = request.Album; - } - - if (item is MusicVideo musicVideo) - { - musicVideo.Album = request.Album; - } - - if (item is Series series) - { - series.Status = GetSeriesStatus(request); - - if (request.AirDays != null) - { - series.AirDays = request.AirDays; - series.AirTime = request.AirTime; - } - } - } - - private SeriesStatus? GetSeriesStatus(BaseItemDto item) - { - if (string.IsNullOrEmpty(item.Status)) - { - return null; - } - - return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true); - } - } -} diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs deleted file mode 100644 index c0146dfee..000000000 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ /dev/null @@ -1,1110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Api.Movies; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; - -namespace MediaBrowser.Api.Library -{ - [Route("/Items/{Id}/File", "GET", Summary = "Gets the original file of an item")] - [Authenticated] - public class GetFile - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// <summary> - /// Class GetCriticReviews - /// </summary> - [Route("/Items/{Id}/CriticReviews", "GET", Summary = "Gets critic reviews for an item")] - [Authenticated] - public class GetCriticReviews : IReturn<QueryResult<BaseItemDto>> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - } - - /// <summary> - /// Class GetThemeSongs - /// </summary> - [Route("/Items/{Id}/ThemeSongs", "GET", Summary = "Gets theme songs for an item")] - [Authenticated] - public class GetThemeSongs : IReturn<ThemeMediaResult> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool InheritFromParent { get; set; } - } - - /// <summary> - /// Class GetThemeVideos - /// </summary> - [Route("/Items/{Id}/ThemeVideos", "GET", Summary = "Gets theme videos for an item")] - [Authenticated] - public class GetThemeVideos : IReturn<ThemeMediaResult> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool InheritFromParent { get; set; } - } - - /// <summary> - /// Class GetThemeVideos - /// </summary> - [Route("/Items/{Id}/ThemeMedia", "GET", Summary = "Gets theme videos and songs for an item")] - [Authenticated] - public class GetThemeMedia : IReturn<AllThemeMediaResult> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool InheritFromParent { get; set; } - } - - [Route("/Library/Refresh", "POST", Summary = "Starts a library scan")] - [Authenticated(Roles = "Admin")] - public class RefreshLibrary : IReturnVoid - { - } - - [Route("/Items/{Id}", "DELETE", Summary = "Deletes an item from the library and file system")] - [Authenticated] - public class DeleteItem : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Items", "DELETE", Summary = "Deletes an item from the library and file system")] - [Authenticated] - public class DeleteItems : IReturnVoid - { - [ApiMember(Name = "Ids", Description = "Ids", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Ids { get; set; } - } - - [Route("/Items/Counts", "GET")] - [Authenticated] - public class GetItemCounts : IReturn<ItemCounts> - { - [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "IsFavorite", Description = "Optional. Get counts of favorite items", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsFavorite { get; set; } - } - - [Route("/Items/{Id}/Ancestors", "GET", Summary = "Gets all parents of an item")] - [Authenticated] - public class GetAncestors : IReturn<BaseItemDto[]> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// <summary> - /// Class GetPhyscialPaths - /// </summary> - [Route("/Library/PhysicalPaths", "GET", Summary = "Gets a list of physical paths from virtual folders")] - [Authenticated(Roles = "Admin")] - public class GetPhyscialPaths : IReturn<List<string>> - { - } - - [Route("/Library/MediaFolders", "GET", Summary = "Gets all user media folders.")] - [Authenticated] - public class GetMediaFolders : IReturn<QueryResult<BaseItemDto>> - { - [ApiMember(Name = "IsHidden", Description = "Optional. Filter by folders that are marked hidden, or not.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? IsHidden { get; set; } - } - - [Route("/Library/Series/Added", "POST", Summary = "Reports that new episodes of a series have been added by an external source")] - [Route("/Library/Series/Updated", "POST", Summary = "Reports that new episodes of a series have been added by an external source")] - [Authenticated] - public class PostUpdatedSeries : IReturnVoid - { - [ApiMember(Name = "TvdbId", Description = "Tvdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")] - public string TvdbId { get; set; } - } - - [Route("/Library/Movies/Added", "POST", Summary = "Reports that new movies have been added by an external source")] - [Route("/Library/Movies/Updated", "POST", Summary = "Reports that new movies have been added by an external source")] - [Authenticated] - public class PostUpdatedMovies : IReturnVoid - { - [ApiMember(Name = "TmdbId", Description = "Tmdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")] - public string TmdbId { get; set; } - [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "POST")] - public string ImdbId { get; set; } - } - - public class MediaUpdateInfo - { - public string Path { get; set; } - - // Created, Modified, Deleted - public string UpdateType { get; set; } - } - - [Route("/Library/Media/Updated", "POST", Summary = "Reports that new movies have been added by an external source")] - [Authenticated] - public class PostUpdatedMedia : IReturnVoid - { - [ApiMember(Name = "Updates", Description = "A list of updated media paths", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")] - public List<MediaUpdateInfo> Updates { get; set; } - } - - [Route("/Items/{Id}/Download", "GET", Summary = "Downloads item media")] - [Authenticated(Roles = "download")] - public class GetDownload - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] - [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")] - [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] - [Route("/Shows/{Id}/Similar", "GET", Summary = "Finds tv shows similar to a given one.")] - [Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")] - [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] - [Authenticated] - public class GetSimilarItems : BaseGetSimilarItemsFromItem - { - } - - [Route("/Libraries/AvailableOptions", "GET")] - [Authenticated(AllowBeforeStartupWizard = true)] - public class GetLibraryOptionsInfo : IReturn<LibraryOptionsResult> - { - public string LibraryContentType { get; set; } - public bool IsNewLibrary { get; set; } - } - - public class LibraryOptionInfo - { - public string Name { get; set; } - public bool DefaultEnabled { get; set; } - } - - public class LibraryOptionsResult - { - public LibraryOptionInfo[] MetadataSavers { get; set; } - public LibraryOptionInfo[] MetadataReaders { get; set; } - public LibraryOptionInfo[] SubtitleFetchers { get; set; } - public LibraryTypeOptions[] TypeOptions { get; set; } - } - - public class LibraryTypeOptions - { - public string Type { get; set; } - public LibraryOptionInfo[] MetadataFetchers { get; set; } - public LibraryOptionInfo[] ImageFetchers { get; set; } - public ImageType[] SupportedImageTypes { get; set; } - public ImageOption[] DefaultImageOptions { get; set; } - } - - /// <summary> - /// Class LibraryService - /// </summary> - public class LibraryService : BaseApiService - { - private readonly IProviderManager _providerManager; - private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - private readonly IActivityManager _activityManager; - private readonly ILocalizationManager _localization; - private readonly ILibraryMonitor _libraryMonitor; - - private readonly ILogger<MoviesService> _moviesServiceLogger; - - /// <summary> - /// Initializes a new instance of the <see cref="LibraryService" /> class. - /// </summary> - public LibraryService( - ILogger<LibraryService> logger, - ILogger<MoviesService> moviesServiceLogger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IProviderManager providerManager, - ILibraryManager libraryManager, - IUserManager userManager, - IDtoService dtoService, - IAuthorizationContext authContext, - IActivityManager activityManager, - ILocalizationManager localization, - ILibraryMonitor libraryMonitor) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _providerManager = providerManager; - _libraryManager = libraryManager; - _userManager = userManager; - _dtoService = dtoService; - _authContext = authContext; - _activityManager = activityManager; - _localization = localization; - _libraryMonitor = libraryMonitor; - _moviesServiceLogger = moviesServiceLogger; - } - - private string[] GetRepresentativeItemTypes(string contentType) - { - return contentType switch - { - CollectionType.BoxSets => new[] {"BoxSet"}, - CollectionType.Playlists => new[] {"Playlist"}, - CollectionType.Movies => new[] {"Movie"}, - CollectionType.TvShows => new[] {"Series", "Season", "Episode"}, - CollectionType.Books => new[] {"Book"}, - CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"}, - CollectionType.HomeVideos => new[] {"Video", "Photo"}, - CollectionType.Photos => new[] {"Video", "Photo"}, - CollectionType.MusicVideos => new[] {"MusicVideo"}, - _ => new[] {"Series", "Season", "Episode", "Movie"} - }; - } - - private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary) - { - if (isNewLibrary) - { - return false; - } - - var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions - .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) - .ToArray(); - - if (metadataOptions.Length == 0) - { - return true; - } - - return metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase)); - } - - private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary) - { - if (isNewLibrary) - { - if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) - { - return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase) - || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase) - || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase)); - } - - return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase); - } - - var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions - .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - - return metadataOptions.Length == 0 - || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); - } - - private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary) - { - if (isNewLibrary) - { - if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) - { - return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase) - && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase) - && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase) - && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase); - } - - return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase); - } - - var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions - .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - - if (metadataOptions.Length == 0) - { - return true; - } - - return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); - } - - public object Get(GetLibraryOptionsInfo request) - { - var result = new LibraryOptionsResult(); - - var types = GetRepresentativeItemTypes(request.LibraryContentType); - var isNewLibrary = request.IsNewLibrary; - var typesList = types.ToList(); - - var plugins = _providerManager.GetAllMetadataPlugins() - .Where(i => types.Contains(i.ItemType, StringComparer.OrdinalIgnoreCase)) - .OrderBy(i => typesList.IndexOf(i.ItemType)) - .ToList(); - - result.MetadataSavers = plugins - .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver)) - .Select(i => new LibraryOptionInfo - { - Name = i.Name, - DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary) - }) - .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) - .Select(x => x.First()) - .ToArray(); - - result.MetadataReaders = plugins - .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider)) - .Select(i => new LibraryOptionInfo - { - Name = i.Name, - DefaultEnabled = true - }) - .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) - .Select(x => x.First()) - .ToArray(); - - result.SubtitleFetchers = plugins - .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher)) - .Select(i => new LibraryOptionInfo - { - Name = i.Name, - DefaultEnabled = true - }) - .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) - .Select(x => x.First()) - .ToArray(); - - var typeOptions = new List<LibraryTypeOptions>(); - - foreach (var type in types) - { - TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions); - - typeOptions.Add(new LibraryTypeOptions - { - Type = type, - - MetadataFetchers = plugins - .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) - .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher)) - .Select(i => new LibraryOptionInfo - { - Name = i.Name, - DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary) - }) - .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) - .Select(x => x.First()) - .ToArray(), - - ImageFetchers = plugins - .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) - .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher)) - .Select(i => new LibraryOptionInfo - { - Name = i.Name, - DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary) - }) - .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) - .Select(x => x.First()) - .ToArray(), - - SupportedImageTypes = plugins - .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) - .SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>()) - .Distinct() - .ToArray(), - - DefaultImageOptions = defaultImageOptions ?? Array.Empty<ImageOption>() - }); - } - - result.TypeOptions = typeOptions.ToArray(); - - return result; - } - - public object Get(GetSimilarItems request) - { - var item = string.IsNullOrEmpty(request.Id) ? - (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : - _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - - var program = item as IHasProgramAttributes; - - if (item is Movie || (program != null && program.IsMovie) || item is Trailer) - { - return new MoviesService( - _moviesServiceLogger, - ServerConfigurationManager, - ResultFactory, - _userManager, - _libraryManager, - _dtoService, - _authContext) - { - Request = Request, - - }.GetSimilarItemsResult(request); - } - - if (program != null && program.IsSeries) - { - return GetSimilarItemsResult(request, new[] { typeof(Series).Name }); - } - - if (item is Episode || (item is IItemByName && !(item is MusicArtist))) - { - return new QueryResult<BaseItemDto>(); - } - - return GetSimilarItemsResult(request, new[] { item.GetType().Name }); - } - - private QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, string[] includeItemTypes) - { - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - var item = string.IsNullOrEmpty(request.Id) ? - (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : - _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var query = new InternalItemsQuery(user) - { - Limit = request.Limit, - IncludeItemTypes = includeItemTypes, - SimilarTo = item, - DtoOptions = dtoOptions, - EnableTotalRecordCount = false - }; - - // ExcludeArtistIds - if (!string.IsNullOrEmpty(request.ExcludeArtistIds)) - { - query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds); - } - - List<BaseItem> itemsResult; - - if (item is MusicArtist) - { - query.IncludeItemTypes = Array.Empty<string>(); - - itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList(); - } - else - { - itemsResult = _libraryManager.GetItemList(query); - } - - var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); - - var result = new QueryResult<BaseItemDto> - { - Items = returnList, - TotalRecordCount = itemsResult.Count - }; - - return result; - } - - public object Get(GetMediaFolders request) - { - var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList(); - - if (request.IsHidden.HasValue) - { - var val = request.IsHidden.Value; - - items = items.Where(i => i.IsHidden == val).ToList(); - } - - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = new QueryResult<BaseItemDto> - { - TotalRecordCount = items.Count, - - Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray() - }; - - return result; - } - - public void Post(PostUpdatedSeries request) - { - var series = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Series).Name }, - DtoOptions = new DtoOptions(false) - { - EnableImages = false - } - - }).Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); - - foreach (var item in series) - { - _libraryMonitor.ReportFileSystemChanged(item.Path); - } - } - - public void Post(PostUpdatedMedia request) - { - if (request.Updates != null) - { - foreach (var item in request.Updates) - { - _libraryMonitor.ReportFileSystemChanged(item.Path); - } - } - } - - public void Post(PostUpdatedMovies request) - { - var movies = _libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Movie).Name }, - DtoOptions = new DtoOptions(false) - { - EnableImages = false - } - - }); - - if (!string.IsNullOrWhiteSpace(request.ImdbId)) - { - movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToList(); - } - else if (!string.IsNullOrWhiteSpace(request.TmdbId)) - { - movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList(); - } - else - { - movies = new List<BaseItem>(); - } - - foreach (var item in movies) - { - _libraryMonitor.ReportFileSystemChanged(item.Path); - } - } - - public Task<object> Get(GetDownload request) - { - var item = _libraryManager.GetItemById(request.Id); - var auth = _authContext.GetAuthorizationInfo(Request); - - var user = auth.User; - - if (user != null) - { - if (!item.CanDownload(user)) - { - throw new ArgumentException("Item does not support downloading"); - } - } - else - { - if (!item.CanDownload()) - { - throw new ArgumentException("Item does not support downloading"); - } - } - - var headers = new Dictionary<string, string>(); - - if (user != null) - { - LogDownload(item, user, auth); - } - - var path = item.Path; - - // Quotes are valid in linux. They'll possibly cause issues here - var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); - if (!string.IsNullOrWhiteSpace(filename)) - { - // Kestrel doesn't support non-ASCII characters in headers - if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]")) - { - // Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2 - headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename); - } - else - { - headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\""; - } - } - - return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - Path = path, - ResponseHeaders = headers - }); - } - - private void LogDownload(BaseItem item, User user, AuthorizationInfo auth) - { - try - { - _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog( - string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name), - "UserDownloadingContent", - auth.UserId) - { - ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device), - }); - } - catch - { - // Logged at lower levels - } - } - - public Task<object> Get(GetFile request) - { - var item = _libraryManager.GetItemById(request.Id); - - return ResultFactory.GetStaticFileResult(Request, item.Path); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPhyscialPaths request) - { - var result = _libraryManager.RootFolder.Children - .SelectMany(c => c.PhysicalLocations) - .ToList(); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetAncestors request) - { - var result = GetAncestors(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the ancestors. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{BaseItemDto[]}.</returns> - public List<BaseItemDto> GetAncestors(GetAncestors request) - { - var item = _libraryManager.GetItemById(request.Id); - - var baseItemDtos = new List<BaseItemDto>(); - - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - var dtoOptions = GetDtoOptions(_authContext, request); - - BaseItem parent = item.GetParent(); - - while (parent != null) - { - if (user != null) - { - parent = TranslateParentItem(parent, user); - } - - baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user)); - - parent = parent.GetParent(); - } - - return baseItemDtos; - } - - private BaseItem TranslateParentItem(BaseItem item, User user) - { - return item.GetParent() is AggregateFolder - ? _libraryManager.GetUserRootFolder().GetChildren(user, true) - .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)) - : item; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetCriticReviews request) - { - return new QueryResult<BaseItemDto>(); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetItemCounts request) - { - var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); - - var counts = new ItemCounts - { - AlbumCount = GetCount(typeof(MusicAlbum), user, request), - EpisodeCount = GetCount(typeof(Episode), user, request), - MovieCount = GetCount(typeof(Movie), user, request), - SeriesCount = GetCount(typeof(Series), user, request), - SongCount = GetCount(typeof(Audio), user, request), - MusicVideoCount = GetCount(typeof(MusicVideo), user, request), - BoxSetCount = GetCount(typeof(BoxSet), user, request), - BookCount = GetCount(typeof(Book), user, request) - }; - - return ToOptimizedResult(counts); - } - - private int GetCount(Type type, User user, GetItemCounts request) - { - var query = new InternalItemsQuery(user) - { - IncludeItemTypes = new[] { type.Name }, - Limit = 0, - Recursive = true, - IsVirtualItem = false, - IsFavorite = request.IsFavorite, - DtoOptions = new DtoOptions(false) - { - EnableImages = false - } - }; - - return _libraryManager.GetItemsResult(query).TotalRecordCount; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public async Task Post(RefreshLibrary request) - { - try - { - await _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error refreshing library"); - } - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(DeleteItems request) - { - var ids = string.IsNullOrWhiteSpace(request.Ids) - ? Array.Empty<string>() - : request.Ids.Split(','); - - foreach (var i in ids) - { - var item = _libraryManager.GetItemById(i); - var auth = _authContext.GetAuthorizationInfo(Request); - var user = auth.User; - - if (!item.CanDelete(user)) - { - if (ids.Length > 1) - { - throw new SecurityException("Unauthorized access"); - } - - continue; - } - - _libraryManager.DeleteItem(item, new DeleteOptions - { - DeleteFileLocation = true - }, true); - } - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(DeleteItem request) - { - Delete(new DeleteItems - { - Ids = request.Id - }); - } - - public object Get(GetThemeMedia request) - { - var themeSongs = GetThemeSongs(new GetThemeSongs - { - InheritFromParent = request.InheritFromParent, - Id = request.Id, - UserId = request.UserId - - }); - - var themeVideos = GetThemeVideos(new GetThemeVideos - { - InheritFromParent = request.InheritFromParent, - Id = request.Id, - UserId = request.UserId - - }); - - return ToOptimizedResult(new AllThemeMediaResult - { - ThemeSongsResult = themeSongs, - ThemeVideosResult = themeVideos, - - SoundtrackSongsResult = new ThemeMediaResult() - }); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetThemeSongs request) - { - var result = GetThemeSongs(request); - - return ToOptimizedResult(result); - } - - private ThemeMediaResult GetThemeSongs(GetThemeSongs request) - { - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - var item = string.IsNullOrEmpty(request.Id) - ? (!request.UserId.Equals(Guid.Empty) - ? _libraryManager.GetUserRootFolder() - : _libraryManager.RootFolder) - : _libraryManager.GetItemById(request.Id); - - if (item == null) - { - throw new ResourceNotFoundException("Item not found."); - } - - IEnumerable<BaseItem> themeItems; - - while (true) - { - themeItems = item.GetThemeSongs(); - - if (themeItems.Any() || !request.InheritFromParent) - { - break; - } - - var parent = item.GetParent(); - if (parent == null) - { - break; - } - item = parent; - } - - var dtoOptions = GetDtoOptions(_authContext, request); - var items = themeItems - .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) - .ToArray(); - - return new ThemeMediaResult - { - Items = items, - TotalRecordCount = items.Length, - OwnerId = item.Id - }; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetThemeVideos request) - { - return ToOptimizedResult(GetThemeVideos(request)); - } - - public ThemeMediaResult GetThemeVideos(GetThemeVideos request) - { - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - var item = string.IsNullOrEmpty(request.Id) - ? (!request.UserId.Equals(Guid.Empty) - ? _libraryManager.GetUserRootFolder() - : _libraryManager.RootFolder) - : _libraryManager.GetItemById(request.Id); - - if (item == null) - { - throw new ResourceNotFoundException("Item not found."); - } - - IEnumerable<BaseItem> themeItems; - - while (true) - { - themeItems = item.GetThemeVideos(); - - if (themeItems.Any() || !request.InheritFromParent) - { - break; - } - - var parent = item.GetParent(); - if (parent == null) - { - break; - } - item = parent; - } - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = themeItems - .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) - .ToArray(); - - return new ThemeMediaResult - { - Items = items, - TotalRecordCount = items.Length, - OwnerId = item.Id - }; - } - } -} diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs deleted file mode 100644 index 1e300814f..000000000 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ /dev/null @@ -1,412 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Library -{ - /// <summary> - /// Class GetDefaultVirtualFolders - /// </summary> - [Route("/Library/VirtualFolders", "GET")] - public class GetVirtualFolders : IReturn<List<VirtualFolderInfo>> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - public string UserId { get; set; } - } - - [Route("/Library/VirtualFolders", "POST")] - public class AddVirtualFolder : IReturnVoid - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - - /// <summary> - /// Gets or sets the type of the collection. - /// </summary> - /// <value>The type of the collection.</value> - public string CollectionType { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [refresh library]. - /// </summary> - /// <value><c>true</c> if [refresh library]; otherwise, <c>false</c>.</value> - public bool RefreshLibrary { get; set; } - - /// <summary> - /// Gets or sets the path. - /// </summary> - /// <value>The path.</value> - public string[] Paths { get; set; } - - public LibraryOptions LibraryOptions { get; set; } - } - - [Route("/Library/VirtualFolders", "DELETE")] - public class RemoveVirtualFolder : IReturnVoid - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [refresh library]. - /// </summary> - /// <value><c>true</c> if [refresh library]; otherwise, <c>false</c>.</value> - public bool RefreshLibrary { get; set; } - } - - [Route("/Library/VirtualFolders/Name", "POST")] - public class RenameVirtualFolder : IReturnVoid - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string NewName { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [refresh library]. - /// </summary> - /// <value><c>true</c> if [refresh library]; otherwise, <c>false</c>.</value> - public bool RefreshLibrary { get; set; } - } - - [Route("/Library/VirtualFolders/Paths", "POST")] - public class AddMediaPath : IReturnVoid - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Path { get; set; } - - public MediaPathInfo PathInfo { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [refresh library]. - /// </summary> - /// <value><c>true</c> if [refresh library]; otherwise, <c>false</c>.</value> - public bool RefreshLibrary { get; set; } - } - - [Route("/Library/VirtualFolders/Paths/Update", "POST")] - public class UpdateMediaPath : IReturnVoid - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - - public MediaPathInfo PathInfo { get; set; } - } - - [Route("/Library/VirtualFolders/Paths", "DELETE")] - public class RemoveMediaPath : IReturnVoid - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Name { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - public string Path { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [refresh library]. - /// </summary> - /// <value><c>true</c> if [refresh library]; otherwise, <c>false</c>.</value> - 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> - [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] - public class LibraryStructureService : BaseApiService - { - /// <summary> - /// The _app paths - /// </summary> - private readonly IServerApplicationPaths _appPaths; - - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - private readonly ILibraryMonitor _libraryMonitor; - - - /// <summary> - /// Initializes a new instance of the <see cref="LibraryStructureService" /> class. - /// </summary> - public LibraryStructureService( - ILogger<LibraryStructureService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILibraryManager libraryManager, - ILibraryMonitor libraryMonitor) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _appPaths = serverConfigurationManager.ApplicationPaths; - _libraryManager = libraryManager; - _libraryMonitor = libraryMonitor; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetVirtualFolders request) - { - var result = _libraryManager.GetVirtualFolders(true); - - return ToOptimizedResult(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 Task Post(AddVirtualFolder request) - { - var libraryOptions = request.LibraryOptions ?? new LibraryOptions(); - - if (request.Paths != null && request.Paths.Length > 0) - { - libraryOptions.PathInfos = request.Paths.Select(i => new MediaPathInfo { Path = i }).ToArray(); - } - - return _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, libraryOptions, request.RefreshLibrary); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(RenameVirtualFolder request) - { - if (string.IsNullOrWhiteSpace(request.Name)) - { - throw new ArgumentNullException(nameof(request)); - } - - if (string.IsNullOrWhiteSpace(request.NewName)) - { - throw new ArgumentNullException(nameof(request)); - } - - var rootFolderPath = _appPaths.DefaultUserViewsPath; - - var currentPath = Path.Combine(rootFolderPath, request.Name); - var newPath = Path.Combine(rootFolderPath, request.NewName); - - if (!Directory.Exists(currentPath)) - { - throw new FileNotFoundException("The media collection does not exist"); - } - - if (!string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase) && Directory.Exists(newPath)) - { - throw new ArgumentException("Media library already exists at " + newPath + "."); - } - - _libraryMonitor.Stop(); - - try - { - // Changing capitalization. Handle windows case insensitivity - if (string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase)) - { - var tempPath = Path.Combine(rootFolderPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); - Directory.Move(currentPath, tempPath); - currentPath = tempPath; - } - - Directory.Move(currentPath, newPath); - } - finally - { - CollectionFolder.OnCollectionFolderChange(); - - Task.Run(() => - { - // No need to start if scanning the library because it will handle it - if (request.RefreshLibrary) - { - _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); - - _libraryMonitor.Start(); - } - }); - } - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Delete(RemoveVirtualFolder request) - { - return _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(AddMediaPath request) - { - if (string.IsNullOrWhiteSpace(request.Name)) - { - throw new ArgumentNullException(nameof(request)); - } - - _libraryMonitor.Stop(); - - try - { - var mediaPath = request.PathInfo ?? new MediaPathInfo - { - Path = request.Path - }; - - _libraryManager.AddMediaPath(request.Name, mediaPath); - } - finally - { - Task.Run(() => - { - // No need to start if scanning the library because it will handle it - if (request.RefreshLibrary) - { - _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); - - _libraryMonitor.Start(); - } - }); - } - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(UpdateMediaPath request) - { - if (string.IsNullOrWhiteSpace(request.Name)) - { - throw new ArgumentNullException(nameof(request)); - } - - _libraryManager.UpdateMediaPath(request.Name, request.PathInfo); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(RemoveMediaPath request) - { - if (string.IsNullOrWhiteSpace(request.Name)) - { - throw new ArgumentNullException(nameof(request)); - } - - _libraryMonitor.Stop(); - - try - { - _libraryManager.RemoveMediaPath(request.Name, request.Path); - } - finally - { - Task.Run(() => - { - // No need to start if scanning the library because it will handle it - if (request.RefreshLibrary) - { - _libraryManager.ValidateMediaLibrary(new SimpleProgress<double>(), CancellationToken.None); - } - else - { - // Need to add a delay here or directory watchers may still pick up the changes - var task = Task.Delay(1000); - // Have to block here to allow exceptions to bubble - Task.WaitAll(task); - - _libraryMonitor.Start(); - } - }); - } - } - } -} diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs deleted file mode 100644 index 5fe4c0cca..000000000 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ /dev/null @@ -1,1278 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Api.UserLibrary; -using MediaBrowser.Common; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; - -namespace MediaBrowser.Api.LiveTv -{ - /// <summary> - /// This is insecure right now to avoid windows phone refactoring - /// </summary> - [Route("/LiveTv/Info", "GET", Summary = "Gets available live tv services.")] - [Authenticated] - public class GetLiveTvInfo : IReturn<LiveTvInfo> - { - } - - [Route("/LiveTv/Channels", "GET", Summary = "Gets available live tv channels.")] - [Authenticated] - public class GetChannels : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions - { - [ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public ChannelType? Type { get; set; } - - [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsMovie { get; set; } - - [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSeries { get; set; } - - [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsNews { get; set; } - - [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsKids { get; set; } - - [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSports { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "IsFavorite", Description = "Filter by channels that are favorites, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsFavorite { get; set; } - - [ApiMember(Name = "IsLiked", Description = "Filter by channels that are liked, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsLiked { get; set; } - - [ApiMember(Name = "IsDisliked", Description = "Filter by channels that are disliked, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsDisliked { get; set; } - - [ApiMember(Name = "EnableFavoriteSorting", Description = "Incorporate favorite and like status into channel sorting.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool EnableFavoriteSorting { 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 = "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> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, 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 = "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 string SortBy { get; set; } - - public SortOrder? SortOrder { get; set; } - - /// <summary> - /// Gets the order by. - /// </summary> - /// <returns>IEnumerable{ItemSortBy}.</returns> - public string[] GetOrderBy() - { - var val = SortBy; - - if (string.IsNullOrEmpty(val)) - { - return Array.Empty<string>(); - } - - return val.Split(','); - } - - public GetChannels() - { - AddCurrentProgram = true; - } - } - - [Route("/LiveTv/Channels/{Id}", "GET", Summary = "Gets a live tv channel")] - [Authenticated] - public class GetChannel : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - [Route("/LiveTv/Recordings", "GET", Summary = "Gets live tv recordings")] - [Authenticated] - public class GetRecordings : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions - { - [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ChannelId { get; set; } - - [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "Status", Description = "Optional filter by recording status.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public RecordingStatus? Status { get; set; } - - [ApiMember(Name = "Status", Description = "Optional filter by recordings that are in progress, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsInProgress { get; set; } - - [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by recordings belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SeriesTimerId { 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 = "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> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, 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; } - - 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 bool? IsMovie { get; set; } - public bool? IsSeries { get; set; } - public bool? IsKids { get; set; } - public bool? IsSports { get; set; } - public bool? IsNews { get; set; } - public bool? IsLibraryItem { get; set; } - - public GetRecordings() - { - EnableTotalRecordCount = true; - } - } - - [Route("/LiveTv/Recordings/Series", "GET", Summary = "Gets live tv recordings")] - [Authenticated] - public class GetRecordingSeries : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions - { - [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ChannelId { get; set; } - - [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - - [ApiMember(Name = "GroupId", Description = "Optional filter by recording group.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string GroupId { get; set; } - - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "Status", Description = "Optional filter by recording status.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public RecordingStatus? Status { get; set; } - - [ApiMember(Name = "Status", Description = "Optional filter by recordings that are in progress, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsInProgress { get; set; } - - [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by recordings belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SeriesTimerId { 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 = "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> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, 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; } - - 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 GetRecordingSeries() - { - EnableTotalRecordCount = true; - } - } - - [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")] - [Authenticated] - public class GetRecordingGroups : IReturn<QueryResult<BaseItemDto>> - { - [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - } - - [Route("/LiveTv/Recordings/Folders", "GET", Summary = "Gets recording folders")] - [Authenticated] - public class GetRecordingFolders : IReturn<BaseItemDto[]> - { - [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - [Route("/LiveTv/Recordings/{Id}", "GET", Summary = "Gets a live tv recording")] - [Authenticated] - public class GetRecording : IReturn<BaseItemDto> - { - [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - [Route("/LiveTv/Tuners/{Id}/Reset", "POST", Summary = "Resets a tv tuner")] - [Authenticated] - public class ResetTuner : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Tuner Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/LiveTv/Timers/{Id}", "GET", Summary = "Gets a live tv timer")] - [Authenticated] - public class GetTimer : IReturn<TimerInfoDto> - { - [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/LiveTv/Timers/Defaults", "GET", Summary = "Gets default values for a new timer")] - [Authenticated] - public class GetDefaultTimer : IReturn<SeriesTimerInfoDto> - { - [ApiMember(Name = "ProgramId", Description = "Optional, to attach default values based on a program.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ProgramId { get; set; } - } - - [Route("/LiveTv/Timers", "GET", Summary = "Gets live tv timers")] - [Authenticated] - public class GetTimers : IReturn<QueryResult<TimerInfoDto>> - { - [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ChannelId { get; set; } - - [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by timers belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SeriesTimerId { get; set; } - - public bool? IsActive { get; set; } - - public bool? IsScheduled { get; set; } - } - - [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")] - [Authenticated] - public class GetPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions - { - [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ChannelIds { get; set; } - - [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public Guid UserId { get; set; } - - [ApiMember(Name = "MinStartDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string MinStartDate { get; set; } - - [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasAired { get; set; } - public bool? IsAiring { get; set; } - - [ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string MaxStartDate { get; set; } - - [ApiMember(Name = "MinEndDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string MinEndDate { get; set; } - - [ApiMember(Name = "MaxEndDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string MaxEndDate { get; set; } - - [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsMovie { get; set; } - - [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSeries { get; set; } - - [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsNews { get; set; } - - [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsKids { get; set; } - - [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSports { get; set; } - - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Name, StartDate", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string SortBy { get; set; } - - [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SortOrder { get; set; } - - [ApiMember(Name = "Genres", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string Genres { get; set; } - - [ApiMember(Name = "GenreIds", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string GenreIds { 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; } - - public bool EnableTotalRecordCount { 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; } - - [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? EnableUserData { get; set; } - - public string SeriesTimerId { get; set; } - public Guid LibrarySeriesId { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, 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; } - - public GetPrograms() - { - EnableTotalRecordCount = true; - } - } - - [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")] - [Authenticated] - public class GetRecommendedPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions - { - public bool EnableTotalRecordCount { get; set; } - - public GetRecommendedPrograms() - { - EnableTotalRecordCount = true; - } - - [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public Guid UserId { get; set; } - - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "IsAiring", Description = "Optional. Filter by programs that are currently airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsAiring { get; set; } - - [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasAired { get; set; } - - [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSeries { get; set; } - - [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsMovie { get; set; } - - [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsNews { get; set; } - - [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsKids { get; set; } - - [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSports { 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 = "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; } - - [ApiMember(Name = "GenreIds", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string GenreIds { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, 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")] - [Authenticated] - public class GetProgram : IReturn<BaseItemDto> - { - [ApiMember(Name = "Id", Description = "Program Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - - [Route("/LiveTv/Recordings/{Id}", "DELETE", Summary = "Deletes a live tv recording")] - [Authenticated] - public class DeleteRecording : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } - } - - [Route("/LiveTv/Timers/{Id}", "DELETE", Summary = "Cancels a live tv timer")] - [Authenticated] - public class CancelTimer : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/LiveTv/Timers/{Id}", "POST", Summary = "Updates a live tv timer")] - [Authenticated] - public class UpdateTimer : TimerInfoDto, IReturnVoid - { - } - - [Route("/LiveTv/Timers", "POST", Summary = "Creates a live tv timer")] - [Authenticated] - public class CreateTimer : TimerInfoDto, IReturnVoid - { - } - - [Route("/LiveTv/SeriesTimers/{Id}", "GET", Summary = "Gets a live tv series timer")] - [Authenticated] - public class GetSeriesTimer : IReturn<TimerInfoDto> - { - [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/LiveTv/SeriesTimers", "GET", Summary = "Gets live tv series timers")] - [Authenticated] - public class GetSeriesTimers : IReturn<QueryResult<SeriesTimerInfoDto>> - { - [ApiMember(Name = "SortBy", Description = "Optional. Sort by SortName or Priority", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string SortBy { get; set; } - - [ApiMember(Name = "SortOrder", Description = "Optional. Sort in Ascending or Descending order", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public SortOrder SortOrder { get; set; } - } - - [Route("/LiveTv/SeriesTimers/{Id}", "DELETE", Summary = "Cancels a live tv series timer")] - [Authenticated] - public class CancelSeriesTimer : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/LiveTv/SeriesTimers/{Id}", "POST", Summary = "Updates a live tv series timer")] - [Authenticated] - public class UpdateSeriesTimer : SeriesTimerInfoDto, IReturnVoid - { - } - - [Route("/LiveTv/SeriesTimers", "POST", Summary = "Creates a live tv series timer")] - [Authenticated] - public class CreateSeriesTimer : SeriesTimerInfoDto, IReturnVoid - { - } - - [Route("/LiveTv/Recordings/Groups/{Id}", "GET", Summary = "Gets a recording group")] - [Authenticated] - public class GetRecordingGroup : IReturn<BaseItemDto> - { - [ApiMember(Name = "Id", Description = "Recording group Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/LiveTv/GuideInfo", "GET", Summary = "Gets guide info")] - [Authenticated] - public class GetGuideInfo : IReturn<GuideInfo> - { - } - - [Route("/LiveTv/TunerHosts", "POST", Summary = "Adds a tuner host")] - [Authenticated] - public class AddTunerHost : TunerHostInfo, IReturn<TunerHostInfo> - { - } - - [Route("/LiveTv/TunerHosts", "DELETE", Summary = "Deletes a tuner host")] - [Authenticated] - public class DeleteTunerHost : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Tuner host id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/LiveTv/ListingProviders/Default", "GET")] - [Authenticated] - public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo> - { - } - - [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")] - [Authenticated] - public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo> - { - public bool ValidateLogin { get; set; } - public bool ValidateListings { get; set; } - public string Pw { get; set; } - } - - [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")] - [Authenticated] - public class DeleteListingProvider : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")] - [Authenticated] - public class GetLineups : IReturn<List<NameIdPair>> - { - [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "Type", Description = "Provider Type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Type { get; set; } - - [ApiMember(Name = "Location", Description = "Location", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Location { get; set; } - - [ApiMember(Name = "Country", Description = "Country", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Country { get; set; } - } - - [Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")] - [Authenticated] - public class GetSchedulesDirectCountries - { - } - - [Route("/LiveTv/ChannelMappingOptions")] - [Authenticated] - public class GetChannelMappingOptions - { - [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query")] - public string ProviderId { get; set; } - } - - [Route("/LiveTv/ChannelMappings")] - [Authenticated] - public class SetChannelMapping - { - [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query")] - public string ProviderId { get; set; } - public string TunerChannelId { get; set; } - public string ProviderChannelId { get; set; } - } - - public class ChannelMappingOptions - { - public List<TunerChannelMapping> TunerChannels { get; set; } - public List<NameIdPair> ProviderChannels { get; set; } - public NameValuePair[] Mappings { get; set; } - public string ProviderName { get; set; } - } - - [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")] - public class GetLiveStreamFile - { - public string Id { get; set; } - public string Container { get; set; } - } - - [Route("/LiveTv/LiveRecordings/{Id}/stream", "GET", Summary = "Gets a live tv channel")] - public class GetLiveRecordingFile - { - public string Id { get; set; } - } - - [Route("/LiveTv/TunerHosts/Types", "GET")] - [Authenticated] - public class GetTunerHostTypes : IReturn<List<NameIdPair>> - { - - } - - [Route("/LiveTv/Tuners/Discvover", "GET")] - [Authenticated] - public class DiscoverTuners : IReturn<List<TunerHostInfo>> - { - public bool NewDevicesOnly { get; set; } - } - - public class LiveTvService : BaseApiService - { - private readonly ILiveTvManager _liveTvManager; - private readonly IUserManager _userManager; - private readonly IHttpClient _httpClient; - private readonly ILibraryManager _libraryManager; - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - private readonly ISessionContext _sessionContext; - private readonly IStreamHelper _streamHelper; - private readonly IMediaSourceManager _mediaSourceManager; - - public LiveTvService( - ILogger<LiveTvService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IMediaSourceManager mediaSourceManager, - IStreamHelper streamHelper, - ILiveTvManager liveTvManager, - IUserManager userManager, - IHttpClient httpClient, - ILibraryManager libraryManager, - IDtoService dtoService, - IAuthorizationContext authContext, - ISessionContext sessionContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _mediaSourceManager = mediaSourceManager; - _streamHelper = streamHelper; - _liveTvManager = liveTvManager; - _userManager = userManager; - _httpClient = httpClient; - _libraryManager = libraryManager; - _dtoService = dtoService; - _authContext = authContext; - _sessionContext = sessionContext; - } - - public object Get(GetTunerHostTypes request) - { - var list = _liveTvManager.GetTunerHostTypes(); - return ToOptimizedResult(list); - } - - public object Get(GetRecordingFolders request) - { - var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); - var folders = _liveTvManager.GetRecordingFolders(user); - - var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user); - - var result = new QueryResult<BaseItemDto> - { - Items = returnArray, - TotalRecordCount = returnArray.Count - }; - - return ToOptimizedResult(result); - } - - public object Get(GetLiveRecordingFile request) - { - var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id); - - if (string.IsNullOrWhiteSpace(path)) - { - throw new FileNotFoundException(); - } - - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) - { - [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path) - }; - - return new ProgressiveFileCopier(_streamHelper, path, outputHeaders, Logger) - { - AllowEndOfFile = false - }; - } - - public async Task<object> Get(DiscoverTuners request) - { - var result = await _liveTvManager.DiscoverTuners(request.NewDevicesOnly, CancellationToken.None).ConfigureAwait(false); - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetLiveStreamFile request) - { - var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(request.Id, CancellationToken.None).ConfigureAwait(false); - - var directStreamProvider = liveStreamInfo; - - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) - { - [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file." + request.Container) - }; - - return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger) - { - AllowEndOfFile = false - }; - } - - public object Get(GetDefaultListingProvider request) - { - return ToOptimizedResult(new ListingsProviderInfo()); - } - - public async Task<object> Post(SetChannelMapping request) - { - return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelId, request.ProviderChannelId).ConfigureAwait(false); - } - - public async Task<object> Get(GetChannelMappingOptions request) - { - var config = GetConfiguration(); - - var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(request.ProviderId, i.Id, StringComparison.OrdinalIgnoreCase)); - - var listingsProviderName = _liveTvManager.ListingProviders.First(i => string.Equals(i.Type, listingsProviderInfo.Type, StringComparison.OrdinalIgnoreCase)).Name; - - var tunerChannels = await _liveTvManager.GetChannelsForListingsProvider(request.ProviderId, CancellationToken.None) - .ConfigureAwait(false); - - var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(request.ProviderId, CancellationToken.None) - .ConfigureAwait(false); - - var mappings = listingsProviderInfo.ChannelMappings; - - var result = new ChannelMappingOptions - { - TunerChannels = tunerChannels.Select(i => _liveTvManager.GetTunerChannelMapping(i, mappings, providerChannels)).ToList(), - - ProviderChannels = providerChannels.Select(i => new NameIdPair - { - Name = i.Name, - Id = i.Id - - }).ToList(), - - Mappings = mappings, - - ProviderName = listingsProviderName - }; - - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetSchedulesDirectCountries request) - { - // https://json.schedulesdirect.org/20141201/available/countries - - var response = await _httpClient.Get(new HttpRequestOptions - { - Url = "https://json.schedulesdirect.org/20141201/available/countries", - BufferContent = false - - }).ConfigureAwait(false); - - return ResultFactory.GetResult(Request, response, "application/json"); - } - - private void AssertUserCanManageLiveTv() - { - var user = _sessionContext.GetUser(Request); - - if (user == null) - { - throw new SecurityException("Anonymous live tv management is not allowed."); - } - - if (!user.Policy.EnableLiveTvManagement) - { - throw new SecurityException("The current user does not have permission to manage live tv."); - } - } - - public async Task<object> Post(AddListingProvider request) - { - if (request.Pw != null) - { - request.Password = GetHashedString(request.Pw); - } - - request.Pw = null; - - var result = await _liveTvManager.SaveListingProvider(request, request.ValidateLogin, request.ValidateListings).ConfigureAwait(false); - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the hashed string. - /// </summary> - private string GetHashedString(string str) - { - // SchedulesDirect requires a SHA1 hash of the user's password - // https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token - using SHA1 sha = SHA1.Create(); - - return Hex.Encode( - sha.ComputeHash(Encoding.UTF8.GetBytes(str))); - } - - public void Delete(DeleteListingProvider request) - { - _liveTvManager.DeleteListingsProvider(request.Id); - } - - public async Task<object> Post(AddTunerHost request) - { - var result = await _liveTvManager.SaveTunerHost(request).ConfigureAwait(false); - return ToOptimizedResult(result); - } - - public void Delete(DeleteTunerHost request) - { - var config = GetConfiguration(); - - config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray(); - - ServerConfigurationManager.SaveConfiguration("livetv", config); - } - - private LiveTvOptions GetConfiguration() - { - return ServerConfigurationManager.GetConfiguration<LiveTvOptions>("livetv"); - } - - private void UpdateConfiguration(LiveTvOptions options) - { - ServerConfigurationManager.SaveConfiguration("livetv", options); - } - - public async Task<object> Get(GetLineups request) - { - var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false); - - return ToOptimizedResult(info); - } - - public object Get(GetLiveTvInfo request) - { - var info = _liveTvManager.GetLiveTvInfo(CancellationToken.None); - - return ToOptimizedResult(info); - } - - public object Get(GetChannels request) - { - var options = GetDtoOptions(_authContext, request); - - var channelResult = _liveTvManager.GetInternalChannels(new LiveTvChannelQuery - { - ChannelType = request.Type, - UserId = request.UserId, - StartIndex = request.StartIndex, - Limit = request.Limit, - IsFavorite = request.IsFavorite, - IsLiked = request.IsLiked, - IsDisliked = request.IsDisliked, - EnableFavoriteSorting = request.EnableFavoriteSorting, - IsMovie = request.IsMovie, - IsSeries = request.IsSeries, - IsNews = request.IsNews, - IsKids = request.IsKids, - IsSports = request.IsSports, - SortBy = request.GetOrderBy(), - SortOrder = request.SortOrder ?? SortOrder.Ascending, - AddCurrentProgram = request.AddCurrentProgram - - }, options, CancellationToken.None); - - var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); - - RemoveFields(options); - - options.AddCurrentProgram = request.AddCurrentProgram; - - var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, options, user); - - var result = new QueryResult<BaseItemDto> - { - Items = returnArray, - TotalRecordCount = channelResult.TotalRecordCount - }; - - return ToOptimizedResult(result); - } - - private void RemoveFields(DtoOptions options) - { - var fields = options.Fields.ToList(); - - fields.Remove(ItemFields.CanDelete); - fields.Remove(ItemFields.CanDownload); - fields.Remove(ItemFields.DisplayPreferencesId); - fields.Remove(ItemFields.Etag); - options.Fields = fields.ToArray(); - } - - public object Get(GetChannel request) - { - var user = _userManager.GetUserById(request.UserId); - - var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); - - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetPrograms request) - { - var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); - - var query = new InternalItemsQuery(user) - { - ChannelIds = ApiEntryPoint.Split(request.ChannelIds, ',', true).Select(i => new Guid(i)).ToArray(), - HasAired = request.HasAired, - IsAiring = request.IsAiring, - EnableTotalRecordCount = request.EnableTotalRecordCount - }; - - if (!string.IsNullOrEmpty(request.MinStartDate)) - { - query.MinStartDate = DateTime.Parse(request.MinStartDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - if (!string.IsNullOrEmpty(request.MinEndDate)) - { - query.MinEndDate = DateTime.Parse(request.MinEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - if (!string.IsNullOrEmpty(request.MaxStartDate)) - { - query.MaxStartDate = DateTime.Parse(request.MaxStartDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - if (!string.IsNullOrEmpty(request.MaxEndDate)) - { - query.MaxEndDate = DateTime.Parse(request.MaxEndDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - query.StartIndex = request.StartIndex; - query.Limit = request.Limit; - query.OrderBy = BaseItemsRequest.GetOrderBy(request.SortBy, request.SortOrder); - query.IsNews = request.IsNews; - query.IsMovie = request.IsMovie; - query.IsSeries = request.IsSeries; - query.IsKids = request.IsKids; - query.IsSports = request.IsSports; - query.SeriesTimerId = request.SeriesTimerId; - query.Genres = (request.Genres ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - query.GenreIds = GetGuids(request.GenreIds); - - if (!request.LibrarySeriesId.Equals(Guid.Empty)) - { - query.IsSeries = true; - - if (_libraryManager.GetItemById(request.LibrarySeriesId) is Series series) - { - query.Name = series.Name; - } - } - - var result = await _liveTvManager.GetPrograms(query, GetDtoOptions(_authContext, request), CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public object Get(GetRecommendedPrograms request) - { - var user = _userManager.GetUserById(request.UserId); - - var query = new InternalItemsQuery(user) - { - IsAiring = request.IsAiring, - Limit = request.Limit, - HasAired = request.HasAired, - IsSeries = request.IsSeries, - IsMovie = request.IsMovie, - IsKids = request.IsKids, - IsNews = request.IsNews, - IsSports = request.IsSports, - EnableTotalRecordCount = request.EnableTotalRecordCount - }; - - query.GenreIds = GetGuids(request.GenreIds); - - var result = _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(_authContext, request), CancellationToken.None); - - return ToOptimizedResult(result); - } - - public object Post(GetPrograms request) - { - return Get(request); - } - - public object Get(GetRecordings request) - { - var options = GetDtoOptions(_authContext, request); - - var result = _liveTvManager.GetRecordings(new RecordingQuery - { - ChannelId = request.ChannelId, - UserId = request.UserId, - StartIndex = request.StartIndex, - Limit = request.Limit, - Status = request.Status, - SeriesTimerId = request.SeriesTimerId, - IsInProgress = request.IsInProgress, - EnableTotalRecordCount = request.EnableTotalRecordCount, - IsMovie = request.IsMovie, - IsNews = request.IsNews, - IsSeries = request.IsSeries, - IsKids = request.IsKids, - IsSports = request.IsSports, - IsLibraryItem = request.IsLibraryItem, - Fields = request.GetItemFields(), - ImageTypeLimit = request.ImageTypeLimit, - EnableImages = request.EnableImages - - }, options); - - return ToOptimizedResult(result); - } - - public object Get(GetRecordingSeries request) - { - return ToOptimizedResult(new QueryResult<BaseItemDto>()); - } - - public object Get(GetRecording request) - { - var user = _userManager.GetUserById(request.UserId); - - var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); - - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetTimer request) - { - var result = await _liveTvManager.GetTimer(request.Id, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetTimers request) - { - var result = await _liveTvManager.GetTimers(new TimerQuery - { - ChannelId = request.ChannelId, - SeriesTimerId = request.SeriesTimerId, - IsActive = request.IsActive, - IsScheduled = request.IsScheduled - - }, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public void Delete(DeleteRecording request) - { - AssertUserCanManageLiveTv(); - - _libraryManager.DeleteItem(_libraryManager.GetItemById(request.Id), new DeleteOptions - { - DeleteFileLocation = false - }); - } - - public Task Delete(CancelTimer request) - { - AssertUserCanManageLiveTv(); - - return _liveTvManager.CancelTimer(request.Id); - } - - public Task Post(UpdateTimer request) - { - AssertUserCanManageLiveTv(); - - return _liveTvManager.UpdateTimer(request, CancellationToken.None); - } - - public async Task<object> Get(GetSeriesTimers request) - { - var result = await _liveTvManager.GetSeriesTimers(new SeriesTimerQuery - { - SortOrder = request.SortOrder, - SortBy = request.SortBy - - }, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetSeriesTimer request) - { - var result = await _liveTvManager.GetSeriesTimer(request.Id, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public Task Delete(CancelSeriesTimer request) - { - AssertUserCanManageLiveTv(); - - return _liveTvManager.CancelSeriesTimer(request.Id); - } - - public Task Post(UpdateSeriesTimer request) - { - AssertUserCanManageLiveTv(); - - return _liveTvManager.UpdateSeriesTimer(request, CancellationToken.None); - } - - public async Task<object> Get(GetDefaultTimer request) - { - if (string.IsNullOrEmpty(request.ProgramId)) - { - var result = await _liveTvManager.GetNewTimerDefaults(CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - else - { - var result = await _liveTvManager.GetNewTimerDefaults(request.ProgramId, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - } - - public async Task<object> Get(GetProgram request) - { - var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); - - var result = await _liveTvManager.GetProgram(request.Id, CancellationToken.None, user).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public Task Post(CreateSeriesTimer request) - { - AssertUserCanManageLiveTv(); - - return _liveTvManager.CreateSeriesTimer(request, CancellationToken.None); - } - - public Task Post(CreateTimer request) - { - AssertUserCanManageLiveTv(); - - return _liveTvManager.CreateTimer(request, CancellationToken.None); - } - - public object Get(GetRecordingGroups request) - { - return ToOptimizedResult(new QueryResult<BaseItemDto>()); - } - - public object Get(GetRecordingGroup request) - { - throw new FileNotFoundException(); - } - - public object Get(GetGuideInfo request) - { - return ToOptimizedResult(_liveTvManager.GetGuideInfo()); - } - - public Task Post(ResetTuner request) - { - AssertUserCanManageLiveTv(); - - return _liveTvManager.ResetTuner(request.Id, CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs deleted file mode 100644 index 4c608d9a3..000000000 --- a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.LiveTv -{ - public class ProgressiveFileCopier : IAsyncStreamWriter, IHasHeaders - { - private readonly ILogger _logger; - private readonly string _path; - private readonly Dictionary<string, string> _outputHeaders; - - public bool AllowEndOfFile = true; - - private readonly IDirectStreamProvider _directStreamProvider; - private IStreamHelper _streamHelper; - - public ProgressiveFileCopier(IStreamHelper streamHelper, string path, Dictionary<string, string> outputHeaders, ILogger logger) - { - _path = path; - _outputHeaders = outputHeaders; - _logger = logger; - _streamHelper = streamHelper; - } - - public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, IStreamHelper streamHelper, Dictionary<string, string> outputHeaders, ILogger logger) - { - _directStreamProvider = directStreamProvider; - _outputHeaders = outputHeaders; - _logger = logger; - _streamHelper = streamHelper; - } - - public IDictionary<string, string> Headers => _outputHeaders; - - public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) - { - if (_directStreamProvider != null) - { - await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); - return; - } - - var fileOptions = FileOptions.SequentialScan; - - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - if (Environment.OSVersion.Platform != PlatformID.Win32NT) - { - fileOptions |= FileOptions.Asynchronous; - } - - using (var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, fileOptions)) - { - var emptyReadLimit = AllowEndOfFile ? 20 : 100; - var eofCount = 0; - while (eofCount < emptyReadLimit) - { - int bytesRead; - bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - - if (bytesRead == 0) - { - eofCount++; - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - else - { - eofCount = 0; - } - } - } - } - } -} diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs deleted file mode 100644 index 6a69d2656..000000000 --- a/MediaBrowser.Api/LocalizationService.cs +++ /dev/null @@ -1,111 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetCultures - /// </summary> - [Route("/Localization/Cultures", "GET", Summary = "Gets known cultures")] - public class GetCultures : IReturn<CultureDto[]> - { - } - - /// <summary> - /// Class GetCountries - /// </summary> - [Route("/Localization/Countries", "GET", Summary = "Gets known countries")] - public class GetCountries : IReturn<CountryInfo[]> - { - } - - /// <summary> - /// Class ParentalRatings - /// </summary> - [Route("/Localization/ParentalRatings", "GET", Summary = "Gets known parental ratings")] - public class GetParentalRatings : IReturn<ParentalRating[]> - { - } - - /// <summary> - /// Class ParentalRatings - /// </summary> - [Route("/Localization/Options", "GET", Summary = "Gets localization options")] - public class GetLocalizationOptions : IReturn<LocalizationOption[]> - { - } - - /// <summary> - /// Class CulturesService - /// </summary> - [Authenticated(AllowBeforeStartupWizard = true)] - public class LocalizationService : BaseApiService - { - /// <summary> - /// The _localization - /// </summary> - private readonly ILocalizationManager _localization; - - /// <summary> - /// Initializes a new instance of the <see cref="LocalizationService"/> class. - /// </summary> - /// <param name="localization">The localization.</param> - public LocalizationService( - ILogger<LocalizationService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILocalizationManager localization) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _localization = localization; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetParentalRatings request) - { - var result = _localization.GetParentalRatings(); - - return ToOptimizedResult(result); - } - - public object Get(GetLocalizationOptions request) - { - var result = _localization.GetLocalizationOptions(); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetCountries request) - { - var result = _localization.GetCountries(); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetCultures request) - { - var result = _localization.GetCultures(); - - return ToOptimizedResult(result); - } - } - -} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj deleted file mode 100644 index d703bdb05..000000000 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ /dev/null @@ -1,23 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <!-- ProjectGuid is only included as a requirement for SonarQube analysis --> - <PropertyGroup> - <ProjectGuid>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</ProjectGuid> - </PropertyGroup> - - <ItemGroup> - <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> - <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> - </ItemGroup> - - <ItemGroup> - <Compile Include="..\SharedVersion.cs" /> - </ItemGroup> - - <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> - <GenerateAssemblyInfo>false</GenerateAssemblyInfo> - <GenerateDocumentationFile>true</GenerateDocumentationFile> - </PropertyGroup> - -</Project> diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs deleted file mode 100644 index 95a37dfc5..000000000 --- a/MediaBrowser.Api/Movies/CollectionService.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using MediaBrowser.Controller.Collections; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Collections; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Movies -{ - [Route("/Collections", "POST", Summary = "Creates a new collection")] - public class CreateCollection : IReturn<CollectionCreationResult> - { - [ApiMember(Name = "IsLocked", Description = "Whether or not to lock the new collection.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool IsLocked { get; set; } - - [ApiMember(Name = "Name", Description = "The name of the new collection.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } - - [ApiMember(Name = "ParentId", Description = "Optional - create the collection within a specific folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ParentId { get; set; } - - [ApiMember(Name = "Ids", Description = "Item Ids to add to the collection", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string Ids { get; set; } - } - - [Route("/Collections/{Id}/Items", "POST", Summary = "Adds items to a collection")] - public class AddToCollection : IReturnVoid - { - [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Ids { get; set; } - - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Collections/{Id}/Items", "DELETE", Summary = "Removes items from a collection")] - public class RemoveFromCollection : IReturnVoid - { - [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Ids { get; set; } - - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Authenticated] - public class CollectionService : BaseApiService - { - private readonly ICollectionManager _collectionManager; - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - - public CollectionService( - ILogger<CollectionService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ICollectionManager collectionManager, - IDtoService dtoService, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _collectionManager = collectionManager; - _dtoService = dtoService; - _authContext = authContext; - } - - public object Post(CreateCollection request) - { - var userId = _authContext.GetAuthorizationInfo(Request).UserId; - - var parentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId); - - var item = _collectionManager.CreateCollection(new CollectionCreationOptions - { - IsLocked = request.IsLocked, - Name = request.Name, - ParentId = parentId, - ItemIdList = SplitValue(request.Ids, ','), - UserIds = new[] { userId } - - }); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var dto = _dtoService.GetBaseItemDto(item, dtoOptions); - - return new CollectionCreationResult - { - Id = dto.Id - }; - } - - public void Post(AddToCollection request) - { - _collectionManager.AddToCollection(new Guid(request.Id), SplitValue(request.Ids, ',')); - } - - public void Delete(RemoveFromCollection request) - { - _collectionManager.RemoveFromCollection(new Guid(request.Id), SplitValue(request.Ids, ',')); - } - } -} diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs deleted file mode 100644 index cdd027634..000000000 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ /dev/null @@ -1,411 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Movies -{ - [Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")] - public class GetMovieRecommendations : IReturn<RecommendationDto[]>, IHasDtoOptions - { - [ApiMember(Name = "CategoryLimit", Description = "The max number of categories to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int CategoryLimit { get; set; } - - [ApiMember(Name = "ItemLimit", Description = "The max number of items to return per category", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int ItemLimit { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// </summary> - /// <value>The parent id.</value> - [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ParentId { 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; } - - public GetMovieRecommendations() - { - CategoryLimit = 5; - ItemLimit = 8; - } - - public string Fields { get; set; } - } - - /// <summary> - /// Class MoviesService - /// </summary> - [Authenticated] - public class MoviesService : BaseApiService - { - /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - private readonly ILibraryManager _libraryManager; - - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - - /// <summary> - /// Initializes a new instance of the <see cref="MoviesService" /> class. - /// </summary> - public MoviesService( - ILogger<MoviesService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IDtoService dtoService, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _libraryManager = libraryManager; - _dtoService = dtoService; - _authContext = authContext; - } - - public object Get(GetMovieRecommendations request) - { - var user = _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = GetRecommendationCategories(user, request.ParentId, request.CategoryLimit, request.ItemLimit, dtoOptions); - - return ToOptimizedResult(result); - } - - public QueryResult<BaseItemDto> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) - { - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - var item = string.IsNullOrEmpty(request.Id) ? - (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : - _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - - var itemTypes = new List<string> { typeof(Movie).Name }; - if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) - { - itemTypes.Add(typeof(Trailer).Name); - itemTypes.Add(typeof(LiveTvProgram).Name); - } - - var dtoOptions = GetDtoOptions(_authContext, request); - - var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - Limit = request.Limit, - IncludeItemTypes = itemTypes.ToArray(), - IsMovie = true, - SimilarTo = item, - EnableGroupByMetadataKey = true, - DtoOptions = dtoOptions - - }); - - var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); - - var result = new QueryResult<BaseItemDto> - { - Items = returnList, - - TotalRecordCount = itemsResult.Count - }; - - return result; - } - - private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, string parentId, int categoryLimit, int itemLimit, DtoOptions dtoOptions) - { - var categories = new List<RecommendationDto>(); - - var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? Guid.Empty : new Guid(parentId); - - var query = new InternalItemsQuery(user) - { - IncludeItemTypes = new[] - { - typeof(Movie).Name, - //typeof(Trailer).Name, - //typeof(LiveTvProgram).Name - }, - // IsMovie = true - OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), - Limit = 7, - ParentId = parentIdGuid, - Recursive = true, - IsPlayed = true, - DtoOptions = dtoOptions - }; - - var recentlyPlayedMovies = _libraryManager.GetItemList(query); - - var itemTypes = new List<string> { typeof(Movie).Name }; - if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) - { - itemTypes.Add(typeof(Trailer).Name); - itemTypes.Add(typeof(LiveTvProgram).Name); - } - - var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - IncludeItemTypes = itemTypes.ToArray(), - IsMovie = true, - OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), - Limit = 10, - IsFavoriteOrLiked = true, - ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(), - EnableGroupByMetadataKey = true, - ParentId = parentIdGuid, - Recursive = true, - DtoOptions = dtoOptions - - }); - - var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList(); - // Get recently played directors - var recentDirectors = GetDirectors(mostRecentMovies) - .ToList(); - - // Get recently played actors - var recentActors = GetActors(mostRecentMovies) - .ToList(); - - var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator(); - var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator(); - - var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator(); - var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator(); - - var categoryTypes = new List<IEnumerator<RecommendationDto>> - { - // Give this extra weight - similarToRecentlyPlayed, - similarToRecentlyPlayed, - - // Give this extra weight - similarToLiked, - similarToLiked, - - hasDirectorFromRecentlyPlayed, - hasActorFromRecentlyPlayed - }; - - while (categories.Count < categoryLimit) - { - var allEmpty = true; - - foreach (var category in categoryTypes) - { - if (category.MoveNext()) - { - categories.Add(category.Current); - allEmpty = false; - - if (categories.Count >= categoryLimit) - { - break; - } - } - } - - if (allEmpty) - { - break; - } - } - - return categories.OrderBy(i => i.RecommendationType); - } - - private IEnumerable<RecommendationDto> GetWithDirector(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) - { - var itemTypes = new List<string> { typeof(Movie).Name }; - if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) - { - itemTypes.Add(typeof(Trailer).Name); - itemTypes.Add(typeof(LiveTvProgram).Name); - } - - foreach (var name in names) - { - var items = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - Person = name, - // Account for duplicates by imdb id, since the database doesn't support this yet - Limit = itemLimit + 2, - PersonTypes = new[] { PersonType.Director }, - IncludeItemTypes = itemTypes.ToArray(), - IsMovie = true, - EnableGroupByMetadataKey = true, - DtoOptions = dtoOptions - - }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) - .Select(x => x.First()) - .Take(itemLimit) - .ToList(); - - if (items.Count > 0) - { - var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); - - yield return new RecommendationDto - { - BaselineItemName = name, - CategoryId = name.GetMD5(), - RecommendationType = type, - Items = returnItems - }; - } - } - } - - private IEnumerable<RecommendationDto> GetWithActor(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) - { - var itemTypes = new List<string> { typeof(Movie).Name }; - if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) - { - itemTypes.Add(typeof(Trailer).Name); - itemTypes.Add(typeof(LiveTvProgram).Name); - } - - foreach (var name in names) - { - var items = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - Person = name, - // Account for duplicates by imdb id, since the database doesn't support this yet - Limit = itemLimit + 2, - IncludeItemTypes = itemTypes.ToArray(), - IsMovie = true, - EnableGroupByMetadataKey = true, - DtoOptions = dtoOptions - - }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) - .Select(x => x.First()) - .Take(itemLimit) - .ToList(); - - if (items.Count > 0) - { - var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user); - - yield return new RecommendationDto - { - BaselineItemName = name, - CategoryId = name.GetMD5(), - RecommendationType = type, - Items = returnItems - }; - } - } - } - - private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type) - { - var itemTypes = new List<string> { typeof(Movie).Name }; - if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) - { - itemTypes.Add(typeof(Trailer).Name); - itemTypes.Add(typeof(LiveTvProgram).Name); - } - - foreach (var item in baselineItems) - { - var similar = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - Limit = itemLimit, - IncludeItemTypes = itemTypes.ToArray(), - IsMovie = true, - SimilarTo = item, - EnableGroupByMetadataKey = true, - DtoOptions = dtoOptions - - }); - - if (similar.Count > 0) - { - var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user); - - yield return new RecommendationDto - { - BaselineItemName = item.Name, - CategoryId = item.Id, - RecommendationType = type, - Items = returnItems - }; - } - } - } - - private IEnumerable<string> GetActors(List<BaseItem> items) - { - var people = _libraryManager.GetPeople(new InternalPeopleQuery - { - ExcludePersonTypes = new[] - { - PersonType.Director - }, - MaxListOrder = 3 - }); - - var itemIds = items.Select(i => i.Id).ToList(); - - return people - .Where(i => itemIds.Contains(i.ItemId)) - .Select(i => i.Name) - .DistinctNames(); - } - - private IEnumerable<string> GetDirectors(List<BaseItem> items) - { - var people = _libraryManager.GetPeople(new InternalPeopleQuery - { - PersonTypes = new[] - { - PersonType.Director - } - }); - - var itemIds = items.Select(i => i.Id).ToList(); - - return people - .Where(i => itemIds.Contains(i.ItemId)) - .Select(i => i.Name) - .DistinctNames(); - } - } -} diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs deleted file mode 100644 index 0b5334235..000000000 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ /dev/null @@ -1,89 +0,0 @@ -using MediaBrowser.Api.UserLibrary; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Movies -{ - [Route("/Trailers", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] - public class Getrailers : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>> - { - } - - /// <summary> - /// Class TrailersService - /// </summary> - [Authenticated] - public class TrailersService : BaseApiService - { - /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - - /// <summary> - /// The logger for the created <see cref="ItemsService"/> instances. - /// </summary> - private readonly ILogger<ItemsService> _logger; - - private readonly IDtoService _dtoService; - private readonly ILocalizationManager _localizationManager; - private readonly IJsonSerializer _json; - private readonly IAuthorizationContext _authContext; - - public TrailersService( - ILoggerFactory loggerFactory, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IDtoService dtoService, - ILocalizationManager localizationManager, - IJsonSerializer json, - IAuthorizationContext authContext) - : base(loggerFactory.CreateLogger<TrailersService>(), serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _libraryManager = libraryManager; - _dtoService = dtoService; - _localizationManager = localizationManager; - _json = json; - _authContext = authContext; - _logger = loggerFactory.CreateLogger<ItemsService>(); - } - - public object Get(Getrailers request) - { - var json = _json.SerializeToString(request); - var getItems = _json.DeserializeFromString<GetItems>(json); - - getItems.IncludeItemTypes = "Trailer"; - - return new ItemsService( - _logger, - ServerConfigurationManager, - ResultFactory, - _userManager, - _libraryManager, - _localizationManager, - _dtoService, - _authContext) - { - Request = Request, - - }.Get(getItems); - } - } -} diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs deleted file mode 100644 index 58c95d053..000000000 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Music -{ - [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] - public class GetSimilarAlbums : BaseGetSimilarItemsFromItem - { - } - - [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] - public class GetSimilarArtists : BaseGetSimilarItemsFromItem - { - } - - [Authenticated] - public class AlbumsService : BaseApiService - { - /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> - /// The _user data repository - /// </summary> - private readonly IUserDataManager _userDataRepository; - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - private readonly IItemRepository _itemRepo; - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - - public AlbumsService( - ILogger<AlbumsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - IUserDataManager userDataRepository, - ILibraryManager libraryManager, - IItemRepository itemRepo, - IDtoService dtoService, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _userDataRepository = userDataRepository; - _libraryManager = libraryManager; - _itemRepo = itemRepo; - _dtoService = dtoService; - _authContext = authContext; - } - - public object Get(GetSimilarArtists request) - { - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, - _itemRepo, - _libraryManager, - _userDataRepository, - _dtoService, - Logger, - request, new[] { typeof(MusicArtist) }, - SimilarItemsHelper.GetSimiliarityScore); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetSimilarAlbums request) - { - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, - _itemRepo, - _libraryManager, - _userDataRepository, - _dtoService, - Logger, - request, new[] { typeof(MusicAlbum) }, - GetAlbumSimilarityScore); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the album similarity score. - /// </summary> - /// <param name="item1">The item1.</param> - /// <param name="item1People">The item1 people.</param> - /// <param name="allPeople">All people.</param> - /// <param name="item2">The item2.</param> - /// <returns>System.Int32.</returns> - private int GetAlbumSimilarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2) - { - var points = SimilarItemsHelper.GetSimiliarityScore(item1, item1People, allPeople, item2); - - var album1 = (MusicAlbum)item1; - var album2 = (MusicAlbum)item2; - - var artists1 = album1 - .GetAllArtists() - .DistinctNames() - .ToList(); - - var artists2 = new HashSet<string>( - album2.GetAllArtists().DistinctNames(), - StringComparer.OrdinalIgnoreCase); - - return points + artists1.Where(artists2.Contains).Sum(i => 5); - } - } -} diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs deleted file mode 100644 index cacec8d64..000000000 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Playlists; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Music -{ - [Route("/Songs/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given song")] - public class GetInstantMixFromSong : BaseGetSimilarItemsFromItem - { - } - - [Route("/Albums/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given album")] - public class GetInstantMixFromAlbum : BaseGetSimilarItemsFromItem - { - } - - [Route("/Playlists/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given playlist")] - public class GetInstantMixFromPlaylist : BaseGetSimilarItemsFromItem - { - } - - [Route("/MusicGenres/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")] - public class GetInstantMixFromMusicGenre : BaseGetSimilarItems - { - [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - [Route("/Artists/InstantMix", "GET", Summary = "Creates an instant playlist based on a given artist")] - public class GetInstantMixFromArtistId : BaseGetSimilarItems - { - [ApiMember(Name = "Id", Description = "The artist Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/MusicGenres/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")] - public class GetInstantMixFromMusicGenreId : BaseGetSimilarItems - { - [ApiMember(Name = "Id", Description = "The genre Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given item")] - public class GetInstantMixFromItem : BaseGetSimilarItemsFromItem - { - } - - [Authenticated] - public class InstantMixService : BaseApiService - { - private readonly IUserManager _userManager; - - private readonly IDtoService _dtoService; - private readonly ILibraryManager _libraryManager; - private readonly IMusicManager _musicManager; - private readonly IAuthorizationContext _authContext; - - public InstantMixService( - ILogger<InstantMixService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - IDtoService dtoService, - IMusicManager musicManager, - ILibraryManager libraryManager, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _dtoService = dtoService; - _musicManager = musicManager; - _libraryManager = libraryManager; - _authContext = authContext; - } - - public object Get(GetInstantMixFromItem request) - { - var item = _libraryManager.GetItemById(request.Id); - - var user = _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); - - return GetResult(items, user, request, dtoOptions); - } - - public object Get(GetInstantMixFromArtistId request) - { - var item = _libraryManager.GetItemById(request.Id); - - var user = _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); - - return GetResult(items, user, request, dtoOptions); - } - - public object Get(GetInstantMixFromMusicGenreId request) - { - var item = _libraryManager.GetItemById(request.Id); - - var user = _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); - - return GetResult(items, user, request, dtoOptions); - } - - public object Get(GetInstantMixFromSong request) - { - var item = _libraryManager.GetItemById(request.Id); - - var user = _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions); - - return GetResult(items, user, request, dtoOptions); - } - - public object Get(GetInstantMixFromAlbum request) - { - var album = _libraryManager.GetItemById(request.Id); - - var user = _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions); - - return GetResult(items, user, request, dtoOptions); - } - - public object Get(GetInstantMixFromPlaylist request) - { - var playlist = (Playlist)_libraryManager.GetItemById(request.Id); - - var user = _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions); - - return GetResult(items, user, request, dtoOptions); - } - - public object Get(GetInstantMixFromMusicGenre request) - { - var user = _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var items = _musicManager.GetInstantMixFromGenres(new[] { request.Name }, user, dtoOptions); - - return GetResult(items, user, request, dtoOptions); - } - - private object GetResult(List<BaseItem> items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions) - { - var list = items; - - var result = new QueryResult<BaseItemDto> - { - TotalRecordCount = list.Count - }; - - if (request.Limit.HasValue) - { - list = list.Take(request.Limit.Value).ToList(); - } - - var returnList = _dtoService.GetBaseItemDtos(list, dtoOptions, user); - - result.Items = returnList; - - return result; - } - - } -} diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs deleted file mode 100644 index 444354a99..000000000 --- a/MediaBrowser.Api/PackageService.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Updates; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetPackage - /// </summary> - [Route("/Packages/{Name}", "GET", Summary = "Gets a package, by name or assembly guid")] - [Authenticated] - public class GetPackage : IReturn<PackageInfo> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The name of the package", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "AssemblyGuid", Description = "The guid of the associated assembly", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AssemblyGuid { get; set; } - } - - /// <summary> - /// Class GetPackages - /// </summary> - [Route("/Packages", "GET", Summary = "Gets available packages")] - [Authenticated] - public class GetPackages : IReturn<PackageInfo[]> - { - } - - /// <summary> - /// Class InstallPackage - /// </summary> - [Route("/Packages/Installed/{Name}", "POST", Summary = "Installs a package")] - [Authenticated(Roles = "Admin")] - public class InstallPackage : IReturnVoid - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "Package name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "AssemblyGuid", Description = "Guid of the associated assembly", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string AssemblyGuid { get; set; } - - /// <summary> - /// Gets or sets the version. - /// </summary> - /// <value>The version.</value> - [ApiMember(Name = "Version", Description = "Optional version. Defaults to latest version.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Version { get; set; } - } - - /// <summary> - /// Class CancelPackageInstallation - /// </summary> - [Route("/Packages/Installing/{Id}", "DELETE", Summary = "Cancels a package installation")] - [Authenticated(Roles = "Admin")] - public class CancelPackageInstallation : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Installation Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - /// <summary> - /// Class PackageService - /// </summary> - public class PackageService : BaseApiService - { - private readonly IInstallationManager _installationManager; - - public PackageService( - ILogger<PackageService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IInstallationManager installationManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _installationManager = installationManager; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPackage request) - { - var packages = _installationManager.GetAvailablePackages().GetAwaiter().GetResult(); - var result = _installationManager.FilterPackages( - packages, - request.Name, - string.IsNullOrEmpty(request.AssemblyGuid) ? default : Guid.Parse(request.AssemblyGuid)).FirstOrDefault(); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public async Task<object> Get(GetPackages request) - { - IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); - - return ToOptimizedResult(packages.ToArray()); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <exception cref="ResourceNotFoundException"></exception> - public async Task Post(InstallPackage request) - { - var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); - var package = _installationManager.GetCompatibleVersions( - packages, - request.Name, - string.IsNullOrEmpty(request.AssemblyGuid) ? Guid.Empty : Guid.Parse(request.AssemblyGuid), - string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version)).FirstOrDefault(); - - if (package == null) - { - throw new ResourceNotFoundException( - string.Format( - CultureInfo.InvariantCulture, - "Package not found: {0}", - request.Name)); - } - - await _installationManager.InstallPackage(package); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(CancelPackageInstallation request) - { - _installationManager.CancelInstallation(new Guid(request.Id)); - } - } -} diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs deleted file mode 100644 index f796aa486..000000000 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ /dev/null @@ -1,1003 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback -{ - /// <summary> - /// Class BaseStreamingService - /// </summary> - public abstract class BaseStreamingService : BaseApiService - { - protected virtual bool EnableOutputInSubFolder => false; - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - protected IUserManager UserManager { get; private set; } - - /// <summary> - /// Gets or sets the library manager. - /// </summary> - /// <value>The library manager.</value> - protected ILibraryManager LibraryManager { get; private set; } - - /// <summary> - /// Gets or sets the iso manager. - /// </summary> - /// <value>The iso manager.</value> - protected IIsoManager IsoManager { get; private set; } - - /// <summary> - /// Gets or sets the media encoder. - /// </summary> - /// <value>The media encoder.</value> - protected IMediaEncoder MediaEncoder { get; private set; } - - protected IFileSystem FileSystem { get; private set; } - - protected IDlnaManager DlnaManager { get; private set; } - - protected IDeviceManager DeviceManager { get; private set; } - - protected IMediaSourceManager MediaSourceManager { get; private set; } - - protected IJsonSerializer JsonSerializer { get; private set; } - - protected IAuthorizationContext AuthorizationContext { get; private set; } - - protected EncodingHelper EncodingHelper { get; set; } - - /// <summary> - /// Gets the type of the transcoding job. - /// </summary> - /// <value>The type of the transcoding job.</value> - protected abstract TranscodingJobType TranscodingJobType { get; } - - /// <summary> - /// Initializes a new instance of the <see cref="BaseStreamingService" /> class. - /// </summary> - protected BaseStreamingService( - ILogger<BaseStreamingService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - EncodingHelper encodingHelper) - : base(logger, serverConfigurationManager, httpResultFactory) - { - UserManager = userManager; - LibraryManager = libraryManager; - IsoManager = isoManager; - MediaEncoder = mediaEncoder; - FileSystem = fileSystem; - DlnaManager = dlnaManager; - DeviceManager = deviceManager; - MediaSourceManager = mediaSourceManager; - JsonSerializer = jsonSerializer; - AuthorizationContext = authorizationContext; - - EncodingHelper = encodingHelper; - } - - /// <summary> - /// Gets the command line arguments. - /// </summary> - protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding); - - /// <summary> - /// Gets the output file extension. - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.String.</returns> - protected virtual string GetOutputFileExtension(StreamState state) - { - return Path.GetExtension(state.RequestedUrl); - } - - /// <summary> - /// Gets the output file path. - /// </summary> - private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension) - { - var data = $"{state.MediaPath}-{state.UserAgent}-{state.Request.DeviceId}-{state.Request.PlaySessionId}"; - - var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture); - var ext = outputFileExtension?.ToLowerInvariant(); - var folder = ServerConfigurationManager.GetTranscodePath(); - - return EnableOutputInSubFolder - ? Path.Combine(folder, filename, filename + ext) - : Path.Combine(folder, filename + ext); - } - - protected virtual string GetDefaultEncoderPreset() - { - return "superfast"; - } - - private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource) - { - if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath)) - { - state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false); - } - - if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId)) - { - var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest - { - OpenToken = state.MediaSource.OpenToken - }, cancellationTokenSource.Token).ConfigureAwait(false); - - EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl); - - if (state.VideoRequest != null) - { - EncodingHelper.TryStreamCopy(state); - } - } - - if (state.MediaSource.BufferMs.HasValue) - { - await Task.Delay(state.MediaSource.BufferMs.Value, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - - /// <summary> - /// Starts the FFMPEG. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="outputPath">The output path.</param> - /// <param name="cancellationTokenSource">The cancellation token source.</param> - /// <param name="workingDirectory">The working directory.</param> - /// <returns>Task.</returns> - protected async Task<TranscodingJob> StartFfMpeg( - StreamState state, - string outputPath, - CancellationTokenSource cancellationTokenSource, - string workingDirectory = null) - { - Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); - - await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); - - if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var auth = AuthorizationContext.GetAuthorizationInfo(Request); - if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding) - { - ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); - - throw new ArgumentException("User does not have access to video transcoding"); - } - } - - var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); - - var process = new Process() - { - StartInfo = new ProcessStartInfo() - { - WindowStyle = ProcessWindowStyle.Hidden, - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - - FileName = MediaEncoder.EncoderPath, - Arguments = GetCommandLineArguments(outputPath, encodingOptions, state, true), - WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory, - - ErrorDialog = false - }, - EnableRaisingEvents = true - }; - - var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, - state.Request.PlaySessionId, - state.MediaSource.LiveStreamId, - Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - TranscodingJobType, - process, - state.Request.DeviceId, - state, - cancellationTokenSource); - - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - Logger.LogInformation(commandLineLogMessage); - - var logFilePrefix = "ffmpeg-transcode"; - if (state.VideoRequest != null - && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - logFilePrefix = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase) - ? "ffmpeg-remux" : "ffmpeg-directstream"; - } - - var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt"); - - // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - - var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); - - process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state); - - try - { - process.Start(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error starting ffmpeg"); - - ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); - - throw; - } - - Logger.LogDebug("Launched ffmpeg process"); - state.TranscodingJob = transcodingJob; - - // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream); - - // Wait for the file to exist before proceeeding - var ffmpegTargetFile = state.WaitForPath ?? outputPath; - Logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile); - while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited) - { - await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false); - } - - Logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile); - - if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited) - { - await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); - - if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited) - { - await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - - if (!transcodingJob.HasExited) - { - StartThrottler(state, transcodingJob); - } - Logger.LogDebug("StartFfMpeg() finished successfully"); - - return transcodingJob; - } - - private void StartThrottler(StreamState state, TranscodingJob transcodingJob) - { - if (EnableThrottling(state)) - { - transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager, FileSystem); - state.TranscodingThrottler.Start(); - } - } - - private bool EnableThrottling(StreamState state) - { - var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); - - // enable throttling when NOT using hardware acceleration - if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) - { - 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); - } - - return false; - } - - /// <summary> - /// Processes the exited. - /// </summary> - /// <param name="process">The process.</param> - /// <param name="job">The job.</param> - /// <param name="state">The state.</param> - private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state) - { - if (job != null) - { - job.HasExited = true; - } - - Logger.LogDebug("Disposing stream resources"); - state.Dispose(); - - if (process.ExitCode == 0) - { - Logger.LogInformation("FFMpeg exited with code 0"); - } - else - { - Logger.LogError("FFMpeg exited with code {0}", process.ExitCode); - } - - process.Dispose(); - } - - /// <summary> - /// Parses the parameters. - /// </summary> - /// <param name="request">The request.</param> - private void ParseParams(StreamRequest request) - { - var vals = request.Params.Split(';'); - - var videoRequest = request as VideoStreamRequest; - - for (var i = 0; i < vals.Length; i++) - { - var val = vals[i]; - - if (string.IsNullOrWhiteSpace(val)) - { - continue; - } - - switch (i) - { - case 0: - request.DeviceProfileId = val; - break; - case 1: - request.DeviceId = val; - break; - case 2: - request.MediaSourceId = val; - break; - case 3: - request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - break; - case 4: - if (videoRequest != null) - { - videoRequest.VideoCodec = val; - } - - break; - case 5: - request.AudioCodec = val; - break; - case 6: - if (videoRequest != null) - { - videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); - } - - break; - case 7: - if (videoRequest != null) - { - videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture); - } - - break; - case 8: - if (videoRequest != null) - { - videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture); - } - - break; - case 9: - request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture); - break; - case 10: - request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture); - break; - case 11: - if (videoRequest != null) - { - videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture); - } - - break; - case 12: - if (videoRequest != null) - { - videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture); - } - - break; - case 13: - if (videoRequest != null) - { - videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture); - } - - break; - case 14: - request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture); - break; - case 15: - if (videoRequest != null) - { - videoRequest.Level = val; - } - - break; - case 16: - if (videoRequest != null) - { - videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture); - } - - break; - case 17: - if (videoRequest != null) - { - videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture); - } - - break; - case 18: - if (videoRequest != null) - { - videoRequest.Profile = val; - } - - break; - case 19: - // cabac no longer used - break; - case 20: - request.PlaySessionId = val; - break; - case 21: - // api_key - break; - case 22: - request.LiveStreamId = val; - break; - case 23: - // Duplicating ItemId because of MediaMonkey - break; - case 24: - if (videoRequest != null) - { - videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - - break; - case 25: - if (!string.IsNullOrWhiteSpace(val) && videoRequest != null) - { - if (Enum.TryParse(val, out SubtitleDeliveryMethod method)) - { - videoRequest.SubtitleMethod = method; - } - } - - break; - case 26: - request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture); - break; - case 27: - if (videoRequest != null) - { - videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - - break; - case 28: - request.Tag = val; - break; - case 29: - if (videoRequest != null) - { - videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - - break; - case 30: - request.SubtitleCodec = val; - break; - case 31: - if (videoRequest != null) - { - videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - - break; - case 32: - if (videoRequest != null) - { - videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); - } - - break; - case 33: - request.TranscodeReasons = val; - break; - } - } - } - - /// <summary> - /// Parses query parameters as StreamOptions. - /// </summary> - /// <param name="request">The stream request.</param> - private void ParseStreamOptions(StreamRequest request) - { - foreach (var param in Request.QueryString) - { - if (char.IsLower(param.Key[0])) - { - // This was probably not parsed initially and should be a StreamOptions - // TODO: This should be incorporated either in the lower framework for parsing requests - // or the generated URL should correctly serialize it - request.StreamOptions[param.Key] = param.Value; - } - } - } - - /// <summary> - /// Parses the dlna headers. - /// </summary> - /// <param name="request">The request.</param> - private void ParseDlnaHeaders(StreamRequest request) - { - if (!request.StartTimeTicks.HasValue) - { - var timeSeek = GetHeader("TimeSeekRange.dlna.org"); - - request.StartTimeTicks = ParseTimeSeekHeader(timeSeek); - } - } - - /// <summary> - /// Parses the time seek header. - /// </summary> - private long? ParseTimeSeekHeader(string value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return null; - } - - const string Npt = "npt="; - if (!value.StartsWith(Npt, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Invalid timeseek header"); - } - int index = value.IndexOf('-'); - value = index == -1 - ? value.Substring(Npt.Length) - : value.Substring(Npt.Length, index - Npt.Length); - - if (value.IndexOf(':') == -1) - { - // Parses npt times in the format of '417.33' - if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds)) - { - return TimeSpan.FromSeconds(seconds).Ticks; - } - - throw new ArgumentException("Invalid timeseek header"); - } - - // Parses npt times in the format of '10:19:25.7' - var tokens = value.Split(new[] { ':' }, 3); - double secondsSum = 0; - var timeFactor = 3600; - - foreach (var time in tokens) - { - if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out var digit)) - { - secondsSum += digit * timeFactor; - } - else - { - throw new ArgumentException("Invalid timeseek header"); - } - timeFactor /= 60; - } - return TimeSpan.FromSeconds(secondsSum).Ticks; - } - - /// <summary> - /// Gets the state. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>StreamState.</returns> - protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken) - { - ParseDlnaHeaders(request); - - if (!string.IsNullOrWhiteSpace(request.Params)) - { - ParseParams(request); - } - - ParseStreamOptions(request); - - var url = Request.PathInfo; - - if (string.IsNullOrEmpty(request.AudioCodec)) - { - request.AudioCodec = EncodingHelper.InferAudioCodec(url); - } - - var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) || - string.Equals(GetHeader("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase); - - var state = new StreamState(MediaSourceManager, TranscodingJobType) - { - Request = request, - RequestedUrl = url, - UserAgent = Request.UserAgent, - EnableDlnaHeaders = enableDlnaHeaders - }; - - var auth = AuthorizationContext.GetAuthorizationInfo(Request); - if (!auth.UserId.Equals(Guid.Empty)) - { - state.User = UserManager.GetUserById(auth.UserId); - } - - //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || - // (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || - // (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) - //{ - // state.SegmentLength = 6; - //} - - if (state.VideoRequest != null && !string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec)) - { - state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.AudioCodec)) - { - state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i)) - ?? state.SupportedAudioCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) - { - state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i)) - ?? state.SupportedSubtitleCodecs.FirstOrDefault(); - } - - var item = LibraryManager.GetItemById(request.Id); - - state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - - //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? - // item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); - //if (primaryImage != null) - //{ - // state.AlbumCoverPath = primaryImage.Path; - //} - - MediaSourceInfo mediaSource = null; - if (string.IsNullOrWhiteSpace(request.LiveStreamId)) - { - var currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? - ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) - : null; - - if (currentJob != null) - { - mediaSource = currentJob.MediaSource; - } - - if (mediaSource == null) - { - var mediaSources = await MediaSourceManager.GetPlaybackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false); - - mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources[0] - : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId)); - - if (mediaSource == null && Guid.Parse(request.MediaSourceId) == request.Id) - { - mediaSource = mediaSources[0]; - } - } - } - else - { - var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false); - mediaSource = liveStreamInfo.Item1; - state.DirectStreamProvider = liveStreamInfo.Item2; - } - - var videoRequest = request as VideoStreamRequest; - - EncodingHelper.AttachMediaSourceInfo(state, mediaSource, url); - - var container = Path.GetExtension(state.RequestedUrl); - - if (string.IsNullOrEmpty(container)) - { - container = request.Container; - } - - if (string.IsNullOrEmpty(container)) - { - container = request.Static ? - StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio) : - GetOutputFileExtension(state); - } - - state.OutputContainer = (container ?? string.Empty).TrimStart('.'); - - state.OutputAudioBitrate = EncodingHelper.GetAudioBitrateParam(state.Request, state.AudioStream); - - state.OutputAudioCodec = state.Request.AudioCodec; - - state.OutputAudioChannels = EncodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); - - if (videoRequest != null) - { - state.OutputVideoCodec = state.VideoRequest.VideoCodec; - state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - - if (videoRequest != null) - { - EncodingHelper.TryStreamCopy(state); - } - - if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var resolution = ResolutionNormalizer.Normalize( - state.VideoStream?.BitRate, - state.VideoStream?.Width, - state.VideoStream?.Height, - state.OutputVideoBitrate.Value, - state.VideoStream?.Codec, - state.OutputVideoCodec, - videoRequest.MaxWidth, - videoRequest.MaxHeight); - - videoRequest.MaxWidth = resolution.MaxWidth; - videoRequest.MaxHeight = resolution.MaxHeight; - } - } - - ApplyDeviceProfileSettings(state); - - var ext = string.IsNullOrWhiteSpace(state.OutputContainer) - ? GetOutputFileExtension(state) - : ('.' + state.OutputContainer); - - var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); - - state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext); - - return state; - } - - private void ApplyDeviceProfileSettings(StreamState state) - { - var headers = Request.Headers; - - if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId)) - { - state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId); - } - else if (!string.IsNullOrWhiteSpace(state.Request.DeviceId)) - { - var caps = DeviceManager.GetCapabilities(state.Request.DeviceId); - - state.DeviceProfile = caps == null ? DlnaManager.GetProfile(headers) : caps.DeviceProfile; - } - - var profile = state.DeviceProfile; - - if (profile == null) - { - // Don't use settings from the default profile. - // Only use a specific profile if it was requested. - return; - } - - var audioCodec = state.ActualOutputAudioCodec; - var videoCodec = state.ActualOutputVideoCodec; - - var mediaProfile = state.VideoRequest == null ? - profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) : - profile.GetVideoMediaProfile(state.OutputContainer, - audioCodec, - videoCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetVideoProfile, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TargetTimestamp, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC); - - if (mediaProfile != null) - { - state.MimeType = mediaProfile.MimeType; - } - - if (!state.Request.Static) - { - var transcodingProfile = state.VideoRequest == null ? - profile.GetAudioTranscodingProfile(state.OutputContainer, audioCodec) : - profile.GetVideoTranscodingProfile(state.OutputContainer, audioCodec, videoCodec); - - if (transcodingProfile != null) - { - state.EstimateContentLength = transcodingProfile.EstimateContentLength; - //state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; - state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - - if (state.VideoRequest != null) - { - state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps; - state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; - } - } - } - } - - /// <summary> - /// Adds the dlna headers. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed) - { - if (!state.EnableDlnaHeaders) - { - return; - } - - var profile = state.DeviceProfile; - - var transferMode = GetHeader("transferMode.dlna.org"); - responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode; - responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*"; - - if (state.RunTimeTicks.HasValue) - { - if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase)) - { - var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds; - responseHeaders["MediaInfo.sec"] = string.Format( - CultureInfo.InvariantCulture, - "SEC_Duration={0};", - Convert.ToInt32(ms)); - } - - if (!isStaticallyStreamed && profile != null) - { - AddTimeSeekResponseHeaders(state, responseHeaders); - } - } - - if (profile == null) - { - profile = DlnaManager.GetDefaultProfile(); - } - - var audioCodec = state.ActualOutputAudioCodec; - - if (state.VideoRequest == null) - { - responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile).BuildAudioHeader( - state.OutputContainer, - audioCodec, - state.OutputAudioBitrate, - state.OutputAudioSampleRate, - state.OutputAudioChannels, - state.OutputAudioBitDepth, - isStaticallyStreamed, - state.RunTimeTicks, - state.TranscodeSeekInfo); - } - else - { - var videoCodec = state.ActualOutputVideoCodec; - - responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile).BuildVideoHeader( - state.OutputContainer, - videoCodec, - audioCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetTimestamp, - isStaticallyStreamed, - state.RunTimeTicks, - state.TargetVideoProfile, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TranscodeSeekInfo, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC).FirstOrDefault() ?? string.Empty; - } - } - - private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders) - { - var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture); - var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture); - - responseHeaders["TimeSeekRange.dlna.org"] = string.Format( - CultureInfo.InvariantCulture, - "npt={0}-{1}/{1}", - startSeconds, - runtimeSeconds); - responseHeaders["X-AvailableSeekRange"] = string.Format( - CultureInfo.InvariantCulture, - "1 npt={0}-{1}", - startSeconds, - runtimeSeconds); - } - } -} diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs deleted file mode 100644 index 627421aac..000000000 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ /dev/null @@ -1,342 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback.Hls -{ - /// <summary> - /// Class BaseHlsService - /// </summary> - public abstract class BaseHlsService : BaseStreamingService - { - public BaseHlsService( - ILogger<BaseHlsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - EncodingHelper encodingHelper) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext, - encodingHelper) - { - } - - /// <summary> - /// Gets the audio arguments. - /// </summary> - protected abstract string GetAudioArguments(StreamState state, EncodingOptions encodingOptions); - - /// <summary> - /// Gets the video arguments. - /// </summary> - protected abstract string GetVideoArguments(StreamState state, EncodingOptions encodingOptions); - - /// <summary> - /// Gets the segment file extension. - /// </summary> - protected string GetSegmentFileExtension(StreamRequest request) - { - var segmentContainer = request.SegmentContainer; - if (!string.IsNullOrWhiteSpace(segmentContainer)) - { - return "." + segmentContainer; - } - - return ".ts"; - } - - /// <summary> - /// Gets the type of the transcoding job. - /// </summary> - /// <value>The type of the transcoding job.</value> - protected override TranscodingJobType TranscodingJobType => TranscodingJobType.Hls; - - /// <summary> - /// Processes the request async. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="isLive">if set to <c>true</c> [is live].</param> - /// <returns>Task{System.Object}.</returns> - /// <exception cref="ArgumentException">A video bitrate is required - /// or - /// An audio bitrate is required</exception> - protected async Task<object> ProcessRequestAsync(StreamRequest request, bool isLive) - { - var cancellationTokenSource = new CancellationTokenSource(); - - var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); - - TranscodingJob job = null; - var playlist = state.OutputFilePath; - - if (!File.Exists(playlist)) - { - var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlist); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); - try - { - if (!File.Exists(playlist)) - { - // If the playlist doesn't already exist, startup ffmpeg - try - { - job = await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false); - job.IsLiveOutput = isLive; - } - catch - { - state.Dispose(); - throw; - } - - var minSegments = state.MinSegments; - if (minSegments > 0) - { - await WaitForMinimumSegmentCount(playlist, minSegments, cancellationTokenSource.Token).ConfigureAwait(false); - } - } - } - finally - { - transcodingLock.Release(); - } - } - - if (isLive) - { - job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); - - if (job != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(job); - } - return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - var audioBitrate = state.OutputAudioBitrate ?? 0; - var videoBitrate = state.OutputVideoBitrate ?? 0; - - var baselineStreamBitrate = 64000; - - var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate); - - job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); - - if (job != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(job); - } - - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - private string GetLivePlaylistText(string path, int segmentLength) - { - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var reader = new StreamReader(stream); - - var text = reader.ReadToEnd(); - - text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT"); - - var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture); - - text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase); - //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase); - - return text; - } - - private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate) - { - var builder = new StringBuilder(); - - builder.AppendLine("#EXTM3U"); - - // Pad a little to satisfy the apple hls validator - var paddedBitrate = Convert.ToInt32(bitrate * 1.15); - - // Main stream - builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(CultureInfo.InvariantCulture)); - var playlistUrl = "hls/" + Path.GetFileName(firstPlaylist).Replace(".m3u8", "/stream.m3u8"); - builder.AppendLine(playlistUrl); - - return builder.ToString(); - } - - protected virtual async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken) - { - Logger.LogDebug("Waiting for {0} segments in {1}", segmentCount, playlist); - - while (!cancellationToken.IsCancellationRequested) - { - try - { - // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written - var fileStream = GetPlaylistFileStream(playlist); - await using (fileStream.ConfigureAwait(false)) - { - using var reader = new StreamReader(fileStream); - var count = 0; - - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - - if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) - { - count++; - if (count >= segmentCount) - { - Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist); - return; - } - } - } - } - - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - catch (IOException) - { - // May get an error if the file is locked - } - - await Task.Delay(50, cancellationToken).ConfigureAwait(false); - } - } - - protected Stream GetPlaylistFileStream(string path) - { - return new FileStream( - path, - FileMode.Open, - FileAccess.Read, - FileShare.ReadWrite, - IODefaults.FileStreamBufferSize, - FileOptions.SequentialScan); - } - - protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) - { - var itsOffsetMs = 0; - - var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(CultureInfo.InvariantCulture)); - - var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec); - - var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); - - // If isEncoding is true we're actually starting ffmpeg - var startNumberParam = isEncoding ? GetStartNumber(state).ToString(CultureInfo.InvariantCulture) : "0"; - - var baseUrlParam = string.Empty; - - if (state.Request is GetLiveHlsStream) - { - baseUrlParam = string.Format(" -hls_base_url \"{0}/\"", - "hls/" + Path.GetFileNameWithoutExtension(outputPath)); - } - - var useGenericSegmenter = true; - if (useGenericSegmenter) - { - var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); - - var timeDeltaParam = string.Empty; - - var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); - if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) - { - segmentFormat = "mpegts"; - } - - baseUrlParam = string.Format("\"{0}/\"", "hls/" + Path.GetFileNameWithoutExtension(outputPath)); - - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_entry_prefix {12} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", - inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), - threads, - EncodingHelper.GetMapArgs(state), - GetVideoArguments(state, encodingOptions), - GetAudioArguments(state, encodingOptions), - state.SegmentLength.ToString(CultureInfo.InvariantCulture), - startNumberParam, - outputPath, - outputTsArg, - timeDeltaParam, - segmentFormat, - baseUrlParam - ).Trim(); - } - - // add when stream copying? - // -avoid_negative_ts make_zero -fflags +genpts - - var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"", - itsOffset, - inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), - threads, - EncodingHelper.GetMapArgs(state), - GetVideoArguments(state, encodingOptions), - GetAudioArguments(state, encodingOptions), - state.SegmentLength.ToString(CultureInfo.InvariantCulture), - startNumberParam, - state.HlsListSize.ToString(CultureInfo.InvariantCulture), - baseUrlParam, - outputPath - ).Trim(); - - return args; - } - - protected override string GetDefaultEncoderPreset() - { - return "veryfast"; - } - - protected virtual int GetStartNumber(StreamState state) - { - return 0; - } - } -} diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs deleted file mode 100644 index b8ea9c6da..000000000 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ /dev/null @@ -1,1230 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using MimeTypes = MediaBrowser.Model.Net.MimeTypes; - -namespace MediaBrowser.Api.Playback.Hls -{ - /// <summary> - /// Options is needed for chromecast. Threw Head in there since it's related - /// </summary> - [Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")] - [Route("/Videos/{Id}/master.m3u8", "HEAD", Summary = "Gets a video stream using HTTP live streaming.")] - public class GetMasterHlsVideoPlaylist : VideoStreamRequest, IMasterHlsRequest - { - public bool EnableAdaptiveBitrateStreaming { get; set; } - - public GetMasterHlsVideoPlaylist() - { - EnableAdaptiveBitrateStreaming = true; - } - } - - [Route("/Audio/{Id}/master.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")] - [Route("/Audio/{Id}/master.m3u8", "HEAD", Summary = "Gets an audio stream using HTTP live streaming.")] - public class GetMasterHlsAudioPlaylist : StreamRequest, IMasterHlsRequest - { - public bool EnableAdaptiveBitrateStreaming { get; set; } - - public GetMasterHlsAudioPlaylist() - { - EnableAdaptiveBitrateStreaming = true; - } - } - - public interface IMasterHlsRequest - { - bool EnableAdaptiveBitrateStreaming { get; set; } - } - - [Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")] - public class GetVariantHlsVideoPlaylist : VideoStreamRequest - { - } - - [Route("/Audio/{Id}/main.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")] - public class GetVariantHlsAudioPlaylist : StreamRequest - { - } - - [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] - public class GetHlsVideoSegment : VideoStreamRequest - { - public string PlaylistId { get; set; } - - /// <summary> - /// Gets or sets the segment id. - /// </summary> - /// <value>The segment id.</value> - public string SegmentId { get; set; } - } - - [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] - public class GetHlsAudioSegment : StreamRequest - { - public string PlaylistId { get; set; } - - /// <summary> - /// Gets or sets the segment id. - /// </summary> - /// <value>The segment id.</value> - public string SegmentId { get; set; } - } - - [Authenticated] - public class DynamicHlsService : BaseHlsService - { - public DynamicHlsService( - ILogger<DynamicHlsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - INetworkManager networkManager, - EncodingHelper encodingHelper) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext, - encodingHelper) - { - NetworkManager = networkManager; - } - - protected INetworkManager NetworkManager { get; private set; } - - public Task<object> Get(GetMasterHlsVideoPlaylist request) - { - return GetMasterPlaylistInternal(request, "GET"); - } - - public Task<object> Head(GetMasterHlsVideoPlaylist request) - { - return GetMasterPlaylistInternal(request, "HEAD"); - } - - public Task<object> Get(GetMasterHlsAudioPlaylist request) - { - return GetMasterPlaylistInternal(request, "GET"); - } - - public Task<object> Head(GetMasterHlsAudioPlaylist request) - { - return GetMasterPlaylistInternal(request, "HEAD"); - } - - public Task<object> Get(GetVariantHlsVideoPlaylist request) - { - return GetVariantPlaylistInternal(request, true, "main"); - } - - public Task<object> Get(GetVariantHlsAudioPlaylist request) - { - return GetVariantPlaylistInternal(request, false, "main"); - } - - public Task<object> Get(GetHlsVideoSegment request) - { - return GetDynamicSegment(request, request.SegmentId); - } - - public Task<object> Get(GetHlsAudioSegment request) - { - return GetDynamicSegment(request, request.SegmentId); - } - - private async Task<object> GetDynamicSegment(StreamRequest request, string segmentId) - { - if ((request.StartTimeTicks ?? 0) > 0) - { - throw new ArgumentException("StartTimeTicks is not allowed."); - } - - var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - - var requestedIndex = int.Parse(segmentId, NumberStyles.Integer, CultureInfo.InvariantCulture); - - var state = await GetState(request, cancellationToken).ConfigureAwait(false); - - var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); - - var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex); - - var segmentExtension = GetSegmentFileExtension(state.Request); - - TranscodingJob job = null; - - if (File.Exists(segmentPath)) - { - job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - Logger.LogDebug("returning {0} [it exists, try 1]", segmentPath); - return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); - } - - var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlistPath); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); - var released = false; - var startTranscoding = false; - - try - { - if (File.Exists(segmentPath)) - { - job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - transcodingLock.Release(); - released = true; - Logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); - return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); - } - else - { - var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); - var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength; - - if (currentTranscodingIndex == null) - { - Logger.LogDebug("Starting transcoding because currentTranscodingIndex=null"); - startTranscoding = true; - } - else if (requestedIndex < currentTranscodingIndex.Value) - { - Logger.LogDebug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", requestedIndex, currentTranscodingIndex); - startTranscoding = true; - } - else if (requestedIndex - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange) - { - Logger.LogDebug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", requestedIndex - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, requestedIndex); - startTranscoding = true; - } - if (startTranscoding) - { - // If the playlist doesn't already exist, startup ffmpeg - try - { - await ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); - - if (currentTranscodingIndex.HasValue) - { - DeleteLastFile(playlistPath, segmentExtension, 0); - } - - request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex); - - state.WaitForPath = segmentPath; - job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); - } - catch - { - state.Dispose(); - throw; - } - - //await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false); - } - else - { - job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - if (job.TranscodingThrottler != null) - { - await job.TranscodingThrottler.UnpauseTranscoding(); - } - } - } - } - finally - { - if (!released) - { - transcodingLock.Release(); - } - } - - //Logger.LogInformation("waiting for {0}", segmentPath); - //while (!File.Exists(segmentPath)) - //{ - // await Task.Delay(50, cancellationToken).ConfigureAwait(false); - //} - - Logger.LogDebug("returning {0} [general case]", segmentPath); - job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false); - } - - private const int BufferSize = 81920; - - private long GetStartPositionTicks(StreamState state, int requestedIndex) - { - double startSeconds = 0; - var lengths = GetSegmentLengths(state); - - if (requestedIndex >= lengths.Length) - { - var msg = string.Format("Invalid segment index requested: {0} - Segment count: {1}", requestedIndex, lengths.Length); - throw new ArgumentException(msg); - } - - for (var i = 0; i < requestedIndex; i++) - { - startSeconds += lengths[i]; - } - - var position = TimeSpan.FromSeconds(startSeconds).Ticks; - return position; - } - - private long GetEndPositionTicks(StreamState state, int requestedIndex) - { - double startSeconds = 0; - var lengths = GetSegmentLengths(state); - - if (requestedIndex >= lengths.Length) - { - var msg = string.Format("Invalid segment index requested: {0} - Segment count: {1}", requestedIndex, lengths.Length); - throw new ArgumentException(msg); - } - - for (var i = 0; i <= requestedIndex; i++) - { - startSeconds += lengths[i]; - } - - var position = TimeSpan.FromSeconds(startSeconds).Ticks; - return position; - } - - private double[] GetSegmentLengths(StreamState state) - { - var result = new List<double>(); - - var ticks = state.RunTimeTicks ?? 0; - - var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks; - - while (ticks > 0) - { - var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks; - - result.Add(TimeSpan.FromTicks(length).TotalSeconds); - - ticks -= length; - } - - return result.ToArray(); - } - - public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) - { - var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType); - - if (job == null || job.HasExited) - { - return null; - } - - var file = GetLastTranscodingFile(playlist, segmentExtension, FileSystem); - - if (file == null) - { - return null; - } - - var playlistFilename = Path.GetFileNameWithoutExtension(playlist); - - var indexString = Path.GetFileNameWithoutExtension(file.Name).Substring(playlistFilename.Length); - - return int.Parse(indexString, NumberStyles.Integer, CultureInfo.InvariantCulture); - } - - private void DeleteLastFile(string playlistPath, string segmentExtension, int retryCount) - { - var file = GetLastTranscodingFile(playlistPath, segmentExtension, FileSystem); - - if (file != null) - { - DeleteFile(file.FullName, retryCount); - } - } - - private void DeleteFile(string path, int retryCount) - { - if (retryCount >= 5) - { - return; - } - - Logger.LogDebug("Deleting partial HLS file {path}", path); - - try - { - FileSystem.DeleteFile(path); - } - catch (IOException ex) - { - Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path); - - var task = Task.Delay(100); - Task.WaitAll(task); - DeleteFile(path, retryCount + 1); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path); - } - } - - private static FileSystemMetadata GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem) - { - var folder = Path.GetDirectoryName(playlist); - - var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty; - - try - { - return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false) - .Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase)) - .OrderByDescending(fileSystem.GetLastWriteTimeUtc) - .FirstOrDefault(); - } - catch (IOException) - { - return null; - } - } - - protected override int GetStartNumber(StreamState state) - { - return GetStartNumber(state.VideoRequest); - } - - private int GetStartNumber(VideoStreamRequest request) - { - var segmentId = "0"; - - if (request is GetHlsVideoSegment segmentRequest) - { - segmentId = segmentRequest.SegmentId; - } - - return int.Parse(segmentId, NumberStyles.Integer, CultureInfo.InvariantCulture); - } - - private string GetSegmentPath(StreamState state, string playlist, int index) - { - var folder = Path.GetDirectoryName(playlist); - - var filename = Path.GetFileNameWithoutExtension(playlist); - - return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request)); - } - - private async Task<object> GetSegmentResult(StreamState state, - string playlistPath, - string segmentPath, - string segmentExtension, - int segmentIndex, - TranscodingJob transcodingJob, - CancellationToken cancellationToken) - { - var segmentExists = File.Exists(segmentPath); - if (segmentExists) - { - if (transcodingJob != null && transcodingJob.HasExited) - { - // Transcoding job is over, so assume all existing files are ready - Logger.LogDebug("serving up {0} as transcode is over", segmentPath); - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } - - var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); - - // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready - if (segmentIndex < currentTranscodingIndex) - { - Logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex); - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } - } - - var nextSegmentPath = GetSegmentPath(state, playlistPath, segmentIndex + 1); - if (transcodingJob != null) - { - while (!cancellationToken.IsCancellationRequested && !transcodingJob.HasExited) - { - // To be considered ready, the segment file has to exist AND - // either the transcoding job should be done or next segment should also exist - if (segmentExists) - { - if (transcodingJob.HasExited || File.Exists(nextSegmentPath)) - { - Logger.LogDebug("serving up {0} as it deemed ready", segmentPath); - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } - } - else - { - segmentExists = File.Exists(segmentPath); - if (segmentExists) - { - continue; // avoid unnecessary waiting if segment just became available - } - } - - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - - if (!File.Exists(segmentPath)) - { - Logger.LogWarning("cannot serve {0} as transcoding quit before we got there", segmentPath); - } - else - { - Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath); - } - cancellationToken.ThrowIfCancellationRequested(); - } - else - { - Logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath); - } - - return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false); - } - - private Task<object> GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob) - { - var segmentEndingPositionTicks = GetEndPositionTicks(state, index); - - return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - Path = segmentPath, - FileShare = FileShare.ReadWrite, - OnComplete = () => - { - Logger.LogDebug("finished serving {0}", segmentPath); - if (transcodingJob != null) - { - transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); - ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); - } - } - }); - } - - private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method) - { - var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); - - if (string.IsNullOrEmpty(request.MediaSourceId)) - { - throw new ArgumentException("MediaSourceId is required"); - } - - var playlistText = string.Empty; - - if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) - { - var audioBitrate = state.OutputAudioBitrate ?? 0; - var videoBitrate = state.OutputVideoBitrate ?? 0; - - playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate); - } - - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - private string GetMasterPlaylistFileText(StreamState state, int totalBitrate) - { - var builder = new StringBuilder(); - - builder.AppendLine("#EXTM3U"); - - var isLiveStream = state.IsSegmentedLiveStream; - - var queryStringIndex = Request.RawUrl.IndexOf('?'); - var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - - // from universal audio service - if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer)) - { - queryString += "&SegmentContainer=" + state.Request.SegmentContainer; - } - // from universal audio service - if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1) - { - queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons; - } - - // Main stream - var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8"; - - playlistUrl += queryString; - - var request = state.Request; - - var subtitleStreams = state.MediaSource - .MediaStreams - .Where(i => i.IsTextSubtitleStream) - .ToList(); - - var subtitleGroup = subtitleStreams.Count > 0 && - request is GetMasterHlsVideoPlaylist && - (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest.EnableSubtitlesInManifest) ? - "subs" : - null; - - // If we're burning in subtitles then don't add additional subs to the manifest - if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) - { - subtitleGroup = null; - } - - if (!string.IsNullOrWhiteSpace(subtitleGroup)) - { - AddSubtitles(state, subtitleStreams, builder); - } - - AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup); - - if (EnableAdaptiveBitrateStreaming(state, isLiveStream)) - { - var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0; - - // By default, vary by just 200k - var variation = GetBitrateVariation(totalBitrate); - - var newBitrate = totalBitrate - variation; - var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation); - AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup); - - variation *= 2; - newBitrate = totalBitrate - variation; - variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation); - AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup); - } - - return builder.ToString(); - } - - private string ReplaceBitrate(string url, int oldValue, int newValue) - { - return url.Replace( - "videobitrate=" + oldValue.ToString(CultureInfo.InvariantCulture), - "videobitrate=" + newValue.ToString(CultureInfo.InvariantCulture), - StringComparison.OrdinalIgnoreCase); - } - - private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder) - { - var selectedIndex = state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Hls ? (int?)null : state.SubtitleStream.Index; - - foreach (var stream in subtitles) - { - const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\""; - - var name = stream.DisplayTitle; - - var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index; - var isForced = stream.IsForced; - - var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}", - state.Request.MediaSourceId, - stream.Index.ToString(CultureInfo.InvariantCulture), - 30.ToString(CultureInfo.InvariantCulture), - AuthorizationContext.GetAuthorizationInfo(Request).Token); - - var line = string.Format(format, - name, - isDefault ? "YES" : "NO", - isForced ? "YES" : "NO", - url, - stream.Language ?? "Unknown"); - - builder.AppendLine(line); - } - } - - private bool EnableAdaptiveBitrateStreaming(StreamState state, bool isLiveStream) - { - // Within the local network this will likely do more harm than good. - if (Request.IsLocal || NetworkManager.IsInLocalNetwork(Request.RemoteIp)) - { - return false; - } - - if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming) - { - return false; - } - - if (isLiveStream || string.IsNullOrWhiteSpace(state.MediaPath)) - { - // Opening live streams is so slow it's not even worth it - return false; - } - - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (!state.IsOutputVideo) - { - return false; - } - - // Having problems in android - return false; - //return state.VideoRequest.VideoBitRate.HasValue; - } - - /// <summary> - /// Get the H.26X level of the output video stream. - /// </summary> - /// <param name="state">StreamState of the current stream.</param> - /// <returns>H.26X level of the output video stream.</returns> - private int? GetOutputVideoCodecLevel(StreamState state) - { - string levelString; - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) - && state.VideoStream.Level.HasValue) - { - levelString = state.VideoStream?.Level.ToString(); - } - else - { - levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec); - } - - if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel)) - { - return parsedLevel; - } - - return null; - } - - /// <summary> - /// Gets a formatted string of the output audio codec, for use in the CODECS field. - /// </summary> - /// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/> - /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/> - /// <param name="state">StreamState of the current stream.</param> - /// <returns>Formatted audio codec string.</returns> - private string GetPlaylistAudioCodecs(StreamState state) - { - - if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase)) - { - string profile = state.GetRequestedProfiles("aac").FirstOrDefault(); - - return HlsCodecStringFactory.GetAACString(profile); - } - else if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase)) - { - return HlsCodecStringFactory.GetMP3String(); - } - else if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase)) - { - return HlsCodecStringFactory.GetAC3String(); - } - else if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase)) - { - return HlsCodecStringFactory.GetEAC3String(); - } - - return string.Empty; - } - - /// <summary> - /// Gets a formatted string of the output video codec, for use in the CODECS field. - /// </summary> - /// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/> - /// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/> - /// <param name="state">StreamState of the current stream.</param> - /// <returns>Formatted video codec string.</returns> - private string GetPlaylistVideoCodecs(StreamState state, string codec, int level) - { - if (level == 0) - { - // This is 0 when there's no requested H.26X level in the device profile - // and the source is not encoded in H.26X - Logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist"); - return string.Empty; - } - - if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) - { - string profile = state.GetRequestedProfiles("h264").FirstOrDefault(); - - return HlsCodecStringFactory.GetH264String(profile, level); - } - else if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) - { - string profile = state.GetRequestedProfiles("h265").FirstOrDefault(); - - return HlsCodecStringFactory.GetH265String(profile, level); - } - - return string.Empty; - } - - /// <summary> - /// Appends a CODECS field containing formatted strings of - /// the active streams output video and audio codecs. - /// </summary> - /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> - /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/> - /// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/> - /// <param name="builder">StringBuilder to append the field to.</param> - /// <param name="state">StreamState of the current stream.</param> - private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state) - { - // Video - string videoCodecs = string.Empty; - int? videoCodecLevel = GetOutputVideoCodecLevel(state); - if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue) - { - videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value); - } - - // Audio - string audioCodecs = string.Empty; - if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec)) - { - audioCodecs = GetPlaylistAudioCodecs(state); - } - - StringBuilder codecs = new StringBuilder(); - - codecs.Append(videoCodecs) - .Append(',') - .Append(audioCodecs); - - if (codecs.Length > 1) - { - builder.Append(",CODECS=\"") - .Append(codecs) - .Append('"'); - } - } - - /// <summary> - /// Appends a FRAME-RATE field containing the framerate of the output stream. - /// </summary> - /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> - /// <param name="builder">StringBuilder to append the field to.</param> - /// <param name="state">StreamState of the current stream.</param> - private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state) - { - double? framerate = null; - if (state.TargetFramerate.HasValue) - { - framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3); - } - else if (state.VideoStream?.RealFrameRate != null) - { - framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3); - } - - if (framerate.HasValue) - { - builder.Append(",FRAME-RATE=\"") - .Append(framerate.Value) - .Append('"'); - } - } - - /// <summary> - /// Appends a RESOLUTION field containing the resolution of the output stream. - /// </summary> - /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/> - /// <param name="builder">StringBuilder to append the field to.</param> - /// <param name="state">StreamState of the current stream.</param> - private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state) - { - if (state.OutputWidth.HasValue && state.OutputHeight.HasValue) - { - builder.Append(",RESOLUTION=\"") - .Append(state.OutputWidth.GetValueOrDefault()) - .Append('x') - .Append(state.OutputHeight.GetValueOrDefault()) - .Append('"'); - } - } - - private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup) - { - builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=") - .Append(bitrate.ToString(CultureInfo.InvariantCulture)) - .Append(",AVERAGE-BANDWIDTH=") - .Append(bitrate.ToString(CultureInfo.InvariantCulture)); - - AppendPlaylistCodecsField(builder, state); - - AppendPlaylistResolutionField(builder, state); - - AppendPlaylistFramerateField(builder, state); - - if (!string.IsNullOrWhiteSpace(subtitleGroup)) - { - builder.Append(",SUBTITLES=\"") - .Append(subtitleGroup) - .Append('"'); - } - - builder.Append(Environment.NewLine); - builder.AppendLine(url); - } - - private int GetBitrateVariation(int bitrate) - { - // By default, vary by just 50k - var variation = 50000; - - if (bitrate >= 10000000) - { - variation = 2000000; - } - else if (bitrate >= 5000000) - { - variation = 1500000; - } - else if (bitrate >= 3000000) - { - variation = 1000000; - } - else if (bitrate >= 2000000) - { - variation = 500000; - } - else if (bitrate >= 1000000) - { - variation = 300000; - } - else if (bitrate >= 600000) - { - variation = 200000; - } - else if (bitrate >= 400000) - { - variation = 100000; - } - - return variation; - } - - private async Task<object> GetVariantPlaylistInternal(StreamRequest request, bool isOutputVideo, string name) - { - var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); - - var segmentLengths = GetSegmentLengths(state); - - var builder = new StringBuilder(); - - builder.AppendLine("#EXTM3U"); - builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); - builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling(segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength).ToString(CultureInfo.InvariantCulture)); - builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); - - var queryStringIndex = Request.RawUrl.IndexOf('?'); - var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - - //if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1) - //{ - // queryString = string.Empty; - //} - - var index = 0; - - foreach (var length in segmentLengths) - { - builder.AppendLine("#EXTINF:" + length.ToString("0.0000", CultureInfo.InvariantCulture) + ", nodesc"); - - builder.AppendLine(string.Format("hls1/{0}/{1}{2}{3}", - - name, - index.ToString(CultureInfo.InvariantCulture), - GetSegmentFileExtension(request), - queryString)); - - index++; - } - - builder.AppendLine("#EXT-X-ENDLIST"); - - var playlistText = builder.ToString(); - - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions) - { - var audioCodec = EncodingHelper.GetAudioEncoder(state); - - if (!state.IsOutputVideo) - { - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return "-acodec copy"; - } - - var audioTranscodeParams = new List<string>(); - - audioTranscodeParams.Add("-acodec " + audioCodec); - - if (state.OutputAudioBitrate.HasValue) - { - audioTranscodeParams.Add("-ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (state.OutputAudioChannels.HasValue) - { - audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); - } - - if (state.OutputAudioSampleRate.HasValue) - { - audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); - } - - audioTranscodeParams.Add("-vn"); - return string.Join(" ", audioTranscodeParams.ToArray()); - } - - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.EnableBreakOnNonKeyFrames(videoCodec)) - { - return "-codec:a:0 copy -copypriorss:a:0 0"; - } - - return "-codec:a:0 copy"; - } - - var args = "-codec:a:0 " + audioCodec; - - var channels = state.OutputAudioChannels; - - if (channels.HasValue) - { - args += " -ac " + channels.Value; - } - - var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) - { - args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); - } - - if (state.OutputAudioSampleRate.HasValue) - { - args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); - } - - args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true); - - return args; - } - - protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions) - { - if (!state.IsOutputVideo) - { - return string.Empty; - } - - var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - var args = "-codec:v:0 " + codec; - - // if (state.EnableMpegtsM2TsMode) - // { - // args += " -mpegts_m2ts_mode 1"; - // } - - // See if we can save come cpu cycles by avoiding encoding - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) - { - string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream); - if (!string.IsNullOrEmpty(bitStreamArgs)) - { - args += " " + bitStreamArgs; - } - } - - //args += " -flags -global_header"; - } - else - { - var gopArg = string.Empty; - var keyFrameArg = string.Format( - CultureInfo.InvariantCulture, - " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"", - GetStartNumber(state) * state.SegmentLength, - state.SegmentLength); - - var framerate = state.VideoStream?.RealFrameRate; - - if (framerate.HasValue) - { - // This is to make sure keyframe interval is limited to our segment, - // as forcing keyframes is not enough. - // Example: we encoded half of desired length, then codec detected - // scene cut and inserted a keyframe; next forced keyframe would - // be created outside of segment, which breaks seeking - // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe - gopArg = string.Format( - CultureInfo.InvariantCulture, - " -g {0} -keyint_min {0} -sc_threshold 0", - Math.Ceiling(state.SegmentLength * framerate.Value) - ); - } - - args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()); - - // Unable to force key frames using these hw encoders, set key frames by GOP - if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)) - { - args += " " + gopArg; - } - else - { - args += " " + keyFrameArg + gopArg; - } - - //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; - - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - - // This is for graphical subs - if (hasGraphicalSubs) - { - args += EncodingHelper.GetGraphicalSubtitleParam(state, encodingOptions, codec); - } - // Add resolution params, if specified - else - { - args += EncodingHelper.GetOutputSizeParam(state, encodingOptions, codec); - } - - // -start_at_zero is necessary to use with -ss when seeking, - // otherwise the target position cannot be determined. - if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) - { - args += " -start_at_zero"; - } - - //args += " -flags -global_header"; - } - - if (!string.IsNullOrEmpty(state.OutputVideoSync)) - { - args += " -vsync " + state.OutputVideoSync; - } - - args += EncodingHelper.GetOutputFFlags(state); - - return args; - } - - protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) - { - var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec); - - if (state.BaseRequest.BreakOnNonKeyFrames) - { - // FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe - // breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable - // to produce a missing part of video stream before first keyframe is encountered, which may lead to - // awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js - Logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request"); - state.BaseRequest.BreakOnNonKeyFrames = false; - } - - var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions); - - // If isEncoding is true we're actually starting ffmpeg - var startNumber = GetStartNumber(state); - var startNumberParam = isEncoding ? startNumber.ToString(CultureInfo.InvariantCulture) : "0"; - - var mapArgs = state.IsOutputVideo ? EncodingHelper.GetMapArgs(state) : string.Empty; - - var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); - - var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.'); - if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase)) - { - segmentFormat = "mpegts"; - } - - return string.Format( - "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"", - inputModifier, - EncodingHelper.GetInputArgument(state, encodingOptions), - threads, - mapArgs, - GetVideoArguments(state, encodingOptions), - GetAudioArguments(state, encodingOptions), - state.SegmentLength.ToString(CultureInfo.InvariantCulture), - segmentFormat, - startNumberParam, - outputTsArg, - outputPath - ).Trim(); - } - } -} diff --git a/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs b/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs deleted file mode 100644 index 3bbb77a65..000000000 --- a/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Text; - - -namespace MediaBrowser.Api.Playback -{ - /// <summary> - /// Get various codec strings for use in HLS playlists. - /// </summary> - static class HlsCodecStringFactory - { - - /// <summary> - /// Gets a MP3 codec string. - /// </summary> - /// <returns>MP3 codec string.</returns> - public static string GetMP3String() - { - return "mp4a.40.34"; - } - - /// <summary> - /// Gets an AAC codec string. - /// </summary> - /// <param name="profile">AAC profile.</param> - /// <returns>AAC codec string.</returns> - public static string GetAACString(string profile) - { - StringBuilder result = new StringBuilder("mp4a", 9); - - if (string.Equals(profile, "HE", StringComparison.OrdinalIgnoreCase)) - { - result.Append(".40.5"); - } - else - { - // Default to LC if profile is invalid - result.Append(".40.2"); - } - - return result.ToString(); - } - - /// <summary> - /// Gets a H.264 codec string. - /// </summary> - /// <param name="profile">H.264 profile.</param> - /// <param name="level">H.264 level.</param> - /// <returns>H.264 string.</returns> - public static string GetH264String(string profile, int level) - { - StringBuilder result = new StringBuilder("avc1", 11); - - if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase)) - { - result.Append(".6400"); - } - else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase)) - { - result.Append(".4D40"); - } - else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase)) - { - result.Append(".42E0"); - } - else - { - // Default to constrained baseline if profile is invalid - result.Append(".4240"); - } - - string levelHex = level.ToString("X2"); - result.Append(levelHex); - - return result.ToString(); - } - - /// <summary> - /// Gets a H.265 codec string. - /// </summary> - /// <param name="profile">H.265 profile.</param> - /// <param name="level">H.265 level.</param> - /// <returns>H.265 string.</returns> - public static string GetH265String(string profile, int level) - { - // The h265 syntax is a bit of a mystery at the time this comment was written. - // This is what I've found through various sources: - // FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN] - StringBuilder result = new StringBuilder("hev1", 16); - - if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase)) - { - result.Append(".2.6"); - } - else - { - // Default to main if profile is invalid - result.Append(".1.6"); - } - - result.Append(".L") - .Append(level * 3) - .Append(".B0"); - - return result.ToString(); - } - - /// <summary> - /// Gets an AC-3 codec string. - /// </summary> - /// <returns>AC-3 codec string.</returns> - public static string GetAC3String() - { - return "mp4a.a5"; - } - - /// <summary> - /// Gets an E-AC-3 codec string. - /// </summary> - /// <returns>E-AC-3 codec string.</returns> - public static string GetEAC3String() - { - return "mp4a.a6"; - } - } -} diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs deleted file mode 100644 index 87ccde2e0..000000000 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback.Hls -{ - /// <summary> - /// Class GetHlsAudioSegment - /// </summary> - // Can't require authentication just yet due to seeing some requests come from Chrome without full query string - //[Authenticated] - [Route("/Audio/{Id}/hls/{SegmentId}/stream.mp3", "GET")] - [Route("/Audio/{Id}/hls/{SegmentId}/stream.aac", "GET")] - public class GetHlsAudioSegmentLegacy - { - // TODO: Deprecate with new iOS app - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - public string Id { get; set; } - - /// <summary> - /// Gets or sets the segment id. - /// </summary> - /// <value>The segment id.</value> - public string SegmentId { get; set; } - } - - /// <summary> - /// Class GetHlsVideoSegment - /// </summary> - [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")] - [Authenticated] - public class GetHlsPlaylistLegacy - { - // TODO: Deprecate with new iOS app - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - public string Id { get; set; } - - public string PlaylistId { get; set; } - } - - [Route("/Videos/ActiveEncodings", "DELETE")] - [Authenticated] - public class StopEncodingProcess - { - [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string DeviceId { get; set; } - - [ApiMember(Name = "PlaySessionId", Description = "The play session id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string PlaySessionId { get; set; } - } - - /// <summary> - /// Class GetHlsVideoSegment - /// </summary> - // Can't require authentication just yet due to seeing some requests come from Chrome without full query string - //[Authenticated] - [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")] - public class GetHlsVideoSegmentLegacy : VideoStreamRequest - { - public string PlaylistId { get; set; } - - /// <summary> - /// Gets or sets the segment id. - /// </summary> - /// <value>The segment id.</value> - public string SegmentId { get; set; } - } - - public class HlsSegmentService : BaseApiService - { - private readonly IFileSystem _fileSystem; - - public HlsSegmentService( - ILogger<HlsSegmentService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IFileSystem fileSystem) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _fileSystem = fileSystem; - } - - public Task<object> Get(GetHlsPlaylistLegacy request) - { - var file = request.PlaylistId + Path.GetExtension(Request.PathInfo); - file = Path.Combine(ServerConfigurationManager.GetTranscodePath(), file); - - return GetFileResult(file, file); - } - - public Task Delete(StopEncodingProcess request) - { - return ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetHlsVideoSegmentLegacy request) - { - var file = request.SegmentId + Path.GetExtension(Request.PathInfo); - var transcodeFolderPath = ServerConfigurationManager.GetTranscodePath(); - - file = Path.Combine(transcodeFolderPath, file); - - var normalizedPlaylistId = request.PlaylistId; - - var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath) - .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); - - return GetFileResult(file, playlistPath); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetHlsAudioSegmentLegacy request) - { - // TODO: Deprecate with new iOS app - var file = request.SegmentId + Path.GetExtension(Request.PathInfo); - file = Path.Combine(ServerConfigurationManager.GetTranscodePath(), file); - - return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite); - } - - private Task<object> GetFileResult(string path, string playlistPath) - { - var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); - - return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - Path = path, - FileShare = FileShare.ReadWrite, - OnComplete = () => - { - if (transcodingJob != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); - } - } - }); - } - } -} diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs deleted file mode 100644 index d1c53c1c1..000000000 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.Globalization; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback.Hls -{ - [Route("/Videos/{Id}/live.m3u8", "GET")] - public class GetLiveHlsStream : VideoStreamRequest - { - } - - /// <summary> - /// Class VideoHlsService - /// </summary> - [Authenticated] - public class VideoHlsService : BaseHlsService - { - public VideoHlsService( - ILogger<VideoHlsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - EncodingHelper encodingHelper) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext, - encodingHelper) - { - } - - public Task<object> Get(GetLiveHlsStream request) - { - return ProcessRequestAsync(request, true); - } - - /// <summary> - /// Gets the audio arguments. - /// </summary> - protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions) - { - var codec = EncodingHelper.GetAudioEncoder(state); - - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return "-codec:a:0 copy"; - } - - var args = "-codec:a:0 " + codec; - - var channels = state.OutputAudioChannels; - - if (channels.HasValue) - { - args += " -ac " + channels.Value; - } - - var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) - { - args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); - } - - if (state.OutputAudioSampleRate.HasValue) - { - args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); - } - - args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true); - - return args; - } - - /// <summary> - /// Gets the video arguments. - /// </summary> - protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions) - { - if (!state.IsOutputVideo) - { - return string.Empty; - } - - var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - - var args = "-codec:v:0 " + codec; - - // if (state.EnableMpegtsM2TsMode) - // { - // args += " -mpegts_m2ts_mode 1"; - // } - - // See if we can save come cpu cycles by avoiding encoding - if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) - { - // if h264_mp4toannexb is ever added, do not use it for live tv - if (state.VideoStream != null && - !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) - { - string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream); - if (!string.IsNullOrEmpty(bitStreamArgs)) - { - args += " " + bitStreamArgs; - } - } - } - else - { - var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", - state.SegmentLength.ToString(CultureInfo.InvariantCulture)); - - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - - args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()) + keyFrameArg; - - // Add resolution params, if specified - if (!hasGraphicalSubs) - { - args += EncodingHelper.GetOutputSizeParam(state, encodingOptions, codec); - } - - // This is for internal graphical subs - if (hasGraphicalSubs) - { - args += EncodingHelper.GetGraphicalSubtitleParam(state, encodingOptions, codec); - } - } - - args += " -flags -global_header"; - - if (!string.IsNullOrEmpty(state.OutputVideoSync)) - { - args += " -vsync " + state.OutputVideoSync; - } - - args += EncodingHelper.GetOutputFFlags(state); - - return args; - } - } -} diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs deleted file mode 100644 index e2d771ec6..000000000 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ /dev/null @@ -1,672 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1402 -#pragma warning disable SA1649 - -using System; -using System.Buffers; -using System.Globalization; -using System.Linq; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Session; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback -{ - [Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")] - public class GetPlaybackInfo : IReturn<PlaybackInfoResponse> - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - [Route("/Items/{Id}/PlaybackInfo", "POST", Summary = "Gets live playback media info for an item")] - public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse> - { - } - - [Route("/LiveStreams/Open", "POST", Summary = "Opens a media source")] - public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse> - { - } - - [Route("/LiveStreams/Close", "POST", Summary = "Closes a media source")] - public class CloseMediaSource : IReturnVoid - { - [ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string LiveStreamId { get; set; } - } - - [Route("/Playback/BitrateTest", "GET")] - public class GetBitrateTestBytes - { - [ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")] - public int Size { get; set; } - - public GetBitrateTestBytes() - { - // 100k - Size = 102400; - } - } - - [Authenticated] - public class MediaInfoService : BaseApiService - { - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IDeviceManager _deviceManager; - private readonly ILibraryManager _libraryManager; - private readonly INetworkManager _networkManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly IUserManager _userManager; - private readonly IAuthorizationContext _authContext; - - public MediaInfoService( - ILogger<MediaInfoService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IMediaSourceManager mediaSourceManager, - IDeviceManager deviceManager, - ILibraryManager libraryManager, - INetworkManager networkManager, - IMediaEncoder mediaEncoder, - IUserManager userManager, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _mediaSourceManager = mediaSourceManager; - _deviceManager = deviceManager; - _libraryManager = libraryManager; - _networkManager = networkManager; - _mediaEncoder = mediaEncoder; - _userManager = userManager; - _authContext = authContext; - } - - public object Get(GetBitrateTestBytes request) - { - const int MaxSize = 10_000_000; - - var size = request.Size; - - if (size <= 0) - { - throw new ArgumentException($"The requested size ({size}) is equal to or smaller than 0.", nameof(request)); - } - - if (size > MaxSize) - { - throw new ArgumentException($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).", nameof(request)); - } - - byte[] buffer = ArrayPool<byte>.Shared.Rent(size); - try - { - new Random().NextBytes(buffer); - return ResultFactory.GetResult(null, buffer, "application/octet-stream"); - } - finally - { - ArrayPool<byte>.Shared.Return(buffer); - } - } - - public async Task<object> Get(GetPlaybackInfo request) - { - var result = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }).ConfigureAwait(false); - return ToOptimizedResult(result); - } - - public async Task<object> Post(OpenMediaSource request) - { - var result = await OpenMediaSource(request).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - private async Task<LiveStreamResponse> OpenMediaSource(OpenMediaSource request) - { - var authInfo = _authContext.GetAuthorizationInfo(Request); - - var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false); - - var profile = request.DeviceProfile; - if (profile == null) - { - var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); - if (caps != null) - { - profile = caps.DeviceProfile; - } - } - - if (profile != null) - { - var item = _libraryManager.GetItemById(request.ItemId); - - SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, - request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, - request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, true, true, true); - } - else - { - if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl)) - { - result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId; - } - } - - if (result.MediaSource != null) - { - NormalizeMediaSourceContainer(result.MediaSource, profile, DlnaProfileType.Video); - } - - return result; - } - - public void Post(CloseMediaSource request) - { - _mediaSourceManager.CloseLiveStream(request.LiveStreamId).GetAwaiter().GetResult(); - } - - public async Task<PlaybackInfoResponse> GetPlaybackInfo(GetPostedPlaybackInfo request) - { - var authInfo = _authContext.GetAuthorizationInfo(Request); - - var profile = request.DeviceProfile; - - Logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile); - - if (profile == null) - { - var caps = _deviceManager.GetCapabilities(authInfo.DeviceId); - if (caps != null) - { - profile = caps.DeviceProfile; - } - } - - var info = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false); - - if (profile != null) - { - var mediaSourceId = request.MediaSourceId; - - SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, request.EnableTranscoding, request.AllowVideoStreamCopy, request.AllowAudioStreamCopy); - } - - if (request.AutoOpenLiveStream) - { - var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId) ? info.MediaSources.FirstOrDefault() : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId, StringComparison.Ordinal)); - - if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId)) - { - var openStreamResult = await OpenMediaSource(new OpenMediaSource - { - AudioStreamIndex = request.AudioStreamIndex, - DeviceProfile = request.DeviceProfile, - EnableDirectPlay = request.EnableDirectPlay, - EnableDirectStream = request.EnableDirectStream, - ItemId = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MaxStreamingBitrate = request.MaxStreamingBitrate, - PlaySessionId = info.PlaySessionId, - StartTimeTicks = request.StartTimeTicks, - SubtitleStreamIndex = request.SubtitleStreamIndex, - UserId = request.UserId, - OpenToken = mediaSource.OpenToken - }).ConfigureAwait(false); - - info.MediaSources = new[] { openStreamResult.MediaSource }; - } - } - - if (info.MediaSources != null) - { - foreach (var mediaSource in info.MediaSources) - { - NormalizeMediaSourceContainer(mediaSource, profile, DlnaProfileType.Video); - } - } - - return info; - } - - private void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type) - { - mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type); - } - - public async Task<object> Post(GetPostedPlaybackInfo request) - { - var result = await GetPlaybackInfo(request).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - private async Task<PlaybackInfoResponse> GetPlaybackInfo(Guid id, Guid userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null) - { - var user = _userManager.GetUserById(userId); - var item = _libraryManager.GetItemById(id); - var result = new PlaybackInfoResponse(); - - MediaSourceInfo[] mediaSources; - if (string.IsNullOrWhiteSpace(liveStreamId)) - { - - // TODO handle supportedLiveMediaTypes? - var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false); - - if (string.IsNullOrWhiteSpace(mediaSourceId)) - { - mediaSources = mediaSourcesList.ToArray(); - } - else - { - mediaSources = mediaSourcesList - .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - } - } - else - { - var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false); - - mediaSources = new[] { mediaSource }; - } - - if (mediaSources.Length == 0) - { - result.MediaSources = Array.Empty<MediaSourceInfo>(); - - if (!result.ErrorCode.HasValue) - { - result.ErrorCode = PlaybackErrorCode.NoCompatibleStream; - } - } - else - { - // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it - // Should we move this directly into MediaSourceManager? - result.MediaSources = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources)); - - result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - } - - return result; - } - - private void SetDeviceSpecificData( - Guid itemId, - PlaybackInfoResponse result, - DeviceProfile profile, - AuthorizationInfo auth, - long? maxBitrate, - long startTimeTicks, - string mediaSourceId, - int? audioStreamIndex, - int? subtitleStreamIndex, - int? maxAudioChannels, - Guid userId, - bool enableDirectPlay, - bool forceDirectPlayRemoteMediaSource, - bool enableDirectStream, - bool enableTranscoding, - bool allowVideoStreamCopy, - bool allowAudioStreamCopy) - { - var item = _libraryManager.GetItemById(itemId); - - foreach (var mediaSource in result.MediaSources) - { - SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, forceDirectPlayRemoteMediaSource, enableDirectStream, enableTranscoding, allowVideoStreamCopy, allowAudioStreamCopy); - } - - SortMediaSources(result, maxBitrate); - } - - private void SetDeviceSpecificData( - BaseItem item, - MediaSourceInfo mediaSource, - DeviceProfile profile, - AuthorizationInfo auth, - long? maxBitrate, - long startTimeTicks, - string mediaSourceId, - int? audioStreamIndex, - int? subtitleStreamIndex, - int? maxAudioChannels, - string playSessionId, - Guid userId, - bool enableDirectPlay, - bool forceDirectPlayRemoteMediaSource, - bool enableDirectStream, - bool enableTranscoding, - bool allowVideoStreamCopy, - bool allowAudioStreamCopy) - { - var streamBuilder = new StreamBuilder(_mediaEncoder, Logger); - - var options = new VideoOptions - { - MediaSources = new[] { mediaSource }, - Context = EncodingContext.Streaming, - DeviceId = auth.DeviceId, - ItemId = item.Id, - Profile = profile, - MaxAudioChannels = maxAudioChannels - }; - - if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase)) - { - options.MediaSourceId = mediaSourceId; - options.AudioStreamIndex = audioStreamIndex; - options.SubtitleStreamIndex = subtitleStreamIndex; - } - - var user = _userManager.GetUserById(userId); - - if (!enableDirectPlay) - { - mediaSource.SupportsDirectPlay = false; - } - - if (!enableDirectStream) - { - mediaSource.SupportsDirectStream = false; - } - - if (!enableTranscoding) - { - mediaSource.SupportsTranscoding = false; - } - - if (item is Audio) - { - Logger.LogInformation("User policy for {0}. EnableAudioPlaybackTranscoding: {1}", user.Name, user.Policy.EnableAudioPlaybackTranscoding); - } - else - { - Logger.LogInformation("User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}", - user.Name, - user.Policy.EnablePlaybackRemuxing, - user.Policy.EnableVideoPlaybackTranscoding, - user.Policy.EnableAudioPlaybackTranscoding); - } - - // Beginning of Playback Determination: Attempt DirectPlay first - if (mediaSource.SupportsDirectPlay) - { - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) - { - mediaSource.SupportsDirectPlay = false; - } - else - { - var supportsDirectStream = mediaSource.SupportsDirectStream; - - // Dummy this up to fool StreamBuilder - mediaSource.SupportsDirectStream = true; - options.MaxBitrate = maxBitrate; - - if (item is Audio) - { - if (!user.Policy.EnableAudioPlaybackTranscoding) - { - 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) - ? streamBuilder.BuildAudioItem(options) - : streamBuilder.BuildVideoItem(options); - - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectPlay = false; - } - - // Set this back to what it was - mediaSource.SupportsDirectStream = supportsDirectStream; - - if (streamInfo != null) - { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - } - } - } - - if (mediaSource.SupportsDirectStream) - { - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) - { - mediaSource.SupportsDirectStream = false; - } - else - { - options.MaxBitrate = GetMaxBitrate(maxBitrate, user); - - if (item is Audio) - { - if (!user.Policy.EnableAudioPlaybackTranscoding) - { - 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) - ? streamBuilder.BuildAudioItem(options) - : streamBuilder.BuildVideoItem(options); - - if (streamInfo == null || !streamInfo.IsDirectStream) - { - mediaSource.SupportsDirectStream = false; - } - - if (streamInfo != null) - { - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - } - } - } - - if (mediaSource.SupportsTranscoding) - { - options.MaxBitrate = GetMaxBitrate(maxBitrate, user); - - // The MediaSource supports direct stream, now test to see if the client supports it - var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) - ? streamBuilder.BuildAudioItem(options) - : streamBuilder.BuildVideoItem(options); - - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) - { - if (streamInfo != null) - { - streamInfo.PlaySessionId = playSessionId; - streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); - mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; - mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - - // Do this after the above so that StartPositionTicks is set - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - } - } - else - { - if (streamInfo != null) - { - streamInfo.PlaySessionId = playSessionId; - - if (streamInfo.PlayMethod == PlayMethod.Transcode) - { - streamInfo.StartPositionTicks = startTimeTicks; - mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-'); - - if (!allowVideoStreamCopy) - { - mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; - } - if (!allowAudioStreamCopy) - { - mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; - } - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - } - - if (!allowAudioStreamCopy) - { - mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; - } - - mediaSource.TranscodingContainer = streamInfo.Container; - mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; - - // Do this after the above so that StartPositionTicks is set - SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); - } - } - } - - foreach (var attachment in mediaSource.MediaAttachments) - { - attachment.DeliveryUrl = string.Format( - CultureInfo.InvariantCulture, - "/Videos/{0}/{1}/Attachments/{2}", - item.Id, - mediaSource.Id, - attachment.Index); - } - } - - private long? GetMaxBitrate(long? clientMaxBitrate, User user) - { - var maxBitrate = clientMaxBitrate; - var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0; - - if (remoteClientMaxBitrate <= 0) - { - remoteClientMaxBitrate = ServerConfigurationManager.Configuration.RemoteClientBitrateLimit; - } - - if (remoteClientMaxBitrate > 0) - { - var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.RemoteIp); - - Logger.LogInformation("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.RemoteIp, isInLocalNetwork); - if (!isInLocalNetwork) - { - maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate); - } - } - - return maxBitrate; - } - - private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken) - { - var profiles = info.GetSubtitleProfiles(_mediaEncoder, false, "-", accessToken); - mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex; - - mediaSource.TranscodeReasons = info.TranscodeReasons; - - foreach (var profile in profiles) - { - foreach (var stream in mediaSource.MediaStreams) - { - if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index) - { - stream.DeliveryMethod = profile.DeliveryMethod; - - if (profile.DeliveryMethod == SubtitleDeliveryMethod.External) - { - stream.DeliveryUrl = profile.Url.TrimStart('-'); - stream.IsExternalUrl = profile.IsExternalUrl; - } - } - } - } - } - - private void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate) - { - var originalList = result.MediaSources.ToList(); - - result.MediaSources = result.MediaSources.OrderBy(i => - { - // Nothing beats direct playing a file - if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File) - { - return 0; - } - - return 1; - - }).ThenBy(i => - { - // Let's assume direct streaming a file is just as desirable as direct playing a remote url - if (i.SupportsDirectPlay || i.SupportsDirectStream) - { - return 0; - } - - return 1; - - }).ThenBy(i => - { - return i.Protocol switch - { - MediaProtocol.File => 0, - _ => 1, - }; - }).ThenBy(i => - { - if (maxBitrate.HasValue && i.Bitrate.HasValue) - { - return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2; - } - - return 1; - - }).ThenBy(originalList.IndexOf) - .ToArray(); - } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs deleted file mode 100644 index 34c7986ca..000000000 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback.Progressive -{ - /// <summary> - /// Class GetAudioStream - /// </summary> - [Route("/Audio/{Id}/stream.{Container}", "GET", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/stream", "GET", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/stream.{Container}", "HEAD", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/stream", "HEAD", Summary = "Gets an audio stream")] - public class GetAudioStream : StreamRequest - { - } - - /// <summary> - /// Class AudioService - /// </summary> - // TODO: In order to autheneticate this in the future, Dlna playback will require updating - //[Authenticated] - public class AudioService : BaseProgressiveStreamingService - { - public AudioService( - ILogger<AudioService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IHttpClient httpClient, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - EncodingHelper encodingHelper) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - httpClient, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext, - encodingHelper) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetAudioStream request) - { - return ProcessRequest(request, false); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Head(GetAudioStream request) - { - return ProcessRequest(request, true); - } - - protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) - { - return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath); - } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs deleted file mode 100644 index c7bf055fb..000000000 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ /dev/null @@ -1,436 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; - -namespace MediaBrowser.Api.Playback.Progressive -{ - /// <summary> - /// Class BaseProgressiveStreamingService - /// </summary> - public abstract class BaseProgressiveStreamingService : BaseStreamingService - { - protected IHttpClient HttpClient { get; private set; } - - public BaseProgressiveStreamingService( - ILogger<BaseProgressiveStreamingService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IHttpClient httpClient, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - EncodingHelper encodingHelper) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext, - encodingHelper) - { - HttpClient = httpClient; - } - - /// <summary> - /// Gets the output file extension. - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.String.</returns> - protected override string GetOutputFileExtension(StreamState state) - { - var ext = base.GetOutputFileExtension(state); - - if (!string.IsNullOrEmpty(ext)) - { - return ext; - } - - var isVideoRequest = state.VideoRequest != null; - - // Try to infer based on the desired video codec - if (isVideoRequest) - { - var videoCodec = state.VideoRequest.VideoCodec; - - if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase)) - { - return ".ts"; - } - if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return ".ogv"; - } - if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return ".webm"; - } - if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return ".asf"; - } - } - - // Try to infer based on the desired audio codec - if (!isVideoRequest) - { - var audioCodec = state.Request.AudioCodec; - - if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".aac"; - } - if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".mp3"; - } - if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".ogg"; - } - if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".wma"; - } - } - - return null; - } - - /// <summary> - /// Gets the type of the transcoding job. - /// </summary> - /// <value>The type of the transcoding job.</value> - protected override TranscodingJobType TranscodingJobType => TranscodingJobType.Progressive; - - /// <summary> - /// Processes the request. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> - /// <returns>Task.</returns> - protected async Task<object> ProcessRequest(StreamRequest request, bool isHeadRequest) - { - var cancellationTokenSource = new CancellationTokenSource(); - - var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); - - var responseHeaders = new Dictionary<string, string>(); - - if (request.Static && state.DirectStreamProvider != null) - { - AddDlnaHeaders(state, responseHeaders, true); - - using (state) - { - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - // TODO: Don't hardcode this - outputHeaders[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file.ts"); - - return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None) - { - AllowEndOfFile = false - }; - } - } - - // Static remote stream - if (request.Static && state.InputProtocol == MediaProtocol.Http) - { - AddDlnaHeaders(state, responseHeaders, true); - - using (state) - { - return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); - } - } - - if (request.Static && state.InputProtocol != MediaProtocol.File) - { - throw new ArgumentException(string.Format("Input protocol {0} cannot be streamed statically.", state.InputProtocol)); - } - - var outputPath = state.OutputFilePath; - var outputPathExists = File.Exists(outputPath); - - var transcodingJob = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); - var isTranscodeCached = outputPathExists && transcodingJob != null; - - AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached); - - // Static stream - if (request.Static) - { - var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath); - - using (state) - { - if (state.MediaSource.IsInfiniteStream) - { - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) - { - [HeaderNames.ContentType] = contentType - }; - - - return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, CancellationToken.None) - { - AllowEndOfFile = false - }; - } - - TimeSpan? cacheDuration = null; - - if (!string.IsNullOrEmpty(request.Tag)) - { - cacheDuration = TimeSpan.FromDays(365); - } - - return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - ResponseHeaders = responseHeaders, - ContentType = contentType, - IsHeadRequest = isHeadRequest, - Path = state.MediaPath, - CacheDuration = cacheDuration - - }).ConfigureAwait(false); - } - } - - //// 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(request, state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); - } - catch - { - state.Dispose(); - - throw; - } - } - - /// <summary> - /// Gets the static remote stream result. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> - /// <param name="cancellationTokenSource">The cancellation token source.</param> - /// <returns>Task{System.Object}.</returns> - private async Task<object> GetStaticRemoteStreamResult( - StreamState state, - Dictionary<string, string> responseHeaders, - bool isHeadRequest, - CancellationTokenSource cancellationTokenSource) - { - var options = new HttpRequestOptions - { - Url = state.MediaPath, - BufferContent = false, - CancellationToken = cancellationTokenSource.Token - }; - - if (state.RemoteHttpHeaders.TryGetValue(HeaderNames.UserAgent, out var useragent)) - { - options.UserAgent = useragent; - } - - var response = await HttpClient.GetResponse(options).ConfigureAwait(false); - - responseHeaders[HeaderNames.AcceptRanges] = "none"; - - // Seeing cases of -1 here - if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) - { - responseHeaders[HeaderNames.ContentLength] = response.ContentLength.Value.ToString(CultureInfo.InvariantCulture); - } - - if (isHeadRequest) - { - using (response) - { - return ResultFactory.GetResult(null, Array.Empty<byte>(), response.ContentType, responseHeaders); - } - } - - var result = new StaticRemoteStreamWriter(response); - - result.Headers[HeaderNames.ContentType] = response.ContentType; - - // Add the response headers to the result object - foreach (var header in responseHeaders) - { - result.Headers[header.Key] = header.Value; - } - - return result; - } - - /// <summary> - /// Gets the stream result. - /// </summary> - /// <param name="state">The state.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> - /// <param name="cancellationTokenSource">The cancellation token source.</param> - /// <returns>Task{System.Object}.</returns> - private async Task<object> GetStreamResult(StreamRequest request, StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource) - { - // Use the command line args with a dummy playlist path - var outputPath = state.OutputFilePath; - - responseHeaders[HeaderNames.AcceptRanges] = "none"; - - var contentType = state.GetMimeType(outputPath); - - // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response - var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; - - if (contentLength.HasValue) - { - responseHeaders[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - } - - // Headers only - if (isHeadRequest) - { - var streamResult = ResultFactory.GetResult(null, Array.Empty<byte>(), contentType, responseHeaders); - - if (streamResult is IHasHeaders hasHeaders) - { - if (contentLength.HasValue) - { - hasHeaders.Headers[HeaderNames.ContentLength] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - } - else - { - hasHeaders.Headers.Remove(HeaderNames.ContentLength); - } - } - - return streamResult; - } - - var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); - await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); - try - { - TranscodingJob job; - - if (!File.Exists(outputPath)) - { - job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false); - } - else - { - job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); - state.Dispose(); - } - - var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) - { - [HeaderNames.ContentType] = contentType - }; - - - // Add the response headers to the result object - foreach (var item in responseHeaders) - { - outputHeaders[item.Key] = item.Value; - } - - return new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None); - } - finally - { - transcodingLock.Release(); - } - } - - /// <summary> - /// Gets the length of the estimated content. - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.Nullable{System.Int64}.</returns> - private long? GetEstimatedContentLength(StreamState state) - { - var totalBitrate = state.TotalOutputBitrate ?? 0; - - if (totalBitrate > 0 && state.RunTimeTicks.HasValue) - { - return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8); - } - - return null; - } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs deleted file mode 100644 index a53b848f9..000000000 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; -using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; - -namespace MediaBrowser.Api.Playback.Progressive -{ - public class ProgressiveFileCopier : IAsyncStreamWriter, IHasHeaders - { - 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; - - private long _bytesWritten = 0; - public long StartPosition { get; set; } - public bool AllowEndOfFile = true; - - private readonly IDirectStreamProvider _directStreamProvider; - - 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 ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) - { - _directStreamProvider = directStreamProvider; - _outputHeaders = outputHeaders; - _job = job; - _logger = logger; - _cancellationToken = cancellationToken; - } - - public IDictionary<string, string> Headers => _outputHeaders; - - private Stream GetInputStream(bool allowAsyncFileRead) - { - var fileOptions = FileOptions.SequentialScan; - - if (allowAsyncFileRead) - { - fileOptions |= FileOptions.Asynchronous; - } - - return new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions); - } - - public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) - { - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token; - - try - { - if (_directStreamProvider != null) - { - await _directStreamProvider.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false); - return; - } - - var eofCount = 0; - - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - var allowAsyncFileRead = OperatingSystem.Id != OperatingSystemId.Windows; - - using (var inputStream = GetInputStream(allowAsyncFileRead)) - { - if (StartPosition > 0) - { - inputStream.Position = StartPosition; - } - - while (eofCount < 20 || !AllowEndOfFile) - { - int bytesRead; - if (allowAsyncFileRead) - { - bytesRead = await CopyToInternalAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - } - else - { - bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - } - - //var position = fs.Position; - //_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) - { - if (_job == null || _job.HasExited) - { - eofCount++; - } - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - else - { - eofCount = 0; - } - } - } - } - finally - { - if (_job != null) - { - ApiEntryPoint.Instance.OnTranscodeEndRequest(_job); - } - } - } - - private async Task<int> CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken) - { - var array = new byte[IODefaults.CopyToBufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = source.Read(array, 0, array.Length)) != 0) - { - var bytesToWrite = bytesRead; - - if (bytesToWrite > 0) - { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - - _bytesWritten += bytesRead; - totalBytesRead += bytesRead; - - if (_job != null) - { - _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); - } - } - } - - return totalBytesRead; - } - - private async Task<int> CopyToInternalAsync(Stream source, Stream destination, CancellationToken cancellationToken) - { - var array = new byte[IODefaults.CopyToBufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) - { - var bytesToWrite = bytesRead; - - if (bytesToWrite > 0) - { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - - _bytesWritten += bytesRead; - totalBytesRead += bytesRead; - - if (_job != null) - { - _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten); - } - } - } - - return totalBytesRead; - } - } -} diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs deleted file mode 100644 index 4de81655c..000000000 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback.Progressive -{ - /// <summary> - /// Class GetVideoStream - /// </summary> - [Route("/Videos/{Id}/stream.mpegts", "GET")] - [Route("/Videos/{Id}/stream.ts", "GET")] - [Route("/Videos/{Id}/stream.webm", "GET")] - [Route("/Videos/{Id}/stream.asf", "GET")] - [Route("/Videos/{Id}/stream.wmv", "GET")] - [Route("/Videos/{Id}/stream.ogv", "GET")] - [Route("/Videos/{Id}/stream.mp4", "GET")] - [Route("/Videos/{Id}/stream.m4v", "GET")] - [Route("/Videos/{Id}/stream.mkv", "GET")] - [Route("/Videos/{Id}/stream.mpeg", "GET")] - [Route("/Videos/{Id}/stream.mpg", "GET")] - [Route("/Videos/{Id}/stream.avi", "GET")] - [Route("/Videos/{Id}/stream.m2ts", "GET")] - [Route("/Videos/{Id}/stream.3gp", "GET")] - [Route("/Videos/{Id}/stream.wmv", "GET")] - [Route("/Videos/{Id}/stream.wtv", "GET")] - [Route("/Videos/{Id}/stream.mov", "GET")] - [Route("/Videos/{Id}/stream.iso", "GET")] - [Route("/Videos/{Id}/stream.flv", "GET")] - [Route("/Videos/{Id}/stream.rm", "GET")] - [Route("/Videos/{Id}/stream", "GET")] - [Route("/Videos/{Id}/stream.ts", "HEAD")] - [Route("/Videos/{Id}/stream.webm", "HEAD")] - [Route("/Videos/{Id}/stream.asf", "HEAD")] - [Route("/Videos/{Id}/stream.wmv", "HEAD")] - [Route("/Videos/{Id}/stream.ogv", "HEAD")] - [Route("/Videos/{Id}/stream.mp4", "HEAD")] - [Route("/Videos/{Id}/stream.m4v", "HEAD")] - [Route("/Videos/{Id}/stream.mkv", "HEAD")] - [Route("/Videos/{Id}/stream.mpeg", "HEAD")] - [Route("/Videos/{Id}/stream.mpg", "HEAD")] - [Route("/Videos/{Id}/stream.avi", "HEAD")] - [Route("/Videos/{Id}/stream.3gp", "HEAD")] - [Route("/Videos/{Id}/stream.wmv", "HEAD")] - [Route("/Videos/{Id}/stream.wtv", "HEAD")] - [Route("/Videos/{Id}/stream.m2ts", "HEAD")] - [Route("/Videos/{Id}/stream.mov", "HEAD")] - [Route("/Videos/{Id}/stream.iso", "HEAD")] - [Route("/Videos/{Id}/stream.flv", "HEAD")] - [Route("/Videos/{Id}/stream", "HEAD")] - public class GetVideoStream : VideoStreamRequest - { - - } - - /// <summary> - /// Class VideoService - /// </summary> - // TODO: In order to autheneticate this in the future, Dlna playback will require updating - //[Authenticated] - public class VideoService : BaseProgressiveStreamingService - { - public VideoService( - ILogger<VideoService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IHttpClient httpClient, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - EncodingHelper encodingHelper) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - httpClient, - userManager, - libraryManager, - isoManager, - mediaEncoder, - fileSystem, - dlnaManager, - deviceManager, - mediaSourceManager, - jsonSerializer, - authorizationContext, - encodingHelper) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Get(GetVideoStream request) - { - return ProcessRequest(request, false); - } - - /// <summary> - /// Heads the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public Task<object> Head(GetVideoStream request) - { - return ProcessRequest(request, true); - } - - protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) - { - return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultEncoderPreset()); - } - } -} diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs deleted file mode 100644 index 3b8b29995..000000000 --- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Playback -{ - /// <summary> - /// Class StaticRemoteStreamWriter - /// </summary> - public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders - { - /// <summary> - /// The _input stream - /// </summary> - private readonly HttpResponseInfo _response; - - /// <summary> - /// The _options - /// </summary> - private readonly IDictionary<string, string> _options = new Dictionary<string, string>(); - - public StaticRemoteStreamWriter(HttpResponseInfo response) - { - _response = response; - } - - /// <summary> - /// Gets the options. - /// </summary> - /// <value>The options.</value> - public IDictionary<string, string> Headers => _options; - - public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) - { - using (_response) - { - await _response.Content.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs deleted file mode 100644 index 9ba8eda91..000000000 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ /dev/null @@ -1,33 +0,0 @@ -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.Playback -{ - /// <summary> - /// Class StreamRequest - /// </summary> - public class StreamRequest : BaseEncodingJobOptions - { - [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceProfileId { get; set; } - - public string Params { get; set; } - public string PlaySessionId { get; set; } - public string Tag { get; set; } - public string SegmentContainer { get; set; } - - public int? SegmentLength { get; set; } - public int? MinSegments { get; set; } - } - - public class VideoStreamRequest : StreamRequest - { - /// <summary> - /// Gets a value indicating whether this instance has fixed resolution. - /// </summary> - /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value> - public bool HasFixedResolution => Width.HasValue || Height.HasValue; - - public bool EnableSubtitlesInManifest { get; set; } - } -} diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs deleted file mode 100644 index d5d2f58c0..000000000 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; - -namespace MediaBrowser.Api.Playback -{ - public class StreamState : EncodingJobInfo, IDisposable - { - private readonly IMediaSourceManager _mediaSourceManager; - private bool _disposed = false; - - public string RequestedUrl { get; set; } - - public StreamRequest Request - { - get => (StreamRequest)BaseRequest; - set - { - BaseRequest = value; - - IsVideoRequest = VideoRequest != null; - } - } - - public TranscodingThrottler TranscodingThrottler { get; set; } - - public VideoStreamRequest VideoRequest => Request as VideoStreamRequest; - - public IDirectStreamProvider DirectStreamProvider { get; set; } - - public string WaitForPath { get; set; } - - public bool IsOutputVideo => Request is VideoStreamRequest; - - public int SegmentLength - { - get - { - if (Request.SegmentLength.HasValue) - { - return Request.SegmentLength.Value; - } - - if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - var userAgent = UserAgent ?? string.Empty; - - if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || - userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) - { - if (IsSegmentedLiveStream) - { - return 6; - } - - return 6; - } - - if (IsSegmentedLiveStream) - { - return 3; - } - - return 6; - } - - return 3; - } - } - - public int MinSegments - { - get - { - if (Request.MinSegments.HasValue) - { - return Request.MinSegments.Value; - } - - return SegmentLength >= 10 ? 2 : 3; - } - } - - public string UserAgent { get; set; } - - public bool EstimateContentLength { get; set; } - - public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - - public bool EnableDlnaHeaders { get; set; } - - public DeviceProfile DeviceProfile { get; set; } - - public TranscodingJob TranscodingJob { get; set; } - - public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType) - : base(transcodingType) - { - _mediaSourceManager = mediaSourceManager; - } - - public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) - { - ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - // REVIEW: Is this the right place for this? - if (MediaSource.RequiresClosing - && string.IsNullOrWhiteSpace(Request.LiveStreamId) - && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) - { - _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult(); - } - - TranscodingThrottler?.Dispose(); - } - - TranscodingThrottler = null; - TranscodingJob = null; - - _disposed = true; - } - } -} diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs deleted file mode 100644 index 0e73d77ef..000000000 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback -{ - public class TranscodingThrottler : IDisposable - { - private readonly TranscodingJob _job; - private readonly ILogger _logger; - private Timer _timer; - private bool _isPaused; - private readonly IConfigurationManager _config; - private readonly IFileSystem _fileSystem; - - public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config, IFileSystem fileSystem) - { - _job = job; - _logger = logger; - _config = config; - _fileSystem = fileSystem; - } - - private EncodingOptions GetOptions() - { - return _config.GetConfiguration<EncodingOptions>("encoding"); - } - - public void Start() - { - _timer = new Timer(TimerCallback, null, 5000, 5000); - } - - private async void TimerCallback(object state) - { - if (_job.HasExited) - { - DisposeTimer(); - return; - } - - var options = GetOptions(); - - if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds)) - { - await PauseTranscoding(); - } - else - { - await UnpauseTranscoding(); - } - } - - private async Task PauseTranscoding() - { - if (!_isPaused) - { - _logger.LogDebug("Sending pause command to ffmpeg"); - - try - { - await _job.Process.StandardInput.WriteAsync("c"); - _isPaused = true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error pausing transcoding"); - } - } - } - - public async Task UnpauseTranscoding() - { - if (_isPaused) - { - _logger.LogDebug("Sending resume command to ffmpeg"); - - try - { - await _job.Process.StandardInput.WriteLineAsync(); - _isPaused = false; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error resuming transcoding"); - } - } - } - - private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds) - { - var bytesDownloaded = job.BytesDownloaded ?? 0; - var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; - var downloadPositionTicks = job.DownloadPositionTicks ?? 0; - - var path = job.Path; - var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; - - if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) - { - // HLS - time-based consideration - - var targetGap = gapLengthInTicks; - var gap = transcodingPositionTicks - downloadPositionTicks; - - if (gap < targetGap) - { - _logger.LogDebug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); - return false; - } - - _logger.LogDebug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); - return true; - } - - if (bytesDownloaded > 0 && transcodingPositionTicks > 0) - { - // Progressive Streaming - byte-based consideration - - try - { - var bytesTranscoded = job.BytesTranscoded ?? _fileSystem.GetFileInfo(path).Length; - - // Estimate the bytes the transcoder should be ahead - double gapFactor = gapLengthInTicks; - gapFactor /= transcodingPositionTicks; - var targetGap = bytesTranscoded * gapFactor; - - var gap = bytesTranscoded - bytesDownloaded; - - if (gap < targetGap) - { - _logger.LogDebug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); - return false; - } - - _logger.LogDebug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting output size"); - return false; - } - } - - _logger.LogDebug("No throttle data for " + path); - return false; - } - - public async Task Stop() - { - DisposeTimer(); - await UnpauseTranscoding(); - } - - public void Dispose() - { - DisposeTimer(); - } - - private void DisposeTimer() - { - if (_timer != null) - { - _timer.Dispose(); - _timer = null; - } - } - } -} diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs deleted file mode 100644 index a3b319d44..000000000 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ /dev/null @@ -1,382 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Api.Playback.Hls; -using MediaBrowser.Api.Playback.Progressive; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Playback -{ - public class BaseUniversalRequest - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - - public Guid UserId { get; set; } - public string AudioCodec { get; set; } - public string Container { get; set; } - - public int? MaxAudioChannels { get; set; } - public int? TranscodingAudioChannels { get; set; } - - public long? MaxStreamingBitrate { get; set; } - - [ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public long? StartTimeTicks { get; set; } - - public string TranscodingContainer { get; set; } - public string TranscodingProtocol { get; set; } - public int? MaxAudioSampleRate { get; set; } - public int? MaxAudioBitDepth { get; set; } - - public bool EnableRedirection { get; set; } - public bool EnableRemoteMedia { get; set; } - public bool BreakOnNonKeyFrames { get; set; } - - public BaseUniversalRequest() - { - EnableRedirection = true; - } - } - - [Route("/Audio/{Id}/universal.{Container}", "GET", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/universal", "GET", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/universal.{Container}", "HEAD", Summary = "Gets an audio stream")] - [Route("/Audio/{Id}/universal", "HEAD", Summary = "Gets an audio stream")] - public class GetUniversalAudioStream : BaseUniversalRequest - { - } - - [Authenticated] - public class UniversalAudioService : BaseApiService - { - private readonly EncodingHelper _encodingHelper; - private readonly ILoggerFactory _loggerFactory; - - public UniversalAudioService( - ILogger<UniversalAudioService> logger, - ILoggerFactory loggerFactory, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IHttpClient httpClient, - IUserManager userManager, - ILibraryManager libraryManager, - IIsoManager isoManager, - IMediaEncoder mediaEncoder, - IFileSystem fileSystem, - IDlnaManager dlnaManager, - IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - INetworkManager networkManager, - EncodingHelper encodingHelper) - : base(logger, serverConfigurationManager, httpResultFactory) - { - HttpClient = httpClient; - UserManager = userManager; - LibraryManager = libraryManager; - IsoManager = isoManager; - MediaEncoder = mediaEncoder; - FileSystem = fileSystem; - DlnaManager = dlnaManager; - DeviceManager = deviceManager; - MediaSourceManager = mediaSourceManager; - JsonSerializer = jsonSerializer; - AuthorizationContext = authorizationContext; - NetworkManager = networkManager; - _encodingHelper = encodingHelper; - _loggerFactory = loggerFactory; - } - - protected IHttpClient HttpClient { get; private set; } - protected IUserManager UserManager { get; private set; } - protected ILibraryManager LibraryManager { get; private set; } - protected IIsoManager IsoManager { get; private set; } - protected IMediaEncoder MediaEncoder { get; private set; } - protected IFileSystem FileSystem { get; private set; } - protected IDlnaManager DlnaManager { get; private set; } - protected IDeviceManager DeviceManager { get; private set; } - protected IMediaSourceManager MediaSourceManager { get; private set; } - protected IJsonSerializer JsonSerializer { get; private set; } - protected IAuthorizationContext AuthorizationContext { get; private set; } - protected INetworkManager NetworkManager { get; private set; } - - public Task<object> Get(GetUniversalAudioStream request) - { - return GetUniversalStream(request, false); - } - - public Task<object> Head(GetUniversalAudioStream request) - { - return GetUniversalStream(request, true); - } - - private DeviceProfile GetDeviceProfile(GetUniversalAudioStream request) - { - var deviceProfile = new DeviceProfile(); - - var directPlayProfiles = new List<DirectPlayProfile>(); - - var containers = (request.Container ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - foreach (var container in containers) - { - var parts = container.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - - var audioCodecs = parts.Length == 1 ? null : string.Join(",", parts.Skip(1).ToArray()); - - directPlayProfiles.Add(new DirectPlayProfile - { - Type = DlnaProfileType.Audio, - Container = parts[0], - AudioCodec = audioCodecs - }); - } - - deviceProfile.DirectPlayProfiles = directPlayProfiles.ToArray(); - - deviceProfile.TranscodingProfiles = new[] - { - new TranscodingProfile - { - Type = DlnaProfileType.Audio, - Context = EncodingContext.Streaming, - Container = request.TranscodingContainer, - AudioCodec = request.AudioCodec, - Protocol = request.TranscodingProtocol, - BreakOnNonKeyFrames = request.BreakOnNonKeyFrames, - MaxAudioChannels = request.TranscodingAudioChannels?.ToString(CultureInfo.InvariantCulture) - } - }; - - var codecProfiles = new List<CodecProfile>(); - var conditions = new List<ProfileCondition>(); - - if (request.MaxAudioSampleRate.HasValue) - { - // codec profile - conditions.Add(new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - IsRequired = false, - Property = ProfileConditionValue.AudioSampleRate, - Value = request.MaxAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) - }); - } - - if (request.MaxAudioBitDepth.HasValue) - { - // codec profile - conditions.Add(new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - IsRequired = false, - Property = ProfileConditionValue.AudioBitDepth, - Value = request.MaxAudioBitDepth.Value.ToString(CultureInfo.InvariantCulture) - }); - } - - if (request.MaxAudioChannels.HasValue) - { - // codec profile - conditions.Add(new ProfileCondition - { - Condition = ProfileConditionType.LessThanEqual, - IsRequired = false, - Property = ProfileConditionValue.AudioChannels, - Value = request.MaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) - }); - } - - if (conditions.Count > 0) - { - // codec profile - codecProfiles.Add(new CodecProfile - { - Type = CodecType.Audio, - Container = request.Container, - Conditions = conditions.ToArray() - }); - } - - deviceProfile.CodecProfiles = codecProfiles.ToArray(); - - return deviceProfile; - } - - private async Task<object> GetUniversalStream(GetUniversalAudioStream request, bool isHeadRequest) - { - var deviceProfile = GetDeviceProfile(request); - - AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId; - - var mediaInfoService = new MediaInfoService( - _loggerFactory.CreateLogger<MediaInfoService>(), - ServerConfigurationManager, - ResultFactory, - MediaSourceManager, - DeviceManager, - LibraryManager, - NetworkManager, - MediaEncoder, - UserManager, - AuthorizationContext) - { - Request = Request - }; - - var playbackInfoResult = await mediaInfoService.GetPlaybackInfo(new GetPostedPlaybackInfo - { - Id = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MaxStreamingBitrate = request.MaxStreamingBitrate, - StartTimeTicks = request.StartTimeTicks, - UserId = request.UserId, - DeviceProfile = deviceProfile, - MediaSourceId = request.MediaSourceId - - }).ConfigureAwait(false); - - var mediaSource = playbackInfoResult.MediaSources[0]; - - if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http) - { - if (request.EnableRedirection) - { - if (mediaSource.IsRemote && request.EnableRemoteMedia) - { - return ResultFactory.GetRedirectResult(mediaSource.Path); - } - } - } - - var isStatic = mediaSource.SupportsDirectStream; - - if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) - { - var service = new DynamicHlsService( - _loggerFactory.CreateLogger<DynamicHlsService>(), - ServerConfigurationManager, - ResultFactory, - UserManager, - LibraryManager, - IsoManager, - MediaEncoder, - FileSystem, - DlnaManager, - DeviceManager, - MediaSourceManager, - JsonSerializer, - AuthorizationContext, - NetworkManager, - _encodingHelper) - { - Request = Request - }; - - var transcodingProfile = deviceProfile.TranscodingProfiles[0]; - - // hls segment container can only be mpegts or fmp4 per ffmpeg documentation - // TODO: remove this when we switch back to the segment muxer - var supportedHLSContainers = new[] { "mpegts", "fmp4" }; - - var newRequest = new GetMasterHlsAudioPlaylist - { - AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)), - AudioCodec = transcodingProfile.AudioCodec, - Container = ".m3u8", - DeviceId = request.DeviceId, - Id = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MediaSourceId = mediaSource.Id, - PlaySessionId = playbackInfoResult.PlaySessionId, - StartTimeTicks = request.StartTimeTicks, - Static = isStatic, - // fallback to mpegts if device reports some weird value unsupported by hls - SegmentContainer = Array.Exists(supportedHLSContainers, element => element == request.TranscodingContainer) ? request.TranscodingContainer : "mpegts", - AudioSampleRate = request.MaxAudioSampleRate, - MaxAudioBitDepth = request.MaxAudioBitDepth, - BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames, - TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()) - }; - - if (isHeadRequest) - { - return await service.Head(newRequest).ConfigureAwait(false); - } - return await service.Get(newRequest).ConfigureAwait(false); - } - else - { - var service = new AudioService( - _loggerFactory.CreateLogger<AudioService>(), - ServerConfigurationManager, - ResultFactory, - HttpClient, - UserManager, - LibraryManager, - IsoManager, - MediaEncoder, - FileSystem, - DlnaManager, - DeviceManager, - MediaSourceManager, - JsonSerializer, - AuthorizationContext, - _encodingHelper) - { - Request = Request - }; - - var newRequest = new GetAudioStream - { - AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)), - AudioCodec = request.AudioCodec, - Container = isStatic ? null : ("." + mediaSource.TranscodingContainer), - DeviceId = request.DeviceId, - Id = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MediaSourceId = mediaSource.Id, - PlaySessionId = playbackInfoResult.PlaySessionId, - StartTimeTicks = request.StartTimeTicks, - Static = isStatic, - AudioSampleRate = request.MaxAudioSampleRate, - MaxAudioBitDepth = request.MaxAudioBitDepth, - TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()) - }; - - if (isHeadRequest) - { - return await service.Head(newRequest).ConfigureAwait(false); - } - - return await service.Get(newRequest).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs deleted file mode 100644 index 953b00e35..000000000 --- a/MediaBrowser.Api/PlaylistService.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Playlists; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Playlists; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Playlists", "POST", Summary = "Creates a new playlist")] - public class CreatePlaylist : IReturn<PlaylistCreationResult> - { - [ApiMember(Name = "Name", Description = "The name of the new playlist.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } - - [ApiMember(Name = "Ids", Description = "Item Ids to add to the playlist", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string Ids { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - - [ApiMember(Name = "MediaType", Description = "The playlist media type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MediaType { get; set; } - } - - [Route("/Playlists/{Id}/Items", "POST", Summary = "Adds items to a playlist")] - public class AddToPlaylist : IReturnVoid - { - [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Ids { get; set; } - - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public Guid UserId { get; set; } - } - - [Route("/Playlists/{Id}/Items/{ItemId}/Move/{NewIndex}", "POST", Summary = "Moves a playlist item")] - public class MoveItem : IReturnVoid - { - [ApiMember(Name = "ItemId", Description = "ItemId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemId { get; set; } - - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "NewIndex", Description = "NewIndex", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public int NewIndex { get; set; } - } - - [Route("/Playlists/{Id}/Items", "DELETE", Summary = "Removes items from a playlist")] - public class RemoveFromPlaylist : IReturnVoid - { - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - - [ApiMember(Name = "EntryIds", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string EntryIds { get; set; } - } - - [Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")] - public class GetPlaylistItems : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions - { - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, 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] - public class PlaylistService : BaseApiService - { - private readonly IPlaylistManager _playlistManager; - private readonly IDtoService _dtoService; - private readonly IUserManager _userManager; - private readonly ILibraryManager _libraryManager; - private readonly IAuthorizationContext _authContext; - - public PlaylistService( - ILogger<PlaylistService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IDtoService dtoService, - IPlaylistManager playlistManager, - IUserManager userManager, - ILibraryManager libraryManager, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _dtoService = dtoService; - _playlistManager = playlistManager; - _userManager = userManager; - _libraryManager = libraryManager; - _authContext = authContext; - } - - public void Post(MoveItem request) - { - _playlistManager.MoveItem(request.Id, request.ItemId, request.NewIndex); - } - - public async Task<object> Post(CreatePlaylist request) - { - var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest - { - Name = request.Name, - ItemIdList = GetGuids(request.Ids), - UserId = request.UserId, - MediaType = request.MediaType - - }).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public void Post(AddToPlaylist request) - { - _playlistManager.AddToPlaylist(request.Id, GetGuids(request.Ids), request.UserId); - } - - public void Delete(RemoveFromPlaylist request) - { - _playlistManager.RemoveFromPlaylist(request.Id, request.EntryIds.Split(',')); - } - - public object Get(GetPlaylistItems request) - { - var playlist = (Playlist)_libraryManager.GetItemById(request.Id); - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - var items = playlist.GetManageableItems().ToArray(); - - var count = items.Length; - - if (request.StartIndex.HasValue) - { - items = items.Skip(request.StartIndex.Value).ToArray(); - } - - if (request.Limit.HasValue) - { - items = items.Take(request.Limit.Value).ToArray(); - } - - var dtoOptions = GetDtoOptions(_authContext, request); - - var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user); - - for (int index = 0; index < dtos.Count; index++) - { - dtos[index].PlaylistItemId = items[index].Item1.Id; - } - - var result = new QueryResult<BaseItemDto> - { - Items = dtos, - TotalRecordCount = count - }; - - return ToOptimizedResult(result); - } - } -} diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs deleted file mode 100644 index 7f74511ee..000000000 --- a/MediaBrowser.Api/PluginService.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class Plugins - /// </summary> - [Route("/Plugins", "GET", Summary = "Gets a list of currently installed plugins")] - [Authenticated] - public class GetPlugins : IReturn<PluginInfo[]> - { - public bool? IsAppStoreEnabled { get; set; } - } - - /// <summary> - /// Class UninstallPlugin - /// </summary> - [Route("/Plugins/{Id}", "DELETE", Summary = "Uninstalls a plugin")] - [Authenticated(Roles = "Admin")] - public class UninstallPlugin : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Plugin Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - /// <summary> - /// Class GetPluginConfiguration - /// </summary> - [Route("/Plugins/{Id}/Configuration", "GET", Summary = "Gets a plugin's configuration")] - [Authenticated] - public class GetPluginConfiguration - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Plugin Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// <summary> - /// Class UpdatePluginConfiguration - /// </summary> - [Route("/Plugins/{Id}/Configuration", "POST", Summary = "Updates a plugin's configuration")] - [Authenticated] - public class UpdatePluginConfiguration : IRequiresRequestStream, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Plugin Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// The raw Http Request Input Stream - /// </summary> - /// <value>The request stream.</value> - public Stream RequestStream { get; set; } - } - - //TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins, - // delete all these registration endpoints. They are only kept for compatibility. - [Route("/Registrations/{Name}", "GET", Summary = "Gets registration status for a feature", IsHidden = true)] - [Authenticated] - public class GetRegistration : IReturn<RegistrationInfo> - { - [ApiMember(Name = "Name", Description = "Feature Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - /// <summary> - /// Class GetPluginSecurityInfo - /// </summary> - [Route("/Plugins/SecurityInfo", "GET", Summary = "Gets plugin registration information", IsHidden = true)] - [Authenticated] - public class GetPluginSecurityInfo : IReturn<PluginSecurityInfo> - { - } - - /// <summary> - /// Class UpdatePluginSecurityInfo - /// </summary> - [Route("/Plugins/SecurityInfo", "POST", Summary = "Updates plugin registration information", IsHidden = true)] - [Authenticated(Roles = "Admin")] - public class UpdatePluginSecurityInfo : PluginSecurityInfo, IReturnVoid - { - } - - [Route("/Plugins/RegistrationRecords/{Name}", "GET", Summary = "Gets registration status for a feature", IsHidden = true)] - [Authenticated] - public class GetRegistrationStatus - { - [ApiMember(Name = "Name", Description = "Feature Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - // TODO these two classes are only kept for compability with paid plugins and should be removed - public class RegistrationInfo - { - public string Name { get; set; } - public DateTime ExpirationDate { get; set; } - public bool IsTrial { get; set; } - public bool IsRegistered { get; set; } - } - - public class MBRegistrationRecord - { - public DateTime ExpirationDate { get; set; } - public bool IsRegistered { get; set; } - public bool RegChecked { get; set; } - public bool RegError { get; set; } - public bool TrialVersion { get; set; } - public bool IsValid { get; set; } - } - - public class PluginSecurityInfo - { - public string SupporterKey { get; set; } - public bool IsMBSupporter { get; set; } - } - /// <summary> - /// Class PluginsService - /// </summary> - public class PluginService : BaseApiService - { - /// <summary> - /// The _json serializer - /// </summary> - private readonly IJsonSerializer _jsonSerializer; - - /// <summary> - /// The _app host - /// </summary> - private readonly IApplicationHost _appHost; - private readonly IInstallationManager _installationManager; - - public PluginService( - ILogger<PluginService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IJsonSerializer jsonSerializer, - IApplicationHost appHost, - IInstallationManager installationManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _appHost = appHost; - _installationManager = installationManager; - _jsonSerializer = jsonSerializer; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetRegistrationStatus request) - { - var record = new MBRegistrationRecord - { - IsRegistered = true, - RegChecked = true, - TrialVersion = false, - IsValid = true, - RegError = false - }; - - return ToOptimizedResult(record); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPlugins request) - { - var result = _appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToArray(); - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPluginConfiguration request) - { - var guid = new Guid(request.Id); - var plugin = _appHost.Plugins.First(p => p.Id == guid) as IHasPluginConfiguration; - - return ToOptimizedResult(plugin.Configuration); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPluginSecurityInfo request) - { - var result = new PluginSecurityInfo - { - IsMBSupporter = true, - SupporterKey = "IAmTotallyLegit" - }; - - return ToOptimizedResult(result); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(UpdatePluginSecurityInfo request) - { - return Task.CompletedTask; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public async Task Post(UpdatePluginConfiguration request) - { - // We need to parse this manually because we told service stack not to with IRequiresRequestStream - // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs - var id = Guid.Parse(GetPathValue(1)); - - if (!(_appHost.Plugins.First(p => p.Id == id) is IHasPluginConfiguration plugin)) - { - throw new FileNotFoundException(); - } - - var configuration = (await _jsonSerializer.DeserializeFromStreamAsync(request.RequestStream, plugin.ConfigurationType).ConfigureAwait(false)) as BasePluginConfiguration; - - plugin.UpdateConfiguration(configuration); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(UninstallPlugin request) - { - var guid = new Guid(request.Id); - var plugin = _appHost.Plugins.First(p => p.Id == guid); - - _installationManager.UninstallPlugin(plugin); - } - } -} diff --git a/MediaBrowser.Api/Properties/AssemblyInfo.cs b/MediaBrowser.Api/Properties/AssemblyInfo.cs deleted file mode 100644 index 078af3e30..000000000 --- a/MediaBrowser.Api/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MediaBrowser.Api")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] -[assembly: InternalsVisibleTo("Jellyfin.Api.Tests")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs deleted file mode 100644 index e08a8482e..000000000 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Tasks; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.ScheduledTasks -{ - /// <summary> - /// Class GetScheduledTask - /// </summary> - [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")] - public class GetScheduledTask : IReturn<TaskInfo> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// <summary> - /// Class GetScheduledTasks - /// </summary> - [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")] - public class GetScheduledTasks : IReturn<TaskInfo[]> - { - [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsHidden { get; set; } - - [ApiMember(Name = "IsEnabled", Description = "Optional filter tasks that are enabled, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsEnabled { get; set; } - } - - /// <summary> - /// Class StartScheduledTask - /// </summary> - [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")] - public class StartScheduledTask : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// <summary> - /// Class StopScheduledTask - /// </summary> - [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")] - public class StopScheduledTask : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - /// <summary> - /// Class UpdateScheduledTaskTriggers - /// </summary> - [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")] - public class UpdateScheduledTaskTriggers : List<TaskTriggerInfo>, IReturnVoid - { - /// <summary> - /// Gets or sets the task id. - /// </summary> - /// <value>The task id.</value> - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// <summary> - /// Class ScheduledTasksService - /// </summary> - [Authenticated(Roles = "Admin")] - public class ScheduledTaskService : BaseApiService - { - /// <summary> - /// The task manager. - /// </summary> - private readonly ITaskManager _taskManager; - - /// <summary> - /// Initializes a new instance of the <see cref="ScheduledTaskService" /> class. - /// </summary> - /// <param name="taskManager">The task manager.</param> - /// <exception cref="ArgumentNullException">taskManager</exception> - public ScheduledTaskService( - ILogger<ScheduledTaskService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ITaskManager taskManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _taskManager = taskManager; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>IEnumerable{TaskInfo}.</returns> - public object Get(GetScheduledTasks request) - { - IEnumerable<IScheduledTaskWorker> result = _taskManager.ScheduledTasks - .OrderBy(i => i.Name); - - if (request.IsHidden.HasValue) - { - var val = request.IsHidden.Value; - - result = result.Where(i => - { - var isHidden = false; - - if (i.ScheduledTask is IConfigurableScheduledTask configurableTask) - { - isHidden = configurableTask.IsHidden; - } - - return isHidden == val; - }); - } - - if (request.IsEnabled.HasValue) - { - var val = request.IsEnabled.Value; - - result = result.Where(i => - { - var isEnabled = true; - - if (i.ScheduledTask is IConfigurableScheduledTask configurableTask) - { - isEnabled = configurableTask.IsEnabled; - } - - return isEnabled == val; - }); - } - - var infos = result - .Select(ScheduledTaskHelpers.GetTaskInfo) - .ToArray(); - - return ToOptimizedResult(infos); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>IEnumerable{TaskInfo}.</returns> - /// <exception cref="ResourceNotFoundException">Task not found</exception> - public object Get(GetScheduledTask request) - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id)); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - var result = ScheduledTaskHelpers.GetTaskInfo(task); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <exception cref="ResourceNotFoundException">Task not found</exception> - public void Post(StartScheduledTask request) - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id)); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - _taskManager.Execute(task, new TaskOptions()); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <exception cref="ResourceNotFoundException">Task not found</exception> - public void Delete(StopScheduledTask request) - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id)); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - _taskManager.Cancel(task); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <exception cref="ResourceNotFoundException">Task not found</exception> - public void Post(UpdateScheduledTaskTriggers request) - { - // We need to parse this manually because we told service stack not to with IRequiresRequestStream - // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs - var id = GetPathValue(1).ToString(); - - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.Ordinal)); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - task.Triggers = request.ToArray(); - } - } -} diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs deleted file mode 100644 index 14b9b3618..000000000 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Tasks; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.ScheduledTasks -{ - /// <summary> - /// Class ScheduledTasksWebSocketListener - /// </summary> - public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, WebSocketListenerState> - { - /// <summary> - /// Gets or sets the task manager. - /// </summary> - /// <value>The task manager.</value> - private ITaskManager TaskManager { get; set; } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - protected override string Name => "ScheduledTasksInfo"; - - /// <summary> - /// Initializes a new instance of the <see cref="ScheduledTasksWebSocketListener" /> class. - /// </summary> - public ScheduledTasksWebSocketListener(ILogger<ScheduledTasksWebSocketListener> logger, ITaskManager taskManager) - : base(logger) - { - TaskManager = taskManager; - - TaskManager.TaskExecuting += TaskManager_TaskExecuting; - TaskManager.TaskCompleted += TaskManager_TaskCompleted; - } - - void TaskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) - { - SendData(true); - e.Task.TaskProgress -= Argument_TaskProgress; - } - - void TaskManager_TaskExecuting(object sender, GenericEventArgs<IScheduledTaskWorker> e) - { - SendData(true); - e.Argument.TaskProgress += Argument_TaskProgress; - } - - void Argument_TaskProgress(object sender, GenericEventArgs<double> e) - { - SendData(false); - } - - /// <summary> - /// Gets the data to send. - /// </summary> - /// <returns>Task{IEnumerable{TaskInfo}}.</returns> - protected override Task<IEnumerable<TaskInfo>> GetDataToSend() - { - return Task.FromResult(TaskManager.ScheduledTasks - .OrderBy(i => i.Name) - .Select(ScheduledTaskHelpers.GetTaskInfo) - .Where(i => !i.IsHidden)); - } - - protected override void Dispose(bool dispose) - { - TaskManager.TaskExecuting -= TaskManager_TaskExecuting; - TaskManager.TaskCompleted -= TaskManager_TaskCompleted; - - base.Dispose(dispose); - } - } -} diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs deleted file mode 100644 index e9d339c6e..000000000 --- a/MediaBrowser.Api/SearchService.cs +++ /dev/null @@ -1,333 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Search; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetSearchHints - /// </summary> - [Route("/Search/Hints", "GET", Summary = "Gets search hints based on a search term")] - public class GetSearchHints : IReturn<SearchHintResult> - { - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Supply a user id to search within a user's library or omit to search all.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Search characters used to find items - /// </summary> - /// <value>The index by.</value> - [ApiMember(Name = "SearchTerm", Description = "The search term to filter on", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SearchTerm { get; set; } - - - [ApiMember(Name = "IncludePeople", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludePeople { get; set; } - - [ApiMember(Name = "IncludeMedia", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeMedia { get; set; } - - [ApiMember(Name = "IncludeGenres", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeGenres { get; set; } - - [ApiMember(Name = "IncludeStudios", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeStudios { get; set; } - - [ApiMember(Name = "IncludeArtists", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeArtists { get; set; } - - [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string IncludeItemTypes { get; set; } - - [ApiMember(Name = "ExcludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ExcludeItemTypes { get; set; } - - [ApiMember(Name = "MediaTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string MediaTypes { get; set; } - - public string ParentId { get; set; } - - [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsMovie { get; set; } - - [ApiMember(Name = "IsSeries", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSeries { get; set; } - - [ApiMember(Name = "IsNews", Description = "Optional filter for news.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsNews { get; set; } - - [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsKids { get; set; } - - [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] - public bool? IsSports { get; set; } - - public GetSearchHints() - { - IncludeArtists = true; - IncludeGenres = true; - IncludeMedia = true; - IncludePeople = true; - IncludeStudios = true; - } - } - - /// <summary> - /// Class SearchService - /// </summary> - [Authenticated] - public class SearchService : BaseApiService - { - /// <summary> - /// The _search engine - /// </summary> - private readonly ISearchEngine _searchEngine; - private readonly ILibraryManager _libraryManager; - private readonly IDtoService _dtoService; - private readonly IImageProcessor _imageProcessor; - - /// <summary> - /// Initializes a new instance of the <see cref="SearchService" /> class. - /// </summary> - /// <param name="searchEngine">The search engine.</param> - /// <param name="libraryManager">The library manager.</param> - /// <param name="dtoService">The dto service.</param> - /// <param name="imageProcessor">The image processor.</param> - public SearchService( - ILogger<SearchService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISearchEngine searchEngine, - ILibraryManager libraryManager, - IDtoService dtoService, - IImageProcessor imageProcessor) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _searchEngine = searchEngine; - _libraryManager = libraryManager; - _dtoService = dtoService; - _imageProcessor = imageProcessor; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetSearchHints request) - { - var result = GetSearchHintsAsync(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the search hints async. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{IEnumerable{SearchHintResult}}.</returns> - private SearchHintResult GetSearchHintsAsync(GetSearchHints request) - { - var result = _searchEngine.GetSearchHints(new SearchQuery - { - Limit = request.Limit, - SearchTerm = request.SearchTerm, - IncludeArtists = request.IncludeArtists, - IncludeGenres = request.IncludeGenres, - IncludeMedia = request.IncludeMedia, - IncludePeople = request.IncludePeople, - IncludeStudios = request.IncludeStudios, - StartIndex = request.StartIndex, - UserId = request.UserId, - IncludeItemTypes = ApiEntryPoint.Split(request.IncludeItemTypes, ',', true), - ExcludeItemTypes = ApiEntryPoint.Split(request.ExcludeItemTypes, ',', true), - MediaTypes = ApiEntryPoint.Split(request.MediaTypes, ',', true), - ParentId = request.ParentId, - - IsKids = request.IsKids, - IsMovie = request.IsMovie, - IsNews = request.IsNews, - IsSeries = request.IsSeries, - IsSports = request.IsSports - - }); - - return new SearchHintResult - { - TotalRecordCount = result.TotalRecordCount, - - SearchHints = result.Items.Select(GetSearchHintResult).ToArray() - }; - } - - /// <summary> - /// Gets the search hint result. - /// </summary> - /// <param name="hintInfo">The hint info.</param> - /// <returns>SearchHintResult.</returns> - private SearchHint GetSearchHintResult(SearchHintInfo hintInfo) - { - var item = hintInfo.Item; - - var result = new SearchHint - { - Name = item.Name, - IndexNumber = item.IndexNumber, - ParentIndexNumber = item.ParentIndexNumber, - Id = item.Id, - Type = item.GetClientTypeName(), - MediaType = item.MediaType, - MatchedTerm = hintInfo.MatchedTerm, - RunTimeTicks = item.RunTimeTicks, - ProductionYear = item.ProductionYear, - ChannelId = item.ChannelId, - EndDate = item.EndDate - }; - - // legacy - result.ItemId = result.Id; - - if (item.IsFolder) - { - result.IsFolder = true; - } - - var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary); - - if (primaryImageTag != null) - { - result.PrimaryImageTag = primaryImageTag; - result.PrimaryImageAspectRatio = _dtoService.GetPrimaryImageAspectRatio(item); - } - - SetThumbImageInfo(result, item); - SetBackdropImageInfo(result, item); - - switch (item) - { - case IHasSeries hasSeries: - result.Series = hasSeries.SeriesName; - break; - case LiveTvProgram program: - result.StartDate = program.StartDate; - break; - case Series series: - if (series.Status.HasValue) - { - result.Status = series.Status.Value.ToString(); - } - - break; - case MusicAlbum album: - result.Artists = album.Artists; - result.AlbumArtist = album.AlbumArtist; - break; - case Audio song: - result.AlbumArtist = song.AlbumArtists.FirstOrDefault(); - result.Artists = song.Artists; - - MusicAlbum musicAlbum = song.AlbumEntity; - - if (musicAlbum != null) - { - result.Album = musicAlbum.Name; - result.AlbumId = musicAlbum.Id; - } - else - { - result.Album = song.Album; - } - - break; - } - - if (!item.ChannelId.Equals(Guid.Empty)) - { - var channel = _libraryManager.GetItemById(item.ChannelId); - result.ChannelName = channel?.Name; - } - - return result; - } - - private void SetThumbImageInfo(SearchHint hint, BaseItem item) - { - var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null; - - if (itemWithImage == null && item is Episode) - { - itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb); - } - - if (itemWithImage == null) - { - itemWithImage = GetParentWithImage<BaseItem>(item, ImageType.Thumb); - } - - if (itemWithImage != null) - { - var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Thumb); - - if (tag != null) - { - hint.ThumbImageTag = tag; - hint.ThumbImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture); - } - } - } - - private void SetBackdropImageInfo(SearchHint hint, BaseItem item) - { - var itemWithImage = (item.HasImage(ImageType.Backdrop) ? item : null) - ?? GetParentWithImage<BaseItem>(item, ImageType.Backdrop); - - if (itemWithImage != null) - { - var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Backdrop); - - if (tag != null) - { - hint.BackdropImageTag = tag; - hint.BackdropImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture); - } - } - } - - private T GetParentWithImage<T>(BaseItem item, ImageType type) - where T : BaseItem - { - return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type)); - } - } -} diff --git a/MediaBrowser.Api/Sessions/ApiKeyService.cs b/MediaBrowser.Api/Sessions/ApiKeyService.cs deleted file mode 100644 index 5102ce0a7..000000000 --- a/MediaBrowser.Api/Sessions/ApiKeyService.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Globalization; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Sessions -{ - [Route("/Auth/Keys", "GET")] - [Authenticated(Roles = "Admin")] - public class GetKeys - { - } - - [Route("/Auth/Keys/{Key}", "DELETE")] - [Authenticated(Roles = "Admin")] - public class RevokeKey - { - [ApiMember(Name = "Key", Description = "Authentication key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Key { get; set; } - } - - [Route("/Auth/Keys", "POST")] - [Authenticated(Roles = "Admin")] - public class CreateKey - { - [ApiMember(Name = "App", Description = "Name of the app using the authentication key", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string App { get; set; } - } - - public class ApiKeyService : BaseApiService - { - private readonly ISessionManager _sessionManager; - - private readonly IAuthenticationRepository _authRepo; - - private readonly IServerApplicationHost _appHost; - - public ApiKeyService( - ILogger<ApiKeyService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionManager sessionManager, - IServerApplicationHost appHost, - IAuthenticationRepository authRepo) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _sessionManager = sessionManager; - _authRepo = authRepo; - _appHost = appHost; - } - - public void Delete(RevokeKey request) - { - _sessionManager.RevokeToken(request.Key); - } - - public void Post(CreateKey request) - { - _authRepo.Create(new AuthenticationInfo - { - AppName = request.App, - AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - DateCreated = DateTime.UtcNow, - DeviceId = _appHost.SystemId, - DeviceName = _appHost.FriendlyName, - AppVersion = _appHost.ApplicationVersionString - }); - } - - public object Get(GetKeys request) - { - var result = _authRepo.Get(new AuthenticationInfoQuery - { - HasUser = false - }); - - return result; - } - } -} diff --git a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs deleted file mode 100644 index 0e74c9267..000000000 --- a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Sessions -{ - /// <summary> - /// Class SessionInfoWebSocketListener - /// </summary> - public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState> - { - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - protected override string Name => "Sessions"; - - /// <summary> - /// The _kernel - /// </summary> - private readonly ISessionManager _sessionManager; - - /// <summary> - /// Initializes a new instance of the <see cref="SessionInfoWebSocketListener"/> class. - /// </summary> - public SessionInfoWebSocketListener(ILogger<SessionInfoWebSocketListener> logger, ISessionManager sessionManager) - : base(logger) - { - _sessionManager = sessionManager; - - _sessionManager.SessionStarted += OnSessionManagerSessionStarted; - _sessionManager.SessionEnded += OnSessionManagerSessionEnded; - _sessionManager.PlaybackStart += OnSessionManagerPlaybackStart; - _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; - _sessionManager.PlaybackProgress += OnSessionManagerPlaybackProgress; - _sessionManager.CapabilitiesChanged += OnSessionManagerCapabilitiesChanged; - _sessionManager.SessionActivity += OnSessionManagerSessionActivity; - } - - private void OnSessionManagerSessionActivity(object sender, SessionEventArgs e) - { - SendData(false); - } - - private void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e) - { - SendData(true); - } - - private void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e) - { - SendData(!e.IsAutomated); - } - - private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) - { - SendData(true); - } - - private void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e) - { - SendData(true); - } - - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) - { - SendData(true); - } - - private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) - { - SendData(true); - } - - /// <summary> - /// Gets the data to send. - /// </summary> - /// <returns>Task{SystemInfo}.</returns> - protected override Task<IEnumerable<SessionInfo>> GetDataToSend() - { - return Task.FromResult(_sessionManager.Sessions); - } - - /// <inheritdoc /> - protected override void Dispose(bool dispose) - { - _sessionManager.SessionStarted -= OnSessionManagerSessionStarted; - _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; - _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart; - _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; - _sessionManager.PlaybackProgress -= OnSessionManagerPlaybackProgress; - _sessionManager.CapabilitiesChanged -= OnSessionManagerCapabilitiesChanged; - _sessionManager.SessionActivity -= OnSessionManagerSessionActivity; - - base.Dispose(dispose); - } - } -} diff --git a/MediaBrowser.Api/Sessions/SessionService.cs b/MediaBrowser.Api/Sessions/SessionService.cs deleted file mode 100644 index 020bb5042..000000000 --- a/MediaBrowser.Api/Sessions/SessionService.cs +++ /dev/null @@ -1,498 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Session; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Sessions -{ - /// <summary> - /// Class GetSessions. - /// </summary> - [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] - [Authenticated] - public class GetSessions : IReturn<SessionInfo[]> - { - [ApiMember(Name = "ControllableByUserId", Description = "Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid ControllableByUserId { get; set; } - - [ApiMember(Name = "DeviceId", Description = "Filter by device Id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - - public int? ActiveWithinSeconds { get; set; } - } - - /// <summary> - /// Class DisplayContent. - /// </summary> - [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] - [Authenticated] - public class DisplayContent : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// Artist, Genre, Studio, Person, or any kind of BaseItem - /// </summary> - /// <value>The type of the item.</value> - [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemType { get; set; } - - /// <summary> - /// Artist name, genre name, item Id, etc - /// </summary> - /// <value>The item identifier.</value> - [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemId { get; set; } - - /// <summary> - /// Gets or sets the name of the item. - /// </summary> - /// <value>The name of the item.</value> - [ApiMember(Name = "ItemName", Description = "The name of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemName { get; set; } - } - - [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] - [Authenticated] - public class Play : PlayRequest - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] - [Authenticated] - public class SendPlaystateCommand : PlaystateRequest, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendSystemCommand : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// Gets or sets the command. - /// </summary> - /// <value>The play command.</value> - [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Command { get; set; } - } - - [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendGeneralCommand : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// Gets or sets the command. - /// </summary> - /// <value>The play command.</value> - [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Command { get; set; } - } - - [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendFullGeneralCommand : GeneralCommand, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] - [Authenticated] - public class SendMessageCommand : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Text { get; set; } - - [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Header { get; set; } - - [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? TimeoutMs { get; set; } - } - - [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")] - [Authenticated] - public class AddUserToSession : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } - } - - [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")] - [Authenticated] - public class RemoveUserFromSession : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } - } - - [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] - [Authenticated] - public class PostCapabilities : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlayableMediaTypes { get; set; } - - [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string SupportedCommands { get; set; } - - [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool SupportsMediaControl { get; set; } - - [ApiMember(Name = "SupportsSync", Description = "Determines whether sync is supported.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool SupportsSync { get; set; } - - [ApiMember(Name = "SupportsPersistentIdentifier", Description = "Determines whether the device supports a unique identifier.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool SupportsPersistentIdentifier { get; set; } - - public PostCapabilities() - { - SupportsPersistentIdentifier = true; - } - } - - [Route("/Sessions/Capabilities/Full", "POST", Summary = "Updates capabilities for a device")] - [Authenticated] - public class PostFullCapabilities : ClientCapabilities, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Sessions/Viewing", "POST", Summary = "Reports that a session is viewing an item")] - [Authenticated] - public class ReportViewing : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string SessionId { get; set; } - - [ApiMember(Name = "ItemId", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemId { get; set; } - } - - [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] - [Authenticated] - public class ReportSessionEnded : IReturnVoid - { - } - - [Route("/Auth/Providers", "GET")] - [Authenticated(Roles = "Admin")] - public class GetAuthProviders : IReturn<NameIdPair[]> - { - } - - [Route("/Auth/PasswordResetProviders", "GET")] - [Authenticated(Roles = "Admin")] - public class GetPasswordResetProviders : IReturn<NameIdPair[]> - { - } - - /// <summary> - /// Class SessionsService. - /// </summary> - public class SessionService : BaseApiService - { - /// <summary> - /// The session manager. - /// </summary> - private readonly ISessionManager _sessionManager; - - private readonly IUserManager _userManager; - private readonly IAuthorizationContext _authContext; - private readonly IDeviceManager _deviceManager; - private readonly ISessionContext _sessionContext; - - public SessionService( - ILogger<SessionService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionManager sessionManager, - IUserManager userManager, - IAuthorizationContext authContext, - IDeviceManager deviceManager, - ISessionContext sessionContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _sessionManager = sessionManager; - _userManager = userManager; - _authContext = authContext; - _deviceManager = deviceManager; - _sessionContext = sessionContext; - } - - public object Get(GetAuthProviders request) - { - return _userManager.GetAuthenticationProviders(); - } - - public object Get(GetPasswordResetProviders request) - { - return _userManager.GetPasswordResetProviders(); - } - - public void Post(ReportSessionEnded request) - { - var auth = _authContext.GetAuthorizationInfo(Request); - - _sessionManager.Logout(auth.Token); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetSessions request) - { - var result = _sessionManager.Sessions; - - if (!string.IsNullOrEmpty(request.DeviceId)) - { - result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase)); - } - - if (!request.ControllableByUserId.Equals(Guid.Empty)) - { - result = result.Where(i => i.SupportsRemoteControl); - - var user = _userManager.GetUserById(request.ControllableByUserId); - - if (!user.Policy.EnableRemoteControlOfOtherUsers) - { - result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(request.ControllableByUserId)); - } - - if (!user.Policy.EnableSharedDeviceControl) - { - result = result.Where(i => !i.UserId.Equals(Guid.Empty)); - } - - if (request.ActiveWithinSeconds.HasValue && request.ActiveWithinSeconds.Value > 0) - { - var minActiveDate = DateTime.UtcNow.AddSeconds(0 - request.ActiveWithinSeconds.Value); - result = result.Where(i => i.LastActivityDate >= minActiveDate); - } - - result = result.Where(i => - { - var deviceId = i.DeviceId; - - if (!string.IsNullOrWhiteSpace(deviceId)) - { - if (!_deviceManager.CanAccessDevice(user, deviceId)) - { - return false; - } - } - - return true; - }); - } - - return ToOptimizedResult(result.ToArray()); - } - - public Task Post(SendPlaystateCommand request) - { - return _sessionManager.SendPlaystateCommand(GetSession(_sessionContext).Id, request.Id, request, CancellationToken.None); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(DisplayContent request) - { - var command = new BrowseRequest - { - ItemId = request.ItemId, - ItemName = request.ItemName, - ItemType = request.ItemType - }; - - return _sessionManager.SendBrowseCommand(GetSession(_sessionContext).Id, request.Id, command, CancellationToken.None); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(SendSystemCommand request) - { - var name = request.Command; - if (Enum.TryParse(name, true, out GeneralCommandType commandType)) - { - name = commandType.ToString(); - } - - var currentSession = GetSession(_sessionContext); - var command = new GeneralCommand - { - Name = name, - ControllingUserId = currentSession.UserId - }; - - return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(SendMessageCommand request) - { - var command = new MessageCommand - { - Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, - TimeoutMs = request.TimeoutMs, - Text = request.Text - }; - - return _sessionManager.SendMessageCommand(GetSession(_sessionContext).Id, request.Id, command, CancellationToken.None); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(Play request) - { - return _sessionManager.SendPlayCommand(GetSession(_sessionContext).Id, request.Id, request, CancellationToken.None); - } - - public Task Post(SendGeneralCommand request) - { - var currentSession = GetSession(_sessionContext); - - var command = new GeneralCommand - { - Name = request.Command, - ControllingUserId = currentSession.UserId - }; - - return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - } - - public Task Post(SendFullGeneralCommand request) - { - var currentSession = GetSession(_sessionContext); - - request.ControllingUserId = currentSession.UserId; - - return _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); - } - - public void Post(AddUserToSession request) - { - _sessionManager.AddAdditionalUser(request.Id, new Guid(request.UserId)); - } - - public void Delete(RemoveUserFromSession request) - { - _sessionManager.RemoveAdditionalUser(request.Id, new Guid(request.UserId)); - } - - public void Post(PostCapabilities request) - { - if (string.IsNullOrWhiteSpace(request.Id)) - { - request.Id = GetSession(_sessionContext).Id; - } - - _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities - { - PlayableMediaTypes = SplitValue(request.PlayableMediaTypes, ','), - SupportedCommands = SplitValue(request.SupportedCommands, ','), - SupportsMediaControl = request.SupportsMediaControl, - SupportsSync = request.SupportsSync, - SupportsPersistentIdentifier = request.SupportsPersistentIdentifier - }); - } - - public void Post(PostFullCapabilities request) - { - if (string.IsNullOrWhiteSpace(request.Id)) - { - request.Id = GetSession(_sessionContext).Id; - } - - _sessionManager.ReportCapabilities(request.Id, request); - } - - public void Post(ReportViewing request) - { - request.SessionId = GetSession(_sessionContext).Id; - - _sessionManager.ReportNowViewingItem(request.SessionId, request.ItemId); - } - } -} diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs deleted file mode 100644 index 44bb24ef2..000000000 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class BaseGetSimilarItemsFromItem - /// </summary> - public class BaseGetSimilarItemsFromItem : BaseGetSimilarItems - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - public string ExcludeArtistIds { get; set; } - } - - public class BaseGetSimilarItems : IReturn<QueryResult<BaseItemDto>>, 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> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Fields { get; set; } - } - - /// <summary> - /// Class SimilarItemsHelper - /// </summary> - public static class SimilarItemsHelper - { - internal static QueryResult<BaseItemDto> GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore) - { - var user = !request.UserId.Equals(Guid.Empty) ? userManager.GetUserById(request.UserId) : null; - - var item = string.IsNullOrEmpty(request.Id) ? - (!request.UserId.Equals(Guid.Empty) ? libraryManager.GetUserRootFolder() : - libraryManager.RootFolder) : libraryManager.GetItemById(request.Id); - - var query = new InternalItemsQuery(user) - { - IncludeItemTypes = includeTypes.Select(i => i.Name).ToArray(), - Recursive = true, - DtoOptions = dtoOptions - }; - - // ExcludeArtistIds - if (!string.IsNullOrEmpty(request.ExcludeArtistIds)) - { - query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds); - } - - var inputItems = libraryManager.GetItemList(query); - - var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore) - .ToList(); - - var returnItems = items; - - if (request.Limit.HasValue) - { - returnItems = returnItems.Take(request.Limit.Value).ToList(); - } - - var dtos = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); - - return new QueryResult<BaseItemDto> - { - Items = dtos, - - TotalRecordCount = items.Count - }; - } - - /// <summary> - /// Gets the similaritems. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="libraryManager">The library manager.</param> - /// <param name="inputItems">The input items.</param> - /// <param name="getSimilarityScore">The get similarity score.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, ILibraryManager libraryManager, IEnumerable<BaseItem> inputItems, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore) - { - var itemId = item.Id; - inputItems = inputItems.Where(i => i.Id != itemId); - var itemPeople = libraryManager.GetPeople(item); - var allPeople = libraryManager.GetPeople(new InternalPeopleQuery - { - AppearsInItemId = item.Id - }); - - return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, itemPeople, allPeople, i))) - .Where(i => i.Item2 > 2) - .OrderByDescending(i => i.Item2) - .Select(i => i.Item1); - } - - private static IEnumerable<string> GetTags(BaseItem item) - { - return item.Tags; - } - - /// <summary> - /// Gets the similiarity score. - /// </summary> - /// <param name="item1">The item1.</param> - /// <param name="item1People">The item1 people.</param> - /// <param name="allPeople">All people.</param> - /// <param name="item2">The item2.</param> - /// <returns>System.Int32.</returns> - internal static int GetSimiliarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2) - { - var points = 0; - - if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase)) - { - points += 10; - } - - // Find common genres - points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10); - - // Find common tags - points += GetTags(item1).Where(i => GetTags(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10); - - // Find common studios - points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 3); - - var item2PeopleNames = allPeople.Where(i => i.ItemId == item2.Id) - .Select(i => i.Name) - .Where(i => !string.IsNullOrWhiteSpace(i)) - .DistinctNames() - .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); - - points += item1People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i => - { - if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase)) - { - return 5; - } - if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase)) - { - return 2; - } - - return 1; - }); - - if (item1.ProductionYear.HasValue && item2.ProductionYear.HasValue) - { - var diff = Math.Abs(item1.ProductionYear.Value - item2.ProductionYear.Value); - - // Add if they came out within the same decade - if (diff < 10) - { - points += 2; - } - - // And more if within five years - if (diff < 5) - { - points += 2; - } - } - - return points; - } - - } -} diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs deleted file mode 100644 index f2968c6b5..000000000 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ /dev/null @@ -1,300 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using MimeTypes = MediaBrowser.Model.Net.MimeTypes; - -namespace MediaBrowser.Api.Subtitles -{ - [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")] - [Authenticated(Roles = "Admin")] - public class DeleteSubtitle - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")] - public int Index { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")] - [Authenticated] - public class SearchRemoteSubtitles : IReturn<RemoteSubtitleInfo[]> - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Language { get; set; } - - public bool? IsPerfectMatch { get; set; } - } - - [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")] - [Authenticated] - public class DownloadRemoteSubtitles : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } - - [ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SubtitleId { get; set; } - } - - [Route("/Providers/Subtitles/Subtitles/{Id}", "GET")] - [Authenticated] - public class GetRemoteSubtitles : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")] - [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/{StartPositionTicks}/Stream.{Format}", "GET", Summary = "Gets subtitles in a specified format.")] - public class GetSubtitle - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - - [ApiMember(Name = "Format", Description = "Format", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Format { get; set; } - - [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public long StartPositionTicks { get; set; } - - [ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public long? EndPositionTicks { get; set; } - - [ApiMember(Name = "CopyTimestamps", Description = "CopyTimestamps", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool CopyTimestamps { get; set; } - public bool AddVttTimeMap { get; set; } - } - - [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")] - [Authenticated] - public class GetSubtitlePlaylist - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - - [ApiMember(Name = "SegmentLength", Description = "The subtitle srgment length", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")] - public int SegmentLength { get; set; } - } - - public class SubtitleService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly ISubtitleManager _subtitleManager; - private readonly ISubtitleEncoder _subtitleEncoder; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IProviderManager _providerManager; - private readonly IFileSystem _fileSystem; - private readonly IAuthorizationContext _authContext; - - public SubtitleService( - ILogger<SubtitleService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILibraryManager libraryManager, - ISubtitleManager subtitleManager, - ISubtitleEncoder subtitleEncoder, - IMediaSourceManager mediaSourceManager, - IProviderManager providerManager, - IFileSystem fileSystem, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _libraryManager = libraryManager; - _subtitleManager = subtitleManager; - _subtitleEncoder = subtitleEncoder; - _mediaSourceManager = mediaSourceManager; - _providerManager = providerManager; - _fileSystem = fileSystem; - _authContext = authContext; - } - - public async Task<object> Get(GetSubtitlePlaylist request) - { - var item = (Video)_libraryManager.GetItemById(new Guid(request.Id)); - - var mediaSource = await _mediaSourceManager.GetMediaSource(item, request.MediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false); - - var builder = new StringBuilder(); - - var runtime = mediaSource.RunTimeTicks ?? -1; - - if (runtime <= 0) - { - throw new ArgumentException("HLS Subtitles are not supported for this media."); - } - - var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks; - if (segmentLengthTicks <= 0) - { - throw new ArgumentException("segmentLength was not given, or it was given incorrectly. (It should be bigger than 0)"); - } - - builder.AppendLine("#EXTM3U"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture)); - builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); - builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); - - long positionTicks = 0; - - var accessToken = _authContext.GetAuthorizationInfo(Request).Token; - - while (positionTicks < runtime) - { - var remaining = runtime - positionTicks; - var lengthTicks = Math.Min(remaining, segmentLengthTicks); - - builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture) + ","); - - var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); - - var url = string.Format("stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}", - positionTicks.ToString(CultureInfo.InvariantCulture), - endPositionTicks.ToString(CultureInfo.InvariantCulture), - accessToken); - - builder.AppendLine(url); - - positionTicks += segmentLengthTicks; - } - - builder.AppendLine("#EXT-X-ENDLIST"); - - return ResultFactory.GetResult(Request, builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); - } - - public async Task<object> Get(GetSubtitle request) - { - if (string.Equals(request.Format, "js", StringComparison.OrdinalIgnoreCase)) - { - request.Format = "json"; - } - if (string.IsNullOrEmpty(request.Format)) - { - var item = (Video)_libraryManager.GetItemById(request.Id); - - var idString = request.Id.ToString("N", CultureInfo.InvariantCulture); - var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null) - .First(i => string.Equals(i.Id, request.MediaSourceId ?? idString)); - - var subtitleStream = mediaSource.MediaStreams - .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index); - - return await ResultFactory.GetStaticFileResult(Request, subtitleStream.Path).ConfigureAwait(false); - } - - if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap) - { - using var stream = await GetSubtitles(request).ConfigureAwait(false); - using var reader = new StreamReader(stream); - - var text = reader.ReadToEnd(); - - text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000"); - - return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format)); - } - - return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format)); - } - - private Task<Stream> GetSubtitles(GetSubtitle request) - { - var item = _libraryManager.GetItemById(request.Id); - - return _subtitleEncoder.GetSubtitles(item, - request.MediaSourceId, - request.Index, - request.Format, - request.StartPositionTicks, - request.EndPositionTicks ?? 0, - request.CopyTimestamps, - CancellationToken.None); - } - - public async Task<object> Get(SearchRemoteSubtitles request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - return await _subtitleManager.SearchSubtitles(video, request.Language, request.IsPerfectMatch, CancellationToken.None).ConfigureAwait(false); - } - - public Task Delete(DeleteSubtitle request) - { - var item = _libraryManager.GetItemById(request.Id); - return _subtitleManager.DeleteSubtitles(item, request.Index); - } - - public async Task<object> Get(GetRemoteSubtitles request) - { - var result = await _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).ConfigureAwait(false); - - return ResultFactory.GetResult(Request, result.Stream, MimeTypes.GetMimeType("file." + result.Format)); - } - - public void Post(DownloadRemoteSubtitles request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - Task.Run(async () => - { - try - { - await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None) - .ConfigureAwait(false); - - _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error downloading subtitles"); - } - }); - } - } -} diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs deleted file mode 100644 index 91f85db6f..000000000 --- a/MediaBrowser.Api/SuggestionsService.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Users/{UserId}/Suggestions", "GET", Summary = "Gets items based on a query.")] - public class GetSuggestedItems : IReturn<QueryResult<BaseItemDto>> - { - public string MediaType { get; set; } - public string Type { get; set; } - public Guid UserId { get; set; } - public bool EnableTotalRecordCount { get; set; } - public int? StartIndex { get; set; } - public int? Limit { get; set; } - - public string[] GetMediaTypes() - { - return (MediaType ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetIncludeItemTypes() - { - return (Type ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - } - - public class SuggestionsService : BaseApiService - { - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - private readonly IUserManager _userManager; - private readonly ILibraryManager _libraryManager; - - public SuggestionsService( - ILogger<SuggestionsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IDtoService dtoService, - IAuthorizationContext authContext, - IUserManager userManager, - ILibraryManager libraryManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _dtoService = dtoService; - _authContext = authContext; - _userManager = userManager; - _libraryManager = libraryManager; - } - - public object Get(GetSuggestedItems request) - { - return GetResultItems(request); - } - - private QueryResult<BaseItemDto> GetResultItems(GetSuggestedItems request) - { - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - var dtoOptions = GetDtoOptions(_authContext, request); - var result = GetItems(request, user, dtoOptions); - - var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user); - - return new QueryResult<BaseItemDto> - { - TotalRecordCount = result.TotalRecordCount, - Items = dtoList - }; - } - - private QueryResult<BaseItem> GetItems(GetSuggestedItems request, User user, DtoOptions dtoOptions) - { - return _libraryManager.GetItemsResult(new InternalItemsQuery(user) - { - OrderBy = new[] { ItemSortBy.Random }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Descending)).ToArray(), - MediaTypes = request.GetMediaTypes(), - IncludeItemTypes = request.GetIncludeItemTypes(), - IsVirtualItem = false, - StartIndex = request.StartIndex, - Limit = request.Limit, - DtoOptions = dtoOptions, - EnableTotalRecordCount = request.EnableTotalRecordCount, - Recursive = true - }); - } - } -} diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs deleted file mode 100644 index 1e14ea552..000000000 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ /dev/null @@ -1,302 +0,0 @@ -using System.Threading; -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.SyncPlay; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.SyncPlay; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.SyncPlay -{ - [Route("/SyncPlay/{SessionId}/NewGroup", "POST", Summary = "Create a new SyncPlay group")] - [Authenticated] - public class SyncPlayNewGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/SyncPlay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing SyncPlay group")] - [Authenticated] - public class SyncPlayJoinGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// <summary> - /// Gets or sets the Group id. - /// </summary> - /// <value>The Group id to join.</value> - [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string GroupId { get; set; } - - /// <summary> - /// Gets or sets the playing item id. - /// </summary> - /// <value>The client's currently playing item id.</value> - [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlayingItemId { get; set; } - } - - [Route("/SyncPlay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined SyncPlay group")] - [Authenticated] - public class SyncPlayLeaveGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/SyncPlay/{SessionId}/ListGroups", "POST", Summary = "List SyncPlay groups")] - [Authenticated] - public class SyncPlayListGroups : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// <summary> - /// Gets or sets the filter item id. - /// </summary> - /// <value>The filter item id.</value> - [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string FilterItemId { get; set; } - } - - [Route("/SyncPlay/{SessionId}/PlayRequest", "POST", Summary = "Request play in SyncPlay group")] - [Authenticated] - public class SyncPlayPlayRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/SyncPlay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in SyncPlay group")] - [Authenticated] - public class SyncPlayPauseRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/SyncPlay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in SyncPlay group")] - [Authenticated] - public class SyncPlaySeekRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] - public long PositionTicks { get; set; } - } - - [Route("/SyncPlay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in SyncPlay group while buffering")] - [Authenticated] - public class SyncPlayBufferingRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// <summary> - /// Gets or sets the date used to pin PositionTicks in time. - /// </summary> - /// <value>The date related to PositionTicks.</value> - [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string When { get; set; } - - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] - public long PositionTicks { get; set; } - - /// <summary> - /// Gets or sets whether this is a buffering or a buffering-done request. - /// </summary> - /// <value><c>true</c> if buffering is complete; <c>false</c> otherwise.</value> - [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool BufferingDone { get; set; } - } - - [Route("/SyncPlay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] - [Authenticated] - public class SyncPlayUpdatePing : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] - public double Ping { get; set; } - } - - /// <summary> - /// Class SyncPlayService. - /// </summary> - public class SyncPlayService : BaseApiService - { - /// <summary> - /// The session context. - /// </summary> - private readonly ISessionContext _sessionContext; - - /// <summary> - /// The SyncPlay manager. - /// </summary> - private readonly ISyncPlayManager _syncPlayManager; - - public SyncPlayService( - ILogger<SyncPlayService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionContext sessionContext, - ISyncPlayManager syncPlayManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _sessionContext = sessionContext; - _syncPlayManager = syncPlayManager; - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayNewGroup request) - { - var currentSession = GetSession(_sessionContext); - _syncPlayManager.NewGroup(currentSession, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayJoinGroup request) - { - var currentSession = GetSession(_sessionContext); - - Guid groupId; - Guid playingItemId = Guid.Empty; - - if (!Guid.TryParse(request.GroupId, out groupId)) - { - Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId); - return; - } - - // Both null and empty strings mean that client isn't playing anything - if (!string.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) - { - Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); - return; - } - - var joinRequest = new JoinGroupRequest() - { - GroupId = groupId, - PlayingItemId = playingItemId - }; - - _syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayLeaveGroup request) - { - var currentSession = GetSession(_sessionContext); - _syncPlayManager.LeaveGroup(currentSession, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <value>The requested list of groups.</value> - public List<GroupInfoView> Post(SyncPlayListGroups request) - { - var currentSession = GetSession(_sessionContext); - var filterItemId = Guid.Empty; - - if (!string.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) - { - Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); - } - - return _syncPlayManager.ListGroups(currentSession, filterItemId); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayPlayRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Play - }; - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayPauseRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Pause - }; - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlaySeekRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Seek, - PositionTicks = request.PositionTicks - }; - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayBufferingRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new PlaybackRequest() - { - Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, - When = DateTime.Parse(request.When), - PositionTicks = request.PositionTicks - }; - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayUpdatePing request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.UpdatePing, - Ping = Convert.ToInt64(request.Ping) - }; - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Api/SyncPlay/TimeSyncService.cs b/MediaBrowser.Api/SyncPlay/TimeSyncService.cs deleted file mode 100644 index 4a9307e62..000000000 --- a/MediaBrowser.Api/SyncPlay/TimeSyncService.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.SyncPlay; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.SyncPlay -{ - [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] - public class GetUtcTime : IReturnVoid - { - // Nothing - } - - /// <summary> - /// Class TimeSyncService. - /// </summary> - public class TimeSyncService : BaseApiService - { - public TimeSyncService( - ILogger<TimeSyncService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory) - : base(logger, serverConfigurationManager, httpResultFactory) - { - // Do nothing - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <value>The current UTC time response.</value> - public UtcTimeResponse Get(GetUtcTime request) - { - // Important to keep the following line at the beginning - var requestReceptionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); - - var response = new UtcTimeResponse(); - response.RequestReceptionTime = requestReceptionTime; - - // Important to keep the following two lines at the end - var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); - response.ResponseTransmissionTime = responseTransmissionTime; - - // Implementing NTP on such a high level results in this useless - // information being sent. On the other hand it enables future additions. - return response; - } - } -} diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs deleted file mode 100644 index a6bacad4f..000000000 --- a/MediaBrowser.Api/System/ActivityLogService.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using Jellyfin.Data.Entities; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.System -{ - [Route("/System/ActivityLog/Entries", "GET", Summary = "Gets activity log entries")] - public class GetActivityLogs : IReturn<QueryResult<ActivityLogEntry>> - { - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MinDate { get; set; } - - public bool? HasUserId { get; set; } - } - - [Authenticated(Roles = "Admin")] - public class ActivityLogService : BaseApiService - { - private readonly IActivityManager _activityManager; - - public ActivityLogService( - ILogger<ActivityLogService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IActivityManager activityManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _activityManager = activityManager; - } - - public object Get(GetActivityLogs request) - { - DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ? - (DateTime?)null : - DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - - var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>( - entries => entries.Where(entry => entry.DateCreated >= minDate)); - - var result = _activityManager.GetPagedResult(filterFunc, request.StartIndex, request.Limit); - - return ToOptimizedResult(result); - } - } -} diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs deleted file mode 100644 index 8e4860be4..000000000 --- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Threading.Tasks; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.System -{ - /// <summary> - /// Class SessionInfoWebSocketListener - /// </summary> - public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState> - { - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - protected override string Name => "ActivityLogEntry"; - - /// <summary> - /// The _kernel - /// </summary> - private readonly IActivityManager _activityManager; - - public ActivityLogWebSocketListener(ILogger<ActivityLogWebSocketListener> logger, IActivityManager activityManager) : base(logger) - { - _activityManager = activityManager; - _activityManager.EntryCreated += OnEntryCreated; - } - - private void OnEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e) - { - SendData(true); - } - - /// <summary> - /// Gets the data to send. - /// </summary> - /// <returns>Task{SystemInfo}.</returns> - protected override Task<ActivityLogEntry[]> GetDataToSend() - { - return Task.FromResult(Array.Empty<ActivityLogEntry>()); - } - - /// <inheritdoc /> - protected override void Dispose(bool dispose) - { - _activityManager.EntryCreated -= OnEntryCreated; - - base.Dispose(dispose); - } - } -} diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs deleted file mode 100644 index c57cc93d5..000000000 --- a/MediaBrowser.Api/System/SystemService.cs +++ /dev/null @@ -1,226 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.System -{ - /// <summary> - /// Class GetSystemInfo - /// </summary> - [Route("/System/Info", "GET", Summary = "Gets information about the server")] - [Authenticated(EscapeParentalControl = true, AllowBeforeStartupWizard = true)] - public class GetSystemInfo : IReturn<SystemInfo> - { - - } - - [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")] - public class GetPublicSystemInfo : IReturn<PublicSystemInfo> - { - - } - - [Route("/System/Ping", "POST")] - [Route("/System/Ping", "GET")] - public class PingSystem : IReturnVoid - { - - } - - /// <summary> - /// Class RestartApplication - /// </summary> - [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] - [Authenticated(Roles = "Admin", AllowLocal = true)] - public class RestartApplication - { - } - - /// <summary> - /// This is currently not authenticated because the uninstaller needs to be able to shutdown the server. - /// </summary> - [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] - [Authenticated(Roles = "Admin", AllowLocal = true)] - public class ShutdownApplication - { - } - - [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] - [Authenticated(Roles = "Admin")] - public class GetServerLogs : IReturn<LogFile[]> - { - } - - [Route("/System/Endpoint", "GET", Summary = "Gets information about the request endpoint")] - [Authenticated] - public class GetEndpointInfo : IReturn<EndPointInfo> - { - public string Endpoint { get; set; } - } - - [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")] - [Authenticated(Roles = "Admin")] - public class GetLogFile - { - [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Name { get; set; } - } - - [Route("/System/WakeOnLanInfo", "GET", Summary = "Gets wake on lan information")] - [Authenticated] - public class GetWakeOnLanInfo : IReturn<WakeOnLanInfo[]> - { - - } - - /// <summary> - /// Class SystemInfoService - /// </summary> - public class SystemService : BaseApiService - { - /// <summary> - /// The _app host - /// </summary> - private readonly IServerApplicationHost _appHost; - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - - private readonly INetworkManager _network; - - /// <summary> - /// Initializes a new instance of the <see cref="SystemService" /> class. - /// </summary> - /// <param name="appHost">The app host.</param> - /// <param name="fileSystem">The file system.</param> - /// <exception cref="ArgumentNullException">jsonSerializer</exception> - public SystemService( - ILogger<SystemService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IServerApplicationHost appHost, - IFileSystem fileSystem, - INetworkManager network) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _appPaths = serverConfigurationManager.ApplicationPaths; - _appHost = appHost; - _fileSystem = fileSystem; - _network = network; - } - - public object Post(PingSystem request) - { - return _appHost.Name; - } - - public object Get(GetWakeOnLanInfo request) - { - var result = _appHost.GetWakeOnLanInfo(); - - return ToOptimizedResult(result); - } - - public object Get(GetServerLogs request) - { - IEnumerable<FileSystemMetadata> files; - - try - { - files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath, new[] { ".txt", ".log" }, true, false); - } - catch (IOException ex) - { - Logger.LogError(ex, "Error getting logs"); - files = Enumerable.Empty<FileSystemMetadata>(); - } - - var result = files.Select(i => new LogFile - { - DateCreated = _fileSystem.GetCreationTimeUtc(i), - DateModified = _fileSystem.GetLastWriteTimeUtc(i), - Name = i.Name, - Size = i.Length - - }).OrderByDescending(i => i.DateModified) - .ThenByDescending(i => i.DateCreated) - .ThenBy(i => i.Name) - .ToArray(); - - return ToOptimizedResult(result); - } - - public Task<object> Get(GetLogFile request) - { - var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath) - .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase)); - - // For older files, assume fully static - var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite; - - return ResultFactory.GetStaticFileResult(Request, file.FullName, fileShare); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public async Task<object> Get(GetSystemInfo request) - { - var result = await _appHost.GetSystemInfo(CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task<object> Get(GetPublicSystemInfo request) - { - var result = await _appHost.GetPublicSystemInfo(CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(RestartApplication request) - { - _appHost.Restart(); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(ShutdownApplication request) - { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Shutdown().ConfigureAwait(false); - }); - } - - public object Get(GetEndpointInfo request) - { - return ToOptimizedResult(new EndPointInfo - { - IsLocal = Request.IsLocal, - IsInNetwork = _network.IsInLocalNetwork(request.Endpoint ?? Request.RemoteIp) - }); - } - } -} diff --git a/MediaBrowser.Api/TranscodingJob.cs b/MediaBrowser.Api/TranscodingJob.cs deleted file mode 100644 index 8c24e3ce1..000000000 --- a/MediaBrowser.Api/TranscodingJob.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using MediaBrowser.Api.Playback; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dto; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class TranscodingJob. - /// </summary> - public class TranscodingJob - { - /// <summary> - /// Gets or sets the play session identifier. - /// </summary> - /// <value>The play session identifier.</value> - public string PlaySessionId { get; set; } - - /// <summary> - /// Gets or sets the live stream identifier. - /// </summary> - /// <value>The live stream identifier.</value> - public string LiveStreamId { get; set; } - - public bool IsLiveOutput { get; set; } - - /// <summary> - /// Gets or sets the path. - /// </summary> - /// <value>The path.</value> - public MediaSourceInfo MediaSource { get; set; } - public string Path { get; set; } - /// <summary> - /// Gets or sets the type. - /// </summary> - /// <value>The type.</value> - public TranscodingJobType Type { get; set; } - /// <summary> - /// Gets or sets the process. - /// </summary> - /// <value>The process.</value> - public Process Process { get; set; } - public ILogger Logger { get; private set; } - /// <summary> - /// Gets or sets the active request count. - /// </summary> - /// <value>The active request count.</value> - public int ActiveRequestCount { get; set; } - /// <summary> - /// Gets or sets the kill timer. - /// </summary> - /// <value>The kill timer.</value> - private Timer KillTimer { get; set; } - - public string DeviceId { get; set; } - - public CancellationTokenSource CancellationTokenSource { get; set; } - - public object ProcessLock = new object(); - - public bool HasExited { get; set; } - public bool IsUserPaused { get; set; } - - public string Id { get; set; } - - public float? Framerate { get; set; } - public double? CompletionPercentage { get; set; } - - 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; } - - public TranscodingThrottler TranscodingThrottler { get; set; } - - private readonly object _timerLock = new object(); - - public DateTime LastPingDate { get; set; } - public int PingTimeout { get; set; } - - public TranscodingJob(ILogger logger) - { - Logger = logger; - } - - public void StopKillTimer() - { - lock (_timerLock) - { - KillTimer?.Change(Timeout.Infinite, Timeout.Infinite); - } - } - - public void DisposeKillTimer() - { - lock (_timerLock) - { - if (KillTimer != null) - { - KillTimer.Dispose(); - KillTimer = null; - } - } - } - - public void StartKillTimer(Action<object> callback) - { - StartKillTimer(callback, PingTimeout); - } - - public void StartKillTimer(Action<object> callback, int intervalMs) - { - if (HasExited) - { - return; - } - - lock (_timerLock) - { - if (KillTimer == null) - { - Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite); - } - else - { - Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer.Change(intervalMs, Timeout.Infinite); - } - } - } - - public void ChangeKillTimerIfStarted() - { - if (HasExited) - { - return; - } - - lock (_timerLock) - { - if (KillTimer != null) - { - var intervalMs = PingTimeout; - - Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer.Change(intervalMs, Timeout.Infinite); - } - } - } - } -} diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs deleted file mode 100644 index cd8e8dfbe..000000000 --- a/MediaBrowser.Api/TvShowsService.cs +++ /dev/null @@ -1,498 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.TV; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetNextUpEpisodes - /// </summary> - [Route("/Shows/NextUp", "GET", Summary = "Gets a list of next up episodes")] - public class GetNextUpEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Fields { get; set; } - - [ApiMember(Name = "SeriesId", Description = "Optional. Filter by series id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SeriesId { get; set; } - - /// <summary> - /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// </summary> - /// <value>The parent id.</value> - [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ParentId { 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 = "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; } - - [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? EnableUserData { get; set; } - public bool EnableTotalRecordCount { get; set; } - - public GetNextUpEpisodes() - { - EnableTotalRecordCount = true; - } - } - - [Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")] - public class GetUpcomingEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Fields { get; set; } - - /// <summary> - /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// </summary> - /// <value>The parent id.</value> - [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ParentId { 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 = "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; } - - [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? EnableUserData { get; set; } - } - - [Route("/Shows/{Id}/Episodes", "GET", Summary = "Gets episodes for a tv season")] - public class GetEpisodes : IReturn<QueryResult<BaseItemDto>>, IHasItemFields, IHasDtoOptions - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Fields { get; set; } - - [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "Season", Description = "Optional filter by season number.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public int? Season { get; set; } - - [ApiMember(Name = "SeasonId", Description = "Optional. Filter by season id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SeasonId { get; set; } - - [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsMissing { get; set; } - - [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AdjacentTo { get; set; } - - [ApiMember(Name = "StartItemId", Description = "Optional. Skip through the list until a given item is found.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string StartItemId { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { 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 = "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; } - - [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? EnableUserData { get; set; } - - [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string SortBy { get; set; } - - [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public SortOrder? SortOrder { get; set; } - } - - [Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")] - public class GetSeasons : IReturn<QueryResult<BaseItemDto>>, IHasItemFields, IHasDtoOptions - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Fields { get; set; } - - [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "IsSpecialSeason", Description = "Optional. Filter by special season.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsSpecialSeason { get; set; } - - [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsMissing { get; set; } - - [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AdjacentTo { 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 = "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; } - - [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? EnableUserData { get; set; } - } - - /// <summary> - /// Class TvShowsService - /// </summary> - [Authenticated] - public class TvShowsService : BaseApiService - { - /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - - private readonly IDtoService _dtoService; - private readonly ITVSeriesManager _tvSeriesManager; - private readonly IAuthorizationContext _authContext; - - /// <summary> - /// Initializes a new instance of the <see cref="TvShowsService" /> class. - /// </summary> - /// <param name="userManager">The user manager.</param> - /// <param name="userDataManager">The user data repository.</param> - /// <param name="libraryManager">The library manager.</param> - public TvShowsService( - ILogger<TvShowsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IDtoService dtoService, - ITVSeriesManager tvSeriesManager, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _libraryManager = libraryManager; - _dtoService = dtoService; - _tvSeriesManager = tvSeriesManager; - _authContext = authContext; - } - - public object Get(GetUpcomingEpisodes request) - { - var user = _userManager.GetUserById(request.UserId); - - var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1); - - var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId); - - var options = GetDtoOptions(_authContext, request); - - var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) - { - IncludeItemTypes = new[] { typeof(Episode).Name }, - OrderBy = new[] { ItemSortBy.PremiereDate, ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(), - MinPremiereDate = minPremiereDate, - StartIndex = request.StartIndex, - Limit = request.Limit, - ParentId = parentIdGuid, - Recursive = true, - DtoOptions = options - - }); - - var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user); - - var result = new QueryResult<BaseItemDto> - { - TotalRecordCount = itemsResult.Count, - Items = returnItems - }; - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetNextUpEpisodes request) - { - var options = GetDtoOptions(_authContext, request); - - var result = _tvSeriesManager.GetNextUp(new NextUpQuery - { - Limit = request.Limit, - ParentId = request.ParentId, - SeriesId = request.SeriesId, - StartIndex = request.StartIndex, - UserId = request.UserId, - EnableTotalRecordCount = request.EnableTotalRecordCount - }, options); - - var user = _userManager.GetUserById(request.UserId); - - var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user); - - return ToOptimizedResult(new QueryResult<BaseItemDto> - { - TotalRecordCount = result.TotalRecordCount, - Items = returnItems - }); - } - - /// <summary> - /// Applies the paging. - /// </summary> - /// <param name="items">The items.</param> - /// <param name="startIndex">The start index.</param> - /// <param name="limit">The limit.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - private IEnumerable<BaseItem> ApplyPaging(IEnumerable<BaseItem> items, int? startIndex, int? limit) - { - // Start at - if (startIndex.HasValue) - { - items = items.Skip(startIndex.Value); - } - - // Return limit - if (limit.HasValue) - { - items = items.Take(limit.Value); - } - - return items; - } - - public object Get(GetSeasons request) - { - var user = _userManager.GetUserById(request.UserId); - - var series = GetSeries(request.Id, user); - - if (series == null) - { - throw new ResourceNotFoundException("Series not found"); - } - - var seasons = series.GetItemList(new InternalItemsQuery(user) - { - IsMissing = request.IsMissing, - IsSpecialSeason = request.IsSpecialSeason, - AdjacentTo = request.AdjacentTo - - }); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user); - - return new QueryResult<BaseItemDto> - { - TotalRecordCount = returnItems.Count, - Items = returnItems - }; - } - - private Series GetSeries(string seriesId, User user) - { - if (!string.IsNullOrWhiteSpace(seriesId)) - { - return _libraryManager.GetItemById(seriesId) as Series; - } - - return null; - } - - public object Get(GetEpisodes request) - { - var user = _userManager.GetUserById(request.UserId); - - List<BaseItem> episodes; - - var dtoOptions = GetDtoOptions(_authContext, request); - - if (!string.IsNullOrWhiteSpace(request.SeasonId)) - { - if (!(_libraryManager.GetItemById(new Guid(request.SeasonId)) is Season season)) - { - throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId); - } - - episodes = season.GetEpisodes(user, dtoOptions); - } - else if (request.Season.HasValue) - { - var series = GetSeries(request.Id, user); - - if (series == null) - { - throw new ResourceNotFoundException("Series not found"); - } - - var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value); - - episodes = season == null ? new List<BaseItem>() : ((Season)season).GetEpisodes(user, dtoOptions); - } - else - { - var series = GetSeries(request.Id, user); - - if (series == null) - { - throw new ResourceNotFoundException("Series not found"); - } - - episodes = series.GetEpisodes(user, dtoOptions).ToList(); - } - - // Filter after the fact in case the ui doesn't want them - if (request.IsMissing.HasValue) - { - var val = request.IsMissing.Value; - episodes = episodes.Where(i => ((Episode)i).IsMissingEpisode == val).ToList(); - } - - if (!string.IsNullOrWhiteSpace(request.StartItemId)) - { - episodes = episodes.SkipWhile(i => !string.Equals(i.Id.ToString("N", CultureInfo.InvariantCulture), request.StartItemId, StringComparison.OrdinalIgnoreCase)).ToList(); - } - - // This must be the last filter - if (!string.IsNullOrEmpty(request.AdjacentTo)) - { - episodes = UserViewBuilder.FilterForAdjacency(episodes, request.AdjacentTo).ToList(); - } - - if (string.Equals(request.SortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase)) - { - episodes.Shuffle(); - } - - var returnItems = episodes; - - if (request.StartIndex.HasValue || request.Limit.HasValue) - { - returnItems = ApplyPaging(episodes, request.StartIndex, request.Limit).ToList(); - } - - var dtos = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user); - - return new QueryResult<BaseItemDto> - { - TotalRecordCount = episodes.Count, - Items = dtos - }; - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs deleted file mode 100644 index bef91d54d..000000000 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class GetArtists - /// </summary> - [Route("/Artists", "GET", Summary = "Gets all artists from a given item, folder, or the entire library")] - public class GetArtists : GetItemsByName - { - } - - [Route("/Artists/AlbumArtists", "GET", Summary = "Gets all album artists from a given item, folder, or the entire library")] - public class GetAlbumArtists : GetItemsByName - { - } - - [Route("/Artists/{Name}", "GET", Summary = "Gets an artist, by name")] - public class GetArtist : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - /// <summary> - /// Class ArtistsService - /// </summary> - [Authenticated] - public class ArtistsService : BaseItemsByNameService<MusicArtist> - { - public ArtistsService( - ILogger<ArtistsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IUserDataManager userDataRepository, - IDtoService dtoService, - IAuthorizationContext authorizationContext) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - userDataRepository, - dtoService, - authorizationContext) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetArtist request) - { - return GetItem(request); - } - - /// <summary> - /// Gets the item. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{BaseItemDto}.</returns> - private BaseItemDto GetItem(GetArtist request) - { - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - var item = GetArtist(request.Name, LibraryManager, dtoOptions); - - if (!request.UserId.Equals(Guid.Empty)) - { - var user = UserManager.GetUserById(request.UserId); - - return DtoService.GetBaseItemDto(item, dtoOptions, user); - } - - return DtoService.GetBaseItemDto(item, dtoOptions); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetArtists request) - { - return GetResultSlim(request); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetAlbumArtists request) - { - var result = GetResultSlim(request); - - return ToOptimizedResult(result); - } - - protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) - { - return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query); - } - - /// <summary> - /// Gets all items. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="items">The items.</param> - /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs deleted file mode 100644 index 559082ff4..000000000 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ /dev/null @@ -1,387 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class BaseItemsByNameService - /// </summary> - /// <typeparam name="TItemType">The type of the T item type.</typeparam> - public abstract class BaseItemsByNameService<TItemType> : BaseApiService - where TItemType : BaseItem, IItemByName - { - /// <summary> - /// Initializes a new instance of the <see cref="BaseItemsByNameService{TItemType}" /> class. - /// </summary> - /// <param name="userManager">The user manager.</param> - /// <param name="libraryManager">The library manager.</param> - /// <param name="userDataRepository">The user data repository.</param> - /// <param name="dtoService">The dto service.</param> - protected BaseItemsByNameService( - ILogger<BaseItemsByNameService<TItemType>> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IUserDataManager userDataRepository, - IDtoService dtoService, - IAuthorizationContext authorizationContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - UserManager = userManager; - LibraryManager = libraryManager; - UserDataRepository = userDataRepository; - DtoService = dtoService; - AuthorizationContext = authorizationContext; - } - - /// <summary> - /// Gets the _user manager. - /// </summary> - protected IUserManager UserManager { get; } - - /// <summary> - /// Gets the library manager - /// </summary> - protected ILibraryManager LibraryManager { get; } - - protected IUserDataManager UserDataRepository { get; } - - protected IDtoService DtoService { get; } - - protected IAuthorizationContext AuthorizationContext { get; } - - protected BaseItem GetParentItem(GetItemsByName request) - { - BaseItem parentItem; - - if (!request.UserId.Equals(Guid.Empty)) - { - var user = UserManager.GetUserById(request.UserId); - parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId); - } - else - { - parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId); - } - - return parentItem; - } - - protected string GetParentItemViewType(GetItemsByName request) - { - var parent = GetParentItem(request); - - if (parent is IHasCollectionType collectionFolder) - { - return collectionFolder.CollectionType; - } - - return null; - } - - protected QueryResult<BaseItemDto> GetResultSlim(GetItemsByName request) - { - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - User user = null; - BaseItem parentItem; - - if (!request.UserId.Equals(Guid.Empty)) - { - user = UserManager.GetUserById(request.UserId); - parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId); - } - else - { - parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId); - } - - var excludeItemTypes = request.GetExcludeItemTypes(); - var includeItemTypes = request.GetIncludeItemTypes(); - var mediaTypes = request.GetMediaTypes(); - - var query = new InternalItemsQuery(user) - { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, - MediaTypes = mediaTypes, - StartIndex = request.StartIndex, - Limit = request.Limit, - IsFavorite = request.IsFavorite, - NameLessThan = request.NameLessThan, - NameStartsWith = request.NameStartsWith, - NameStartsWithOrGreater = request.NameStartsWithOrGreater, - Tags = request.GetTags(), - OfficialRatings = request.GetOfficialRatings(), - Genres = request.GetGenres(), - GenreIds = GetGuids(request.GenreIds), - StudioIds = GetGuids(request.StudioIds), - Person = request.Person, - PersonIds = GetGuids(request.PersonIds), - PersonTypes = request.GetPersonTypes(), - Years = request.GetYears(), - MinCommunityRating = request.MinCommunityRating, - DtoOptions = dtoOptions, - SearchTerm = request.SearchTerm, - EnableTotalRecordCount = request.EnableTotalRecordCount - }; - - if (!string.IsNullOrWhiteSpace(request.ParentId)) - { - if (parentItem is Folder) - { - query.AncestorIds = new[] { new Guid(request.ParentId) }; - } - else - { - query.ItemIds = new[] { new Guid(request.ParentId) }; - } - } - - // Studios - if (!string.IsNullOrEmpty(request.Studios)) - { - query.StudioIds = request.Studios.Split('|').Select(i => - { - try - { - return LibraryManager.GetStudio(i); - } - catch - { - return null; - } - }).Where(i => i != null).Select(i => i.Id).ToArray(); - } - - foreach (var filter in request.GetFilters()) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - var result = GetItems(request, query); - - var dtos = result.Items.Select(i => - { - var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, user); - - if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes)) - { - SetItemCounts(dto, i.Item2); - } - return dto; - }); - - return new QueryResult<BaseItemDto> - { - Items = dtos.ToArray(), - TotalRecordCount = result.TotalRecordCount - }; - } - - protected virtual QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) - { - return new QueryResult<(BaseItem, ItemCounts)>(); - } - - private void SetItemCounts(BaseItemDto dto, ItemCounts counts) - { - dto.ChildCount = counts.ItemCount; - dto.ProgramCount = counts.ProgramCount; - dto.SeriesCount = counts.SeriesCount; - dto.EpisodeCount = counts.EpisodeCount; - dto.MovieCount = counts.MovieCount; - dto.TrailerCount = counts.TrailerCount; - dto.AlbumCount = counts.AlbumCount; - dto.SongCount = counts.SongCount; - dto.ArtistCount = counts.ArtistCount; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{ItemsResult}.</returns> - protected QueryResult<BaseItemDto> GetResult(GetItemsByName request) - { - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - User user = null; - BaseItem parentItem; - - if (!request.UserId.Equals(Guid.Empty)) - { - user = UserManager.GetUserById(request.UserId); - parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.GetUserRootFolder() : LibraryManager.GetItemById(request.ParentId); - } - else - { - parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId); - } - - IList<BaseItem> items; - - var excludeItemTypes = request.GetExcludeItemTypes(); - var includeItemTypes = request.GetIncludeItemTypes(); - var mediaTypes = request.GetMediaTypes(); - - var query = new InternalItemsQuery(user) - { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, - MediaTypes = mediaTypes, - DtoOptions = dtoOptions - }; - - bool Filter(BaseItem i) => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes); - - if (parentItem.IsFolder) - { - var folder = (Folder)parentItem; - - if (!request.UserId.Equals(Guid.Empty)) - { - items = request.Recursive ? - folder.GetRecursiveChildren(user, query).ToList() : - folder.GetChildren(user, true).Where(Filter).ToList(); - } - else - { - items = request.Recursive ? - folder.GetRecursiveChildren(Filter) : - folder.Children.Where(Filter).ToList(); - } - } - else - { - items = new[] { parentItem }.Where(Filter).ToList(); - } - - var extractedItems = GetAllItems(request, items); - - var filteredItems = LibraryManager.Sort(extractedItems, user, request.GetOrderBy()); - - var ibnItemsArray = filteredItems.ToList(); - - IEnumerable<BaseItem> ibnItems = ibnItemsArray; - - var result = new QueryResult<BaseItemDto> - { - TotalRecordCount = ibnItemsArray.Count - }; - - if (request.StartIndex.HasValue || request.Limit.HasValue) - { - if (request.StartIndex.HasValue) - { - ibnItems = ibnItems.Skip(request.StartIndex.Value); - } - - if (request.Limit.HasValue) - { - ibnItems = ibnItems.Take(request.Limit.Value); - } - - } - - var 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)); - - result.Items = dtos.Where(i => i != null).ToArray(); - - return result; - } - - /// <summary> - /// Filters the items. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="f">The f.</param> - /// <param name="excludeItemTypes">The exclude item types.</param> - /// <param name="includeItemTypes">The include item types.</param> - /// <param name="mediaTypes">The media types.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes) - { - // Exclude item types - if (excludeItemTypes.Length > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - // Include item types - if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - // Include MediaTypes - if (mediaTypes.Length > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - return true; - } - - /// <summary> - /// Gets all items. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="items">The items.</param> - /// <returns>IEnumerable{Task{`0}}.</returns> - protected abstract IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items); - } - - /// <summary> - /// Class GetItemsByName - /// </summary> - public class GetItemsByName : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>> - { - public GetItemsByName() - { - Recursive = true; - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs deleted file mode 100644 index 7561b5c89..000000000 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ /dev/null @@ -1,475 +0,0 @@ -using System; -using System.Linq; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.UserLibrary -{ - public abstract class BaseItemsRequest : IHasDtoOptions - { - protected BaseItemsRequest() - { - EnableImages = true; - EnableTotalRecordCount = true; - } - - /// <summary> - /// Gets or sets the max offical rating. - /// </summary> - /// <value>The max offical rating.</value> - [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MaxOfficialRating { get; set; } - - [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasThemeSong { get; set; } - - [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasThemeVideo { get; set; } - - [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasSubtitles { get; set; } - - [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasSpecialFeature { get; set; } - - [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasTrailer { get; set; } - - [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AdjacentTo { get; set; } - - [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MinIndexNumber { get; set; } - - [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? ParentIndexNumber { get; set; } - - [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasParentalRating { get; set; } - - [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsHD { get; set; } - - public bool? Is4K { get; set; } - - [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string LocationTypes { get; set; } - - [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ExcludeLocationTypes { get; set; } - - [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsMissing { get; set; } - - [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsUnaired { get; set; } - - [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double? MinCommunityRating { get; set; } - - [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double? MinCriticRating { get; set; } - - [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? AiredDuringSeason { get; set; } - - [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MinPremiereDate { get; set; } - - [ApiMember(Name = "MinDateLastSaved", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MinDateLastSaved { get; set; } - - [ApiMember(Name = "MinDateLastSavedForUser", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MinDateLastSavedForUser { get; set; } - - [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MaxPremiereDate { get; set; } - - [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasOverview { get; set; } - - [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasImdbId { get; set; } - - [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasTmdbId { get; set; } - - [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasTvdbId { get; set; } - - [ApiMember(Name = "ExcludeItemIds", Description = "Optional. If specified, results will be filtered by exxcluding item ids. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ExcludeItemIds { get; set; } - - public bool EnableTotalRecordCount { get; set; } - - /// <summary> - /// Skips over a given number of items within the results. Use for paging. - /// </summary> - /// <value>The start index.</value> - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - /// <summary> - /// Whether or not to perform the query recursively - /// </summary> - /// <value><c>true</c> if recursive; otherwise, <c>false</c>.</value> - [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool Recursive { get; set; } - - public string SearchTerm { get; set; } - - /// <summary> - /// Gets or sets the sort order. - /// </summary> - /// <value>The sort order.</value> - [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SortOrder { get; set; } - - /// <summary> - /// Specify this to localize the search to a specific item or folder. Omit to use the root. - /// </summary> - /// <value>The parent id.</value> - [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ParentId { get; set; } - - /// <summary> - /// Fields to return within the items, in addition to basic information - /// </summary> - /// <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, 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; } - - /// <summary> - /// Gets or sets the exclude item types. - /// </summary> - /// <value>The exclude item types.</value> - [ApiMember(Name = "ExcludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ExcludeItemTypes { get; set; } - - /// <summary> - /// Gets or sets the include item types. - /// </summary> - /// <value>The include item types.</value> - [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string IncludeItemTypes { get; set; } - - /// <summary> - /// Filters to apply to the results - /// </summary> - /// <value>The filters.</value> - [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Filters { get; set; } - - /// <summary> - /// Gets or sets the Isfavorite option - /// </summary> - /// <value>IsFavorite</value> - [ApiMember(Name = "IsFavorite", Description = "Optional filter by items that are marked as favorite, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsFavorite { get; set; } - - /// <summary> - /// Gets or sets the media types. - /// </summary> - /// <value>The media types.</value> - [ApiMember(Name = "MediaTypes", Description = "Optional filter by MediaType. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string MediaTypes { get; set; } - - /// <summary> - /// Gets or sets the image types. - /// </summary> - /// <value>The image types.</value> - [ApiMember(Name = "ImageTypes", Description = "Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ImageTypes { get; set; } - - /// <summary> - /// What to sort the results by - /// </summary> - /// <value>The sort by.</value> - [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string SortBy { get; set; } - - [ApiMember(Name = "IsPlayed", Description = "Optional filter by items that are played, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsPlayed { get; set; } - - /// <summary> - /// Limit results to items containing specific genres - /// </summary> - /// <value>The genres.</value> - [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Genres { get; set; } - - public string GenreIds { get; set; } - - [ApiMember(Name = "OfficialRatings", Description = "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string OfficialRatings { get; set; } - - [ApiMember(Name = "Tags", Description = "Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Tags { get; set; } - - /// <summary> - /// Limit results to items containing specific years - /// </summary> - /// <value>The years.</value> - [ApiMember(Name = "Years", Description = "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Years { 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; } - - /// <summary> - /// Limit results to items containing a specific person - /// </summary> - /// <value>The person.</value> - [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Person { get; set; } - - [ApiMember(Name = "PersonIds", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string PersonIds { get; set; } - - /// <summary> - /// If the Person filter is used, this can also be used to restrict to a specific person type - /// </summary> - /// <value>The type of the person.</value> - [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string PersonTypes { get; set; } - - /// <summary> - /// Limit results to items containing specific studios - /// </summary> - /// <value>The studios.</value> - [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Studios { get; set; } - - [ApiMember(Name = "StudioIds", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string StudioIds { get; set; } - - /// <summary> - /// Gets or sets the studios. - /// </summary> - /// <value>The studios.</value> - [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Artists { get; set; } - - public string ExcludeArtistIds { get; set; } - - [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ArtistIds { get; set; } - - public string AlbumArtistIds { get; set; } - - public string ContributingArtistIds { get; set; } - - [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Albums { get; set; } - - public string AlbumIds { get; set; } - - /// <summary> - /// Gets or sets the item ids. - /// </summary> - /// <value>The item ids.</value> - [ApiMember(Name = "Ids", Description = "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Ids { get; set; } - - /// <summary> - /// Gets or sets the video types. - /// </summary> - /// <value>The video types.</value> - [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string VideoTypes { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the min offical rating. - /// </summary> - /// <value>The min offical rating.</value> - [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MinOfficialRating { get; set; } - - [ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? IsLocked { get; set; } - - [ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? IsPlaceHolder { get; set; } - - [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasOfficialRating { get; set; } - - [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? CollapseBoxSetItems { get; set; } - - public int? MinWidth { get; set; } - public int? MinHeight { get; set; } - public int? MaxWidth { get; set; } - public int? MaxHeight { get; set; } - - /// <summary> - /// Gets or sets the video formats. - /// </summary> - /// <value>The video formats.</value> - [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? Is3D { get; set; } - - /// <summary> - /// Gets or sets the series status. - /// </summary> - /// <value>The series status.</value> - [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string SeriesStatus { get; set; } - - [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameStartsWithOrGreater { get; set; } - - [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameStartsWith { get; set; } - - [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameLessThan { get; set; } - - public string[] GetGenres() - { - return (Genres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetTags() - { - return (Tags ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetOfficialRatings() - { - return (OfficialRatings ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetMediaTypes() - { - return (MediaTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetIncludeItemTypes() - { - return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetExcludeItemTypes() - { - return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public int[] GetYears() - { - return (Years ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); - } - - public string[] GetStudios() - { - return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetPersonTypes() - { - return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public VideoType[] GetVideoTypes() - { - return string.IsNullOrEmpty(VideoTypes) - ? Array.Empty<VideoType>() - : VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(v => Enum.Parse<VideoType>(v, true)).ToArray(); - } - - /// <summary> - /// Gets the filters. - /// </summary> - /// <returns>IEnumerable{ItemFilter}.</returns> - public ItemFilter[] GetFilters() - { - var val = Filters; - - return string.IsNullOrEmpty(val) - ? Array.Empty<ItemFilter>() - : val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(v => Enum.Parse<ItemFilter>(v, true)).ToArray(); - } - - /// <summary> - /// Gets the image types. - /// </summary> - /// <returns>IEnumerable{ImageType}.</returns> - public ImageType[] GetImageTypes() - { - var val = ImageTypes; - - return string.IsNullOrEmpty(val) - ? Array.Empty<ImageType>() - : val.Split(',').Select(v => Enum.Parse<ImageType>(v, true)).ToArray(); - } - - /// <summary> - /// Gets the order by. - /// </summary> - /// <returns>IEnumerable{ItemSortBy}.</returns> - public ValueTuple<string, SortOrder>[] GetOrderBy() - { - return GetOrderBy(SortBy, SortOrder); - } - - public static ValueTuple<string, SortOrder>[] GetOrderBy(string sortBy, string requestedSortOrder) - { - var val = sortBy; - - if (string.IsNullOrEmpty(val)) - { - return Array.Empty<ValueTuple<string, SortOrder>>(); - } - - var vals = val.Split(','); - if (string.IsNullOrWhiteSpace(requestedSortOrder)) - { - requestedSortOrder = "Ascending"; - } - - var sortOrders = requestedSortOrder.Split(','); - - var result = new ValueTuple<string, SortOrder>[vals.Length]; - - for (var i = 0; i < vals.Length; i++) - { - var sortOrderIndex = sortOrders.Length > i ? i : 0; - - var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; - var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) - ? MediaBrowser.Model.Entities.SortOrder.Descending - : MediaBrowser.Model.Entities.SortOrder.Ascending; - - result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder); - } - - return result; - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs deleted file mode 100644 index 1fa272a5f..000000000 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class GetGenres - /// </summary> - [Route("/Genres", "GET", Summary = "Gets all genres from a given item, folder, or the entire library")] - public class GetGenres : GetItemsByName - { - } - - /// <summary> - /// Class GetGenre - /// </summary> - [Route("/Genres/{Name}", "GET", Summary = "Gets a genre, by name")] - public class GetGenre : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - /// <summary> - /// Class GenresService - /// </summary> - [Authenticated] - public class GenresService : BaseItemsByNameService<Genre> - { - public GenresService( - ILogger<GenresService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IUserDataManager userDataRepository, - IDtoService dtoService, - IAuthorizationContext authorizationContext) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - userDataRepository, - dtoService, - authorizationContext) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetGenre request) - { - var result = GetItem(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the item. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{BaseItemDto}.</returns> - private BaseItemDto GetItem(GetGenre request) - { - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - var item = GetGenre(request.Name, LibraryManager, dtoOptions); - - if (!request.UserId.Equals(Guid.Empty)) - { - var user = UserManager.GetUserById(request.UserId); - - return DtoService.GetBaseItemDto(item, dtoOptions, user); - } - - return DtoService.GetBaseItemDto(item, dtoOptions); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetGenres request) - { - var result = GetResultSlim(request); - - return ToOptimizedResult(result); - } - - protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) - { - var viewType = GetParentItemViewType(request); - - if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos)) - { - return LibraryManager.GetMusicGenres(query); - } - - return LibraryManager.GetGenres(query); - } - - /// <summary> - /// Gets all items. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="items">The items.</param> - /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs deleted file mode 100644 index f3c0441e1..000000000 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ /dev/null @@ -1,509 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class GetItems - /// </summary> - [Route("/Items", "GET", Summary = "Gets items based on a query.")] - [Route("/Users/{UserId}/Items", "GET", Summary = "Gets items based on a query.")] - public class GetItems : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>> - { - } - - [Route("/Users/{UserId}/Items/Resume", "GET", Summary = "Gets items based on a query.")] - public class GetResumeItems : BaseItemsRequest, IReturn<QueryResult<BaseItemDto>> - { - } - - /// <summary> - /// Class ItemsService - /// </summary> - [Authenticated] - public class ItemsService : BaseApiService - { - /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - private readonly ILocalizationManager _localization; - - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - - /// <summary> - /// Initializes a new instance of the <see cref="ItemsService" /> class. - /// </summary> - /// <param name="userManager">The user manager.</param> - /// <param name="libraryManager">The library manager.</param> - /// <param name="localization">The localization.</param> - /// <param name="dtoService">The dto service.</param> - public ItemsService( - ILogger<ItemsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - ILocalizationManager localization, - IDtoService dtoService, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _libraryManager = libraryManager; - _localization = localization; - _dtoService = dtoService; - _authContext = authContext; - } - - public object Get(GetResumeItems request) - { - var user = _userManager.GetUserById(request.UserId); - - var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId); - - var options = GetDtoOptions(_authContext, request); - - var ancestorIds = Array.Empty<Guid>(); - - var excludeFolderIds = user.Configuration.LatestItemsExcludes; - if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) - { - ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) - .Where(i => i is Folder) - .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) - .Select(i => i.Id) - .ToArray(); - } - - var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) - { - OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) }, - IsResumable = true, - StartIndex = request.StartIndex, - Limit = request.Limit, - ParentId = parentIdGuid, - Recursive = true, - DtoOptions = options, - MediaTypes = request.GetMediaTypes(), - IsVirtualItem = false, - CollapseBoxSetItems = false, - EnableTotalRecordCount = request.EnableTotalRecordCount, - AncestorIds = ancestorIds, - IncludeItemTypes = request.GetIncludeItemTypes(), - ExcludeItemTypes = request.GetExcludeItemTypes(), - SearchTerm = request.SearchTerm - }); - - var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, options, user); - - var result = new QueryResult<BaseItemDto> - { - StartIndex = request.StartIndex.GetValueOrDefault(), - TotalRecordCount = itemsResult.TotalRecordCount, - Items = returnItems - }; - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetItems request) - { - if (request == null) - { - throw new ArgumentNullException(nameof(request)); - } - - var result = GetItems(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the items. - /// </summary> - /// <param name="request">The request.</param> - private QueryResult<BaseItemDto> GetItems(GetItems request) - { - var user = request.UserId == Guid.Empty ? null : _userManager.GetUserById(request.UserId); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = GetQueryResult(request, dtoOptions, user); - - if (result == null) - { - throw new InvalidOperationException("GetItemsToSerialize returned null"); - } - - if (result.Items == null) - { - throw new InvalidOperationException("GetItemsToSerialize result.Items returned null"); - } - - var dtoList = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user); - - return new QueryResult<BaseItemDto> - { - StartIndex = request.StartIndex.GetValueOrDefault(), - TotalRecordCount = result.TotalRecordCount, - Items = dtoList - }; - } - - /// <summary> - /// Gets the items to serialize. - /// </summary> - private QueryResult<BaseItem> GetQueryResult(GetItems request, DtoOptions dtoOptions, User user) - { - if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) - || string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) - { - request.ParentId = null; - } - - BaseItem item = null; - - if (!string.IsNullOrEmpty(request.ParentId)) - { - item = _libraryManager.GetItemById(request.ParentId); - } - - if (item == null) - { - item = _libraryManager.GetUserRootFolder(); - } - - if (!(item is Folder folder)) - { - folder = _libraryManager.GetUserRootFolder(); - } - - if (folder is IHasCollectionType hasCollectionType - && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) - { - request.Recursive = true; - request.IncludeItemTypes = "Playlist"; - } - - bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id) - // Assume all folders inside an EnabledChannel are enabled - || user.Policy.EnabledChannels.Any(i => new Guid(i) == item.Id); - - var collectionFolders = _libraryManager.GetCollectionFolders(item); - foreach (var collectionFolder in collectionFolders) - { - if (user.Policy.EnabledFolders.Contains( - collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture), - StringComparer.OrdinalIgnoreCase)) - { - isInEnabledFolder = true; - } - } - - if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder && !user.Policy.EnableAllChannels) - { - Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name); - return new QueryResult<BaseItem> - { - Items = Array.Empty<BaseItem>(), - TotalRecordCount = 0, - StartIndex = 0 - }; - } - - if (request.Recursive || !string.IsNullOrEmpty(request.Ids) || !(item is UserRootFolder)) - { - return folder.GetItems(GetItemsQuery(request, dtoOptions, user)); - } - - var itemsArray = folder.GetChildren(user, true); - return new QueryResult<BaseItem> - { - Items = itemsArray, - TotalRecordCount = itemsArray.Count, - StartIndex = 0 - }; - } - - private InternalItemsQuery GetItemsQuery(GetItems request, DtoOptions dtoOptions, User user) - { - var query = new InternalItemsQuery(user) - { - IsPlayed = request.IsPlayed, - MediaTypes = request.GetMediaTypes(), - IncludeItemTypes = request.GetIncludeItemTypes(), - ExcludeItemTypes = request.GetExcludeItemTypes(), - Recursive = request.Recursive, - OrderBy = request.GetOrderBy(), - - IsFavorite = request.IsFavorite, - Limit = request.Limit, - StartIndex = request.StartIndex, - IsMissing = request.IsMissing, - IsUnaired = request.IsUnaired, - CollapseBoxSetItems = request.CollapseBoxSetItems, - NameLessThan = request.NameLessThan, - NameStartsWith = request.NameStartsWith, - NameStartsWithOrGreater = request.NameStartsWithOrGreater, - HasImdbId = request.HasImdbId, - IsPlaceHolder = request.IsPlaceHolder, - IsLocked = request.IsLocked, - MinWidth = request.MinWidth, - MinHeight = request.MinHeight, - MaxWidth = request.MaxWidth, - MaxHeight = request.MaxHeight, - Is3D = request.Is3D, - HasTvdbId = request.HasTvdbId, - HasTmdbId = request.HasTmdbId, - HasOverview = request.HasOverview, - HasOfficialRating = request.HasOfficialRating, - HasParentalRating = request.HasParentalRating, - HasSpecialFeature = request.HasSpecialFeature, - HasSubtitles = request.HasSubtitles, - HasThemeSong = request.HasThemeSong, - HasThemeVideo = request.HasThemeVideo, - HasTrailer = request.HasTrailer, - IsHD = request.IsHD, - Is4K = request.Is4K, - Tags = request.GetTags(), - OfficialRatings = request.GetOfficialRatings(), - Genres = request.GetGenres(), - ArtistIds = GetGuids(request.ArtistIds), - AlbumArtistIds = GetGuids(request.AlbumArtistIds), - ContributingArtistIds = GetGuids(request.ContributingArtistIds), - GenreIds = GetGuids(request.GenreIds), - StudioIds = GetGuids(request.StudioIds), - Person = request.Person, - PersonIds = GetGuids(request.PersonIds), - PersonTypes = request.GetPersonTypes(), - Years = request.GetYears(), - ImageTypes = request.GetImageTypes(), - VideoTypes = request.GetVideoTypes(), - AdjacentTo = request.AdjacentTo, - ItemIds = GetGuids(request.Ids), - MinCommunityRating = request.MinCommunityRating, - MinCriticRating = request.MinCriticRating, - ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId), - ParentIndexNumber = request.ParentIndexNumber, - EnableTotalRecordCount = request.EnableTotalRecordCount, - ExcludeItemIds = GetGuids(request.ExcludeItemIds), - DtoOptions = dtoOptions, - SearchTerm = request.SearchTerm - }; - - if (!string.IsNullOrWhiteSpace(request.Ids) || !string.IsNullOrWhiteSpace(request.SearchTerm)) - { - query.CollapseBoxSetItems = false; - } - - foreach (var filter in request.GetFilters()) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - if (!string.IsNullOrEmpty(request.MinDateLastSaved)) - { - query.MinDateLastSaved = DateTime.Parse(request.MinDateLastSaved, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - if (!string.IsNullOrEmpty(request.MinDateLastSavedForUser)) - { - query.MinDateLastSavedForUser = DateTime.Parse(request.MinDateLastSavedForUser, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - if (!string.IsNullOrEmpty(request.MinPremiereDate)) - { - query.MinPremiereDate = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - if (!string.IsNullOrEmpty(request.MaxPremiereDate)) - { - query.MaxPremiereDate = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - } - - // Filter by Series Status - if (!string.IsNullOrEmpty(request.SeriesStatus)) - { - query.SeriesStatuses = request.SeriesStatus.Split(',').Select(d => (SeriesStatus)Enum.Parse(typeof(SeriesStatus), d, true)).ToArray(); - } - - // ExcludeLocationTypes - if (!string.IsNullOrEmpty(request.ExcludeLocationTypes)) - { - var excludeLocationTypes = request.ExcludeLocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray(); - if (excludeLocationTypes.Contains(LocationType.Virtual)) - { - query.IsVirtualItem = false; - } - } - - if (!string.IsNullOrEmpty(request.LocationTypes)) - { - var requestedLocationTypes = - request.LocationTypes.Split(','); - - if (requestedLocationTypes.Length > 0 && requestedLocationTypes.Length < 4) - { - query.IsVirtualItem = requestedLocationTypes.Contains(LocationType.Virtual.ToString()); - } - } - - // Min official rating - if (!string.IsNullOrWhiteSpace(request.MinOfficialRating)) - { - query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating); - } - - // Max official rating - if (!string.IsNullOrWhiteSpace(request.MaxOfficialRating)) - { - query.MaxParentalRating = _localization.GetRatingLevel(request.MaxOfficialRating); - } - - // Artists - if (!string.IsNullOrEmpty(request.Artists)) - { - query.ArtistIds = request.Artists.Split('|').Select(i => - { - try - { - return _libraryManager.GetArtist(i, new DtoOptions(false)); - } - catch - { - return null; - } - }).Where(i => i != null).Select(i => i.Id).ToArray(); - } - - // ExcludeArtistIds - if (!string.IsNullOrWhiteSpace(request.ExcludeArtistIds)) - { - query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds); - } - - if (!string.IsNullOrWhiteSpace(request.AlbumIds)) - { - query.AlbumIds = GetGuids(request.AlbumIds); - } - - // Albums - if (!string.IsNullOrEmpty(request.Albums)) - { - query.AlbumIds = request.Albums.Split('|').SelectMany(i => - { - return _libraryManager.GetItemIds(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, - Name = i, - Limit = 1 - }); - }).ToArray(); - } - - // Studios - if (!string.IsNullOrEmpty(request.Studios)) - { - query.StudioIds = request.Studios.Split('|').Select(i => - { - try - { - return _libraryManager.GetStudio(i); - } - catch - { - return null; - } - }).Where(i => i != null).Select(i => i.Id).ToArray(); - } - - // Apply default sorting if none requested - if (query.OrderBy.Count == 0) - { - // Albums by artist - if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], "MusicAlbum", StringComparison.OrdinalIgnoreCase)) - { - query.OrderBy = new[] - { - new ValueTuple<string, SortOrder>(ItemSortBy.ProductionYear, SortOrder.Descending), - new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) - }; - } - } - - return query; - } - } - - /// <summary> - /// Class DateCreatedComparer - /// </summary> - public class DateCreatedComparer : IComparer<BaseItem> - { - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return x.DateCreated.CompareTo(y.DateCreated); - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs deleted file mode 100644 index e9caca14a..000000000 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - [Route("/MusicGenres", "GET", Summary = "Gets all music genres from a given item, folder, or the entire library")] - public class GetMusicGenres : GetItemsByName - { - } - - [Route("/MusicGenres/{Name}", "GET", Summary = "Gets a music genre, by name")] - public class GetMusicGenre : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - [Authenticated] - public class MusicGenresService : BaseItemsByNameService<MusicGenre> - { - public MusicGenresService( - ILogger<MusicGenresService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IUserDataManager userDataRepository, - IDtoService dtoService, - IAuthorizationContext authorizationContext) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - userDataRepository, - dtoService, - authorizationContext) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetMusicGenre request) - { - var result = GetItem(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the item. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{BaseItemDto}.</returns> - private BaseItemDto GetItem(GetMusicGenre request) - { - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - var item = GetMusicGenre(request.Name, LibraryManager, dtoOptions); - - if (!request.UserId.Equals(Guid.Empty)) - { - var user = UserManager.GetUserById(request.UserId); - - return DtoService.GetBaseItemDto(item, dtoOptions, user); - } - - return DtoService.GetBaseItemDto(item, dtoOptions); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetMusicGenres request) - { - var result = GetResultSlim(request); - - return ToOptimizedResult(result); - } - - protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) - { - return LibraryManager.GetMusicGenres(query); - } - - /// <summary> - /// Gets all items. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="items">The items.</param> - /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs deleted file mode 100644 index 3204e5219..000000000 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class GetPersons - /// </summary> - [Route("/Persons", "GET", Summary = "Gets all persons from a given item, folder, or the entire library")] - public class GetPersons : GetItemsByName - { - } - - /// <summary> - /// Class GetPerson - /// </summary> - [Route("/Persons/{Name}", "GET", Summary = "Gets a person, by name")] - public class GetPerson : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The person name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - /// <summary> - /// Class PersonsService - /// </summary> - [Authenticated] - public class PersonsService : BaseItemsByNameService<Person> - { - public PersonsService( - ILogger<PersonsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IUserDataManager userDataRepository, - IDtoService dtoService, - IAuthorizationContext authorizationContext) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - userDataRepository, - dtoService, - authorizationContext) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPerson request) - { - var result = GetItem(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the item. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{BaseItemDto}.</returns> - private BaseItemDto GetItem(GetPerson request) - { - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - var item = GetPerson(request.Name, LibraryManager, dtoOptions); - - if (!request.UserId.Equals(Guid.Empty)) - { - var user = UserManager.GetUserById(request.UserId); - - return DtoService.GetBaseItemDto(item, dtoOptions, user); - } - - return DtoService.GetBaseItemDto(item, dtoOptions); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPersons request) - { - return GetResultSlim(request); - } - - /// <summary> - /// Gets all items. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="items">The items.</param> - /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items) - { - throw new NotImplementedException(); - } - - protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) - { - var items = LibraryManager.GetPeopleItems(new InternalPeopleQuery - { - PersonTypes = query.PersonTypes, - NameContains = query.NameContains ?? query.SearchTerm - }); - - if ((query.IsFavorite ?? false) && query.User != null) - { - items = items.Where(i => UserDataRepository.GetUserData(query.User, i).IsFavorite).ToList(); - } - - return new QueryResult<(BaseItem, ItemCounts)> - { - TotalRecordCount = items.Count, - Items = items.Take(query.Limit ?? int.MaxValue).Select(i => (i as BaseItem, new ItemCounts())).ToArray() - }; - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs deleted file mode 100644 index d0faca163..000000000 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ /dev/null @@ -1,456 +0,0 @@ -using System; -using System.Globalization; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Session; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class MarkPlayedItem - /// </summary> - [Route("/Users/{UserId}/PlayedItems/{Id}", "POST", Summary = "Marks an item as played")] - public class MarkPlayedItem : IReturn<UserItemDataDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } - - [ApiMember(Name = "DatePlayed", Description = "The date the item was played (if any). Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string DatePlayed { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// <summary> - /// Class MarkUnplayedItem - /// </summary> - [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE", Summary = "Marks an item as unplayed")] - public class MarkUnplayedItem : IReturn<UserItemDataDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Sessions/Playing", "POST", Summary = "Reports playback has started within a session")] - public class ReportPlaybackStart : PlaybackStartInfo, IReturnVoid - { - } - - [Route("/Sessions/Playing/Progress", "POST", Summary = "Reports playback progress within a session")] - public class ReportPlaybackProgress : PlaybackProgressInfo, IReturnVoid - { - } - - [Route("/Sessions/Playing/Ping", "POST", Summary = "Pings a playback session")] - public class PingPlaybackSession : IReturnVoid - { - [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlaySessionId { get; set; } - } - - [Route("/Sessions/Playing/Stopped", "POST", Summary = "Reports playback has stopped within a session")] - public class ReportPlaybackStopped : PlaybackStopInfo, IReturnVoid - { - } - - /// <summary> - /// Class OnPlaybackStart - /// </summary> - [Route("/Users/{UserId}/PlayingItems/{Id}", "POST", Summary = "Reports that a user has begun playing an item")] - public class OnPlaybackStart : IReturnVoid - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool CanSeek { get; set; } - - [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? AudioStreamIndex { get; set; } - - [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? SubtitleStreamIndex { get; set; } - - [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public PlayMethod PlayMethod { get; set; } - - [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string LiveStreamId { get; set; } - - [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlaySessionId { get; set; } - } - - /// <summary> - /// Class OnPlaybackProgress - /// </summary> - [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST", Summary = "Reports a user's playback progress")] - public class OnPlaybackProgress : IReturnVoid - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MediaSourceId { get; set; } - - /// <summary> - /// Gets or sets the position ticks. - /// </summary> - /// <value>The position ticks.</value> - [ApiMember(Name = "PositionTicks", Description = "Optional. The current position, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public long? PositionTicks { get; set; } - - [ApiMember(Name = "IsPaused", Description = "Indicates if the player is paused.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool IsPaused { get; set; } - - [ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool IsMuted { get; set; } - - [ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? AudioStreamIndex { get; set; } - - [ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? SubtitleStreamIndex { get; set; } - - [ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")] - public int? VolumeLevel { get; set; } - - [ApiMember(Name = "PlayMethod", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public PlayMethod PlayMethod { get; set; } - - [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string LiveStreamId { get; set; } - - [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlaySessionId { get; set; } - - [ApiMember(Name = "RepeatMode", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public RepeatMode RepeatMode { get; set; } - } - - /// <summary> - /// Class OnPlaybackStopped - /// </summary> - [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE", Summary = "Reports that a user has stopped playing an item")] - public class OnPlaybackStopped : IReturnVoid - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "NextMediaType", Description = "The next media type that will play", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string NextMediaType { get; set; } - - /// <summary> - /// Gets or sets the position ticks. - /// </summary> - /// <value>The position ticks.</value> - [ApiMember(Name = "PositionTicks", Description = "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "DELETE")] - public long? PositionTicks { get; set; } - - [ApiMember(Name = "LiveStreamId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string LiveStreamId { get; set; } - - [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlaySessionId { get; set; } - } - - [Authenticated] - public class PlaystateService : BaseApiService - { - private readonly IUserManager _userManager; - private readonly IUserDataManager _userDataRepository; - private readonly ILibraryManager _libraryManager; - private readonly ISessionManager _sessionManager; - private readonly ISessionContext _sessionContext; - private readonly IAuthorizationContext _authContext; - - public PlaystateService( - ILogger<PlaystateService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - IUserDataManager userDataRepository, - ILibraryManager libraryManager, - ISessionManager sessionManager, - ISessionContext sessionContext, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _userDataRepository = userDataRepository; - _libraryManager = libraryManager; - _sessionManager = sessionManager; - _sessionContext = sessionContext; - _authContext = authContext; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Post(MarkPlayedItem request) - { - var result = MarkPlayed(request); - - return ToOptimizedResult(result); - } - - private UserItemDataDto MarkPlayed(MarkPlayedItem request) - { - var user = _userManager.GetUserById(Guid.Parse(request.UserId)); - - DateTime? datePlayed = null; - - if (!string.IsNullOrEmpty(request.DatePlayed)) - { - datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); - } - - var session = GetSession(_sessionContext); - - var dto = UpdatePlayedStatus(user, request.Id, true, datePlayed); - - foreach (var additionalUserInfo in session.AdditionalUsers) - { - var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId); - - UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed); - } - - return dto; - } - - private PlayMethod ValidatePlayMethod(PlayMethod method, string playSessionId) - { - if (method == PlayMethod.Transcode) - { - var job = string.IsNullOrWhiteSpace(playSessionId) ? null : ApiEntryPoint.Instance.GetTranscodingJob(playSessionId); - if (job == null) - { - return PlayMethod.DirectPlay; - } - } - - return method; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(OnPlaybackStart request) - { - Post(new ReportPlaybackStart - { - CanSeek = request.CanSeek, - ItemId = new Guid(request.Id), - MediaSourceId = request.MediaSourceId, - AudioStreamIndex = request.AudioStreamIndex, - SubtitleStreamIndex = request.SubtitleStreamIndex, - PlayMethod = request.PlayMethod, - PlaySessionId = request.PlaySessionId, - LiveStreamId = request.LiveStreamId - }); - } - - public void Post(ReportPlaybackStart request) - { - request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId); - - request.SessionId = GetSession(_sessionContext).Id; - - var task = _sessionManager.OnPlaybackStart(request); - - Task.WaitAll(task); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(OnPlaybackProgress request) - { - Post(new ReportPlaybackProgress - { - ItemId = new Guid(request.Id), - PositionTicks = request.PositionTicks, - IsMuted = request.IsMuted, - IsPaused = request.IsPaused, - MediaSourceId = request.MediaSourceId, - AudioStreamIndex = request.AudioStreamIndex, - SubtitleStreamIndex = request.SubtitleStreamIndex, - VolumeLevel = request.VolumeLevel, - PlayMethod = request.PlayMethod, - PlaySessionId = request.PlaySessionId, - LiveStreamId = request.LiveStreamId, - RepeatMode = request.RepeatMode - }); - } - - public void Post(ReportPlaybackProgress request) - { - request.PlayMethod = ValidatePlayMethod(request.PlayMethod, request.PlaySessionId); - - request.SessionId = GetSession(_sessionContext).Id; - - var task = _sessionManager.OnPlaybackProgress(request); - - Task.WaitAll(task); - } - - public void Post(PingPlaybackSession request) - { - ApiEntryPoint.Instance.PingTranscodingJob(request.PlaySessionId, null); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Delete(OnPlaybackStopped request) - { - return Post(new ReportPlaybackStopped - { - ItemId = new Guid(request.Id), - PositionTicks = request.PositionTicks, - MediaSourceId = request.MediaSourceId, - PlaySessionId = request.PlaySessionId, - LiveStreamId = request.LiveStreamId, - NextMediaType = request.NextMediaType - }); - } - - public async Task Post(ReportPlaybackStopped request) - { - Logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty); - - if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) - { - await ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); - } - - request.SessionId = GetSession(_sessionContext).Id; - - await _sessionManager.OnPlaybackStopped(request); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Delete(MarkUnplayedItem request) - { - var task = MarkUnplayed(request); - - return ToOptimizedResult(task); - } - - private UserItemDataDto MarkUnplayed(MarkUnplayedItem request) - { - var user = _userManager.GetUserById(Guid.Parse(request.UserId)); - - var session = GetSession(_sessionContext); - - var dto = UpdatePlayedStatus(user, request.Id, false, null); - - foreach (var additionalUserInfo in session.AdditionalUsers) - { - var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId); - - UpdatePlayedStatus(additionalUser, request.Id, false, null); - } - - return dto; - } - - /// <summary> - /// Updates the played status. - /// </summary> - /// <param name="user">The user.</param> - /// <param name="itemId">The item id.</param> - /// <param name="wasPlayed">if set to <c>true</c> [was played].</param> - /// <param name="datePlayed">The date played.</param> - /// <returns>Task.</returns> - private UserItemDataDto UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed) - { - var item = _libraryManager.GetItemById(itemId); - - if (wasPlayed) - { - item.MarkPlayed(user, datePlayed, true); - } - else - { - item.MarkUnplayed(user); - } - - return _userDataRepository.GetUserDataDto(item, user); - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs deleted file mode 100644 index 683ce5d09..000000000 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class GetStudios - /// </summary> - [Route("/Studios", "GET", Summary = "Gets all studios from a given item, folder, or the entire library")] - public class GetStudios : GetItemsByName - { - } - - /// <summary> - /// Class GetStudio - /// </summary> - [Route("/Studios/{Name}", "GET", Summary = "Gets a studio, by name")] - public class GetStudio : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The studio name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - /// <summary> - /// Class StudiosService - /// </summary> - [Authenticated] - public class StudiosService : BaseItemsByNameService<Studio> - { - public StudiosService( - ILogger<StudiosService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IUserDataManager userDataRepository, - IDtoService dtoService, - IAuthorizationContext authorizationContext) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - userDataRepository, - dtoService, - authorizationContext) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetStudio request) - { - var result = GetItem(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the item. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{BaseItemDto}.</returns> - private BaseItemDto GetItem(GetStudio request) - { - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - var item = GetStudio(request.Name, LibraryManager, dtoOptions); - - if (!request.UserId.Equals(Guid.Empty)) - { - var user = UserManager.GetUserById(request.UserId); - - return DtoService.GetBaseItemDto(item, dtoOptions, user); - } - - return DtoService.GetBaseItemDto(item, dtoOptions); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetStudios request) - { - var result = GetResultSlim(request); - - return ToOptimizedResult(result); - } - - protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query) - { - return LibraryManager.GetStudios(query); - } - - /// <summary> - /// Gets all items. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="items">The items.</param> - /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs deleted file mode 100644 index 7fa750adb..000000000 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ /dev/null @@ -1,575 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class GetItem - /// </summary> - [Route("/Users/{UserId}/Items/{Id}", "GET", Summary = "Gets an item from a user's library")] - public class GetItem : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// <summary> - /// Class GetItem - /// </summary> - [Route("/Users/{UserId}/Items/Root", "GET", Summary = "Gets the root folder from a user's library")] - public class GetRootFolder : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - } - - /// <summary> - /// Class GetIntros - /// </summary> - [Route("/Users/{UserId}/Items/{Id}/Intros", "GET", Summary = "Gets intros to play before the main media item plays")] - public class GetIntros : IReturn<QueryResult<BaseItemDto>> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the item id. - /// </summary> - /// <value>The item id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// <summary> - /// Class MarkFavoriteItem - /// </summary> - [Route("/Users/{UserId}/FavoriteItems/{Id}", "POST", Summary = "Marks an item as a favorite")] - public class MarkFavoriteItem : IReturn<UserItemDataDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class UnmarkFavoriteItem - /// </summary> - [Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE", Summary = "Unmarks an item as a favorite")] - public class UnmarkFavoriteItem : IReturn<UserItemDataDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class ClearUserItemRating - /// </summary> - [Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE", Summary = "Deletes a user's saved personal rating for an item")] - public class DeleteUserItemRating : IReturn<UserItemDataDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class UpdateUserItemRating - /// </summary> - [Route("/Users/{UserId}/Items/{Id}/Rating", "POST", Summary = "Updates a user's rating for an item")] - public class UpdateUserItemRating : IReturn<UserItemDataDto> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes. - /// </summary> - /// <value><c>true</c> if likes; otherwise, <c>false</c>.</value> - [ApiMember(Name = "Likes", Description = "Whether the user likes the item or not. true/false", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")] - public bool Likes { get; set; } - } - - /// <summary> - /// Class GetLocalTrailers - /// </summary> - [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET", Summary = "Gets local trailers for an item")] - public class GetLocalTrailers : IReturn<BaseItemDto[]> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// <summary> - /// Class GetSpecialFeatures - /// </summary> - [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET", Summary = "Gets special features for an item")] - public class GetSpecialFeatures : IReturn<BaseItemDto[]> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Movie Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Users/{UserId}/Items/Latest", "GET", Summary = "Gets latest media")] - public class GetLatestMedia : IReturn<BaseItemDto[]>, IHasDtoOptions - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "Limit", Description = "Limit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int Limit { get; set; } - - [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid ParentId { get; set; } - - [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Fields { get; set; } - - [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string IncludeItemTypes { get; set; } - - [ApiMember(Name = "IsFolder", Description = "Filter by items that are folders, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsFolder { get; set; } - - [ApiMember(Name = "IsPlayed", Description = "Filter by items that are played, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsPlayed { get; set; } - - [ApiMember(Name = "GroupItems", Description = "Whether or not to group items into a parent container.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool GroupItems { 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 = "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; } - - [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; - GroupItems = true; - } - } - - /// <summary> - /// Class UserLibraryService - /// </summary> - [Authenticated] - public class UserLibraryService : BaseApiService - { - private readonly IUserManager _userManager; - private readonly IUserDataManager _userDataRepository; - private readonly ILibraryManager _libraryManager; - private readonly IDtoService _dtoService; - private readonly IUserViewManager _userViewManager; - private readonly IFileSystem _fileSystem; - private readonly IAuthorizationContext _authContext; - - public UserLibraryService( - ILogger<UserLibraryService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IUserDataManager userDataRepository, - IDtoService dtoService, - IUserViewManager userViewManager, - IFileSystem fileSystem, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _libraryManager = libraryManager; - _userDataRepository = userDataRepository; - _dtoService = dtoService; - _userViewManager = userViewManager; - _fileSystem = fileSystem; - _authContext = authContext; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetSpecialFeatures request) - { - var result = GetAsync(request); - - return ToOptimizedResult(result); - } - - public object Get(GetLatestMedia request) - { - var user = _userManager.GetUserById(request.UserId); - - if (!request.IsPlayed.HasValue) - { - if (user.Configuration.HidePlayedInLatest) - { - request.IsPlayed = false; - } - } - - var dtoOptions = GetDtoOptions(_authContext, request); - - var list = _userViewManager.GetLatestItems(new LatestItemsQuery - { - GroupItems = request.GroupItems, - IncludeItemTypes = ApiEntryPoint.Split(request.IncludeItemTypes, ',', true), - IsPlayed = request.IsPlayed, - Limit = request.Limit, - ParentId = request.ParentId, - UserId = request.UserId, - }, dtoOptions); - - var dtos = list.Select(i => - { - var item = i.Item2[0]; - var childCount = 0; - - if (i.Item1 != null && (i.Item2.Count > 1 || i.Item1 is MusicAlbum)) - { - item = i.Item1; - childCount = i.Item2.Count; - } - - var dto = _dtoService.GetBaseItemDto(item, dtoOptions, user); - - dto.ChildCount = childCount; - - return dto; - }); - - return ToOptimizedResult(dtos.ToArray()); - } - - private BaseItemDto[] GetAsync(GetSpecialFeatures request) - { - var user = _userManager.GetUserById(request.UserId); - - var item = string.IsNullOrEmpty(request.Id) ? - _libraryManager.GetUserRootFolder() : - _libraryManager.GetItemById(request.Id); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var dtos = item - .GetExtras(BaseItem.DisplayExtraTypes) - .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); - - return dtos.ToArray(); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetLocalTrailers request) - { - var user = _userManager.GetUserById(request.UserId); - - var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var dtosExtras = item.GetExtras(new[] { ExtraType.Trailer }) - .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) - .ToArray(); - - if (item is IHasTrailers hasTrailers) - { - var trailers = hasTrailers.GetTrailers(); - var dtosTrailers = _dtoService.GetBaseItemDtos(trailers, dtoOptions, user, item); - var allTrailers = new BaseItemDto[dtosExtras.Length + dtosTrailers.Count]; - dtosExtras.CopyTo(allTrailers, 0); - dtosTrailers.CopyTo(allTrailers, dtosExtras.Length); - return ToOptimizedResult(allTrailers); - } - - return ToOptimizedResult(dtosExtras); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public async Task<object> Get(GetItem request) - { - var user = _userManager.GetUserById(request.UserId); - - var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); - - await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); - - return ToOptimizedResult(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(new DirectoryService(_fileSystem)) - { - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - ForceSave = performFullRefresh - }; - - await item.RefreshMetadata(options, CancellationToken.None).ConfigureAwait(false); - } - } - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetRootFolder request) - { - var user = _userManager.GetUserById(request.UserId); - - var item = _libraryManager.GetUserRootFolder(); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var result = _dtoService.GetBaseItemDto(item, dtoOptions, user); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public async Task<object> Get(GetIntros request) - { - var user = _userManager.GetUserById(request.UserId); - - var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(request.Id); - - var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false); - - var dtoOptions = GetDtoOptions(_authContext, request); - - var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray(); - - var result = new QueryResult<BaseItemDto> - { - Items = dtos, - TotalRecordCount = dtos.Length - }; - - return ToOptimizedResult(result); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Post(MarkFavoriteItem request) - { - var dto = MarkFavorite(request.UserId, request.Id, true); - - return ToOptimizedResult(dto); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Delete(UnmarkFavoriteItem request) - { - var dto = MarkFavorite(request.UserId, request.Id, false); - - return ToOptimizedResult(dto); - } - - /// <summary> - /// Marks the favorite. - /// </summary> - /// <param name="userId">The user id.</param> - /// <param name="itemId">The item id.</param> - /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param> - private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite) - { - var user = _userManager.GetUserById(userId); - - var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); - - // Get the user data for this item - var data = _userDataRepository.GetUserData(user, item); - - // Set favorite status - data.IsFavorite = isFavorite; - - _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); - - return _userDataRepository.GetUserDataDto(item, user); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Delete(DeleteUserItemRating request) - { - var dto = UpdateUserItemRating(request.UserId, request.Id, null); - - return ToOptimizedResult(dto); - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Post(UpdateUserItemRating request) - { - var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes); - - return ToOptimizedResult(dto); - } - - /// <summary> - /// Updates the user item rating. - /// </summary> - /// <param name="userId">The user id.</param> - /// <param name="itemId">The item id.</param> - /// <param name="likes">if set to <c>true</c> [likes].</param> - private UserItemDataDto UpdateUserItemRating(Guid userId, Guid itemId, bool? likes) - { - var user = _userManager.GetUserById(userId); - - var item = itemId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId); - - // Get the user data for this item - var data = _userDataRepository.GetUserData(user, item); - - data.Likes = likes; - - _userDataRepository.SaveUserData(user, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None); - - return _userDataRepository.GetUserDataDto(item, user); - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs deleted file mode 100644 index 0fffb0622..000000000 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Library; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - [Route("/Users/{UserId}/Views", "GET")] - public class GetUserViews : IReturn<QueryResult<BaseItemDto>> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? IncludeExternalContent { get; set; } - public bool IncludeHidden { get; set; } - - public string PresetViews { get; set; } - } - - [Route("/Users/{UserId}/GroupingOptions", "GET")] - public class GetGroupingOptions : IReturn<SpecialViewOption[]> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - } - - public class UserViewsService : BaseApiService - { - private readonly IUserManager _userManager; - private readonly IUserViewManager _userViewManager; - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - private readonly ILibraryManager _libraryManager; - - public UserViewsService( - ILogger<UserViewsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - IUserViewManager userViewManager, - IDtoService dtoService, - IAuthorizationContext authContext, - ILibraryManager libraryManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _userViewManager = userViewManager; - _dtoService = dtoService; - _authContext = authContext; - _libraryManager = libraryManager; - } - - public object Get(GetUserViews request) - { - var query = new UserViewQuery - { - UserId = request.UserId - }; - - if (request.IncludeExternalContent.HasValue) - { - query.IncludeExternalContent = request.IncludeExternalContent.Value; - } - query.IncludeHidden = request.IncludeHidden; - - if (!string.IsNullOrWhiteSpace(request.PresetViews)) - { - query.PresetViews = request.PresetViews.Split(','); - } - - var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty; - if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1) - { - query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows }; - } - - var folders = _userViewManager.GetUserViews(query); - - var dtoOptions = GetDtoOptions(_authContext, request); - var fields = dtoOptions.Fields.ToList(); - - fields.Add(ItemFields.PrimaryImageAspectRatio); - fields.Add(ItemFields.DisplayPreferencesId); - fields.Remove(ItemFields.BasicSyncInfo); - dtoOptions.Fields = fields.ToArray(); - - var user = _userManager.GetUserById(request.UserId); - - var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)) - .ToArray(); - - var result = new QueryResult<BaseItemDto> - { - Items = dtos, - TotalRecordCount = dtos.Length - }; - - return ToOptimizedResult(result); - } - - public object Get(GetGroupingOptions request) - { - var user = _userManager.GetUserById(request.UserId); - - var list = _libraryManager.GetUserRootFolder() - .GetChildren(user, true) - .OfType<Folder>() - .Where(UserView.IsEligibleForGrouping) - .Select(i => new SpecialViewOption - { - Name = i.Name, - Id = i.Id.ToString("N", CultureInfo.InvariantCulture) - - }) - .OrderBy(i => i.Name) - .ToArray(); - - return ToOptimizedResult(list); - } - } - - class SpecialViewOption - { - public string Name { get; set; } - public string Id { get; set; } - } -} diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs deleted file mode 100644 index d023ee90a..000000000 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.UserLibrary -{ - /// <summary> - /// Class GetYears - /// </summary> - [Route("/Years", "GET", Summary = "Gets all years from a given item, folder, or the entire library")] - public class GetYears : GetItemsByName - { - } - - /// <summary> - /// Class GetYear - /// </summary> - [Route("/Years/{Year}", "GET", Summary = "Gets a year")] - public class GetYear : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the year. - /// </summary> - /// <value>The year.</value> - [ApiMember(Name = "Year", Description = "The year", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Year { get; set; } - - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - /// <summary> - /// Class YearsService - /// </summary> - [Authenticated] - public class YearsService : BaseItemsByNameService<Year> - { - public YearsService( - ILogger<YearsService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ILibraryManager libraryManager, - IUserDataManager userDataRepository, - IDtoService dtoService, - IAuthorizationContext authorizationContext) - : base( - logger, - serverConfigurationManager, - httpResultFactory, - userManager, - libraryManager, - userDataRepository, - dtoService, - authorizationContext) - { - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetYear request) - { - var result = GetItem(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the item. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{BaseItemDto}.</returns> - private BaseItemDto GetItem(GetYear request) - { - var item = LibraryManager.GetYear(request.Year); - - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - if (!request.UserId.Equals(Guid.Empty)) - { - var user = UserManager.GetUserById(request.UserId); - - return DtoService.GetBaseItemDto(item, dtoOptions, user); - } - - return DtoService.GetBaseItemDto(item, dtoOptions); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetYears request) - { - var result = GetResult(request); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets all items. - /// </summary> - /// <param name="request">The request.</param> - /// <param name="items">The items.</param> - /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IList<BaseItem> items) - { - return items - .Select(i => i.ProductionYear ?? 0) - .Where(i => i > 0) - .Distinct() - .Select(year => LibraryManager.GetYear(year)); - } - } -} diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs deleted file mode 100644 index 78fc6c694..000000000 --- a/MediaBrowser.Api/UserService.cs +++ /dev/null @@ -1,600 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetUsers - /// </summary> - [Route("/Users", "GET", Summary = "Gets a list of users")] - [Authenticated] - public class GetUsers : IReturn<UserDto[]> - { - [ApiMember(Name = "IsHidden", Description = "Optional filter by IsHidden=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsHidden { get; set; } - - [ApiMember(Name = "IsDisabled", Description = "Optional filter by IsDisabled=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsDisabled { get; set; } - - [ApiMember(Name = "IsGuest", Description = "Optional filter by IsGuest=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsGuest { get; set; } - } - - [Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")] - public class GetPublicUsers : IReturn<UserDto[]> - { - } - - /// <summary> - /// Class GetUser - /// </summary> - [Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")] - [Authenticated(EscapeParentalControl = true)] - public class GetUser : IReturn<UserDto> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class DeleteUser - /// </summary> - [Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")] - [Authenticated(Roles = "Admin")] - public class DeleteUser : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class AuthenticateUser - /// </summary> - [Route("/Users/{Id}/Authenticate", "POST", Summary = "Authenticates a user")] - public class AuthenticateUser : IReturn<AuthenticationResult> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } - - [ApiMember(Name = "Pw", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string Pw { get; set; } - - /// <summary> - /// Gets or sets the password. - /// </summary> - /// <value>The password.</value> - [ApiMember(Name = "Password", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string Password { get; set; } - } - - /// <summary> - /// Class AuthenticateUser - /// </summary> - [Route("/Users/AuthenticateByName", "POST", Summary = "Authenticates a user")] - public class AuthenticateUserByName : IReturn<AuthenticationResult> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Username", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string Username { get; set; } - - /// <summary> - /// Gets or sets the password. - /// </summary> - /// <value>The password.</value> - [ApiMember(Name = "Password", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string Password { get; set; } - - [ApiMember(Name = "Pw", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string Pw { get; set; } - } - - /// <summary> - /// Class UpdateUserPassword - /// </summary> - [Route("/Users/{Id}/Password", "POST", Summary = "Updates a user's password")] - [Authenticated] - public class UpdateUserPassword : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - public Guid Id { get; set; } - - /// <summary> - /// Gets or sets the password. - /// </summary> - /// <value>The password.</value> - public string CurrentPassword { get; set; } - - public string CurrentPw { get; set; } - - public string NewPw { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [reset password]. - /// </summary> - /// <value><c>true</c> if [reset password]; otherwise, <c>false</c>.</value> - public bool ResetPassword { get; set; } - } - - /// <summary> - /// Class UpdateUserEasyPassword - /// </summary> - [Route("/Users/{Id}/EasyPassword", "POST", Summary = "Updates a user's easy password")] - [Authenticated] - public class UpdateUserEasyPassword : IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - public Guid Id { get; set; } - - /// <summary> - /// Gets or sets the new password. - /// </summary> - /// <value>The new password.</value> - public string NewPassword { get; set; } - - public string NewPw { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [reset password]. - /// </summary> - /// <value><c>true</c> if [reset password]; otherwise, <c>false</c>.</value> - public bool ResetPassword { get; set; } - } - - /// <summary> - /// Class UpdateUser - /// </summary> - [Route("/Users/{Id}", "POST", Summary = "Updates a user")] - [Authenticated] - public class UpdateUser : UserDto, IReturnVoid - { - } - - /// <summary> - /// Class UpdateUser - /// </summary> - [Route("/Users/{Id}/Policy", "POST", Summary = "Updates a user policy")] - [Authenticated(Roles = "admin")] - public class UpdateUserPolicy : UserPolicy, IReturnVoid - { - [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class UpdateUser - /// </summary> - [Route("/Users/{Id}/Configuration", "POST", Summary = "Updates a user configuration")] - [Authenticated] - public class UpdateUserConfiguration : UserConfiguration, IReturnVoid - { - [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid Id { get; set; } - } - - /// <summary> - /// Class CreateUser - /// </summary> - [Route("/Users/New", "POST", Summary = "Creates a user")] - [Authenticated(Roles = "Admin")] - public class CreateUserByName : IReturn<UserDto> - { - [ApiMember(Name = "Name", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] - public string Name { get; set; } - - [ApiMember(Name = "Password", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")] - public string Password { get; set; } - } - - [Route("/Users/ForgotPassword", "POST", Summary = "Initiates the forgot password process for a local user")] - public class ForgotPassword : IReturn<ForgotPasswordResult> - { - [ApiMember(Name = "EnteredUsername", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")] - public string EnteredUsername { get; set; } - } - - [Route("/Users/ForgotPassword/Pin", "POST", Summary = "Redeems a forgot password pin")] - public class ForgotPasswordPin : IReturn<PinRedeemResult> - { - [ApiMember(Name = "Pin", IsRequired = false, DataType = "string", ParameterType = "body", Verb = "POST")] - public string Pin { get; set; } - } - - /// <summary> - /// Class UsersService - /// </summary> - public class UserService : BaseApiService - { - /// <summary> - /// The user manager. - /// </summary> - private readonly IUserManager _userManager; - private readonly ISessionManager _sessionMananger; - private readonly INetworkManager _networkManager; - private readonly IDeviceManager _deviceManager; - private readonly IAuthorizationContext _authContext; - - public UserService( - ILogger<UserService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IUserManager userManager, - ISessionManager sessionMananger, - INetworkManager networkManager, - IDeviceManager deviceManager, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _userManager = userManager; - _sessionMananger = sessionMananger; - _networkManager = networkManager; - _deviceManager = deviceManager; - _authContext = authContext; - } - - public object Get(GetPublicUsers request) - { - // If the startup wizard hasn't been completed then just return all users - if (!ServerConfigurationManager.Configuration.IsStartupWizardCompleted) - { - return Get(new GetUsers - { - IsDisabled = false - }); - } - - return Get(new GetUsers - { - IsHidden = false, - IsDisabled = false - }, true, true); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetUsers request) - { - return Get(request, false, false); - } - - private object Get(GetUsers request, bool filterByDevice, bool filterByNetwork) - { - var users = _userManager.Users; - - if (request.IsDisabled.HasValue) - { - users = users.Where(i => i.Policy.IsDisabled == request.IsDisabled.Value); - } - - if (request.IsHidden.HasValue) - { - users = users.Where(i => i.Policy.IsHidden == request.IsHidden.Value); - } - - if (filterByDevice) - { - var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; - - if (!string.IsNullOrWhiteSpace(deviceId)) - { - users = users.Where(i => _deviceManager.CanAccessDevice(i, deviceId)); - } - } - - if (filterByNetwork) - { - if (!_networkManager.IsInLocalNetwork(Request.RemoteIp)) - { - users = users.Where(i => i.Policy.EnableRemoteAccess); - } - } - - var result = users - .OrderBy(u => u.Name) - .Select(i => _userManager.GetUserDto(i, Request.RemoteIp)) - .ToArray(); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetUser request) - { - var user = _userManager.GetUserById(request.Id); - - if (user == null) - { - throw new ResourceNotFoundException("User not found"); - } - - var result = _userManager.GetUserDto(user, Request.RemoteIp); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Delete(DeleteUser request) - { - return DeleteAsync(request); - } - - public Task DeleteAsync(DeleteUser request) - { - var user = _userManager.GetUserById(request.Id); - - if (user == null) - { - throw new ResourceNotFoundException("User not found"); - } - - _sessionMananger.RevokeUserTokens(user.Id, null); - _userManager.DeleteUser(user); - return Task.CompletedTask; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public object Post(AuthenticateUser request) - { - var user = _userManager.GetUserById(request.Id); - - if (user == null) - { - throw new ResourceNotFoundException("User not found"); - } - - if (!string.IsNullOrEmpty(request.Password) && string.IsNullOrEmpty(request.Pw)) - { - throw new MethodNotAllowedException("Hashed-only passwords are not valid for this API."); - } - - // Password should always be null - return Post(new AuthenticateUserByName - { - Username = user.Name, - Password = null, - Pw = request.Pw - }); - } - - public async Task<object> Post(AuthenticateUserByName request) - { - var auth = _authContext.GetAuthorizationInfo(Request); - - try - { - var result = await _sessionMananger.AuthenticateNewSession(new AuthenticationRequest - { - App = auth.Client, - AppVersion = auth.Version, - DeviceId = auth.DeviceId, - DeviceName = auth.Device, - Password = request.Pw, - PasswordSha1 = request.Password, - RemoteEndPoint = Request.RemoteIp, - Username = request.Username - }).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - catch (SecurityException e) - { - // rethrow adding IP address to message - throw new SecurityException($"[{Request.RemoteIp}] {e.Message}", e); - } - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public Task Post(UpdateUserPassword request) - { - return PostAsync(request); - } - - public async Task PostAsync(UpdateUserPassword request) - { - AssertCanUpdateUser(_authContext, _userManager, request.Id, true); - - var user = _userManager.GetUserById(request.Id); - - if (user == null) - { - throw new ResourceNotFoundException("User not found"); - } - - if (request.ResetPassword) - { - await _userManager.ResetPassword(user).ConfigureAwait(false); - } - else - { - var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, Request.RemoteIp, false).ConfigureAwait(false); - - if (success == null) - { - throw new ArgumentException("Invalid user or password entered."); - } - - await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false); - - var currentToken = _authContext.GetAuthorizationInfo(Request).Token; - - _sessionMananger.RevokeUserTokens(user.Id, currentToken); - } - } - - public void Post(UpdateUserEasyPassword request) - { - AssertCanUpdateUser(_authContext, _userManager, request.Id, true); - - var user = _userManager.GetUserById(request.Id); - - if (user == null) - { - throw new ResourceNotFoundException("User not found"); - } - - if (request.ResetPassword) - { - _userManager.ResetEasyPassword(user); - } - else - { - _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword); - } - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public async Task Post(UpdateUser request) - { - var id = Guid.Parse(GetPathValue(1)); - - AssertCanUpdateUser(_authContext, _userManager, id, false); - - var dtoUser = request; - - var user = _userManager.GetUserById(id); - - if (string.Equals(user.Name, dtoUser.Name, StringComparison.Ordinal)) - { - _userManager.UpdateUser(user); - _userManager.UpdateConfiguration(user, dtoUser.Configuration); - } - else - { - await _userManager.RenameUser(user, dtoUser.Name).ConfigureAwait(false); - - _userManager.UpdateConfiguration(dtoUser.Id, dtoUser.Configuration); - } - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public async Task<object> Post(CreateUserByName request) - { - var newUser = _userManager.CreateUser(request.Name); - - // no need to authenticate password for new user - if (request.Password != null) - { - await _userManager.ChangePassword(newUser, request.Password).ConfigureAwait(false); - } - - var result = _userManager.GetUserDto(newUser, Request.RemoteIp); - - return ToOptimizedResult(result); - } - - public async Task<object> Post(ForgotPassword request) - { - var isLocal = Request.IsLocal || _networkManager.IsInLocalNetwork(Request.RemoteIp); - - var result = await _userManager.StartForgotPasswordProcess(request.EnteredUsername, isLocal).ConfigureAwait(false); - - return result; - } - - public async Task<object> Post(ForgotPasswordPin request) - { - var result = await _userManager.RedeemPasswordResetPin(request.Pin).ConfigureAwait(false); - - return result; - } - - public void Post(UpdateUserConfiguration request) - { - AssertCanUpdateUser(_authContext, _userManager, request.Id, false); - - _userManager.UpdateConfiguration(request.Id, request); - - } - - public void Post(UpdateUserPolicy request) - { - var user = _userManager.GetUserById(request.Id); - - // If removing admin access - if (!request.IsAdministrator && user.Policy.IsAdministrator) - { - if (_userManager.Users.Count(i => i.Policy.IsAdministrator) == 1) - { - throw new ArgumentException("There must be at least one user in the system with administrative access."); - } - } - - // If disabling - if (request.IsDisabled && user.Policy.IsAdministrator) - { - throw new ArgumentException("Administrators cannot be disabled."); - } - - // If disabling - if (request.IsDisabled && !user.Policy.IsDisabled) - { - if (_userManager.Users.Count(i => !i.Policy.IsDisabled) == 1) - { - throw new ArgumentException("There must be at least one enabled user in the system."); - } - - var currentToken = _authContext.GetAuthorizationInfo(Request).Token; - _sessionMananger.RevokeUserTokens(user.Id, currentToken); - } - - _userManager.UpdateUserPolicy(request.Id, request); - } - } -} diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs deleted file mode 100644 index b11fd48d3..000000000 --- a/MediaBrowser.Api/VideosService.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Threading; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Videos/{Id}/AdditionalParts", "GET", Summary = "Gets additional parts for a video.")] - [Authenticated] - public class GetAdditionalParts : IReturn<QueryResult<BaseItemDto>> - { - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Videos/{Id}/AlternateSources", "DELETE", Summary = "Removes alternate video sources.")] - [Authenticated(Roles = "Admin")] - public class DeleteAlternateSources : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Videos/MergeVersions", "POST", Summary = "Merges videos into a single record")] - [Authenticated(Roles = "Admin")] - public class MergeVersions : IReturnVoid - { - [ApiMember(Name = "Ids", Description = "Item id list. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string Ids { get; set; } - } - - public class VideosService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; - private readonly IDtoService _dtoService; - private readonly IAuthorizationContext _authContext; - - public VideosService( - ILogger<VideosService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILibraryManager libraryManager, - IUserManager userManager, - IDtoService dtoService, - IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _libraryManager = libraryManager; - _userManager = userManager; - _dtoService = dtoService; - _authContext = authContext; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetAdditionalParts request) - { - var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null; - - var item = string.IsNullOrEmpty(request.Id) - ? (!request.UserId.Equals(Guid.Empty) - ? _libraryManager.GetUserRootFolder() - : _libraryManager.RootFolder) - : _libraryManager.GetItemById(request.Id); - - var dtoOptions = GetDtoOptions(_authContext, request); - - BaseItemDto[] items; - if (item is Video video) - { - items = video.GetAdditionalParts() - .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video)) - .ToArray(); - } - else - { - items = Array.Empty<BaseItemDto>(); - } - - var result = new QueryResult<BaseItemDto> - { - Items = items, - TotalRecordCount = items.Length - }; - - return ToOptimizedResult(result); - } - - public void Delete(DeleteAlternateSources request) - { - var video = (Video)_libraryManager.GetItemById(request.Id); - - foreach (var link in video.GetLinkedAlternateVersions()) - { - link.SetPrimaryVersionId(null); - link.LinkedAlternateVersions = Array.Empty<LinkedChild>(); - - link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - } - - video.LinkedAlternateVersions = Array.Empty<LinkedChild>(); - video.SetPrimaryVersionId(null); - video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - } - - public void Post(MergeVersions request) - { - var items = request.Ids.Split(',') - .Select(i => _libraryManager.GetItemById(i)) - .OfType<Video>() - .ToList(); - - if (items.Count < 2) - { - throw new ArgumentException("Please supply at least two videos to merge."); - } - - var videosWithVersions = items.Where(i => i.MediaSourceCount > 1) - .ToList(); - - var primaryVersion = videosWithVersions.FirstOrDefault(); - if (primaryVersion == null) - { - primaryVersion = items.OrderBy(i => - { - if (i.Video3DFormat.HasValue || i.VideoType != Model.Entities.VideoType.VideoFile) - { - return 1; - } - - return 0; - }) - .ThenByDescending(i => - { - return i.GetDefaultVideoStream()?.Width ?? 0; - }).First(); - } - - var list = primaryVersion.LinkedAlternateVersions.ToList(); - - foreach (var item in items.Where(i => i.Id != primaryVersion.Id)) - { - item.SetPrimaryVersionId(primaryVersion.Id.ToString("N", CultureInfo.InvariantCulture)); - - item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - - list.Add(new LinkedChild - { - Path = item.Path, - ItemId = item.Id - }); - - foreach (var linkedItem in item.LinkedAlternateVersions) - { - if (!list.Any(i => string.Equals(i.Path, linkedItem.Path, StringComparison.OrdinalIgnoreCase))) - { - list.Add(linkedItem); - } - } - - if (item.LinkedAlternateVersions.Length > 0) - { - item.LinkedAlternateVersions = Array.Empty<LinkedChild>(); - item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - } - } - - primaryVersion.LinkedAlternateVersions = list.ToArray(); - primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - } - } -} |
