Merge pull request #2545 from MediaBrowser/dev

Dev
This commit is contained in:
Luke 2017-03-26 12:27:41 -04:00 committed by GitHub
commit 5063c2c310
19 changed files with 227 additions and 482 deletions

View File

@ -16,12 +16,15 @@ namespace Emby.Common.Implementations.Net
internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket
{
#region Fields
private Socket _Socket;
private int _LocalPort;
#endregion
private SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
{
SocketFlags = SocketFlags.None
};
private TaskCompletionSource<SocketReceiveResult> _currentReceiveTaskCompletionSource;
public UdpSocket(Socket socket, int localPort, IPAddress ip)
{
@ -32,6 +35,32 @@ namespace Emby.Common.Implementations.Net
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
_Socket.Bind(new IPEndPoint(ip, _LocalPort));
InitReceiveSocketAsyncEventArgs();
}
private void InitReceiveSocketAsyncEventArgs()
{
var buffer = new byte[8192];
_receiveSocketAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length);
_receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed;
}
private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
{
var tcs = _currentReceiveTaskCompletionSource;
if (tcs != null)
{
_currentReceiveTaskCompletionSource = null;
tcs.TrySetResult(new SocketReceiveResult
{
Buffer = e.Buffer,
ReceivedBytes = e.BytesTransferred,
RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint),
LocalIPAddress = LocalIPAddress
});
}
}
public UdpSocket(Socket socket, IpEndPointInfo endPoint)
@ -40,6 +69,8 @@ namespace Emby.Common.Implementations.Net
_Socket = socket;
_Socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
InitReceiveSocketAsyncEventArgs();
}
public IpAddressInfo LocalIPAddress
@ -57,12 +88,12 @@ namespace Emby.Common.Implementations.Net
var tcs = new TaskCompletionSource<SocketReceiveResult>();
EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0);
var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
state.TaskCompletionSource = tcs;
cancellationToken.Register(() => tcs.TrySetCanceled());
#if NETSTANDARD1_6
var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
state.TaskCompletionSource = tcs;
_Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
.ContinueWith((task, asyncState) =>
{
@ -74,7 +105,15 @@ namespace Emby.Common.Implementations.Net
}
}, state);
#else
_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state);
//var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
//state.TaskCompletionSource = tcs;
//_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state);
_receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint;
_currentReceiveTaskCompletionSource = tcs;
var isPending = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs);
#endif
return tcs.Task;

View File

@ -498,11 +498,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
{
var mappedTunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings);
var tunerChannelId = tunerChannel.TunerChannelId;
if (tunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1)
{
tunerChannelId = tunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
}
var mappedTunerChannelId = GetMappedChannel(tunerChannelId, mappings);
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
{
mappedTunerChannelId = tunerChannel.TunerChannelId;
mappedTunerChannelId = tunerChannelId;
}
var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
@ -644,8 +650,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
{
var existingTimer = _timerProvider.GetAll()
.FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase));
var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ?
null :
_timerProvider.GetTimerByProgramId(timer.ProgramId);
if (existingTimer != null)
{
@ -724,10 +731,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return true;
}
//if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId))
//{
// return true;
//}
if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId))
{
return true;
}
return false;
})
@ -740,7 +747,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
timer.SeriesTimerId = info.Id;
timer.IsManual = true;
_timerProvider.AddOrUpdate(timer);
_timerProvider.AddOrUpdate(timer, false);
}
await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false);
@ -2340,6 +2347,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var existingTimer = _timerProvider.GetTimer(timer.Id);
if (existingTimer == null)
{
existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId)
? null
: _timerProvider.GetTimerByProgramId(timer.ProgramId);
}
if (existingTimer == null)
{
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
@ -2354,9 +2368,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
else
{
// Only update if not currently active
// Only update if not currently active - test both new timer and existing in case Id's are different
// Id's could be different if the timer was created manually prior to series timer creation
ActiveRecordingInfo activeRecordingInfo;
if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo))
if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo) && !_activeRecordings.TryGetValue(existingTimer.Id, out activeRecordingInfo))
{
UpdateExistingTimerWithNewMetadata(existingTimer, timer);

View File

@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4} -y \"{1}\"";
var commandLineArgs = "-i \"{0}\"{5} {2} -map_metadata -1 -threads 0 {3}{4}{6} -y \"{1}\"";
long startTimeTicks = 0;
//if (mediaSource.DateLiveStreamOpened.HasValue)
@ -193,7 +193,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam);
var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ?
" -f mp4 -movflags frag_keyframe+empty_moov" :
string.Empty;
commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), subtitleArgs, durationParam, outputParam);
return inputModifiers + " " + commandLineArgs;
}

