mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-31 12:14:21 -04:00
add temp file with recording conversion
This commit is contained in:
parent
ee6e3b95ca
commit
a75f24e8e1
@ -154,7 +154,7 @@ namespace MediaBrowser.Controller.Persistence
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="query">The query.</param>
|
/// <param name="query">The query.</param>
|
||||||
/// <returns>List<BaseItem>.</returns>
|
/// <returns>List<BaseItem>.</returns>
|
||||||
IEnumerable<BaseItem> GetItemList(InternalItemsQuery query);
|
List<BaseItem> GetItemList(InternalItemsQuery query);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the inherited values.
|
/// Updates the inherited values.
|
||||||
|
@ -1306,7 +1306,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||||||
ItemIds = new[] { item.Id.ToString("N") }
|
ItemIds = new[] { item.Id.ToString("N") }
|
||||||
});
|
});
|
||||||
|
|
||||||
dto.ArtistItems = artistItems.Items
|
dto.AlbumArtists = artistItems.Items
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
var artist = i.Item1;
|
var artist = i.Item1;
|
||||||
|
@ -1059,6 +1059,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
if (recordingStatus == RecordingStatus.Completed)
|
if (recordingStatus == RecordingStatus.Completed)
|
||||||
{
|
{
|
||||||
|
timer.Status = RecordingStatus.Completed;
|
||||||
|
_timerProvider.AddOrUpdate(timer);
|
||||||
|
|
||||||
OnSuccessfulRecording(info.IsSeries, recordPath);
|
OnSuccessfulRecording(info.IsSeries, recordPath);
|
||||||
_timerProvider.Delete(timer);
|
_timerProvider.Delete(timer);
|
||||||
}
|
}
|
||||||
@ -1067,7 +1070,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
const int retryIntervalSeconds = 60;
|
const int retryIntervalSeconds = 60;
|
||||||
_logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds);
|
_logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds);
|
||||||
|
|
||||||
_timerProvider.StartTimer(timer, TimeSpan.FromSeconds(retryIntervalSeconds));
|
timer.Status = RecordingStatus.New;
|
||||||
|
timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds);
|
||||||
|
_timerProvider.AddOrUpdate(timer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1119,7 +1124,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
if (regInfo.IsValid)
|
if (regInfo.IsValid)
|
||||||
{
|
{
|
||||||
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config);
|
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@ -22,8 +24,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
private readonly LiveTvOptions _liveTvOptions;
|
private readonly LiveTvOptions _liveTvOptions;
|
||||||
private bool _hasExited;
|
private bool _hasExited;
|
||||||
private Stream _logFileStream;
|
private Stream _logFileStream;
|
||||||
@ -32,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions)
|
public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions, IHttpClient httpClient)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
@ -40,6 +43,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_json = json;
|
_json = json;
|
||||||
_liveTvOptions = liveTvOptions;
|
_liveTvOptions = liveTvOptions;
|
||||||
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
|
public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
|
||||||
@ -49,20 +53,73 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (mediaSource.RunTimeTicks.HasValue)
|
var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// The media source already has a fixed duration
|
await RecordInternal(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken)
|
||||||
// But add another stop 1 minute later just in case the recording gets stuck for any reason
|
.ConfigureAwait(false);
|
||||||
var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
|
|
||||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
// The media source if infinite so we need to handle stopping ourselves
|
File.Delete(tempfile);
|
||||||
var durationToken = new CancellationTokenSource(duration);
|
}
|
||||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
}
|
||||||
|
|
||||||
|
public async Task RecordInternal(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var httpRequestOptions = new HttpRequestOptions()
|
||||||
|
{
|
||||||
|
Url = mediaSource.Path
|
||||||
|
};
|
||||||
|
|
||||||
|
httpRequestOptions.BufferContent = false;
|
||||||
|
|
||||||
|
using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
_logger.Info("Opened recording stream from tuner provider");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
|
||||||
|
|
||||||
|
using (var output = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
|
{
|
||||||
|
//onStarted();
|
||||||
|
|
||||||
|
_logger.Info("Copying recording stream to file {0}", tempFile);
|
||||||
|
|
||||||
|
var bufferMs = 5000;
|
||||||
|
|
||||||
|
if (mediaSource.RunTimeTicks.HasValue)
|
||||||
|
{
|
||||||
|
// The media source already has a fixed duration
|
||||||
|
// But add another stop 1 minute later just in case the recording gets stuck for any reason
|
||||||
|
var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
|
||||||
|
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The media source if infinite so we need to handle stopping ourselves
|
||||||
|
var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMilliseconds(bufferMs)));
|
||||||
|
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken);
|
||||||
|
|
||||||
|
// Give the temp file a little time to build up
|
||||||
|
await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await RecordFromFile(mediaSource, tempFile, targetFile, onStarted, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
await tempFileTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.Info("Recording completed to file {0}", targetFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
_targetPath = targetFile;
|
_targetPath = targetFile;
|
||||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile));
|
_fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||||
|
|
||||||
@ -79,7 +136,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
RedirectStandardInput = true,
|
RedirectStandardInput = true,
|
||||||
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
Arguments = GetCommandLineArgs(mediaSource, targetFile, duration),
|
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile),
|
||||||
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false
|
ErrorDialog = false
|
||||||
@ -119,7 +176,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
await _taskCompletionSource.Task.ConfigureAwait(false);
|
await _taskCompletionSource.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration)
|
private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)
|
||||||
{
|
{
|
||||||
string videoArgs;
|
string videoArgs;
|
||||||
if (EncodeVideo(mediaSource))
|
if (EncodeVideo(mediaSource))
|
||||||
@ -135,14 +192,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
videoArgs = "-codec:v:0 copy";
|
videoArgs = "-codec:v:0 copy";
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -re -i \"{0}\" -t {4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
|
var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -re -i \"{0}\" -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
|
||||||
|
|
||||||
if (mediaSource.ReadAtNativeFramerate)
|
if (mediaSource.ReadAtNativeFramerate)
|
||||||
{
|
{
|
||||||
commandLineArgs = "-re " + commandLineArgs;
|
commandLineArgs = "-re " + commandLineArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
commandLineArgs = string.Format(commandLineArgs, mediaSource.Path, targetFile, videoArgs, GetAudioArgs(mediaSource), _mediaEncoder.GetTimeParameter(duration.Ticks));
|
commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource));
|
||||||
|
|
||||||
return commandLineArgs;
|
return commandLineArgs;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
using MediaBrowser.Controller.Power;
|
using MediaBrowser.Controller.Power;
|
||||||
|
using MediaBrowser.Model.LiveTv;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
@ -85,6 +86,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
|
|
||||||
private void AddTimer(TimerInfo item)
|
private void AddTimer(TimerInfo item)
|
||||||
{
|
{
|
||||||
|
if (item.Status == RecordingStatus.Completed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var startDate = RecordingHelper.GetStartTime(item);
|
var startDate = RecordingHelper.GetStartTime(item);
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
@ -117,15 +123,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartTimer(TimerInfo item, TimeSpan length)
|
public void StartTimer(TimerInfo item, TimeSpan dueTime)
|
||||||
{
|
{
|
||||||
StopTimer(item);
|
StopTimer(item);
|
||||||
|
|
||||||
var timer = new Timer(TimerCallback, item.Id, length, TimeSpan.Zero);
|
var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
|
||||||
|
|
||||||
if (_timers.TryAdd(item.Id, timer))
|
if (_timers.TryAdd(item.Id, timer))
|
||||||
{
|
{
|
||||||
_logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, length.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
_logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1742,7 +1742,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
return " from TypedBaseItems A";
|
return " from TypedBaseItems A";
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
|
public List<BaseItem> GetItemList(InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (query == null)
|
if (query == null)
|
||||||
{
|
{
|
||||||
@ -1842,6 +1842,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
|
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
|
if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
|
||||||
|
{
|
||||||
|
var list = GetItemList(query);
|
||||||
|
return new QueryResult<BaseItem>
|
||||||
|
{
|
||||||
|
Items = list.ToArray(),
|
||||||
|
TotalRecordCount = list.Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
using (var cmd = _connection.CreateCommand())
|
using (var cmd = _connection.CreateCommand())
|
||||||
@ -2196,6 +2206,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
|
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
|
if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
|
||||||
|
{
|
||||||
|
var list = GetItemIdsList(query);
|
||||||
|
return new QueryResult<Guid>
|
||||||
|
{
|
||||||
|
Items = list.ToArray(),
|
||||||
|
TotalRecordCount = list.Count
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
using (var cmd = _connection.CreateCommand())
|
using (var cmd = _connection.CreateCommand())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user