mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-05 21:33:02 +02:00
restored live tv playback in the web client
This commit is contained in:
parent
a90d892eca
commit
f756e39b9d
|
@ -103,6 +103,7 @@
|
||||||
<Compile Include="Playback\Hls\DynamicHlsService.cs" />
|
<Compile Include="Playback\Hls\DynamicHlsService.cs" />
|
||||||
<Compile Include="Playback\Hls\HlsSegmentService.cs" />
|
<Compile Include="Playback\Hls\HlsSegmentService.cs" />
|
||||||
<Compile Include="Playback\Hls\VideoHlsService.cs" />
|
<Compile Include="Playback\Hls\VideoHlsService.cs" />
|
||||||
|
<Compile Include="Playback\ProgressiveStreamService.cs" />
|
||||||
<Compile Include="Playback\Progressive\AudioService.cs" />
|
<Compile Include="Playback\Progressive\AudioService.cs" />
|
||||||
<Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
|
<Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
|
||||||
<Compile Include="Playback\BaseStreamingService.cs" />
|
<Compile Include="Playback\BaseStreamingService.cs" />
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
@ -36,103 +36,74 @@ namespace MediaBrowser.Api.Music
|
||||||
public class InstantMixService : BaseApiService
|
public class InstantMixService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
|
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
|
private readonly IMusicManager _musicManager;
|
||||||
|
|
||||||
public InstantMixService(IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService)
|
public InstantMixService(IUserManager userManager, IDtoService dtoService, IMusicManager musicManager)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
|
_musicManager = musicManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetInstantMixFromSong request)
|
public object Get(GetInstantMixFromSong request)
|
||||||
{
|
{
|
||||||
var item = _dtoService.GetItemByDtoId(request.Id);
|
var item = (Audio)_dtoService.GetItemByDtoId(request.Id);
|
||||||
|
|
||||||
var result = GetInstantMixResult(request, item.Genres);
|
var user = _userManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
var items = _musicManager.GetInstantMixFromSong(item, user);
|
||||||
|
|
||||||
|
return GetResult(items, user, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetInstantMixFromAlbum request)
|
public object Get(GetInstantMixFromAlbum request)
|
||||||
{
|
{
|
||||||
var album = (MusicAlbum)_dtoService.GetItemByDtoId(request.Id);
|
var album = (MusicAlbum)_dtoService.GetItemByDtoId(request.Id);
|
||||||
|
|
||||||
var genres = album
|
var user = _userManager.GetUserById(request.UserId.Value);
|
||||||
.RecursiveChildren
|
|
||||||
.OfType<Audio>()
|
|
||||||
.SelectMany(i => i.Genres)
|
|
||||||
.Concat(album.Genres)
|
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var result = GetInstantMixResult(request, genres);
|
var items = _musicManager.GetInstantMixFromAlbum(album, user);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return GetResult(items, user, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetInstantMixFromMusicGenre request)
|
public object Get(GetInstantMixFromMusicGenre request)
|
||||||
{
|
{
|
||||||
var genre = GetMusicGenre(request.Name, _libraryManager);
|
var user = _userManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
var result = GetInstantMixResult(request, new[] { genre.Name });
|
var items = _musicManager.GetInstantMixFromGenres(new[] { request.Name }, user);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return GetResult(items, user, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetInstantMixFromArtist request)
|
public object Get(GetInstantMixFromArtist request)
|
||||||
{
|
{
|
||||||
var artist = GetArtist(request.Name, _libraryManager);
|
var user = _userManager.GetUserById(request.UserId.Value);
|
||||||
|
|
||||||
var genres = _libraryManager.RootFolder
|
var items = _musicManager.GetInstantMixFromArtist(request.Name, user);
|
||||||
.RecursiveChildren
|
|
||||||
.OfType<Audio>()
|
|
||||||
.Where(i => i.HasArtist(artist.Name))
|
|
||||||
.SelectMany(i => i.Genres)
|
|
||||||
.Concat(artist.Genres)
|
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var result = GetInstantMixResult(request, genres);
|
return GetResult(items, user, request);
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ItemsResult GetInstantMixResult(BaseGetSimilarItems request, IEnumerable<string> genres)
|
private object GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request)
|
||||||
{
|
{
|
||||||
var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null;
|
|
||||||
|
|
||||||
var fields = request.GetItemFields().ToList();
|
var fields = request.GetItemFields().ToList();
|
||||||
|
|
||||||
var inputItems = user == null
|
var list = items.ToList();
|
||||||
? _libraryManager.RootFolder.RecursiveChildren
|
|
||||||
: user.RootFolder.GetRecursiveChildren(user);
|
|
||||||
|
|
||||||
var genresDictionary = genres.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var limit = request.Limit.HasValue ? request.Limit.Value * 2 : 100;
|
|
||||||
|
|
||||||
var items = inputItems
|
|
||||||
.OfType<Audio>()
|
|
||||||
.Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey)))
|
|
||||||
.OrderByDescending(i => i.Item2)
|
|
||||||
.ThenBy(i => Guid.NewGuid())
|
|
||||||
.Select(i => i.Item1)
|
|
||||||
.Take(limit)
|
|
||||||
.OrderBy(i => Guid.NewGuid())
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var result = new ItemsResult
|
var result = new ItemsResult
|
||||||
{
|
{
|
||||||
TotalRecordCount = items.Count
|
TotalRecordCount = list.Count
|
||||||
};
|
};
|
||||||
|
|
||||||
var dtos = items.Take(request.Limit ?? items.Count)
|
var dtos = list.Take(request.Limit ?? list.Count)
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, fields, user));
|
.Select(i => _dtoService.GetBaseItemDto(i, fields, user));
|
||||||
|
|
||||||
result.Items = dtos.ToArray();
|
result.Items = dtos.ToArray();
|
||||||
|
|
||||||
return result;
|
return ToOptimizedResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -835,11 +835,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
protected string GetInputArgument(StreamState state)
|
protected string GetInputArgument(StreamState state)
|
||||||
{
|
{
|
||||||
if (state.SendInputOverStandardInput)
|
|
||||||
{
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = InputType.File;
|
var type = InputType.File;
|
||||||
|
|
||||||
var inputPath = new[] { state.MediaPath };
|
var inputPath = new[] { state.MediaPath };
|
||||||
|
@ -898,9 +893,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
Arguments = commandLineArgs,
|
Arguments = commandLineArgs,
|
||||||
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false
|
||||||
|
|
||||||
RedirectStandardInput = state.SendInputOverStandardInput
|
|
||||||
},
|
},
|
||||||
|
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
|
@ -933,11 +926,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.SendInputOverStandardInput)
|
|
||||||
{
|
|
||||||
StreamToStandardInput(process, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
||||||
process.BeginOutputReadLine();
|
process.BeginOutputReadLine();
|
||||||
|
|
||||||
|
@ -965,32 +953,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void StreamToStandardInput(Process process, StreamState state)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await StreamToStandardInputInternal(process, state).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
Logger.Debug("Stream to standard input closed normally.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.ErrorException("Error writing to standard input", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StreamToStandardInputInternal(Process process, StreamState state)
|
|
||||||
{
|
|
||||||
state.StandardInputCancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
using (var fileStream = FileSystem.GetFileStream(state.MediaPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
|
||||||
{
|
|
||||||
await new EndlessStreamCopy().CopyStream(fileStream, process.StandardInput.BaseStream, state.StandardInputCancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected int? GetVideoBitrateParamValue(StreamState state)
|
protected int? GetVideoBitrateParamValue(StreamState state)
|
||||||
{
|
{
|
||||||
var bitrate = state.VideoRequest.VideoBitRate;
|
var bitrate = state.VideoRequest.VideoBitRate;
|
||||||
|
@ -1315,11 +1277,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
ParseParams(request);
|
ParseParams(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.ThrowDebugError)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("You asked for a debug error, you got one.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager);
|
var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager);
|
||||||
|
|
||||||
var url = Request.PathInfo;
|
var url = Request.PathInfo;
|
||||||
|
@ -1369,8 +1326,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
state.MediaPath = path;
|
state.MediaPath = path;
|
||||||
state.IsRemote = false;
|
state.IsRemote = false;
|
||||||
|
|
||||||
state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress;
|
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(mediaUrl))
|
else if (!string.IsNullOrEmpty(mediaUrl))
|
||||||
{
|
{
|
||||||
|
@ -1378,7 +1333,8 @@ namespace MediaBrowser.Api.Playback
|
||||||
state.IsRemote = true;
|
state.IsRemote = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//state.RunTimeTicks = recording.RunTimeTicks;
|
state.RunTimeTicks = recording.RunTimeTicks;
|
||||||
|
|
||||||
if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsRemote)
|
if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsRemote)
|
||||||
{
|
{
|
||||||
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
73
MediaBrowser.Api/Playback/ProgressiveStreamService.cs
Normal file
73
MediaBrowser.Api/Playback/ProgressiveStreamService.cs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Api.Playback.Progressive;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.Playback
|
||||||
|
{
|
||||||
|
//public class GetProgressiveAudioStream : StreamRequest
|
||||||
|
//{
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public class ProgressiveStreamService : BaseApiService
|
||||||
|
//{
|
||||||
|
// public object Get(GetProgressiveAudioStream request)
|
||||||
|
// {
|
||||||
|
// return ProcessRequest(request, false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// Gets the specified request.
|
||||||
|
// /// </summary>
|
||||||
|
// /// <param name="request">The request.</param>
|
||||||
|
// /// <returns>System.Object.</returns>
|
||||||
|
// public object Head(GetProgressiveAudioStream request)
|
||||||
|
// {
|
||||||
|
// return ProcessRequest(request, true);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
|
||||||
|
// {
|
||||||
|
// var state = GetState(request, CancellationToken.None).Result;
|
||||||
|
|
||||||
|
// var responseHeaders = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
// if (request.Static && state.IsRemote)
|
||||||
|
// {
|
||||||
|
// AddDlnaHeaders(state, responseHeaders, true);
|
||||||
|
|
||||||
|
// return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var outputPath = GetOutputFilePath(state);
|
||||||
|
// var outputPathExists = File.Exists(outputPath);
|
||||||
|
|
||||||
|
// var isStatic = request.Static ||
|
||||||
|
// (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive));
|
||||||
|
|
||||||
|
// AddDlnaHeaders(state, responseHeaders, isStatic);
|
||||||
|
|
||||||
|
// if (request.Static)
|
||||||
|
// {
|
||||||
|
// var contentType = state.GetMimeType(state.MediaPath);
|
||||||
|
|
||||||
|
// return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
|
||||||
|
// {
|
||||||
|
// var contentType = state.GetMimeType(outputPath);
|
||||||
|
|
||||||
|
// return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
|
||||||
|
// }
|
||||||
|
|
||||||
|
//}
|
||||||
|
}
|
|
@ -67,11 +67,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
[ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[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 DeviceProfileId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For testing purposes
|
|
||||||
/// </summary>
|
|
||||||
public bool ThrowDebugError { get; set; }
|
|
||||||
|
|
||||||
public string Params { get; set; }
|
public string Params { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,8 +51,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
public bool HasMediaStreams { get; set; }
|
public bool HasMediaStreams { get; set; }
|
||||||
|
|
||||||
public bool SendInputOverStandardInput { get; set; }
|
|
||||||
|
|
||||||
public CancellationTokenSource StandardInputCancellationTokenSource { get; set; }
|
public CancellationTokenSource StandardInputCancellationTokenSource { get; set; }
|
||||||
|
|
||||||
public string LiveTvStreamId { get; set; }
|
public string LiveTvStreamId { get; set; }
|
||||||
|
|
|
@ -85,6 +85,13 @@ namespace MediaBrowser.Controller.Dto
|
||||||
/// <returns>ChapterInfoDto.</returns>
|
/// <returns>ChapterInfoDto.</returns>
|
||||||
ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item);
|
ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the media sources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <returns>List{MediaSourceInfo}.</returns>
|
||||||
|
List<MediaSourceInfo> GetMediaSources(BaseItem item);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the item by name dto.
|
/// Gets the item by name dto.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
38
MediaBrowser.Controller/Library/IMusicManager.cs
Normal file
38
MediaBrowser.Controller/Library/IMusicManager.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.Library
|
||||||
|
{
|
||||||
|
public interface IMusicManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instant mix from song.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="user">The user.</param>
|
||||||
|
/// <returns>IEnumerable{Audio}.</returns>
|
||||||
|
IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user);
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instant mix from artist.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name.</param>
|
||||||
|
/// <param name="user">The user.</param>
|
||||||
|
/// <returns>IEnumerable{Audio}.</returns>
|
||||||
|
IEnumerable<Audio> GetInstantMixFromArtist(string name, User user);
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instant mix from album.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="user">The user.</param>
|
||||||
|
/// <returns>IEnumerable{Audio}.</returns>
|
||||||
|
IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user);
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the instant mix from genre.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="genres">The genres.</param>
|
||||||
|
/// <param name="user">The user.</param>
|
||||||
|
/// <returns>IEnumerable{Audio}.</returns>
|
||||||
|
IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.Library;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.Library;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.LiveTv
|
namespace MediaBrowser.Controller.LiveTv
|
||||||
{
|
{
|
||||||
|
@ -14,6 +14,8 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
|
|
||||||
RecordingInfo RecordingInfo { get; set; }
|
RecordingInfo RecordingInfo { get; set; }
|
||||||
|
|
||||||
|
long? RunTimeTicks { get; set; }
|
||||||
|
|
||||||
string GetClientTypeName();
|
string GetClientTypeName();
|
||||||
|
|
||||||
string GetUserDataKey();
|
string GetUserDataKey();
|
||||||
|
|
|
@ -134,6 +134,7 @@
|
||||||
<Compile Include="Library\DeleteOptions.cs" />
|
<Compile Include="Library\DeleteOptions.cs" />
|
||||||
<Compile Include="Library\ILibraryPostScanTask.cs" />
|
<Compile Include="Library\ILibraryPostScanTask.cs" />
|
||||||
<Compile Include="Library\IMetadataSaver.cs" />
|
<Compile Include="Library\IMetadataSaver.cs" />
|
||||||
|
<Compile Include="Library\IMusicManager.cs" />
|
||||||
<Compile Include="Library\ItemUpdateType.cs" />
|
<Compile Include="Library\ItemUpdateType.cs" />
|
||||||
<Compile Include="Library\IUserDataManager.cs" />
|
<Compile Include="Library\IUserDataManager.cs" />
|
||||||
<Compile Include="Library\UserDataSaveEventArgs.cs" />
|
<Compile Include="Library\UserDataSaveEventArgs.cs" />
|
||||||
|
@ -155,11 +156,14 @@
|
||||||
<Compile Include="LiveTv\SeriesTimerInfo.cs" />
|
<Compile Include="LiveTv\SeriesTimerInfo.cs" />
|
||||||
<Compile Include="LiveTv\TimerInfo.cs" />
|
<Compile Include="LiveTv\TimerInfo.cs" />
|
||||||
<Compile Include="Localization\ILocalizationManager.cs" />
|
<Compile Include="Localization\ILocalizationManager.cs" />
|
||||||
|
<Compile Include="MediaEncoding\EncodingOptions.cs" />
|
||||||
<Compile Include="MediaEncoding\ChapterImageRefreshOptions.cs" />
|
<Compile Include="MediaEncoding\ChapterImageRefreshOptions.cs" />
|
||||||
|
<Compile Include="MediaEncoding\EncodingResult.cs" />
|
||||||
<Compile Include="MediaEncoding\IEncodingManager.cs" />
|
<Compile Include="MediaEncoding\IEncodingManager.cs" />
|
||||||
<Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
|
<Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
|
||||||
<Compile Include="MediaEncoding\IMediaEncoder.cs" />
|
<Compile Include="MediaEncoding\IMediaEncoder.cs" />
|
||||||
<Compile Include="MediaEncoding\InternalMediaInfoResult.cs" />
|
<Compile Include="MediaEncoding\InternalMediaInfoResult.cs" />
|
||||||
|
<Compile Include="MediaEncoding\VideoEncodingOptions.cs" />
|
||||||
<Compile Include="Net\IHasResultFactory.cs" />
|
<Compile Include="Net\IHasResultFactory.cs" />
|
||||||
<Compile Include="Net\IHttpResultFactory.cs" />
|
<Compile Include="Net\IHttpResultFactory.cs" />
|
||||||
<Compile Include="Net\IHttpServer.cs" />
|
<Compile Include="Net\IHttpServer.cs" />
|
||||||
|
|
79
MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs
Normal file
79
MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
{
|
||||||
|
public class EncodingOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the item identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The item identifier.</value>
|
||||||
|
public string ItemId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the media source identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The media source identifier.</value>
|
||||||
|
public string MediaSourceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the device profile.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The device profile.</value>
|
||||||
|
public DeviceProfile DeviceProfile { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the output path.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The output path.</value>
|
||||||
|
public string OutputPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The container.</value>
|
||||||
|
public string Container { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the audio codec.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The audio codec.</value>
|
||||||
|
public string AudioCodec { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the start time ticks.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The start time ticks.</value>
|
||||||
|
public long? StartTimeTicks { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum channels.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The maximum channels.</value>
|
||||||
|
public int? MaxAudioChannels { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the channels.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The channels.</value>
|
||||||
|
public int? AudioChannels { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the sample rate.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The sample rate.</value>
|
||||||
|
public int? AudioSampleRate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the bit rate.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The bit rate.</value>
|
||||||
|
public int? AudioBitRate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum audio bit rate.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The maximum audio bit rate.</value>
|
||||||
|
public int? MaxAudioBitRate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
13
MediaBrowser.Controller/MediaEncoding/EncodingResult.cs
Normal file
13
MediaBrowser.Controller/MediaEncoding/EncodingResult.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
{
|
||||||
|
public class EncodingResult
|
||||||
|
{
|
||||||
|
public string OutputPath { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
{
|
||||||
|
public class VideoEncodingOptions : EncodingOptions
|
||||||
|
{
|
||||||
|
public string VideoCodec { get; set; }
|
||||||
|
|
||||||
|
public string VideoProfile { get; set; }
|
||||||
|
|
||||||
|
public double? VideoLevel { get; set; }
|
||||||
|
|
||||||
|
public int? VideoStreamIndex { get; set; }
|
||||||
|
|
||||||
|
public int? AudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
|
|
||||||
|
public int? MaxWidth { get; set; }
|
||||||
|
|
||||||
|
public int? MaxHeight { get; set; }
|
||||||
|
|
||||||
|
public int? Height { get; set; }
|
||||||
|
|
||||||
|
public int? Width { get; set; }
|
||||||
|
}
|
||||||
|
}
|
91
MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
Normal file
91
MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
{
|
||||||
|
public class AudioEncoder
|
||||||
|
{
|
||||||
|
private readonly string _ffmpegPath;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
private readonly IIsoManager _isoManager;
|
||||||
|
private readonly ILiveTvManager _liveTvManager;
|
||||||
|
|
||||||
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
|
public AudioEncoder(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
|
||||||
|
{
|
||||||
|
_ffmpegPath = ffmpegPath;
|
||||||
|
_logger = logger;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_appPaths = appPaths;
|
||||||
|
_isoManager = isoManager;
|
||||||
|
_liveTvManager = liveTvManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task BeginEncoding(InternalEncodingTask task)
|
||||||
|
{
|
||||||
|
return new FFMpegProcess(_ffmpegPath, _logger, _fileSystem, _appPaths, _isoManager, _liveTvManager).Start(task, GetArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetArguments(InternalEncodingTask task, string mountedPath)
|
||||||
|
{
|
||||||
|
var options = task.Request;
|
||||||
|
|
||||||
|
return string.Format("{0} -i {1} {2} -id3v2_version 3 -write_id3v1 1 \"{3}\"",
|
||||||
|
GetInputModifier(task),
|
||||||
|
GetInputArgument(task),
|
||||||
|
GetOutputModifier(task),
|
||||||
|
options.OutputPath).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetInputModifier(InternalEncodingTask task)
|
||||||
|
{
|
||||||
|
return EncodingUtils.GetInputModifier(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetInputArgument(InternalEncodingTask task)
|
||||||
|
{
|
||||||
|
return EncodingUtils.GetInputArgument(new List<string> { task.MediaPath }, task.IsInputRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetOutputModifier(InternalEncodingTask task)
|
||||||
|
{
|
||||||
|
var options = task.Request;
|
||||||
|
|
||||||
|
var audioTranscodeParams = new List<string>
|
||||||
|
{
|
||||||
|
"-threads " + EncodingUtils.GetNumberOfThreads(task, false).ToString(_usCulture),
|
||||||
|
"-vn"
|
||||||
|
};
|
||||||
|
|
||||||
|
var bitrate = EncodingUtils.GetAudioBitrateParam(task);
|
||||||
|
|
||||||
|
if (bitrate.HasValue)
|
||||||
|
{
|
||||||
|
audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture));
|
||||||
|
}
|
||||||
|
|
||||||
|
var channels = EncodingUtils.GetNumAudioChannelsParam(options, task.AudioStream);
|
||||||
|
|
||||||
|
if (channels.HasValue)
|
||||||
|
{
|
||||||
|
audioTranscodeParams.Add("-ac " + channels.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.AudioSampleRate.HasValue)
|
||||||
|
{
|
||||||
|
audioTranscodeParams.Add("-ar " + options.AudioSampleRate.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(" ", audioTranscodeParams.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
233
MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
Normal file
233
MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
{
|
||||||
|
public static class EncodingUtils
|
||||||
|
{
|
||||||
|
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
|
public static string GetInputArgument(List<string> inputFiles, bool isRemote)
|
||||||
|
{
|
||||||
|
if (isRemote)
|
||||||
|
{
|
||||||
|
return GetHttpInputArgument(inputFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetConcatInputArgument(inputFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the concat input argument.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputFiles">The input files.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
private static string GetConcatInputArgument(List<string> inputFiles)
|
||||||
|
{
|
||||||
|
// Get all streams
|
||||||
|
// If there's more than one we'll need to use the concat command
|
||||||
|
if (inputFiles.Count > 1)
|
||||||
|
{
|
||||||
|
var files = string.Join("|", inputFiles);
|
||||||
|
|
||||||
|
return string.Format("concat:\"{0}\"", files);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the input path for video files
|
||||||
|
return GetFileInputArgument(inputFiles[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file input argument.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
private static string GetFileInputArgument(string path)
|
||||||
|
{
|
||||||
|
return string.Format("file:\"{0}\"", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the HTTP input argument.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inputFiles">The input files.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
private static string GetHttpInputArgument(IEnumerable<string> inputFiles)
|
||||||
|
{
|
||||||
|
var url = inputFiles.First();
|
||||||
|
|
||||||
|
return string.Format("\"{0}\"", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetAudioInputModifier(InternalEncodingTask options)
|
||||||
|
{
|
||||||
|
return GetCommonInputModifier(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetInputModifier(InternalEncodingTask options)
|
||||||
|
{
|
||||||
|
var inputModifier = GetCommonInputModifier(options);
|
||||||
|
|
||||||
|
//if (state.VideoRequest != null)
|
||||||
|
//{
|
||||||
|
// inputModifier += " -fflags genpts";
|
||||||
|
//}
|
||||||
|
|
||||||
|
//if (!string.IsNullOrEmpty(state.InputVideoCodec))
|
||||||
|
//{
|
||||||
|
// inputModifier += " -vcodec " + state.InputVideoCodec;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//if (!string.IsNullOrEmpty(state.InputVideoSync))
|
||||||
|
//{
|
||||||
|
// inputModifier += " -vsync " + state.InputVideoSync;
|
||||||
|
//}
|
||||||
|
|
||||||
|
return inputModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCommonInputModifier(InternalEncodingTask options)
|
||||||
|
{
|
||||||
|
var inputModifier = string.Empty;
|
||||||
|
|
||||||
|
if (options.EnableDebugLogging)
|
||||||
|
{
|
||||||
|
inputModifier += "-loglevel debug";
|
||||||
|
}
|
||||||
|
|
||||||
|
var probeSize = GetProbeSizeArgument(options.InputVideoType.HasValue && options.InputVideoType.Value == VideoType.Dvd);
|
||||||
|
inputModifier += " " + probeSize;
|
||||||
|
inputModifier = inputModifier.Trim();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(options.UserAgent))
|
||||||
|
{
|
||||||
|
inputModifier += " -user-agent \"" + options.UserAgent + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
inputModifier += " " + GetFastSeekValue(options.Request);
|
||||||
|
inputModifier = inputModifier.Trim();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(options.InputFormat))
|
||||||
|
{
|
||||||
|
inputModifier += " -f " + options.InputFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(options.InputAudioCodec))
|
||||||
|
{
|
||||||
|
inputModifier += " -acodec " + options.InputAudioCodec;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(options.InputAudioSync))
|
||||||
|
{
|
||||||
|
inputModifier += " -async " + options.InputAudioSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.ReadInputAtNativeFramerate)
|
||||||
|
{
|
||||||
|
inputModifier += " -re";
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFastSeekValue(EncodingOptions options)
|
||||||
|
{
|
||||||
|
var time = options.StartTimeTicks;
|
||||||
|
|
||||||
|
if (time.HasValue)
|
||||||
|
{
|
||||||
|
var seconds = TimeSpan.FromTicks(time.Value).TotalSeconds;
|
||||||
|
|
||||||
|
if (seconds > 0)
|
||||||
|
{
|
||||||
|
return string.Format("-ss {0}", seconds.ToString(UsCulture));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetProbeSizeArgument(bool isDvd)
|
||||||
|
{
|
||||||
|
return isDvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int? GetAudioBitrateParam(InternalEncodingTask task)
|
||||||
|
{
|
||||||
|
if (task.Request.AudioBitRate.HasValue)
|
||||||
|
{
|
||||||
|
// Make sure we don't request a bitrate higher than the source
|
||||||
|
var currentBitrate = task.AudioStream == null ? task.Request.AudioBitRate.Value : task.AudioStream.BitRate ?? task.Request.AudioBitRate.Value;
|
||||||
|
|
||||||
|
return Math.Min(currentBitrate, task.Request.AudioBitRate.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of audio channels to specify on the command line
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">The request.</param>
|
||||||
|
/// <param name="audioStream">The audio stream.</param>
|
||||||
|
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||||
|
public static int? GetNumAudioChannelsParam(EncodingOptions request, MediaStream audioStream)
|
||||||
|
{
|
||||||
|
if (audioStream != null)
|
||||||
|
{
|
||||||
|
if (audioStream.Channels > 2 && string.Equals(request.AudioCodec, "wma", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// wmav2 currently only supports two channel output
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.MaxAudioChannels.HasValue)
|
||||||
|
{
|
||||||
|
if (audioStream != null && audioStream.Channels.HasValue)
|
||||||
|
{
|
||||||
|
return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.MaxAudioChannels.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.AudioChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetNumberOfThreads(InternalEncodingTask state, bool isWebm)
|
||||||
|
{
|
||||||
|
// Use more when this is true. -re will keep cpu usage under control
|
||||||
|
if (state.ReadInputAtNativeFramerate)
|
||||||
|
{
|
||||||
|
if (isWebm)
|
||||||
|
{
|
||||||
|
return Math.Max(Environment.ProcessorCount - 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webm: http://www.webmproject.org/docs/encoder-parameters/
|
||||||
|
// The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads
|
||||||
|
// for the coefficient data if the encoder selected --token-parts > 0 at encode time.
|
||||||
|
|
||||||
|
switch (state.QualitySetting)
|
||||||
|
{
|
||||||
|
case EncodingQuality.HighSpeed:
|
||||||
|
return 2;
|
||||||
|
case EncodingQuality.HighQuality:
|
||||||
|
return 2;
|
||||||
|
case EncodingQuality.MaxQuality:
|
||||||
|
return isWebm ? 2 : 0;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unrecognized MediaEncodingQuality value.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs
Normal file
168
MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
{
|
||||||
|
public class FFMpegProcess : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _ffmpegPath;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
private readonly IIsoManager _isoManager;
|
||||||
|
private readonly ILiveTvManager _liveTvManager;
|
||||||
|
|
||||||
|
private Stream _logFileStream;
|
||||||
|
private InternalEncodingTask _task;
|
||||||
|
private IIsoMount _isoMount;
|
||||||
|
|
||||||
|
public FFMpegProcess(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
|
||||||
|
{
|
||||||
|
_ffmpegPath = ffmpegPath;
|
||||||
|
_logger = logger;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_appPaths = appPaths;
|
||||||
|
_isoManager = isoManager;
|
||||||
|
_liveTvManager = liveTvManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start(InternalEncodingTask task, Func<InternalEncodingTask,string,string> argumentsFactory)
|
||||||
|
{
|
||||||
|
_task = task;
|
||||||
|
if (!File.Exists(_ffmpegPath))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ffmpeg was not found at " + _ffmpegPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(task.Request.OutputPath));
|
||||||
|
|
||||||
|
string mountedPath = null;
|
||||||
|
if (task.InputVideoType.HasValue && task.InputVideoType == VideoType.Iso && task.IsoType.HasValue)
|
||||||
|
{
|
||||||
|
if (_isoManager.CanMount(task.MediaPath))
|
||||||
|
{
|
||||||
|
_isoMount = await _isoManager.Mount(task.MediaPath, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
mountedPath = _isoMount.MountedPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
|
||||||
|
// Must consume both stdout and stderr or deadlocks may occur
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
|
||||||
|
FileName = _ffmpegPath,
|
||||||
|
WorkingDirectory = Path.GetDirectoryName(_ffmpegPath),
|
||||||
|
Arguments = argumentsFactory(task, mountedPath),
|
||||||
|
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false
|
||||||
|
},
|
||||||
|
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
|
||||||
|
|
||||||
|
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-" + task.Id + ".txt");
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
||||||
|
|
||||||
|
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
||||||
|
_logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
|
||||||
|
|
||||||
|
process.Exited += process_Exited;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process.Start();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error starting ffmpeg", ex);
|
||||||
|
|
||||||
|
task.OnError();
|
||||||
|
|
||||||
|
DisposeLogFileStream();
|
||||||
|
|
||||||
|
process.Dispose();
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
task.OnBegin();
|
||||||
|
|
||||||
|
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
|
||||||
|
#pragma warning disable 4014
|
||||||
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||||
|
process.StandardError.BaseStream.CopyToAsync(_logFileStream);
|
||||||
|
#pragma warning restore 4014
|
||||||
|
}
|
||||||
|
|
||||||
|
async void process_Exited(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var process = (Process)sender;
|
||||||
|
|
||||||
|
if (_isoMount != null)
|
||||||
|
{
|
||||||
|
_isoMount.Dispose();
|
||||||
|
_isoMount = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposeLogFileStream();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Info("FFMpeg exited with code {0} for {1}", process.ExitCode, _task.Request.OutputPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_logger.Info("FFMpeg exited with an error for {0}", _task.Request.OutputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
_task.OnCompleted();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_task.LiveTvStreamId))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _liveTvManager.CloseLiveStream(_task.LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error closing live tv stream", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DisposeLogFileStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeLogFileStream()
|
||||||
|
{
|
||||||
|
if (_logFileStream != null)
|
||||||
|
{
|
||||||
|
_logFileStream.Dispose();
|
||||||
|
_logFileStream = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs
Normal file
95
MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
{
|
||||||
|
public class InternalEncodingTask
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
public CancellationTokenSource CancellationTokenSource { get; set; }
|
||||||
|
|
||||||
|
public double ProgressPercentage { get; set; }
|
||||||
|
|
||||||
|
public EncodingOptions Request { get; set; }
|
||||||
|
|
||||||
|
public VideoEncodingOptions VideoRequest
|
||||||
|
{
|
||||||
|
get { return Request as VideoEncodingOptions; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MediaPath { get; set; }
|
||||||
|
public List<string> StreamFileNames { get; set; }
|
||||||
|
public bool IsInputRemote { get; set; }
|
||||||
|
|
||||||
|
public VideoType? InputVideoType { get; set; }
|
||||||
|
public IsoType? IsoType { get; set; }
|
||||||
|
public long? InputRunTimeTicks;
|
||||||
|
|
||||||
|
public string AudioSync = "1";
|
||||||
|
public string VideoSync = "vfr";
|
||||||
|
|
||||||
|
public string InputAudioSync { get; set; }
|
||||||
|
public string InputVideoSync { get; set; }
|
||||||
|
|
||||||
|
public bool DeInterlace { get; set; }
|
||||||
|
|
||||||
|
public bool ReadInputAtNativeFramerate { get; set; }
|
||||||
|
|
||||||
|
public string InputFormat { get; set; }
|
||||||
|
|
||||||
|
public string InputVideoCodec { get; set; }
|
||||||
|
|
||||||
|
public string InputAudioCodec { get; set; }
|
||||||
|
|
||||||
|
public string LiveTvStreamId { get; set; }
|
||||||
|
|
||||||
|
public MediaStream AudioStream { get; set; }
|
||||||
|
public MediaStream VideoStream { get; set; }
|
||||||
|
public MediaStream SubtitleStream { get; set; }
|
||||||
|
public bool HasMediaStreams { get; set; }
|
||||||
|
|
||||||
|
public int SegmentLength = 10;
|
||||||
|
public int HlsListSize;
|
||||||
|
|
||||||
|
public string MimeType { get; set; }
|
||||||
|
public string OrgPn { get; set; }
|
||||||
|
public bool EnableMpegtsM2TsMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user agent.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The user agent.</value>
|
||||||
|
public string UserAgent { get; set; }
|
||||||
|
|
||||||
|
public EncodingQuality QualitySetting { get; set; }
|
||||||
|
|
||||||
|
public InternalEncodingTask()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid().ToString("N");
|
||||||
|
CancellationTokenSource = new CancellationTokenSource();
|
||||||
|
StreamFileNames = new List<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableDebugLogging { get; set; }
|
||||||
|
|
||||||
|
internal void OnBegin()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnCompleted()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnError()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,323 @@
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Controller.Persistence;
|
||||||
|
using MediaBrowser.Model.Configuration;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.LiveTv;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
{
|
||||||
|
public class InternalEncodingTaskFactory
|
||||||
|
{
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly ILiveTvManager _liveTvManager;
|
||||||
|
private readonly IItemRepository _itemRepo;
|
||||||
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
|
public InternalEncodingTaskFactory(ILibraryManager libraryManager, ILiveTvManager liveTvManager, IItemRepository itemRepo, IServerConfigurationManager config)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_liveTvManager = liveTvManager;
|
||||||
|
_itemRepo = itemRepo;
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<InternalEncodingTask> Create(EncodingOptions request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ValidateInput(request);
|
||||||
|
|
||||||
|
var state = new InternalEncodingTask
|
||||||
|
{
|
||||||
|
Request = request
|
||||||
|
};
|
||||||
|
|
||||||
|
var item = string.IsNullOrEmpty(request.MediaSourceId) ?
|
||||||
|
_libraryManager.GetItemById(new Guid(request.ItemId)) :
|
||||||
|
_libraryManager.GetItemById(new Guid(request.MediaSourceId));
|
||||||
|
|
||||||
|
if (item is ILiveTvRecording)
|
||||||
|
{
|
||||||
|
var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
state.InputVideoType = VideoType.VideoFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = recording.RecordingInfo.Path;
|
||||||
|
var mediaUrl = recording.RecordingInfo.Url;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl))
|
||||||
|
{
|
||||||
|
var streamInfo = await _liveTvManager.GetRecordingStream(request.ItemId, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
state.LiveTvStreamId = streamInfo.Id;
|
||||||
|
|
||||||
|
path = streamInfo.Path;
|
||||||
|
mediaUrl = streamInfo.Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(path) && File.Exists(path))
|
||||||
|
{
|
||||||
|
state.MediaPath = path;
|
||||||
|
state.IsInputRemote = false;
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(mediaUrl))
|
||||||
|
{
|
||||||
|
state.MediaPath = mediaUrl;
|
||||||
|
state.IsInputRemote = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.InputRunTimeTicks = recording.RunTimeTicks;
|
||||||
|
if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsInputRemote)
|
||||||
|
{
|
||||||
|
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.ReadInputAtNativeFramerate = recording.RecordingInfo.Status == RecordingStatus.InProgress;
|
||||||
|
state.AudioSync = "1000";
|
||||||
|
state.DeInterlace = true;
|
||||||
|
state.InputVideoSync = "-1";
|
||||||
|
state.InputAudioSync = "1";
|
||||||
|
}
|
||||||
|
else if (item is LiveTvChannel)
|
||||||
|
{
|
||||||
|
var channel = _liveTvManager.GetInternalChannel(request.ItemId);
|
||||||
|
|
||||||
|
if (string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
state.InputVideoType = VideoType.VideoFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
var streamInfo = await _liveTvManager.GetChannelStream(request.ItemId, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
state.LiveTvStreamId = streamInfo.Id;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(streamInfo.Path) && File.Exists(streamInfo.Path))
|
||||||
|
{
|
||||||
|
state.MediaPath = streamInfo.Path;
|
||||||
|
state.IsInputRemote = false;
|
||||||
|
|
||||||
|
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(streamInfo.Url))
|
||||||
|
{
|
||||||
|
state.MediaPath = streamInfo.Url;
|
||||||
|
state.IsInputRemote = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.ReadInputAtNativeFramerate = true;
|
||||||
|
state.AudioSync = "1000";
|
||||||
|
state.DeInterlace = true;
|
||||||
|
state.InputVideoSync = "-1";
|
||||||
|
state.InputAudioSync = "1";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.MediaPath = item.Path;
|
||||||
|
state.IsInputRemote = item.LocationType == LocationType.Remote;
|
||||||
|
|
||||||
|
var video = item as Video;
|
||||||
|
|
||||||
|
if (video != null)
|
||||||
|
{
|
||||||
|
state.InputVideoType = video.VideoType;
|
||||||
|
state.IsoType = video.IsoType;
|
||||||
|
|
||||||
|
state.StreamFileNames = video.PlayableStreamFileNames.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.InputRunTimeTicks = item.RunTimeTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoRequest = request as VideoEncodingOptions;
|
||||||
|
|
||||||
|
var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
|
||||||
|
{
|
||||||
|
ItemId = item.Id
|
||||||
|
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
if (videoRequest != null)
|
||||||
|
{
|
||||||
|
state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
|
||||||
|
state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
|
||||||
|
state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
|
||||||
|
|
||||||
|
if (state.VideoStream != null && state.VideoStream.IsInterlaced)
|
||||||
|
{
|
||||||
|
state.DeInterlace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.HasMediaStreams = mediaStreams.Count > 0;
|
||||||
|
|
||||||
|
state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10;
|
||||||
|
state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440;
|
||||||
|
|
||||||
|
state.QualitySetting = GetQualitySetting();
|
||||||
|
|
||||||
|
ApplyDeviceProfileSettings(state);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateInput(EncodingOptions request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.ItemId))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("ItemId is required.");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(request.OutputPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("OutputPath is required.");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Container))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Container is required.");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(request.AudioCodec))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("AudioCodec is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoRequest = request as VideoEncodingOptions;
|
||||||
|
|
||||||
|
if (videoRequest == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines which stream will be used for playback
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="allStream">All stream.</param>
|
||||||
|
/// <param name="desiredIndex">Index of the desired.</param>
|
||||||
|
/// <param name="type">The type.</param>
|
||||||
|
/// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
|
||||||
|
/// <returns>MediaStream.</returns>
|
||||||
|
private MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
|
||||||
|
{
|
||||||
|
var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
|
||||||
|
|
||||||
|
if (desiredIndex.HasValue)
|
||||||
|
{
|
||||||
|
var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
|
||||||
|
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
|
||||||
|
{
|
||||||
|
return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
|
||||||
|
streams.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just return the first one
|
||||||
|
return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyDeviceProfileSettings(InternalEncodingTask state)
|
||||||
|
{
|
||||||
|
var profile = state.Request.DeviceProfile;
|
||||||
|
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
// Don't use settings from the default profile.
|
||||||
|
// Only use a specific profile if it was requested.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = state.Request.Container;
|
||||||
|
|
||||||
|
var audioCodec = state.Request.AudioCodec;
|
||||||
|
|
||||||
|
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
|
||||||
|
{
|
||||||
|
audioCodec = state.AudioStream.Codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
|
||||||
|
|
||||||
|
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
|
||||||
|
{
|
||||||
|
videoCodec = state.VideoStream.Codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mediaProfile = state.VideoRequest == null ?
|
||||||
|
profile.GetAudioMediaProfile(container, audioCodec, state.AudioStream) :
|
||||||
|
profile.GetVideoMediaProfile(container, audioCodec, videoCodec, state.AudioStream, state.VideoStream);
|
||||||
|
|
||||||
|
if (mediaProfile != null)
|
||||||
|
{
|
||||||
|
state.MimeType = mediaProfile.MimeType;
|
||||||
|
state.OrgPn = mediaProfile.OrgPn;
|
||||||
|
}
|
||||||
|
|
||||||
|
var transcodingProfile = state.VideoRequest == null ?
|
||||||
|
profile.GetAudioTranscodingProfile(container, audioCodec) :
|
||||||
|
profile.GetVideoTranscodingProfile(container, audioCodec, videoCodec);
|
||||||
|
|
||||||
|
if (transcodingProfile != null)
|
||||||
|
{
|
||||||
|
//state.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
||||||
|
state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
|
||||||
|
//state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||||
|
|
||||||
|
foreach (var setting in transcodingProfile.Settings)
|
||||||
|
{
|
||||||
|
switch (setting.Name)
|
||||||
|
{
|
||||||
|
case TranscodingSettingType.VideoProfile:
|
||||||
|
{
|
||||||
|
if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.VideoProfile))
|
||||||
|
{
|
||||||
|
state.VideoRequest.VideoProfile = setting.Value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Unrecognized TranscodingSettingType");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EncodingQuality GetQualitySetting()
|
||||||
|
{
|
||||||
|
var quality = _config.Configuration.MediaEncodingQuality;
|
||||||
|
|
||||||
|
if (quality == EncodingQuality.Auto)
|
||||||
|
{
|
||||||
|
var cpuCount = Environment.ProcessorCount;
|
||||||
|
|
||||||
|
if (cpuCount >= 4)
|
||||||
|
{
|
||||||
|
//return EncodingQuality.HighQuality;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EncodingQuality.HighSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return quality;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,10 @@ using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -122,35 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// <exception cref="System.ArgumentException">Unrecognized InputType</exception>
|
/// <exception cref="System.ArgumentException">Unrecognized InputType</exception>
|
||||||
public string GetInputArgument(string[] inputFiles, InputType type)
|
public string GetInputArgument(string[] inputFiles, InputType type)
|
||||||
{
|
{
|
||||||
string inputPath;
|
return EncodingUtils.GetInputArgument(inputFiles.ToList(), type == InputType.Url);
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case InputType.Bluray:
|
|
||||||
case InputType.Dvd:
|
|
||||||
case InputType.File:
|
|
||||||
inputPath = GetConcatInputArgument(inputFiles);
|
|
||||||
break;
|
|
||||||
case InputType.Url:
|
|
||||||
inputPath = GetHttpInputArgument(inputFiles);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentException("Unrecognized InputType");
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the HTTP input argument.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="inputFiles">The input files.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
private string GetHttpInputArgument(string[] inputFiles)
|
|
||||||
{
|
|
||||||
var url = inputFiles[0];
|
|
||||||
|
|
||||||
return string.Format("\"{0}\"", url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -160,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
public string GetProbeSizeArgument(InputType type)
|
public string GetProbeSizeArgument(InputType type)
|
||||||
{
|
{
|
||||||
return type == InputType.Dvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
|
return EncodingUtils.GetProbeSizeArgument(type == InputType.Dvd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -879,36 +851,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
return memoryStream;
|
return memoryStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the file input argument.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
private string GetFileInputArgument(string path)
|
|
||||||
{
|
|
||||||
return string.Format("file:\"{0}\"", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the concat input argument.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="playableStreamFiles">The playable stream files.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
private string GetConcatInputArgument(string[] playableStreamFiles)
|
|
||||||
{
|
|
||||||
// Get all streams
|
|
||||||
// If there's more than one we'll need to use the concat command
|
|
||||||
if (playableStreamFiles.Length > 1)
|
|
||||||
{
|
|
||||||
var files = string.Join("|", playableStreamFiles);
|
|
||||||
|
|
||||||
return string.Format("concat:\"{0}\"", files);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the input path for video files
|
|
||||||
return GetFileInputArgument(playableStreamFiles[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
|
public Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return new ImageEncoder(FFMpegPath, _logger, _fileSystem, _appPaths).EncodeImage(options, cancellationToken);
|
return new ImageEncoder(FFMpegPath, _logger, _fileSystem, _appPaths).EncodeImage(options, cancellationToken);
|
||||||
|
|
|
@ -48,7 +48,12 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="BdInfo\BdInfoExaminer.cs" />
|
<Compile Include="BdInfo\BdInfoExaminer.cs" />
|
||||||
|
<Compile Include="Encoder\AudioEncoder.cs" />
|
||||||
|
<Compile Include="Encoder\EncodingUtils.cs" />
|
||||||
|
<Compile Include="Encoder\FFMpegProcess.cs" />
|
||||||
<Compile Include="Encoder\ImageEncoder.cs" />
|
<Compile Include="Encoder\ImageEncoder.cs" />
|
||||||
|
<Compile Include="Encoder\InternalEncodingTask.cs" />
|
||||||
|
<Compile Include="Encoder\InternalEncodingTaskFactory.cs" />
|
||||||
<Compile Include="Encoder\MediaEncoder.cs" />
|
<Compile Include="Encoder\MediaEncoder.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -33,6 +33,8 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
/// <value>The external identifier.</value>
|
/// <value>The external identifier.</value>
|
||||||
public string ExternalId { get; set; }
|
public string ExternalId { get; set; }
|
||||||
|
|
||||||
|
public List<MediaSourceInfo> MediaSources { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the image tags.
|
/// Gets or sets the image tags.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -112,6 +114,7 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
public ChannelInfoDto()
|
public ChannelInfoDto()
|
||||||
{
|
{
|
||||||
ImageTags = new Dictionary<ImageType, Guid>();
|
ImageTags = new Dictionary<ImageType, Guid>();
|
||||||
|
MediaSources = new List<MediaSourceInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using System.Diagnostics;
|
using MediaBrowser.Model.Dto;
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using MediaBrowser.Model.Dto;
|
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Library;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using MediaBrowser.Model.Library;
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.LiveTv
|
namespace MediaBrowser.Model.LiveTv
|
||||||
{
|
{
|
||||||
|
@ -248,10 +248,13 @@ namespace MediaBrowser.Model.LiveTv
|
||||||
/// <value>The type.</value>
|
/// <value>The type.</value>
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
public List<MediaSourceInfo> MediaSources { get; set; }
|
||||||
|
|
||||||
public RecordingInfoDto()
|
public RecordingInfoDto()
|
||||||
{
|
{
|
||||||
Genres = new List<string>();
|
Genres = new List<string>();
|
||||||
ImageTags = new Dictionary<ImageType, Guid>();
|
ImageTags = new Dictionary<ImageType, Guid>();
|
||||||
|
MediaSources = new List<MediaSourceInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
|
@ -8,6 +8,7 @@ using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
@ -1064,7 +1065,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
|
dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.MediaSources = GetMediaSources(audio);
|
dto.MediaSources = GetAudioMediaSources(audio);
|
||||||
dto.MediaSourceCount = 1;
|
dto.MediaSourceCount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1100,7 +1101,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.MediaSources))
|
if (fields.Contains(ItemFields.MediaSources))
|
||||||
{
|
{
|
||||||
dto.MediaSources = GetMediaSources(video);
|
dto.MediaSources = GetVideoMediaSources(video);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.Chapters))
|
if (fields.Contains(ItemFields.Chapters))
|
||||||
|
@ -1266,9 +1267,48 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
{
|
{
|
||||||
SetBookProperties(dto, book);
|
SetBookProperties(dto, book);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tvChannel = item as LiveTvChannel;
|
||||||
|
|
||||||
|
if (tvChannel != null)
|
||||||
|
{
|
||||||
|
dto.MediaSources = GetMediaSources(tvChannel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MediaSourceInfo> GetMediaSources(Video item)
|
public List<MediaSourceInfo> GetMediaSources(BaseItem item)
|
||||||
|
{
|
||||||
|
var video = item as Video;
|
||||||
|
|
||||||
|
if (video != null)
|
||||||
|
{
|
||||||
|
return GetVideoMediaSources(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
var audio = item as Audio;
|
||||||
|
|
||||||
|
if (audio != null)
|
||||||
|
{
|
||||||
|
return GetAudioMediaSources(audio);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new List<MediaSourceInfo>
|
||||||
|
{
|
||||||
|
new MediaSourceInfo
|
||||||
|
{
|
||||||
|
Id = item.Id.ToString("N"),
|
||||||
|
LocationType = item.LocationType,
|
||||||
|
Name = item.Name,
|
||||||
|
Path = GetMappedPath(item),
|
||||||
|
MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id }).ToList(),
|
||||||
|
RunTimeTicks = item.RunTimeTicks
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MediaSourceInfo> GetVideoMediaSources(Video item)
|
||||||
{
|
{
|
||||||
var result = item.GetAlternateVersions().Select(GetVersionInfo).ToList();
|
var result = item.GetAlternateVersions().Select(GetVersionInfo).ToList();
|
||||||
|
|
||||||
|
@ -1293,7 +1333,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MediaSourceInfo> GetMediaSources(Audio item)
|
private List<MediaSourceInfo> GetAudioMediaSources(Audio item)
|
||||||
{
|
{
|
||||||
var result = new List<MediaSourceInfo>
|
var result = new List<MediaSourceInfo>
|
||||||
{
|
{
|
||||||
|
|
67
MediaBrowser.Server.Implementations/Library/MusicManager.cs
Normal file
67
MediaBrowser.Server.Implementations/Library/MusicManager.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
{
|
||||||
|
public class MusicManager : IMusicManager
|
||||||
|
{
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
public MusicManager(ILibraryManager libraryManager)
|
||||||
|
{
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user)
|
||||||
|
{
|
||||||
|
return GetInstantMixFromGenres(item.Genres, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Audio> GetInstantMixFromArtist(string name, User user)
|
||||||
|
{
|
||||||
|
var artist = _libraryManager.GetArtist(name);
|
||||||
|
|
||||||
|
var genres = _libraryManager.RootFolder
|
||||||
|
.RecursiveChildren
|
||||||
|
.OfType<Audio>()
|
||||||
|
.Where(i => i.HasArtist(name))
|
||||||
|
.SelectMany(i => i.Genres)
|
||||||
|
.Concat(artist.Genres)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return GetInstantMixFromGenres(genres, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
|
||||||
|
{
|
||||||
|
var genres = item
|
||||||
|
.RecursiveChildren
|
||||||
|
.OfType<Audio>()
|
||||||
|
.SelectMany(i => i.Genres)
|
||||||
|
.Concat(item.Genres)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return GetInstantMixFromGenres(genres, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user)
|
||||||
|
{
|
||||||
|
var inputItems = user.RootFolder.GetRecursiveChildren(user);
|
||||||
|
|
||||||
|
var genresDictionary = genres.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return inputItems
|
||||||
|
.OfType<Audio>()
|
||||||
|
.Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey)))
|
||||||
|
.OrderByDescending(i => i.Item2)
|
||||||
|
.ThenBy(i => Guid.NewGuid())
|
||||||
|
.Select(i => i.Item1)
|
||||||
|
.Take(100)
|
||||||
|
.OrderBy(i => Guid.NewGuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,8 +34,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
|
||||||
{
|
{
|
||||||
var collectionType = args.GetCollectionType();
|
var collectionType = args.GetCollectionType();
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
|
||||||
string.IsNullOrEmpty(collectionType))
|
|
||||||
{
|
{
|
||||||
return new Controller.Entities.Audio.Audio();
|
return new Controller.Entities.Audio.Audio();
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,13 +222,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
RunTimeTicks = (info.EndDate - info.StartDate).Ticks,
|
RunTimeTicks = (info.EndDate - info.StartDate).Ticks,
|
||||||
OriginalAirDate = info.OriginalAirDate,
|
OriginalAirDate = info.OriginalAirDate,
|
||||||
|
|
||||||
MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
|
MediaSources = _dtoService.GetMediaSources((BaseItem)recording)
|
||||||
{
|
|
||||||
ItemId = recording.Id
|
|
||||||
|
|
||||||
}).ToList()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToList();
|
||||||
|
|
||||||
if (info.Status == RecordingStatus.InProgress)
|
if (info.Status == RecordingStatus.InProgress)
|
||||||
{
|
{
|
||||||
var now = DateTime.UtcNow.Ticks;
|
var now = DateTime.UtcNow.Ticks;
|
||||||
|
@ -317,7 +315,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
Type = info.GetClientTypeName(),
|
Type = info.GetClientTypeName(),
|
||||||
Id = info.Id.ToString("N"),
|
Id = info.Id.ToString("N"),
|
||||||
MediaType = info.MediaType,
|
MediaType = info.MediaType,
|
||||||
ExternalId = info.ExternalId
|
ExternalId = info.ExternalId,
|
||||||
|
MediaSources = _dtoService.GetMediaSources(info)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
|
|
|
@ -141,6 +141,7 @@
|
||||||
<Compile Include="IO\LibraryMonitor.cs" />
|
<Compile Include="IO\LibraryMonitor.cs" />
|
||||||
<Compile Include="Library\CoreResolutionIgnoreRule.cs" />
|
<Compile Include="Library\CoreResolutionIgnoreRule.cs" />
|
||||||
<Compile Include="Library\LibraryManager.cs" />
|
<Compile Include="Library\LibraryManager.cs" />
|
||||||
|
<Compile Include="Library\MusicManager.cs" />
|
||||||
<Compile Include="Library\Resolvers\PhotoResolver.cs" />
|
<Compile Include="Library\Resolvers\PhotoResolver.cs" />
|
||||||
<Compile Include="Library\SearchEngine.cs" />
|
<Compile Include="Library\SearchEngine.cs" />
|
||||||
<Compile Include="Library\ResolverHelper.cs" />
|
<Compile Include="Library\ResolverHelper.cs" />
|
||||||
|
|
|
@ -3,8 +3,6 @@ using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
@ -43,6 +41,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IMusicManager _musicManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the configuration manager.
|
/// Gets or sets the configuration manager.
|
||||||
|
@ -688,9 +687,22 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
|
|
||||||
var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null;
|
var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null;
|
||||||
|
|
||||||
var items = command.ItemIds.SelectMany(i => TranslateItemForPlayback(i, user))
|
List<BaseItem> items;
|
||||||
.Where(i => i.LocationType != LocationType.Virtual)
|
|
||||||
.ToList();
|
if (command.PlayCommand == PlayCommand.PlayInstantMix)
|
||||||
|
{
|
||||||
|
items = command.ItemIds.SelectMany(i => TranslateItemForInstantMix(i, user))
|
||||||
|
.Where(i => i.LocationType != LocationType.Virtual)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
command.PlayCommand = PlayCommand.PlayNow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
items = command.ItemIds.SelectMany(i => TranslateItemForPlayback(i, user))
|
||||||
|
.Where(i => i.LocationType != LocationType.Virtual)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
if (command.PlayCommand == PlayCommand.PlayShuffle)
|
if (command.PlayCommand == PlayCommand.PlayShuffle)
|
||||||
{
|
{
|
||||||
|
@ -741,7 +753,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
var folder = (Folder)item;
|
var folder = (Folder)item;
|
||||||
|
|
||||||
var items = user == null ? folder.RecursiveChildren:
|
var items = user == null ? folder.RecursiveChildren :
|
||||||
folder.GetRecursiveChildren(user);
|
folder.GetRecursiveChildren(user);
|
||||||
|
|
||||||
items = items.Where(i => !i.IsFolder);
|
items = items.Where(i => !i.IsFolder);
|
||||||
|
@ -754,6 +766,41 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
return new[] { item };
|
return new[] { item };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<BaseItem> TranslateItemForInstantMix(string id, User user)
|
||||||
|
{
|
||||||
|
var item = _libraryManager.GetItemById(new Guid(id));
|
||||||
|
|
||||||
|
var audio = item as Audio;
|
||||||
|
|
||||||
|
if (audio != null)
|
||||||
|
{
|
||||||
|
return _musicManager.GetInstantMixFromSong(audio, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
var artist = item as MusicArtist;
|
||||||
|
|
||||||
|
if (artist != null)
|
||||||
|
{
|
||||||
|
return _musicManager.GetInstantMixFromArtist(artist.Name, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
var album = item as MusicAlbum;
|
||||||
|
|
||||||
|
if (album != null)
|
||||||
|
{
|
||||||
|
return _musicManager.GetInstantMixFromAlbum(album, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
var genre = item as MusicGenre;
|
||||||
|
|
||||||
|
if (genre != null)
|
||||||
|
{
|
||||||
|
return _musicManager.GetInstantMixFromGenres(new[] { genre.Name }, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BaseItem[] { };
|
||||||
|
}
|
||||||
|
|
||||||
public Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken)
|
public Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var session = GetSessionForRemoteControl(sessionId);
|
var session = GetSessionForRemoteControl(sessionId);
|
||||||
|
|
|
@ -449,6 +449,8 @@ namespace MediaBrowser.ServerApplication
|
||||||
LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager);
|
LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager);
|
||||||
RegisterSingleInstance(LibraryManager);
|
RegisterSingleInstance(LibraryManager);
|
||||||
|
|
||||||
|
RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
|
||||||
|
|
||||||
LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
|
LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
|
||||||
RegisterSingleInstance(LibraryMonitor);
|
RegisterSingleInstance(LibraryMonitor);
|
||||||
|
|
||||||
|
|
|
@ -4058,7 +4058,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
|
||||||
options.ForceTitle = true;
|
options.ForceTitle = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = self.getUrl("Packages/" + packageId + "Reviews", options);
|
var url = self.getUrl("Packages/" + packageId + "/Reviews", options);
|
||||||
|
|
||||||
return self.ajax({
|
return self.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.248" targetFramework="net45" />
|
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.249" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
Loading…
Reference in a new issue