View File

@ -166,5 +166,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
}
public TimerInfo GetTimerByProgramId(string programId)
{
return GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@ -1078,6 +1078,8 @@ namespace Emby.Server.Implementations.LiveTv
var channel = GetInternalChannel(program.ChannelId);
if (channel != null)
{
var channelUserdata = _userDataManager.GetUserData(userId, channel);
if (channelUserdata.Likes ?? false)
@ -1098,6 +1100,7 @@ namespace Emby.Server.Implementations.LiveTv
{
score += channelUserdata.PlayCount;
}
}
return score;
}

View File

@ -275,6 +275,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public int HD { get; set; }
}
protected EncodingOptions GetEncodingOptions()
{
return Config.GetConfiguration<EncodingOptions>("encoding");
}
private string GetHdHrIdFromChannelId(string channelId)
{
return channelId.Split('_')[1];
}
private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, ChannelInfo channelInfo, string profile)
{
int? width = null;
@ -362,14 +372,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
nal = "0";
}
var url = GetApiUrl(info, true) + "/auto/v" + channelId;
// If raw was used, the tuner doesn't support params
if (!string.IsNullOrWhiteSpace(profile)
&& !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
{
url += "?transcode=" + profile;
}
var url = GetApiUrl(info, false);
var id = profile;
if (string.IsNullOrWhiteSpace(id))
@ -378,92 +381,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
id += "_" + url.GetMD5().ToString("N");
var mediaSource = new MediaSourceInfo
{
Path = url,
Protocol = MediaProtocol.Http,
MediaStreams = new List<MediaStream>
{
new MediaStream
{
Type = MediaStreamType.Video,
// Set the index to -1 because we don't know the exact index of the video stream within the container
Index = -1,
IsInterlaced = isInterlaced,
Codec = videoCodec,
Width = width,
Height = height,
BitRate = videoBitrate,
NalLengthSize = nal
},
new MediaStream
{
Type = MediaStreamType.Audio,
// Set the index to -1 because we don't know the exact index of the audio stream within the container
Index = -1,
Codec = audioCodec,
BitRate = audioBitrate
}
},
RequiresOpening = true,
RequiresClosing = false,
BufferMs = 0,
Container = "ts",
Id = id,
SupportsDirectPlay = false,
SupportsDirectStream = true,
SupportsTranscoding = true,
IsInfiniteStream = true
};
mediaSource.InferTotalBitrate();
return mediaSource;
}
protected EncodingOptions GetEncodingOptions()
{
return Config.GetConfiguration<EncodingOptions>("encoding");
}
private string GetHdHrIdFromChannelId(string channelId)
{
return channelId.Split('_')[1];
}
private MediaSourceInfo GetLegacyMediaSource(TunerHostInfo info, string channelId, ChannelInfo channel)
{
int? width = null;
int? height = null;
bool isInterlaced = true;
string videoCodec = null;
string audioCodec = null;
int? videoBitrate = null;
int? audioBitrate = null;
if (channel != null)
{
if (string.IsNullOrWhiteSpace(videoCodec))
{
videoCodec = channel.VideoCodec;
}
audioCodec = channel.AudioCodec;
}
// normalize
if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase))
{
videoCodec = "mpeg2video";
}
string nal = null;
var url = GetApiUrl(info, false);
var id = channelId;
id += "_" + url.GetMD5().ToString("N");
var mediaSource = new MediaSourceInfo
{
Path = url,
@ -527,7 +444,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (isLegacyTuner)
{
list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
}
else
{
@ -579,20 +496,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
}
else
{
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
//var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
//return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
//return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
}
}

View File

@ -120,8 +120,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// send url to start streaming
await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false);
var timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token;
var response = await udpClient.ReceiveAsync(timeoutToken).ConfigureAwait(false);
_logger.Info("Opened HDHR UDP stream from {0}", remoteAddress);
if (!cancellationToken.IsCancellationRequested)
@ -132,8 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
onStarted = () => openTaskCompletionSource.TrySetResult(true);
}
var stream = new UdpClientStream(udpClient);
await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false);
await _multicastStream.CopyUntilCancelled(udpClient, onStarted, cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException ex)
@ -158,7 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
await hdHomerunManager.StopStreaming().ConfigureAwait(false);
udpClient.Dispose();
_liveStreamTaskCompletionSource.TrySetResult(true);
}
}
@ -171,127 +167,4 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return _multicastStream.CopyToAsync(stream);
}
}
// This handles the ReadAsync function only of a Stream object
// This is used to wrap a UDP socket into a stream for MulticastStream which only uses ReadAsync
public class UdpClientStream : Stream
{
private static int RtpHeaderBytes = 12;
private static int PacketSize = 1316;
private readonly ISocket _udpClient;
bool disposed;
public UdpClientStream(ISocket udpClient) : base()
{
_udpClient = udpClient;
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset + count < 0)
throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count");
if (offset + count > buffer.Length)
throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count");
if (disposed)
throw new ObjectDisposedException(typeof(UdpClientStream).ToString());
// This will always receive a 1328 packet size (PacketSize + RtpHeaderSize)
// The RTP header will be stripped so see how many reads we need to make to fill the buffer.
int numReads = count / PacketSize;
int totalBytesRead = 0;
for (int i = 0; i < numReads; ++i)
{
var data = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
// remove rtp header
Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead);
offset += bytesRead;
totalBytesRead += bytesRead;
}
return totalBytesRead;
}
protected override void Dispose(bool disposing)
{
disposed = true;
}
public override bool CanRead
{
get
{
throw new NotImplementedException();
}
}
public override bool CanSeek
{
get
{
throw new NotImplementedException();
}
}
public override bool CanWrite
{
get
{
throw new NotImplementedException();
}
}
public override long Length
{
get
{
throw new NotImplementedException();
}
}
public override long Position
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override void Flush()
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
}
}

View File

@ -144,11 +144,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
channel.TunerChannelId = string.IsNullOrWhiteSpace(tvgId) ? channelId : tvgId;
if (!string.IsNullOrWhiteSpace(channel.TunerChannelId) && channel.TunerChannelId.IndexOf(".json.schedulesdirect.org", StringComparison.OrdinalIgnoreCase) != -1)
{
channel.TunerChannelId = channel.TunerChannelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
}
var channelIdValues = new List<string>();
if (!string.IsNullOrWhiteSpace(channelId))
{

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@ -40,7 +41,52 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var allStreams = _outputStreams.ToList();
foreach (var stream in allStreams)
{
stream.Value.Queue(copy);
stream.Value.Queue(copy, 0, copy.Length);
}
if (onStarted != null)
{
var onStartedCopy = onStarted;
onStarted = null;
Task.Run(onStartedCopy);
}
}
else
{
await Task.Delay(100).ConfigureAwait(false);
}
}
}
private static int RtpHeaderBytes = 12;
public async Task CopyUntilCancelled(ISocket udpClient, Action onStarted, CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
while (!cancellationToken.IsCancellationRequested)
{
var receiveToken = cancellationToken;
// On the first connection attempt, put a timeout to avoid being stuck indefinitely in the event of failure
if (onStarted != null)
{
receiveToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token;
}
var data = await udpClient.ReceiveAsync(receiveToken).ConfigureAwait(false);
var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
if (bytesRead > 0)
{
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead);
var allStreams = _outputStreams.ToList();
foreach (var stream in allStreams)
{
//stream.Value.Queue(data.Buffer, RtpHeaderBytes, bytesRead);
stream.Value.Queue(copy, 0, copy.Length);
}
if (onStarted != null)

View File

@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class QueueStream
{
private readonly Stream _outputStream;
private readonly ConcurrentQueue<byte[]> _queue = new ConcurrentQueue<byte[]>();
private readonly ConcurrentQueue<Tuple<byte[],int,int>> _queue = new ConcurrentQueue<Tuple<byte[], int, int>>();
private CancellationToken _cancellationToken;
public TaskCompletionSource<bool> TaskCompletion { get; private set; }
@ -28,9 +28,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
TaskCompletion = new TaskCompletionSource<bool>();
}
public void Queue(byte[] bytes)
public void Queue(byte[] bytes, int offset, int count)
{
_queue.Enqueue(bytes);
_queue.Enqueue(new Tuple<byte[], int, int>(bytes, offset, count));
}
public void Start(CancellationToken cancellationToken)
@ -39,12 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Task.Run(() => StartInternal());
}
private byte[] Dequeue()
private Tuple<byte[], int, int> Dequeue()
{
byte[] bytes;
if (_queue.TryDequeue(out bytes))
Tuple<byte[], int, int> result;
if (_queue.TryDequeue(out result))
{
return bytes;
return result;
}
return null;
@ -58,10 +58,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
while (true)
{
var bytes = Dequeue();
if (bytes != null)
var result = Dequeue();
if (result != null)
{
await _outputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
await _outputStream.WriteAsync(result.Item1, result.Item2, result.Item3, cancellationToken).ConfigureAwait(false);
}
else
{

View File

@ -1583,6 +1583,15 @@ namespace MediaBrowser.Controller.MediaEncoding
MediaSourceInfo mediaSource,
string requestedUrl)
{
if (state == null)
{
throw new ArgumentNullException("state");
}
if (mediaSource == null)
{
throw new ArgumentNullException("mediaSource");
}
state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container;

View File

@ -647,9 +647,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
if (videoStream != null)
if (videoStream != null && !videoStream.IsInterlaced)
{
var isInterlaced = await DetectInterlaced(mediaInfo, videoStream, inputPath, probeSizeArgument).ConfigureAwait(false);
var isInterlaced = DetectInterlaced(mediaInfo, videoStream);
if (isInterlaced)
{
@ -672,7 +672,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
private async Task<bool> DetectInterlaced(MediaSourceInfo video, MediaStream videoStream, string inputPath, string probeSizeArgument)
private bool DetectInterlaced(MediaSourceInfo video, MediaStream videoStream)
{
var formats = (video.Container ?? string.Empty).Split(',').ToList();
var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) ||
@ -698,165 +698,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
if (video.Protocol != MediaProtocol.File)
{
return false;
}
var args = "{0} -i {1} -map 0:v:{2} -an -filter:v idet -frames:v 500 -an -f null /dev/null";
var process = _processFactory.Create(new ProcessOptions
{
CreateNoWindow = true,
UseShellExecute = false,
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
RedirectStandardError = true,
FileName = FFMpegPath,
Arguments = string.Format(args, probeSizeArgument, inputPath, videoStream.Index.ToString(CultureInfo.InvariantCulture)).Trim(),
IsHidden = true,
ErrorDialog = false,
EnableRaisingEvents = true
});
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var idetFoundInterlaced = false;
using (var processWrapper = new ProcessWrapper(process, this, _logger))
{
try
{
StartProcess(processWrapper);
}
catch (Exception ex)
{
_logger.ErrorException("Error starting ffprobe", ex);
throw;
}
try
{
//process.BeginOutputReadLine();
using (var reader = new StreamReader(process.StandardError.BaseStream))
{
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
if (line.StartsWith("[Parsed_idet", StringComparison.OrdinalIgnoreCase))
{
var idetResult = AnalyzeIdetResult(line);
if (idetResult.HasValue)
{
if (!idetResult.Value)
{
return false;
}
idetFoundInterlaced = true;
}
}
}
}
}
catch
{
StopProcess(processWrapper, 100);
throw;
}
}
return idetFoundInterlaced;
}
private bool? AnalyzeIdetResult(string line)
{
// As you can see, the filter only guessed one frame as progressive.
// Results like this are pretty typical. So if less than 30% of the detections are in the "Undetermined" category, then I only consider the video to be interlaced if at least 65% of the identified frames are in either the TFF or BFF category.
// In this case (310 + 311)/(622) = 99.8% which is well over the 65% metric. I may refine that number with more testing but I honestly do not believe I will need to.
// http://awel.domblogger.net/videoTranscode/interlace.html
var index = line.IndexOf("detection:", StringComparison.OrdinalIgnoreCase);
if (index == -1)
{
return null;
}
line = line.Substring(index).Trim();
var parts = line.Split(' ').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => i.Trim()).ToList();
if (parts.Count < 2)
{
return null;
}
double tff = 0;
double bff = 0;
double progressive = 0;
double undetermined = 0;
double total = 0;
for (var i = 0; i < parts.Count - 1; i++)
{
var part = parts[i];
if (string.Equals(part, "tff:", StringComparison.OrdinalIgnoreCase))
{
tff = GetNextPart(parts, i);
total += tff;
}
else if (string.Equals(part, "bff:", StringComparison.OrdinalIgnoreCase))
{
bff = GetNextPart(parts, i);
total += tff;
}
else if (string.Equals(part, "progressive:", StringComparison.OrdinalIgnoreCase))
{
progressive = GetNextPart(parts, i);
total += progressive;
}
else if (string.Equals(part, "undetermined:", StringComparison.OrdinalIgnoreCase))
{
undetermined = GetNextPart(parts, i);
total += undetermined;
}
}
if (total == 0)
{
return null;
}
if ((undetermined / total) >= .3)
{
return false;
}
if (((tff + bff) / total) >= .4)
{
return true;
}
return false;
}
private int GetNextPart(List<string> parts, int index)
{
var next = parts[index + 1];
int value;
if (int.TryParse(next, NumberStyles.Any, CultureInfo.InvariantCulture, out value))
{
return value;
}
return 0;
}
/// <summary>
/// The us culture
/// </summary>

View File

@ -264,6 +264,8 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <value>The loro_surmixlev.</value>
public string loro_surmixlev { get; set; }
public string field_order { get; set; }
/// <summary>
/// Gets or sets the disposition.
/// </summary>

View File

@ -508,6 +508,11 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.IsAVC = false;
}
if (!string.IsNullOrWhiteSpace(streamInfo.field_order) && !string.Equals(streamInfo.field_order, "progressive", StringComparison.OrdinalIgnoreCase))
{
stream.IsInterlaced = true;
}
// Filter out junk
if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1)
{

View File

@ -34,7 +34,7 @@ namespace MediaBrowser.Model.LiveTv
TunerHosts = new List<TunerHostInfo>();
ListingProviders = new List<ListingsProviderInfo>();
MediaLocationsCreated = new string[] { };
RecordingEncodingFormat = "mp4";
RecordingEncodingFormat = "mkv";
RecordingPostProcessorArguments = "\"{path}\"";
EnableRecordingEncoding = true;
}

View File

@ -386,14 +386,8 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvstatus.html">
<Link>Resources\dashboard-ui\livetvstatus.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-hdhomerun.html">
<Link>Resources\dashboard-ui\livetvtunerprovider-hdhomerun.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-m3u.html">
<Link>Resources\dashboard-ui\livetvtunerprovider-m3u.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-satip.html">
<Link>Resources\dashboard-ui\livetvtunerprovider-satip.html</Link>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtuner.html">
<Link>Resources\dashboard-ui\livetvtuner.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\log.html">
<Link>Resources\dashboard-ui\log.html</Link>
@ -560,12 +554,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardlibrary.html">
<Link>Resources\dashboard-ui\wizardlibrary.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardlivetvguide.html">
<Link>Resources\dashboard-ui\wizardlivetvguide.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardlivetvtuner.html">
<Link>Resources\dashboard-ui\wizardlivetvtuner.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardsettings.html">
<Link>Resources\dashboard-ui\wizardsettings.html</Link>
</BundleResource>
@ -800,6 +788,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\staticbackdrops.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\staticbackdrops.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\thememediaplayer.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\thememediaplayer.js</Link>
</BundleResource>
@ -1253,6 +1244,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\empty.png">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\empty.png</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingbutton.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingbutton.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css</Link>
</BundleResource>
@ -1736,9 +1730,15 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\iap.js">
<Link>Resources\dashboard-ui\components\iap.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\maintabsmanager.js">
<Link>Resources\dashboard-ui\components\maintabsmanager.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\remotecontrol.js">
<Link>Resources\dashboard-ui\components\remotecontrol.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tunerpicker.js">
<Link>Resources\dashboard-ui\components\tunerpicker.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\viewcontainer-lite.js">
<Link>Resources\dashboard-ui\components\viewcontainer-lite.js</Link>
</BundleResource>
@ -2141,8 +2141,8 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\librarysettings.js">
<Link>Resources\dashboard-ui\dashboard\librarysettings.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\livetvtunerprovider-satip.js">
<Link>Resources\dashboard-ui\dashboard\livetvtunerprovider-satip.js</Link>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\livetvtuner.js">
<Link>Resources\dashboard-ui\dashboard\livetvtuner.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\logpage.js">
<Link>Resources\dashboard-ui\dashboard\logpage.js</Link>
@ -2171,6 +2171,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\selectmenu.js">
<Link>Resources\dashboard-ui\legacy\selectmenu.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\offline\offline.html">
<Link>Resources\dashboard-ui\offline\offline.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\offline\offline.js">
<Link>Resources\dashboard-ui\offline\offline.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\addpluginpage.js">
<Link>Resources\dashboard-ui\scripts\addpluginpage.js</Link>
</BundleResource>
@ -2189,9 +2195,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channels.js">
<Link>Resources\dashboard-ui\scripts\channels.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channelslatest.js">
<Link>Resources\dashboard-ui\scripts\channelslatest.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\connectlogin.js">
<Link>Resources\dashboard-ui\scripts\connectlogin.js</Link>
</BundleResource>
@ -2306,12 +2309,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvsuggested.js">
<Link>Resources\dashboard-ui\scripts\livetvsuggested.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js">
<Link>Resources\dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtunerprovider-m3u.js">
<Link>Resources\dashboard-ui\scripts\livetvtunerprovider-m3u.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\localsync.js">
<Link>Resources\dashboard-ui\scripts\localsync.js</Link>
</BundleResource>
@ -2516,12 +2513,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardcontroller.js">
<Link>Resources\dashboard-ui\scripts\wizardcontroller.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardlivetvguide.js">
<Link>Resources\dashboard-ui\scripts\wizardlivetvguide.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardlivetvtuner.js">
<Link>Resources\dashboard-ui\scripts\wizardlivetvtuner.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardsettings.js">
<Link>Resources\dashboard-ui\scripts\wizardsettings.js</Link>
</BundleResource>
@ -2576,6 +2567,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\es.json">
<Link>Resources\dashboard-ui\strings\es.json</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\fa.json">
<Link>Resources\dashboard-ui\strings\fa.json</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\fi.json">
<Link>Resources\dashboard-ui\strings\fi.json</Link>
</BundleResource>

View File

@ -87,10 +87,6 @@ namespace MediaBrowser.Server.Mac
throw new NotImplementedException();
}
protected override void EnableLoopbackInternal(string appName)
{
}
public override bool SupportsRunningAsService
{
get

View File

@ -1,13 +1,14 @@
using Emby.Common.Implementations.IO;
using MediaBrowser.Model.Logging;
using Mono.Unix.Native;
using MediaBrowser.Model.System;
namespace Emby.Server.Mac.Native
{
public class MonoFileSystem : ManagedFileSystem
{
public MonoFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, string tempPath)
: base(logger, supportsAsyncFileStreams, enableManagedInvalidFileNameChars, true, tempPath)
public MonoFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath)
: base(logger, environmentInfo, tempPath)
{
}

View File

@ -1,3 +1,3 @@
using System.Reflection;
[assembly: AssemblyVersion("3.2.8.12")]
[assembly: AssemblyVersion("3.2.8.13")]