Merge pull request #2554 from MediaBrowser/beta

Beta
This commit is contained in:
Luke 2017-03-29 02:25:37 -04:00 committed by GitHub
commit 88e3fcfdc7
133 changed files with 2268 additions and 1864 deletions

View File

@ -6,7 +6,7 @@ using System.Security;
using System.Text; using System.Text;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
namespace MediaBrowser.ServerApplication.Native namespace Emby.Common.Implementations.IO
{ {
public class LnkShortcutHandler :IShortcutHandler public class LnkShortcutHandler :IShortcutHandler
{ {
@ -35,7 +35,6 @@ namespace MediaBrowser.ServerApplication.Native
/// <summary> /// <summary>
/// Class NativeMethods /// Class NativeMethods
/// </summary> /// </summary>
[SuppressUnmanagedCodeSecurity]
public static class NativeMethods public static class NativeMethods
{ {
/// <summary> /// <summary>

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.System;
namespace Emby.Common.Implementations.IO namespace Emby.Common.Implementations.IO
{ {
@ -18,17 +19,21 @@ namespace Emby.Common.Implementations.IO
private readonly bool _supportsAsyncFileStreams; private readonly bool _supportsAsyncFileStreams;
private char[] _invalidFileNameChars; private char[] _invalidFileNameChars;
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>(); private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private bool EnableFileSystemRequestConcat = true; private bool EnableFileSystemRequestConcat;
private string _tempPath; private string _tempPath;
public ManagedFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, bool enableFileSystemRequestConcat, string tempPath) public ManagedFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath)
{ {
Logger = logger; Logger = logger;
_supportsAsyncFileStreams = supportsAsyncFileStreams; _supportsAsyncFileStreams = true;
_tempPath = tempPath; _tempPath = tempPath;
EnableFileSystemRequestConcat = enableFileSystemRequestConcat;
SetInvalidFileNameChars(enableManagedInvalidFileNameChars); // On Linux, this needs to be true or symbolic links are ignored
EnableFileSystemRequestConcat = environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows &&
environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.OSX;
SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
} }
public void AddShortcutHandler(IShortcutHandler handler) public void AddShortcutHandler(IShortcutHandler handler)

View File

@ -2,6 +2,7 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Emby.Common.Implementations.Networking; using Emby.Common.Implementations.Networking;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -59,7 +60,7 @@ namespace Emby.Common.Implementations.Net
#if NET46 #if NET46
Socket.Close(); Socket.Close();
#else #else
Socket.Dispose(); Socket.Dispose();
#endif #endif
} }
@ -96,6 +97,46 @@ namespace Emby.Common.Implementations.Net
_acceptor.StartAccept(); _acceptor.StartAccept();
} }
#if NET46
public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
{
var options = TransmitFileOptions.UseKernelApc;
var completionSource = new TaskCompletionSource<bool>();
var result = Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), new Tuple<Socket, string, TaskCompletionSource<bool>>(Socket, path, completionSource));
return completionSource.Task;
}
private void FileSendCallback(IAsyncResult ar)
{
// Retrieve the socket from the state object.
Tuple<Socket, string, TaskCompletionSource<bool>> data = (Tuple<Socket, string, TaskCompletionSource<bool>>)ar.AsyncState;
var client = data.Item1;
var path = data.Item2;
var taskCompletion = data.Item3;
// Complete sending the data to the remote device.
try {
client.EndSendFile(ar);
taskCompletion.TrySetResult(true);
}
catch(SocketException ex){
_logger.Info("Socket.SendFile failed for {0}. error code {1}", path, ex.SocketErrorCode);
taskCompletion.TrySetException(ex);
}catch(Exception ex){
taskCompletion.TrySetException(ex);
}
}
#else
public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
#endif
public void Dispose() public void Dispose()
{ {
Socket.Dispose(); Socket.Dispose();

View File

@ -52,6 +52,18 @@ namespace Emby.Common.Implementations.Net
{ {
throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex);
} }
catch (ArgumentException ex)
{
if (dualMode)
{
// Mono for BSD incorrectly throws ArgumentException instead of SocketException
throw new SocketCreateException("AddressFamilyNotSupported", ex);
}
else
{
throw;
}
}
} }
public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort) public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort)
@ -59,6 +71,7 @@ namespace Emby.Common.Implementations.Net
if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", "remotePort"); if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", "remotePort");
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
try try
{ {
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
@ -96,10 +109,31 @@ namespace Emby.Common.Implementations.Net
} }
} }
public ISocket CreateUdpBroadcastSocket(int localPort)
{
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
try
{
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
return new UdpSocket(retVal, localPort, IPAddress.Any);
}
catch
{
if (retVal != null)
retVal.Dispose();
throw;
}
}
/// <summary> /// <summary>
/// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port. /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port.
/// </summary> /// </summary>
/// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns> /// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns>
public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort) public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort)
{ {
if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort");

View File

@ -16,12 +16,23 @@ namespace Emby.Common.Implementations.Net
internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket internal sealed class UdpSocket : DisposableManagedObjectBase, ISocket
{ {
#region Fields
private Socket _Socket; private Socket _Socket;
private int _LocalPort; private int _LocalPort;
#endregion
private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
{
SocketFlags = SocketFlags.None
};
private readonly SocketAsyncEventArgs _sendSocketAsyncEventArgs = new SocketAsyncEventArgs()
{
SocketFlags = SocketFlags.None
};
private TaskCompletionSource<SocketReceiveResult> _currentReceiveTaskCompletionSource;
private TaskCompletionSource<int> _currentSendTaskCompletionSource;
private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
public UdpSocket(Socket socket, int localPort, IPAddress ip) public UdpSocket(Socket socket, int localPort, IPAddress ip)
{ {
@ -32,6 +43,61 @@ namespace Emby.Common.Implementations.Net
LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); LocalIPAddress = NetworkManager.ToIpAddressInfo(ip);
_Socket.Bind(new IPEndPoint(ip, _LocalPort)); _Socket.Bind(new IPEndPoint(ip, _LocalPort));
InitReceiveSocketAsyncEventArgs();
}
private void InitReceiveSocketAsyncEventArgs()
{
var receiveBuffer = new byte[8192];
_receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
_receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed;
var sendBuffer = new byte[8192];
_sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length);
_sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed;
}
private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
{
var tcs = _currentReceiveTaskCompletionSource;
if (tcs != null)
{
_currentReceiveTaskCompletionSource = null;
if (e.SocketError == SocketError.Success)
{
tcs.TrySetResult(new SocketReceiveResult
{
Buffer = e.Buffer,
ReceivedBytes = e.BytesTransferred,
RemoteEndPoint = ToIpEndPointInfo(e.RemoteEndPoint as IPEndPoint),
LocalIPAddress = LocalIPAddress
});
}
else
{
tcs.TrySetException(new Exception("SocketError: " + e.SocketError));
}
}
}
private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
{
var tcs = _currentSendTaskCompletionSource;
if (tcs != null)
{
_currentSendTaskCompletionSource = null;
if (e.SocketError == SocketError.Success)
{
tcs.TrySetResult(e.BytesTransferred);
}
else
{
tcs.TrySetException(new Exception("SocketError: " + e.SocketError));
}
}
} }
public UdpSocket(Socket socket, IpEndPointInfo endPoint) public UdpSocket(Socket socket, IpEndPointInfo endPoint)
@ -40,6 +106,8 @@ namespace Emby.Common.Implementations.Net
_Socket = socket; _Socket = socket;
_Socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); _Socket.Connect(NetworkManager.ToIPEndPoint(endPoint));
InitReceiveSocketAsyncEventArgs();
} }
public IpAddressInfo LocalIPAddress public IpAddressInfo LocalIPAddress
@ -48,32 +116,33 @@ namespace Emby.Common.Implementations.Net
private set; private set;
} }
#region ISocket Members
public Task<SocketReceiveResult> ReceiveAsync(CancellationToken cancellationToken) public Task<SocketReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
var tcs = new TaskCompletionSource<SocketReceiveResult>(); var tcs = new TaskCompletionSource<SocketReceiveResult>();
EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0);
var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); var state = new AsyncReceiveState(_Socket, receivedFromEndPoint);
state.TaskCompletionSource = tcs; state.TaskCompletionSource = tcs;
#if NETSTANDARD1_6 cancellationToken.Register(() => tcs.TrySetCanceled());
_Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
.ContinueWith((task, asyncState) => _receiveSocketAsyncEventArgs.RemoteEndPoint = receivedFromEndPoint;
_currentReceiveTaskCompletionSource = tcs;
try
{
var willRaiseEvent = _Socket.ReceiveFromAsync(_receiveSocketAsyncEventArgs);
if (!willRaiseEvent)
{ {
if (task.Status != TaskStatus.Faulted) _receiveSocketAsyncEventArgs_Completed(this, _receiveSocketAsyncEventArgs);
{ }
var receiveState = asyncState as AsyncReceiveState; }
receiveState.RemoteEndPoint = task.Result.RemoteEndPoint; catch (Exception ex)
ProcessResponse(receiveState, () => task.Result.ReceivedBytes, LocalIPAddress); {
} tcs.TrySetException(ex);
}, state); }
#else
_Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.RemoteEndPoint, ProcessResponse, state);
#endif
return tcs.Task; return tcs.Task;
} }
@ -129,15 +198,48 @@ namespace Emby.Common.Implementations.Net
taskSource.TrySetException(ex); taskSource.TrySetException(ex);
} }
//_Socket.SendTo(messageData, new System.Net.IPEndPoint(IPAddress.Parse(RemoteEndPoint.IPAddress), RemoteEndPoint.Port));
return taskSource.Task; return taskSource.Task;
#endif #endif
//ThrowIfDisposed();
//if (buffer == null) throw new ArgumentNullException("messageData");
//if (endPoint == null) throw new ArgumentNullException("endPoint");
//cancellationToken.ThrowIfCancellationRequested();
//var tcs = new TaskCompletionSource<int>();
//cancellationToken.Register(() => tcs.TrySetCanceled());
//_sendSocketAsyncEventArgs.SetBuffer(buffer, 0, size);
//_sendSocketAsyncEventArgs.RemoteEndPoint = NetworkManager.ToIPEndPoint(endPoint);
//_currentSendTaskCompletionSource = tcs;
//var willRaiseEvent = _Socket.SendAsync(_sendSocketAsyncEventArgs);
//if (!willRaiseEvent)
//{
// _sendSocketAsyncEventArgs_Completed(this, _sendSocketAsyncEventArgs);
//}
//return tcs.Task;
} }
#endregion public async Task SendWithLockAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken)
{
ThrowIfDisposed();
#region Overrides //await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
await SendAsync(buffer, size, endPoint, cancellationToken).ConfigureAwait(false);
}
finally
{
//_sendLock.Release();
}
}
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
@ -146,44 +248,19 @@ namespace Emby.Common.Implementations.Net
var socket = _Socket; var socket = _Socket;
if (socket != null) if (socket != null)
socket.Dispose(); socket.Dispose();
}
}
#endregion _sendLock.Dispose();
#region Private Methods var tcs = _currentReceiveTaskCompletionSource;
if (tcs != null)
private static void ProcessResponse(AsyncReceiveState state, Func<int> receiveData, IpAddressInfo localIpAddress) {
{ tcs.TrySetCanceled();
try }
{ var sendTcs = _currentSendTaskCompletionSource;
var bytesRead = receiveData(); if (sendTcs != null)
{
var ipEndPoint = state.RemoteEndPoint as IPEndPoint; sendTcs.TrySetCanceled();
state.TaskCompletionSource.SetResult( }
new SocketReceiveResult
{
Buffer = state.Buffer,
ReceivedBytes = bytesRead,
RemoteEndPoint = ToIpEndPointInfo(ipEndPoint),
LocalIPAddress = localIpAddress
}
);
}
catch (ObjectDisposedException)
{
state.TaskCompletionSource.SetCanceled();
}
catch (SocketException se)
{
if (se.SocketErrorCode != SocketError.Interrupted && se.SocketErrorCode != SocketError.OperationAborted && se.SocketErrorCode != SocketError.Shutdown)
state.TaskCompletionSource.SetException(se);
else
state.TaskCompletionSource.SetCanceled();
}
catch (Exception ex)
{
state.TaskCompletionSource.SetException(ex);
} }
} }
@ -227,10 +304,6 @@ namespace Emby.Common.Implementations.Net
#endif #endif
} }
#endregion
#region Private Classes
private class AsyncReceiveState private class AsyncReceiveState
{ {
public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint) public AsyncReceiveState(Socket socket, EndPoint remoteEndPoint)
@ -247,8 +320,5 @@ namespace Emby.Common.Implementations.Net
public TaskCompletionSource<SocketReceiveResult> TaskCompletionSource { get; set; } public TaskCompletionSource<SocketReceiveResult> TaskCompletionSource { get; set; }
} }
#endregion
} }
} }

View File

@ -208,7 +208,7 @@ namespace Emby.Dlna.Didl
var targetHeight = streamInfo.TargetHeight; var targetHeight = streamInfo.TargetHeight;
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container, var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
streamInfo.VideoCodec, streamInfo.TargetVideoCodec,
streamInfo.TargetAudioCodec, streamInfo.TargetAudioCodec,
targetWidth, targetWidth,
targetHeight, targetHeight,
@ -352,7 +352,7 @@ namespace Emby.Dlna.Didl
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container, var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
streamInfo.TargetAudioCodec, streamInfo.TargetAudioCodec,
streamInfo.VideoCodec, streamInfo.TargetVideoCodec,
streamInfo.TargetAudioBitrate, streamInfo.TargetAudioBitrate,
targetWidth, targetWidth,
targetHeight, targetHeight,

View File

@ -542,7 +542,7 @@ namespace Emby.Dlna.PlayTo
{ {
var list = new ContentFeatureBuilder(profile) var list = new ContentFeatureBuilder(profile)
.BuildVideoHeader(streamInfo.Container, .BuildVideoHeader(streamInfo.Container,
streamInfo.VideoCodec, streamInfo.TargetVideoCodec,
streamInfo.TargetAudioCodec, streamInfo.TargetAudioCodec,
streamInfo.TargetWidth, streamInfo.TargetWidth,
streamInfo.TargetHeight, streamInfo.TargetHeight,

View File

@ -238,7 +238,7 @@ namespace Emby.Drawing
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]); var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer); var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
var imageProcessingLockTaken = false; //var imageProcessingLockTaken = false;
try try
{ {
@ -253,9 +253,9 @@ namespace Emby.Drawing
var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
_fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath)); _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false); //await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true; //imageProcessingLockTaken = true;
_imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat); _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
CopyFile(tmpPath, cacheFilePath); CopyFile(tmpPath, cacheFilePath);
@ -273,13 +273,13 @@ namespace Emby.Drawing
// Just spit out the original file if all the options are default // Just spit out the original file if all the options are default
return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
} }
finally //finally
{ //{
if (imageProcessingLockTaken) // if (imageProcessingLockTaken)
{ // {
_imageProcessingSemaphore.Release(); // _imageProcessingSemaphore.Release();
} // }
} //}
} }
private void CopyFile(string src, string destination) private void CopyFile(string src, string destination)

View File

@ -61,7 +61,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Common.Implementations; using Emby.Common.Implementations;
using Emby.Common.Implementations.Archiving; using Emby.Common.Implementations.Archiving;
using Emby.Common.Implementations.Networking; using Emby.Common.Implementations.IO;
using Emby.Common.Implementations.Reflection; using Emby.Common.Implementations.Reflection;
using Emby.Common.Implementations.Serialization; using Emby.Common.Implementations.Serialization;
using Emby.Common.Implementations.TextEncoding; using Emby.Common.Implementations.TextEncoding;
@ -93,7 +93,7 @@ using Emby.Server.Implementations.Social;
using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Collections;
using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.EntryPoints; using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.FileOrganization; using Emby.Server.Implementations.FileOrganization;
using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.HttpServer.Security;
@ -107,7 +107,6 @@ using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations; using Emby.Server.Implementations;
using Emby.Server.Implementations.ServerManager; using Emby.Server.Implementations.ServerManager;
using Emby.Server.Implementations.Session; using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.Social;
using Emby.Server.Implementations.TV; using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates; using Emby.Server.Implementations.Updates;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
@ -294,6 +293,13 @@ namespace Emby.Server.Core
ImageEncoder = imageEncoder; ImageEncoder = imageEncoder;
SetBaseExceptionMessage(); SetBaseExceptionMessage();
if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
{
fileSystem.AddShortcutHandler(new LnkShortcutHandler());
}
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
} }
private Version _version; private Version _version;
@ -606,7 +612,7 @@ namespace Emby.Server.Core
CertificatePath = GetCertificatePath(true); CertificatePath = GetCertificatePath(true);
Certificate = GetCertificate(CertificatePath); Certificate = GetCertificate(CertificatePath);
HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, SupportsDualModeSockets); HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, FileSystemManager, SupportsDualModeSockets);
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
RegisterSingleInstance(HttpServer, false); RegisterSingleInstance(HttpServer, false);
progress.Report(10); progress.Report(10);
@ -796,17 +802,25 @@ namespace Emby.Server.Core
info.FFMpegFilename = "ffmpeg"; info.FFMpegFilename = "ffmpeg";
info.FFProbeFilename = "ffprobe"; info.FFProbeFilename = "ffprobe";
info.ArchiveType = "7z"; info.ArchiveType = "7z";
info.Version = "20160215"; info.Version = "20170308";
info.DownloadUrls = GetLinuxDownloadUrls(); info.DownloadUrls = GetLinuxDownloadUrls();
} }
else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
{ {
info.FFMpegFilename = "ffmpeg.exe"; info.FFMpegFilename = "ffmpeg.exe";
info.FFProbeFilename = "ffprobe.exe"; info.FFProbeFilename = "ffprobe.exe";
info.Version = "20160410"; info.Version = "20170308";
info.ArchiveType = "7z"; info.ArchiveType = "7z";
info.DownloadUrls = GetWindowsDownloadUrls(); info.DownloadUrls = GetWindowsDownloadUrls();
} }
else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
{
info.FFMpegFilename = "ffmpeg";
info.FFProbeFilename = "ffprobe";
info.ArchiveType = "7z";
info.Version = "20170308";
info.DownloadUrls = GetMacDownloadUrls();
}
else else
{ {
// No version available - user requirement // No version available - user requirement
@ -816,6 +830,20 @@ namespace Emby.Server.Core
return info; return info;
} }
private string[] GetMacDownloadUrls()
{
switch (EnvironmentInfo.SystemArchitecture)
{
case Architecture.X64:
return new[]
{
"https://embydata.com/downloads/ffmpeg/osx/ffmpeg-x64-20170308.7z"
};
}
return new string[] { };
}
private string[] GetWindowsDownloadUrls() private string[] GetWindowsDownloadUrls()
{ {
switch (EnvironmentInfo.SystemArchitecture) switch (EnvironmentInfo.SystemArchitecture)
@ -823,12 +851,12 @@ namespace Emby.Server.Core
case Architecture.X64: case Architecture.X64:
return new[] return new[]
{ {
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win64.7z" "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win64.7z"
}; };
case Architecture.X86: case Architecture.X86:
return new[] return new[]
{ {
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160410-win32.7z" "https://embydata.com/downloads/ffmpeg/windows/ffmpeg-20170308-win32.7z"
}; };
} }
@ -842,12 +870,12 @@ namespace Emby.Server.Core
case Architecture.X64: case Architecture.X64:
return new[] return new[]
{ {
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z" "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-64bit-static.7z"
}; };
case Architecture.X86: case Architecture.X86:
return new[] return new[]
{ {
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z" "https://embydata.com/downloads/ffmpeg/linux/ffmpeg-git-20170301-32bit-static.7z"
}; };
} }
@ -1714,14 +1742,8 @@ namespace Emby.Server.Core
((IProcess)sender).Dispose(); ((IProcess)sender).Dispose();
} }
public void EnableLoopback(string appName) public virtual void EnableLoopback(string appName)
{ {
EnableLoopbackInternal(appName);
}
protected virtual void EnableLoopbackInternal(string appName)
{
} }
private void RegisterModules() private void RegisterModules()

View File

@ -45,6 +45,7 @@ namespace Emby.Server.Core
IXmlSerializer xml, IXmlSerializer xml,
IEnvironmentInfo environment, IEnvironmentInfo environment,
ICertificate certificate, ICertificate certificate,
IFileSystem fileSystem,
bool enableDualModeSockets) bool enableDualModeSockets)
{ {
var logger = logManager.GetLogger("HttpServer"); var logger = logManager.GetLogger("HttpServer");
@ -65,7 +66,8 @@ namespace Emby.Server.Core
certificate, certificate,
new StreamFactory(), new StreamFactory(),
GetParseFn, GetParseFn,
enableDualModeSockets); enableDualModeSockets,
fileSystem);
} }
private static Func<string, object> GetParseFn(Type propertyType) private static Func<string, object> GetParseFn(Type propertyType)

View File

@ -421,17 +421,6 @@ namespace Emby.Server.Core.IO
var path = e.FullPath; var path = e.FullPath;
// For deletes, use the parent path
if (e.ChangeType == WatcherChangeTypes.Deleted)
{
var parentPath = Path.GetDirectoryName(path);
if (!string.IsNullOrWhiteSpace(parentPath))
{
path = parentPath;
}
}
ReportFileSystemChanged(path); ReportFileSystemChanged(path);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -22,13 +23,15 @@ namespace Emby.Server.Implementations.Data
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IApplicationPaths _appPaths;
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem) public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_itemRepo = itemRepo; _itemRepo = itemRepo;
_logger = logger; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_appPaths = appPaths;
} }
public string Name public string Name
@ -150,13 +153,27 @@ namespace Emby.Server.Implementations.Data
try try
{ {
if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path)) var isPathInLibrary = false;
if (allLibraryPaths.Any(i => path.StartsWith(i, StringComparison.Ordinal)) ||
allLibraryPaths.Contains(path, StringComparer.Ordinal) ||
path.StartsWith(_appPaths.ProgramDataPath, StringComparison.Ordinal))
{ {
continue; isPathInLibrary = true;
if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
{
continue;
}
} }
var libraryItem = _libraryManager.GetItemById(item.Item1); var libraryItem = _libraryManager.GetItemById(item.Item1);
if (libraryItem == null)
{
continue;
}
if (libraryItem.IsTopParent) if (libraryItem.IsTopParent)
{ {
continue; continue;
@ -180,7 +197,14 @@ namespace Emby.Server.Implementations.Data
continue; continue;
} }
_logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty); if (isPathInLibrary)
{
_logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
}
else
{
_logger.Info("Deleting item from database {0} because path is no longer in the server library. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
}
await libraryItem.OnFileDeleted().ConfigureAwait(false); await libraryItem.OnFileDeleted().ConfigureAwait(false);
} }

View File

@ -3204,6 +3204,40 @@ namespace Emby.Server.Implementations.Data
} }
} }
private bool IsAlphaNumeric(string str)
{
if (string.IsNullOrWhiteSpace(str))
return false;
for (int i = 0; i < str.Length; i++)
{
if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
return false;
}
return true;
}
private bool IsValidType(string value)
{
return IsAlphaNumeric(value);
}
private bool IsValidMediaType(string value)
{
return IsAlphaNumeric(value);
}
private bool IsValidId(string value)
{
return IsAlphaNumeric(value);
}
private bool IsValidPersonType(string value)
{
return IsAlphaNumeric(value);
}
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "") private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
{ {
if (query.IsResumable ?? false) if (query.IsResumable ?? false)
@ -3423,9 +3457,9 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@ChannelId", query.ChannelIds[0]); statement.TryBind("@ChannelId", query.ChannelIds[0]);
} }
} }
if (query.ChannelIds.Length > 1) else if (query.ChannelIds.Length > 1)
{ {
var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i + "'").ToArray()); var inClause = string.Join(",", query.ChannelIds.Where(IsValidId).Select(i => "'" + i + "'").ToArray());
whereClauses.Add(string.Format("ChannelId in ({0})", inClause)); whereClauses.Add(string.Format("ChannelId in ({0})", inClause));
} }
@ -4157,17 +4191,18 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))"); whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))");
} }
} }
if (query.MediaTypes.Length == 1) var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray();
if (queryMediaTypes.Length == 1)
{ {
whereClauses.Add("MediaType=@MediaTypes"); whereClauses.Add("MediaType=@MediaTypes");
if (statement != null) if (statement != null)
{ {
statement.TryBind("@MediaTypes", query.MediaTypes[0]); statement.TryBind("@MediaTypes", queryMediaTypes[0]);
} }
} }
if (query.MediaTypes.Length > 1) else if (queryMediaTypes.Length > 1)
{ {
var val = string.Join(",", query.MediaTypes.Select(i => "'" + i + "'").ToArray()); var val = string.Join(",", queryMediaTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("MediaType in (" + val + ")"); whereClauses.Add("MediaType in (" + val + ")");
} }
@ -4273,7 +4308,9 @@ namespace Emby.Server.Implementations.Data
//var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0; //var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0;
var enableItemsByName = query.IncludeItemsByName ?? false; var enableItemsByName = query.IncludeItemsByName ?? false;
if (query.TopParentIds.Length == 1) var queryTopParentIds = query.TopParentIds.Where(IsValidId).ToArray();
if (queryTopParentIds.Length == 1)
{ {
if (enableItemsByName) if (enableItemsByName)
{ {
@ -4289,12 +4326,12 @@ namespace Emby.Server.Implementations.Data
} }
if (statement != null) if (statement != null)
{ {
statement.TryBind("@TopParentId", query.TopParentIds[0]); statement.TryBind("@TopParentId", queryTopParentIds[0]);
} }
} }
if (query.TopParentIds.Length > 1) else if (queryTopParentIds.Length > 1)
{ {
var val = string.Join(",", query.TopParentIds.Select(i => "'" + i + "'").ToArray()); var val = string.Join(",", queryTopParentIds.Select(i => "'" + i + "'").ToArray());
if (enableItemsByName) if (enableItemsByName)
{ {
@ -4544,7 +4581,7 @@ namespace Emby.Server.Implementations.Data
return result; return result;
} }
return new[] { value }; return new[] { value }.Where(IsValidType);
} }
public async Task DeleteItem(Guid id, CancellationToken cancellationToken) public async Task DeleteItem(Guid id, CancellationToken cancellationToken)
@ -4696,31 +4733,35 @@ namespace Emby.Server.Implementations.Data
statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidParamValue()); statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidParamValue());
} }
} }
if (query.PersonTypes.Count == 1) var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList();
if (queryPersonTypes.Count == 1)
{ {
whereClauses.Add("PersonType=@PersonType"); whereClauses.Add("PersonType=@PersonType");
if (statement != null) if (statement != null)
{ {
statement.TryBind("@PersonType", query.PersonTypes[0]); statement.TryBind("@PersonType", queryPersonTypes[0]);
} }
} }
if (query.PersonTypes.Count > 1) else if (queryPersonTypes.Count > 1)
{ {
var val = string.Join(",", query.PersonTypes.Select(i => "'" + i + "'").ToArray()); var val = string.Join(",", queryPersonTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("PersonType in (" + val + ")"); whereClauses.Add("PersonType in (" + val + ")");
} }
if (query.ExcludePersonTypes.Count == 1) var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList();
if (queryExcludePersonTypes.Count == 1)
{ {
whereClauses.Add("PersonType<>@PersonType"); whereClauses.Add("PersonType<>@PersonType");
if (statement != null) if (statement != null)
{ {
statement.TryBind("@PersonType", query.ExcludePersonTypes[0]); statement.TryBind("@PersonType", queryExcludePersonTypes[0]);
} }
} }
if (query.ExcludePersonTypes.Count > 1) else if (queryExcludePersonTypes.Count > 1)
{ {
var val = string.Join(",", query.ExcludePersonTypes.Select(i => "'" + i + "'").ToArray()); var val = string.Join(",", queryExcludePersonTypes.Select(i => "'" + i + "'").ToArray());
whereClauses.Add("PersonType not in (" + val + ")"); whereClauses.Add("PersonType not in (" + val + ")");
} }

View File

@ -491,7 +491,7 @@ namespace Emby.Server.Implementations.Dto
} }
} }
//if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess)) if (!(item is LiveTvProgram) || fields.Contains(ItemFields.PlayAccess))
{ {
dto.PlayAccess = item.GetPlayAccess(user); dto.PlayAccess = item.GetPlayAccess(user);
} }
@ -1639,7 +1639,7 @@ namespace Emby.Server.Implementations.Dto
var width = size.Width; var width = size.Width;
var height = size.Height; var height = size.Height;
if (width == 0 || height == 0) if (width.Equals(0) || height.Equals(0))
{ {
return null; return null;
} }

View File

@ -84,7 +84,7 @@
<Compile Include="FileOrganization\NameUtils.cs" /> <Compile Include="FileOrganization\NameUtils.cs" />
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" /> <Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
<Compile Include="FileOrganization\TvFolderOrganizer.cs" /> <Compile Include="FileOrganization\TvFolderOrganizer.cs" />
<Compile Include="HttpServer\GetSwaggerResource.cs" /> <Compile Include="HttpServer\FileWriter.cs" />
<Compile Include="HttpServer\HttpListenerHost.cs" /> <Compile Include="HttpServer\HttpListenerHost.cs" />
<Compile Include="HttpServer\HttpResultFactory.cs" /> <Compile Include="HttpServer\HttpResultFactory.cs" />
<Compile Include="HttpServer\LoggerUtils.cs" /> <Compile Include="HttpServer\LoggerUtils.cs" />
@ -102,7 +102,6 @@
<Compile Include="HttpServer\SocketSharp\WebSocketSharpRequest.cs" /> <Compile Include="HttpServer\SocketSharp\WebSocketSharpRequest.cs" />
<Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" /> <Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" />
<Compile Include="HttpServer\StreamWriter.cs" /> <Compile Include="HttpServer\StreamWriter.cs" />
<Compile Include="HttpServer\SwaggerService.cs" />
<Compile Include="Images\BaseDynamicImageProvider.cs" /> <Compile Include="Images\BaseDynamicImageProvider.cs" />
<Compile Include="IO\FileRefresher.cs" /> <Compile Include="IO\FileRefresher.cs" />
<Compile Include="IO\MbLinkShortcutHandler.cs" /> <Compile Include="IO\MbLinkShortcutHandler.cs" />
@ -170,7 +169,6 @@
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" /> <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" /> <Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" /> <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" /> <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" /> <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" /> <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" />
@ -302,8 +300,8 @@
<HintPath>..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath> <HintPath>..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="MediaBrowser.Naming, Version=1.0.6201.24431, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="MediaBrowser.Naming, Version=1.0.6279.25941, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MediaBrowser.Naming.1.0.4\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath> <HintPath>..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="SQLitePCL.pretty, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="SQLitePCL.pretty, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">

View File

@ -677,20 +677,7 @@ namespace Emby.Server.Implementations.FileOrganization
var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options); var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options);
// MAX_PATH - trailing <NULL> charachter - drive component: 260 - 1 - 3 = 256 var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options);
// Usually newPath would include the drive component, but use 256 to be sure
var maxFilenameLength = 256 - newPath.Length;
if (!newPath.EndsWith(@"\"))
{
// Remove 1 for missing backslash combining path and filename
maxFilenameLength--;
}
// Remove additional 4 chars to prevent PathTooLongException for downloaded subtitles (eg. filename.ext.eng.srt)
maxFilenameLength -= 4;
var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options, maxFilenameLength);
if (string.IsNullOrEmpty(episodeFileName)) if (string.IsNullOrEmpty(episodeFileName))
{ {
@ -742,7 +729,7 @@ namespace Emby.Server.Implementations.FileOrganization
return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName)); return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName));
} }
private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options, int? maxLength) private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options)
{ {
seriesName = _fileSystem.GetValidFilename(seriesName).Trim(); seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
@ -786,32 +773,15 @@ namespace Emby.Server.Implementations.FileOrganization
.Replace("%0e", episodeNumber.ToString("00", _usCulture)) .Replace("%0e", episodeNumber.ToString("00", _usCulture))
.Replace("%00e", episodeNumber.ToString("000", _usCulture)); .Replace("%00e", episodeNumber.ToString("000", _usCulture));
if (maxLength.HasValue && result.Contains("%#")) if (result.Contains("%#"))
{ {
// Substract 3 for the temp token length (%#1, %#2 or %#3) result = result.Replace("%#1", episodeTitle)
int maxRemainingTitleLength = maxLength.Value - result.Length + 3; .Replace("%#2", episodeTitle.Replace(" ", "."))
string shortenedEpisodeTitle = string.Empty; .Replace("%#3", episodeTitle.Replace(" ", "_"));
if (maxRemainingTitleLength > 5)
{
// A title with fewer than 5 letters wouldn't be of much value
shortenedEpisodeTitle = episodeTitle.Substring(0, Math.Min(maxRemainingTitleLength, episodeTitle.Length));
}
result = result.Replace("%#1", shortenedEpisodeTitle)
.Replace("%#2", shortenedEpisodeTitle.Replace(" ", "."))
.Replace("%#3", shortenedEpisodeTitle.Replace(" ", "_"));
} }
if (maxLength.HasValue && result.Length > maxLength.Value) // Finally, call GetValidFilename again in case user customized the episode expression with any invalid filename characters
{ return _fileSystem.GetValidFilename(result).Trim();
// There may be cases where reducing the title length may still not be sufficient to
// stay below maxLength
var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength);
throw new Exception(msg);
}
return result;
} }
private bool IsSameEpisode(string sourcePath, string newPath) private bool IsSameEpisode(string sourcePath, string newPath)

View File

@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Services;
namespace Emby.Server.Implementations.HttpServer
{
public class FileWriter : IHttpResult
{
private ILogger Logger { get; set; }
private string RangeHeader { get; set; }
private bool IsHeadRequest { get; set; }
private long RangeStart { get; set; }
private long RangeEnd { get; set; }
private long RangeLength { get; set; }
private long TotalContentLength { get; set; }
public Action OnComplete { get; set; }
public Action OnError { get; set; }
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
public List<Cookie> Cookies { get; private set; }
public FileShareMode FileShare { get; set; }
/// <summary>
/// The _options
/// </summary>
private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
/// <summary>
/// Gets the options.
/// </summary>
/// <value>The options.</value>
public IDictionary<string, string> Headers
{
get { return _options; }
}
public string Path { get; set; }
public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem)
{
if (string.IsNullOrEmpty(contentType))
{
throw new ArgumentNullException("contentType");
}
Path = path;
Logger = logger;
RangeHeader = rangeHeader;
Headers["Content-Type"] = contentType;
TotalContentLength = fileSystem.GetFileInfo(path).Length;
if (string.IsNullOrWhiteSpace(rangeHeader))
{
Headers["Content-Length"] = TotalContentLength.ToString(UsCulture);
StatusCode = HttpStatusCode.OK;
}
else
{
Headers["Accept-Ranges"] = "bytes";
StatusCode = HttpStatusCode.PartialContent;
SetRangeValues();
}
FileShare = FileShareMode.Read;
Cookies = new List<Cookie>();
}
/// <summary>
/// Sets the range values.
/// </summary>
private void SetRangeValues()
{
var requestedRange = RequestedRanges[0];
// If the requested range is "0-", we can optimize by just doing a stream copy
if (!requestedRange.Value.HasValue)
{
RangeEnd = TotalContentLength - 1;
}
else
{
RangeEnd = requestedRange.Value.Value;
}
RangeStart = requestedRange.Key;
RangeLength = 1 + RangeEnd - RangeStart;
// Content-Length is the length of what we're serving, not the original content
Headers["Content-Length"] = RangeLength.ToString(UsCulture);
Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
}
/// <summary>
/// The _requested ranges
/// </summary>
private List<KeyValuePair<long, long?>> _requestedRanges;
/// <summary>
/// Gets the requested ranges.
/// </summary>
/// <value>The requested ranges.</value>
protected List<KeyValuePair<long, long?>> RequestedRanges
{
get
{
if (_requestedRanges == null)
{
_requestedRanges = new List<KeyValuePair<long, long?>>();
// Example: bytes=0-,32-63
var ranges = RangeHeader.Split('=')[1].Split(',');
foreach (var range in ranges)
{
var vals = range.Split('-');
long start = 0;
long? end = null;
if (!string.IsNullOrEmpty(vals[0]))
{
start = long.Parse(vals[0], UsCulture);
}
if (!string.IsNullOrEmpty(vals[1]))
{
end = long.Parse(vals[1], UsCulture);
}
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
}
}
return _requestedRanges;
}
}
public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken)
{
try
{
// Headers only
if (IsHeadRequest)
{
return;
}
if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
{
Logger.Info("Transmit file {0}", Path);
await response.TransmitFile(Path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
return;
}
await response.TransmitFile(Path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false);
}
finally
{
if (OnComplete != null)
{
OnComplete();
}
}
}
public string ContentType { get; set; }
public IRequest RequestContext { get; set; }
public object Response { get; set; }
public int Status { get; set; }
public HttpStatusCode StatusCode
{
get { return (HttpStatusCode)Status; }
set { Status = (int)value; }
}
public string StatusDescription { get; set; }
}
}

View File

@ -1,17 +0,0 @@
using MediaBrowser.Model.Services;
namespace Emby.Server.Implementations.HttpServer
{
/// <summary>
/// Class GetDashboardResource
/// </summary>
[Route("/swagger-ui/{ResourceName*}", "GET")]
public class GetSwaggerResource
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string ResourceName { get; set; }
}
}

View File

@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.HttpServer
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly ICryptoProvider _cryptoProvider; private readonly ICryptoProvider _cryptoProvider;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IXmlSerializer _xmlSerializer; private readonly IXmlSerializer _xmlSerializer;
private readonly ICertificate _certificate; private readonly ICertificate _certificate;
@ -70,8 +71,7 @@ namespace Emby.Server.Implementations.HttpServer
ILogger logger, ILogger logger,
IServerConfigurationManager config, IServerConfigurationManager config,
string serviceName, string serviceName,
string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets) string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem)
: base()
{ {
Instance = this; Instance = this;
@ -89,6 +89,7 @@ namespace Emby.Server.Implementations.HttpServer
_streamFactory = streamFactory; _streamFactory = streamFactory;
_funcParseFn = funcParseFn; _funcParseFn = funcParseFn;
_enableDualModeSockets = enableDualModeSockets; _enableDualModeSockets = enableDualModeSockets;
_fileSystem = fileSystem;
_config = config; _config = config;
_logger = logger; _logger = logger;
@ -226,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer
_cryptoProvider, _cryptoProvider,
_streamFactory, _streamFactory,
_enableDualModeSockets, _enableDualModeSockets,
GetRequest); GetRequest,
_fileSystem);
} }
private IHttpRequest GetRequest(HttpListenerContext httpContext) private IHttpRequest GetRequest(HttpListenerContext httpContext)

View File

@ -474,10 +474,6 @@ namespace Emby.Server.Implementations.HttpServer
{ {
throw new ArgumentNullException("cacheKey"); throw new ArgumentNullException("cacheKey");
} }
if (options.ContentFactory == null)
{
throw new ArgumentNullException("factoryFn");
}
var key = cacheKey.ToString("N"); var key = cacheKey.ToString("N");
@ -560,30 +556,44 @@ namespace Emby.Server.Implementations.HttpServer
{ {
var rangeHeader = requestContext.Headers.Get("Range"); var rangeHeader = requestContext.Headers.Get("Range");
var stream = await factoryFn().ConfigureAwait(false); if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path))
{
return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
{
OnComplete = options.OnComplete,
OnError = options.OnError,
FileShare = options.FileShare
};
}
if (!string.IsNullOrEmpty(rangeHeader)) if (!string.IsNullOrEmpty(rangeHeader))
{ {
var stream = await factoryFn().ConfigureAwait(false);
return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger)
{ {
OnComplete = options.OnComplete OnComplete = options.OnComplete
}; };
} }
else
responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
if (isHeadRequest)
{ {
stream.Dispose(); var stream = await factoryFn().ConfigureAwait(false);
return GetHttpResult(new byte[] { }, contentType, true); responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
if (isHeadRequest)
{
stream.Dispose();
return GetHttpResult(new byte[] { }, contentType, true);
}
return new StreamWriter(stream, contentType, _logger)
{
OnComplete = options.OnComplete,
OnError = options.OnError
};
} }
return new StreamWriter(stream, contentType, _logger)
{
OnComplete = options.OnComplete,
OnError = options.OnError
};
} }
using (var stream = await factoryFn().ConfigureAwait(false)) using (var stream = await factoryFn().ConfigureAwait(false))

View File

@ -27,10 +27,11 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly ICryptoProvider _cryptoProvider; private readonly ICryptoProvider _cryptoProvider;
private readonly IStreamFactory _streamFactory; private readonly IStreamFactory _streamFactory;
private readonly IFileSystem _fileSystem;
private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory; private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory;
private readonly bool _enableDualMode; private readonly bool _enableDualMode;
public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory) public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem)
{ {
_logger = logger; _logger = logger;
_certificate = certificate; _certificate = certificate;
@ -42,6 +43,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
_streamFactory = streamFactory; _streamFactory = streamFactory;
_enableDualMode = enableDualMode; _enableDualMode = enableDualMode;
_httpRequestFactory = httpRequestFactory; _httpRequestFactory = httpRequestFactory;
_fileSystem = fileSystem;
} }
public Action<Exception, IRequest, bool> ErrorHandler { get; set; } public Action<Exception, IRequest, bool> ErrorHandler { get; set; }
@ -54,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void Start(IEnumerable<string> urlPrefixes) public void Start(IEnumerable<string> urlPrefixes)
{ {
if (_listener == null) if (_listener == null)
_listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider); _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem);
_listener.EnableDualMode = _enableDualMode; _listener.EnableDualMode = _enableDualMode;

View File

@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using SocketHttpListener.Net; using SocketHttpListener.Net;
using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
@ -189,5 +192,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void ClearCookies() public void ClearCookies()
{ {
} }
public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
{
return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken);
}
} }
} }

View File

@ -1,47 +0,0 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Net;
using System.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
namespace Emby.Server.Implementations.HttpServer
{
public class SwaggerService : IService, IRequiresRequest
{
private readonly IServerApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
public SwaggerService(IServerApplicationPaths appPaths, IFileSystem fileSystem, IHttpResultFactory resultFactory)
{
_appPaths = appPaths;
_fileSystem = fileSystem;
_resultFactory = resultFactory;
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetSwaggerResource request)
{
var swaggerDirectory = Path.Combine(_appPaths.ApplicationResourcesPath, "swagger-ui");
var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', _fileSystem.DirectorySeparatorChar));
return _resultFactory.GetStaticFileResult(Request, requestedFile).Result;
}
/// <summary>
/// Gets or sets the result factory.
/// </summary>
/// <value>The result factory.</value>
private readonly IHttpResultFactory _resultFactory;
/// <summary>
/// Gets or sets the request context.
/// </summary>
/// <value>The request context.</value>
public IRequest Request { get; set; }
}
}

View File

@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Images
{ {
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
} }
if (item is Playlist || item is MusicGenre) if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre)
{ {
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
} }

View File

@ -513,6 +513,11 @@ namespace Emby.Server.Implementations.Library
} }
public Guid GetNewItemId(string key, Type type) public Guid GetNewItemId(string key, Type type)
{
return GetNewItemIdInternal(key, type, false);
}
private Guid GetNewItemIdInternal(string key, Type type, bool forceCaseInsensitive)
{ {
if (string.IsNullOrWhiteSpace(key)) if (string.IsNullOrWhiteSpace(key))
{ {
@ -531,7 +536,7 @@ namespace Emby.Server.Implementations.Library
.Replace("/", "\\"); .Replace("/", "\\");
} }
if (!ConfigurationManager.Configuration.EnableCaseSensitiveItemIds) if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
{ {
key = key.ToLower(); key = key.ToLower();
} }
@ -865,7 +870,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Person}.</returns> /// <returns>Task{Person}.</returns>
public Person GetPerson(string name) public Person GetPerson(string name)
{ {
return CreateItemByName<Person>(Person.GetPath(name), name); return CreateItemByName<Person>(Person.GetPath, name);
} }
/// <summary> /// <summary>
@ -875,7 +880,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Studio}.</returns> /// <returns>Task{Studio}.</returns>
public Studio GetStudio(string name) public Studio GetStudio(string name)
{ {
return CreateItemByName<Studio>(Studio.GetPath(name), name); return CreateItemByName<Studio>(Studio.GetPath, name);
} }
/// <summary> /// <summary>
@ -885,7 +890,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Genre}.</returns> /// <returns>Task{Genre}.</returns>
public Genre GetGenre(string name) public Genre GetGenre(string name)
{ {
return CreateItemByName<Genre>(Genre.GetPath(name), name); return CreateItemByName<Genre>(Genre.GetPath, name);
} }
/// <summary> /// <summary>
@ -895,7 +900,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{MusicGenre}.</returns> /// <returns>Task{MusicGenre}.</returns>
public MusicGenre GetMusicGenre(string name) public MusicGenre GetMusicGenre(string name)
{ {
return CreateItemByName<MusicGenre>(MusicGenre.GetPath(name), name); return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name);
} }
/// <summary> /// <summary>
@ -905,7 +910,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{GameGenre}.</returns> /// <returns>Task{GameGenre}.</returns>
public GameGenre GetGameGenre(string name) public GameGenre GetGameGenre(string name)
{ {
return CreateItemByName<GameGenre>(GameGenre.GetPath(name), name); return CreateItemByName<GameGenre>(GameGenre.GetPath, name);
} }
/// <summary> /// <summary>
@ -923,7 +928,7 @@ namespace Emby.Server.Implementations.Library
var name = value.ToString(CultureInfo.InvariantCulture); var name = value.ToString(CultureInfo.InvariantCulture);
return CreateItemByName<Year>(Year.GetPath(name), name); return CreateItemByName<Year>(Year.GetPath, name);
} }
/// <summary> /// <summary>
@ -933,10 +938,10 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task{Genre}.</returns> /// <returns>Task{Genre}.</returns>
public MusicArtist GetArtist(string name) public MusicArtist GetArtist(string name)
{ {
return CreateItemByName<MusicArtist>(MusicArtist.GetPath(name), name); return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name);
} }
private T CreateItemByName<T>(string path, string name) private T CreateItemByName<T>(Func<string,string> getPathFn, string name)
where T : BaseItem, new() where T : BaseItem, new()
{ {
if (typeof(T) == typeof(MusicArtist)) if (typeof(T) == typeof(MusicArtist))
@ -957,7 +962,9 @@ namespace Emby.Server.Implementations.Library
} }
} }
var id = GetNewItemId(path, typeof(T)); var path = getPathFn(name);
var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
var id = GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
var item = GetItemById(id) as T; var item = GetItemById(id) as T;

View File

@ -369,6 +369,8 @@ namespace Emby.Server.Implementations.Library
{ {
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
enableAutoClose = false;
try try
{ {
var tuple = GetProvider(request.OpenToken); var tuple = GetProvider(request.OpenToken);

View File

@ -442,6 +442,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false); result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
foreach (var channel in result)
{
_logger.Info("Found epg channel in {0} {1} {2} {3}", provider.Name, info.ListingsId, channel.Name, channel.Id);
}
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result); _epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
} }
@ -493,11 +498,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId)) 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)) if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
{ {
mappedTunerChannelId = tunerChannel.TunerChannelId; mappedTunerChannelId = tunerChannelId;
} }
var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase)); var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
@ -639,8 +650,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken) public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
{ {
var existingTimer = _timerProvider.GetAll() var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ?
.FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); null :
_timerProvider.GetTimerByProgramId(timer.ProgramId);
if (existingTimer != null) if (existingTimer != null)
{ {
@ -710,7 +722,34 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new InvalidOperationException("SeriesId for program not found"); throw new InvalidOperationException("SeriesId for program not found");
} }
// If any timers have already been manually created, make sure they don't get cancelled
var existingTimers = (await GetTimersAsync(CancellationToken.None).ConfigureAwait(false))
.Where(i =>
{
if (string.Equals(i.ProgramId, info.ProgramId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.ProgramId))
{
return true;
}
if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId))
{
return true;
}
return false;
})
.ToList();
_seriesTimerProvider.Add(info); _seriesTimerProvider.Add(info);
foreach (var timer in existingTimers)
{
timer.SeriesTimerId = info.Id;
timer.IsManual = true;
_timerProvider.AddOrUpdate(timer, false);
}
await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false); await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false);
return info.Id; return info.Id;
@ -991,6 +1030,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (epgChannel == null) if (epgChannel == null)
{ {
_logger.Debug("EPG channel not found for tuner channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
programs = new List<ProgramInfo>(); programs = new List<ProgramInfo>();
} }
else else
@ -1276,6 +1316,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return; return;
} }
var registration = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false);
if (!registration.IsValid)
{
_logger.Warn("Emby Premiere required to use Emby DVR.");
OnTimerOutOfDate(timer);
return;
}
var activeRecordingInfo = new ActiveRecordingInfo var activeRecordingInfo = new ActiveRecordingInfo
{ {
CancellationTokenSource = new CancellationTokenSource(), CancellationTokenSource = new CancellationTokenSource(),
@ -2299,6 +2347,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
var existingTimer = _timerProvider.GetTimer(timer.Id); var existingTimer = _timerProvider.GetTimer(timer.Id);
if (existingTimer == null)
{
existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId)
? null
: _timerProvider.GetTimerByProgramId(timer.ProgramId);
}
if (existingTimer == null) if (existingTimer == null)
{ {
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
@ -2313,12 +2368,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
else 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; 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); UpdateExistingTimerWithNewMetadata(existingTimer, timer);
// Needed by ShouldCancelTimerForSeriesTimer
timer.IsManual = existingTimer.IsManual;
if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
{ {
existingTimer.Status = RecordingStatus.Cancelled; existingTimer.Status = RecordingStatus.Cancelled;
@ -2516,7 +2575,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
list.Add(new VirtualFolderInfo list.Add(new VirtualFolderInfo
{ {
Locations = new List<string> { customPath }, Locations = new List<string> { customPath },
Name = "Recorded Series", Name = "Recorded Shows",
CollectionType = CollectionType.TvShows CollectionType = CollectionType.TvShows
}); });
} }
@ -2531,6 +2590,86 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public ProgramInfo Program { get; set; } public ProgramInfo Program { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; } public CancellationTokenSource CancellationTokenSource { get; set; }
} }
private const int TunerDiscoveryDurationMs = 3000;
public async Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
{
var list = new List<TunerHostInfo>();
var configuredDeviceIds = GetConfiguration().TunerHosts
.Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
.Select(i => i.DeviceId)
.ToList();
foreach (var host in _liveTvManager.TunerHosts)
{
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
if (newDevicesOnly)
{
discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparer.OrdinalIgnoreCase))
.ToList();
}
list.AddRange(discoveredDevices);
}
return list;
}
public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
{
foreach (var host in _liveTvManager.TunerHosts)
{
await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
}
}
private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
{
var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
var configuredDevices = GetConfiguration().TunerHosts
.Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var device in discoveredDevices)
{
var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
if (configuredDevice != null)
{
if (!string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
{
_logger.Info("Tuner url has changed from {0} to {1}", configuredDevice.Url, device.Url);
configuredDevice.Url = device.Url;
await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false);
}
}
}
}
private async Task<List<TunerHostInfo>> DiscoverDevices(ITunerHost host, int discoveryDuationMs, CancellationToken cancellationToken)
{
try
{
var discoveredDevices = await host.DiscoverDevices(discoveryDuationMs, cancellationToken).ConfigureAwait(false);
foreach (var device in discoveredDevices)
{
_logger.Info("Discovered tuner device {0} at {1}", host.Name, device.Url);
}
return discoveredDevices;
}
catch (Exception ex)
{
_logger.ErrorException("Error discovering tuner devices", ex);
return new List<TunerHostInfo>();
}
}
} }
public static class ConfigurationExtension public static class ConfigurationExtension
{ {

View File

@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks); var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
var inputModifiers = "-fflags +genpts -async 1 -vsync -1"; 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; long startTimeTicks = 0;
//if (mediaSource.DateLiveStreamOpened.HasValue) //if (mediaSource.DateLiveStreamOpened.HasValue)
@ -193,7 +193,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn"; 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; 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)); 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

@ -205,6 +205,15 @@ namespace Emby.Server.Implementations.LiveTv.Listings
} }
programInfo.ShowId = uniqueString.GetMD5().ToString("N"); programInfo.ShowId = uniqueString.GetMD5().ToString("N");
// If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped
if (programInfo.IsSeries && !programInfo.IsRepeat)
{
if ((programInfo.EpisodeNumber ?? 0) == 0)
{
programInfo.ShowId = programInfo.ShowId + programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture);
}
}
} }
// Construct an id from the channel and start date // Construct an id from the channel and start date

View File

@ -150,6 +150,21 @@ namespace Emby.Server.Implementations.LiveTv
get { return _listingProviders; } get { return _listingProviders; }
} }
public List<NameIdPair> GetTunerHostTypes()
{
return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
{
Name = i.Name,
Id = i.Type
}).ToList();
}
public Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
{
return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken);
}
void service_DataSourceChanged(object sender, EventArgs e) void service_DataSourceChanged(object sender, EventArgs e)
{ {
if (!_isDisposed) if (!_isDisposed)
@ -1063,25 +1078,28 @@ namespace Emby.Server.Implementations.LiveTv
var channel = GetInternalChannel(program.ChannelId); var channel = GetInternalChannel(program.ChannelId);
var channelUserdata = _userDataManager.GetUserData(userId, channel); if (channel != null)
{
var channelUserdata = _userDataManager.GetUserData(userId, channel);
if (channelUserdata.Likes ?? false) if (channelUserdata.Likes ?? false)
{ {
score += 2; score += 2;
} }
else if (!(channelUserdata.Likes ?? true)) else if (!(channelUserdata.Likes ?? true))
{ {
score -= 2; score -= 2;
} }
if (channelUserdata.IsFavorite) if (channelUserdata.IsFavorite)
{ {
score += 3; score += 3;
} }
if (factorChannelWatchCount) if (factorChannelWatchCount)
{ {
score += channelUserdata.PlayCount; score += channelUserdata.PlayCount;
}
} }
return score; return score;
@ -1180,6 +1198,8 @@ namespace Emby.Server.Implementations.LiveTv
{ {
EmbyTV.EmbyTV.Current.CreateRecordingFolders(); EmbyTV.EmbyTV.Current.CreateRecordingFolders();
await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
var numComplete = 0; var numComplete = 0;
double progressPerService = _services.Count == 0 double progressPerService = _services.Count == 0
? 0 ? 0
@ -2748,7 +2768,7 @@ namespace Emby.Server.Implementations.LiveTv
private bool IsLiveTvEnabled(User user) private bool IsLiveTvEnabled(User user)
{ {
return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count(i => i.IsEnabled) > 0); return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Count > 0);
} }
public IEnumerable<User> GetEnabledUsers() public IEnumerable<User> GetEnabledUsers()
@ -2986,7 +3006,7 @@ namespace Emby.Server.Implementations.LiveTv
if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase)) if (string.Equals(feature, "dvr-l", StringComparison.OrdinalIgnoreCase))
{ {
var config = GetConfiguration(); var config = GetConfiguration();
if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && if (config.TunerHosts.Count > 0 &&
config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
{ {
return Task.FromResult(new MBRegistrationRecord return Task.FromResult(new MBRegistrationRecord
@ -3000,50 +3020,6 @@ namespace Emby.Server.Implementations.LiveTv
return _security.GetRegistrationStatus(feature); return _security.GetRegistrationStatus(feature);
} }
public List<NameValuePair> GetSatIniMappings()
{
return new List<NameValuePair>();
//var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini", StringComparison.OrdinalIgnoreCase) != -1).ToList();
//return names.Select(GetSatIniMappings).Where(i => i != null).DistinctBy(i => i.Value.Split('|')[0]).ToList();
}
public NameValuePair GetSatIniMappings(string resource)
{
return new NameValuePair();
//using (var stream = GetType().Assembly.GetManifestResourceStream(resource))
//{
// using (var reader = new StreamReader(stream))
// {
// var parser = new StreamIniDataParser();
// IniData data = parser.ReadData(reader);
// var satType1 = data["SATTYPE"]["1"];
// var satType2 = data["SATTYPE"]["2"];
// if (string.IsNullOrWhiteSpace(satType2))
// {
// return null;
// }
// var srch = "SatIp.ini.";
// var filename = Path.GetFileName(resource);
// return new NameValuePair
// {
// Name = satType1 + " " + satType2,
// Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length)
// };
// }
//}
}
public Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
{
return Task.FromResult(new List<ChannelInfo>());
//return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
}
public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken) public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
{ {
var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));

View File

@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv
public bool IsHidden public bool IsHidden
{ {
get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count(i => i.IsEnabled) == 0; } get { return _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Count == 0; }
} }
public bool IsEnabled public bool IsEnabled

View File

@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false); var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
var list = result.ToList(); var list = result.ToList();
Logger.Debug("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list)); Logger.Info("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
if (!string.IsNullOrWhiteSpace(key) && list.Count > 0) if (!string.IsNullOrWhiteSpace(key) && list.Count > 0)
{ {
@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected virtual List<TunerHostInfo> GetTunerHosts() protected virtual List<TunerHostInfo> GetTunerHosts()
{ {
return GetConfiguration().TunerHosts return GetConfiguration().TunerHosts
.Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)) .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
.ToList(); .ToList();
} }
@ -135,8 +135,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
// Check to make sure the tuner is available // Check to make sure the tuner is available
// If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error // If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error
if (hostsWithChannel.Count > 1 && if (hostsWithChannel.Count > 1 && !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
!await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
{ {
Logger.Error("Tuner is not currently available"); Logger.Error("Tuner is not currently available");
continue; continue;
@ -208,6 +207,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
foreach (var host in hostsWithChannel) foreach (var host in hostsWithChannel)
{ {
if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
{
continue;
}
try try
{ {
var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
@ -243,7 +247,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken); protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
protected abstract bool IsValidChannelId(string channelId); protected virtual string ChannelIdPrefix
{
get
{
return Type + "_";
}
}
protected virtual bool IsValidChannelId(string channelId)
{
if (string.IsNullOrWhiteSpace(channelId))
{
throw new ArgumentNullException("channelId");
}
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
protected LiveTvOptions GetConfiguration() protected LiveTvOptions GetConfiguration()
{ {

View File

@ -1,158 +0,0 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using System;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunDiscovery : IServerEntryPoint
{
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly ILiveTvManager _liveTvManager;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IHttpClient _httpClient;
private readonly IJsonSerializer _json;
public HdHomerunDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
{
_deviceDiscovery = deviceDiscovery;
_config = config;
_logger = logger;
_liveTvManager = liveTvManager;
_httpClient = httpClient;
_json = json;
}
public void Run()
{
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
string server = null;
var info = e.Argument;
if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
{
string location;
if (info.Headers.TryGetValue("Location", out location))
{
//_logger.Debug("HdHomerun found at {0}", location);
// Just get the beginning of the url
Uri uri;
if (Uri.TryCreate(location, UriKind.Absolute, out uri))
{
var apiUrl = location.Replace(uri.LocalPath, String.Empty, StringComparison.OrdinalIgnoreCase)
.TrimEnd('/');
//_logger.Debug("HdHomerun api url: {0}", apiUrl);
AddDevice(apiUrl);
}
}
}
}
private async void AddDevice(string url)
{
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
var options = GetConfiguration();
if (options.TunerHosts.Any(i =>
string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) &&
UriEquals(i.Url, url)))
{
return;
}
// Strip off the port
url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/');
// Test it by pulling down the lineup
using (var stream = await _httpClient.Get(new HttpRequestOptions
{
Url = string.Format("{0}/discover.json", url),
CancellationToken = CancellationToken.None,
BufferContent = false
}))
{
var response = _json.DeserializeFromStream<HdHomerunHost.DiscoverResponse>(stream);
var existing = GetConfiguration().TunerHosts
.FirstOrDefault(i => string.Equals(i.Type, HdHomerunHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.DeviceId, response.DeviceID, StringComparison.OrdinalIgnoreCase));
if (existing == null)
{
await _liveTvManager.SaveTunerHost(new TunerHostInfo
{
Type = HdHomerunHost.DeviceType,
Url = url,
DeviceId = response.DeviceID
}).ConfigureAwait(false);
}
else
{
if (!string.Equals(existing.Url, url, StringComparison.OrdinalIgnoreCase))
{
existing.Url = url;
await _liveTvManager.SaveTunerHost(existing).ConfigureAwait(false);
}
}
}
}
catch (Exception ex)
{
_logger.ErrorException("Error saving device", ex);
}
finally
{
_semaphore.Release();
}
}
private bool UriEquals(string savedUri, string location)
{
return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase);
}
private string NormalizeUrl(string url)
{
if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
url = "http://" + url;
}
url = url.TrimEnd('/');
// Strip off the port
return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
}
private LiveTvOptions GetConfiguration()
{
return _config.GetConfiguration<LiveTvOptions>("livetv");
}
public void Dispose()
{
}
}
}

View File

@ -20,6 +20,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
@ -30,8 +31,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly IEnvironmentInfo _environment;
public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager) public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
: base(config, logger, jsonSerializer, mediaEncoder) : base(config, logger, jsonSerializer, mediaEncoder)
{ {
_httpClient = httpClient; _httpClient = httpClient;
@ -39,6 +41,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_appHost = appHost; _appHost = appHost;
_socketFactory = socketFactory; _socketFactory = socketFactory;
_networkManager = networkManager; _networkManager = networkManager;
_environment = environment;
} }
public string Name public string Name
@ -56,7 +59,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
get { return "hdhomerun"; } get { return "hdhomerun"; }
} }
private const string ChannelIdPrefix = "hdhr_"; protected override string ChannelIdPrefix
{
get
{
return "hdhr_";
}
}
private string GetChannelId(TunerHostInfo info, Channels i) private string GetChannelId(TunerHostInfo info, Channels i)
{ {
@ -125,7 +134,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
DiscoverResponse response; DiscoverResponse response;
if (_modelCache.TryGetValue(info.Url, out response)) if (_modelCache.TryGetValue(info.Url, out response))
{ {
return response; if ((DateTime.UtcNow - response.DateQueried).TotalHours <= 12)
{
return response;
}
} }
} }
@ -135,8 +147,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
Url = string.Format("{0}/discover.json", GetApiUrl(info, false)), Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CancellationToken = cancellationToken, CancellationToken = cancellationToken,
CacheLength = TimeSpan.FromDays(1),
CacheMode = CacheMode.Unconditional,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds), TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
BufferContent = false BufferContent = false
@ -215,7 +225,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var list = new List<LiveTvTunerInfo>(); var list = new List<LiveTvTunerInfo>();
foreach (var host in GetConfiguration().TunerHosts foreach (var host in GetConfiguration().TunerHosts
.Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))) .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
{ {
try try
{ {
@ -268,6 +278,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public int HD { get; set; } 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) private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, ChannelInfo channelInfo, string profile)
{ {
int? width = null; int? width = null;
@ -355,14 +375,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
nal = "0"; nal = "0";
} }
var url = GetApiUrl(info, true) + "/auto/v" + channelId; var url = GetApiUrl(info, false);
// If raw was used, the tuner doesn't support params
if (!string.IsNullOrWhiteSpace(profile)
&& !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
{
url += "?transcode=" + profile;
}
var id = profile; var id = profile;
if (string.IsNullOrWhiteSpace(id)) if (string.IsNullOrWhiteSpace(id))
@ -371,92 +384,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
id += "_" + url.GetMD5().ToString("N"); 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 var mediaSource = new MediaSourceInfo
{ {
Path = url, Path = url,
@ -520,7 +447,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (isLegacyTuner) if (isLegacyTuner)
{ {
list.Add(GetLegacyMediaSource(info, hdhrId, channelInfo)); list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
} }
else else
{ {
@ -559,26 +486,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return list; return list;
} }
protected override bool IsValidChannelId(string channelId)
{
if (string.IsNullOrWhiteSpace(channelId))
{
throw new ArgumentNullException("channelId");
}
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
{ {
var profile = streamId.Split('_')[0]; var profile = streamId.Split('_')[0];
Logger.Info("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelId, streamId, profile); Logger.Info("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelId, streamId, profile);
if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Channel not found");
}
var hdhrId = GetHdHrIdFromChannelId(channelId); var hdhrId = GetHdHrIdFromChannelId(channelId);
var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false); var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
@ -586,30 +499,40 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo; var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner) if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{ {
var mediaSource = GetLegacyMediaSource(info, hdhrId, channelInfo);
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager); return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
} }
else
// The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet
var enableHttpStream = _environment.OperatingSystem == OperatingSystem.OSX ||
_environment.OperatingSystem == OperatingSystem.BSD;
enableHttpStream = true;
if (enableHttpStream)
{ {
var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile); mediaSource.Protocol = MediaProtocol.Http;
//var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
var httpUrl = GetApiUrl(info, true) + "/auto/v" + hdhrId;
// If raw was used, the tuner doesn't support params
if (!string.IsNullOrWhiteSpace(profile)
&& !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
{
httpUrl += "?transcode=" + profile;
}
mediaSource.Path = httpUrl;
return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost); 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 HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
} }
public async Task Validate(TunerHostInfo info) public async Task Validate(TunerHostInfo info)
{ {
if (!info.IsEnabled)
{
return;
}
lock (_modelCache) lock (_modelCache)
{ {
_modelCache.Clear(); _modelCache.Clear();
@ -651,6 +574,83 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public string BaseURL { get; set; } public string BaseURL { get; set; }
public string LineupURL { get; set; } public string LineupURL { get; set; }
public int TunerCount { get; set; } public int TunerCount { get; set; }
public DateTime DateQueried { get; set; }
public DiscoverResponse()
{
DateQueried = DateTime.UtcNow;
}
}
public async Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
{
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
var list = new List<TunerHostInfo>();
// Create udp broadcast discovery message
byte[] discBytes = { 0, 2, 0, 12, 1, 4, 255, 255, 255, 255, 2, 4, 255, 255, 255, 255, 115, 204, 125, 143 };
using (var udpClient = _socketFactory.CreateUdpBroadcastSocket(0))
{
// Need a way to set the Receive timeout on the socket otherwise this might never timeout?
try
{
await udpClient.SendAsync(discBytes, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
while (!cancellationToken.IsCancellationRequested)
{
var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
var deviceIp = response.RemoteEndPoint.IpAddress.Address;
// check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte
if (response.ReceivedBytes > 13 && response.Buffer[1] == 3)
{
var deviceAddress = "http://" + deviceIp;
var info = await TryGetTunerHostInfo(deviceAddress, cancellationToken).ConfigureAwait(false);
if (info != null)
{
list.Add(info);
}
}
}
}
catch (OperationCanceledException)
{
}
catch
{
// Socket timeout indicates all messages have been received.
}
}
return list;
}
private async Task<TunerHostInfo> TryGetTunerHostInfo(string url, CancellationToken cancellationToken)
{
var hostInfo = new TunerHostInfo
{
Type = Type,
Url = url
};
try
{
var modelInfo = await GetModelInfo(hostInfo, false, cancellationToken).ConfigureAwait(false);
hostInfo.DeviceId = modelInfo.DeviceID;
hostInfo.FriendlyName = modelInfo.FriendlyName;
return hostInfo;
}
catch
{
// logged at lower levels
}
return null;
} }
} }
} }

View File

@ -120,7 +120,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// send url to start streaming // send url to start streaming
await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false); await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, cancellationToken).ConfigureAwait(false);
var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
_logger.Info("Opened HDHR UDP stream from {0}", remoteAddress); _logger.Info("Opened HDHR UDP stream from {0}", remoteAddress);
if (!cancellationToken.IsCancellationRequested) if (!cancellationToken.IsCancellationRequested)
@ -131,12 +130,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
onStarted = () => openTaskCompletionSource.TrySetResult(true); onStarted = () => openTaskCompletionSource.TrySetResult(true);
} }
var stream = new UdpClientStream(udpClient); await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), onStarted, cancellationToken).ConfigureAwait(false);
await _multicastStream.CopyUntilCancelled(stream, onStarted, cancellationToken).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException ex)
{ {
_logger.Info("HDHR UDP stream cancelled or timed out from {0}", remoteAddress);
openTaskCompletionSource.TrySetException(ex);
break; break;
} }
catch (Exception ex) catch (Exception ex)
@ -155,7 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
} }
await hdHomerunManager.StopStreaming().ConfigureAwait(false); await hdHomerunManager.StopStreaming().ConfigureAwait(false);
udpClient.Dispose();
_liveStreamTaskCompletionSource.TrySetResult(true); _liveStreamTaskCompletionSource.TrySetResult(true);
} }
} }

View File

@ -46,8 +46,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
get { return "M3U Tuner"; } get { return "M3U Tuner"; }
} }
private const string ChannelIdPrefix = "m3u_";
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken) protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
{ {
var result = await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false); var result = await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false);
@ -87,16 +85,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
} }
} }
protected override bool IsValidChannelId(string channelId)
{
if (string.IsNullOrWhiteSpace(channelId))
{
throw new ArgumentNullException("channelId");
}
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{ {
var urlHash = info.Url.GetMD5().ToString("N"); var urlHash = info.Url.GetMD5().ToString("N");
@ -176,5 +164,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
return Task.FromResult(true); return Task.FromResult(true);
} }
public Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
{
return Task.FromResult(new List<TunerHostInfo>());
}
} }
} }

View File

@ -7,6 +7,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
namespace Emby.Server.Implementations.LiveTv.TunerHosts namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
@ -34,13 +35,73 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (bytesRead > 0) if (bytesRead > 0)
{ {
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
var allStreams = _outputStreams.ToList(); var allStreams = _outputStreams.ToList();
foreach (var stream in allStreams)
if (allStreams.Count == 1)
{ {
stream.Value.Queue(copy); await allStreams[0].Value.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false);
}
else
{
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
foreach (var stream in allStreams)
{
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)
{
var allStreams = _outputStreams.ToList();
if (allStreams.Count == 1)
{
await allStreams[0].Value.WriteAsync(data.Buffer, 0, bytesRead).ConfigureAwait(false);
}
else
{
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead);
foreach (var stream in allStreams)
{
stream.Value.Queue(copy, 0, copy.Length);
}
} }
if (onStarted != null) if (onStarted != null)

View File

@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class QueueStream public class QueueStream
{ {
private readonly Stream _outputStream; 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; private CancellationToken _cancellationToken;
public TaskCompletionSource<bool> TaskCompletion { get; private set; } public TaskCompletionSource<bool> TaskCompletion { get; private set; }
@ -28,9 +28,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
TaskCompletion = new TaskCompletionSource<bool>(); 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) public void Start(CancellationToken cancellationToken)
@ -39,17 +39,49 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Task.Run(() => StartInternal()); Task.Run(() => StartInternal());
} }
private byte[] Dequeue() private Tuple<byte[], int, int> Dequeue()
{ {
byte[] bytes; Tuple<byte[], int, int> result;
if (_queue.TryDequeue(out bytes)) if (_queue.TryDequeue(out result))
{ {
return bytes; return result;
} }
return null; return null;
} }
private void OnClosed()
{
GC.Collect();
if (OnFinished != null)
{
OnFinished(this);
}
}
public async Task WriteAsync(byte[] bytes, int offset, int count)
{
//return _outputStream.WriteAsync(bytes, offset, count, cancellationToken);
var cancellationToken = _cancellationToken;
try
{
await _outputStream.WriteAsync(bytes, offset, count, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
_logger.Debug("QueueStream cancelled");
TaskCompletion.TrySetCanceled();
OnClosed();
}
catch (Exception ex)
{
_logger.ErrorException("Error in QueueStream", ex);
TaskCompletion.TrySetException(ex);
OnClosed();
}
}
private async Task StartInternal() private async Task StartInternal()
{ {
var cancellationToken = _cancellationToken; var cancellationToken = _cancellationToken;
@ -58,10 +90,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{ {
while (true) while (true)
{ {
var bytes = Dequeue(); var result = Dequeue();
if (bytes != null) 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 else
{ {
@ -81,10 +113,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
} }
finally finally
{ {
if (OnFinished != null) OnClosed();
{
OnFinished(this);
}
} }
} }
} }

View File

@ -408,6 +408,7 @@ namespace Emby.Server.Implementations.Localization
new LocalizatonOption{ Name="Italian", Value="it"}, new LocalizatonOption{ Name="Italian", Value="it"},
new LocalizatonOption{ Name="Kazakh", Value="kk"}, new LocalizatonOption{ Name="Kazakh", Value="kk"},
new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"}, new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"},
new LocalizatonOption{ Name="Persian", Value="fa"},
new LocalizatonOption{ Name="Polish", Value="pl"}, new LocalizatonOption{ Name="Polish", Value="pl"},
new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"}, new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"}, new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},

View File

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Server.Implementations.Images; using Emby.Server.Implementations.Images;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -101,4 +102,35 @@ namespace Emby.Server.Implementations.Playlists
//} //}
} }
public class GenreImageProvider : BaseDynamicImageProvider<Genre>
{
private readonly ILibraryManager _libraryManager;
public GenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
_libraryManager = libraryManager;
}
protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
{
var items = _libraryManager.GetItemList(new InternalItemsQuery
{
Genres = new[] { item.Name },
IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name },
SortBy = new[] { ItemSortBy.Random },
Limit = 4,
Recursive = true,
ImageTypes = new[] { ImageType.Primary }
}).ToList();
return Task.FromResult(GetFinalItems(items));
}
//protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
//{
// return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
//}
}
} }

View File

@ -301,10 +301,12 @@ namespace Emby.Server.Implementations.Security
if (reg.registered) if (reg.registered)
{ {
_logger.Info("Registered for feature {0}", feature);
LicenseFile.AddRegCheck(feature, reg.expDate); LicenseFile.AddRegCheck(feature, reg.expDate);
} }
else else
{ {
_logger.Info("Not registered for feature {0}", feature);
LicenseFile.RemoveRegCheck(feature); LicenseFile.RemoveRegCheck(feature);
} }

View File

@ -12,45 +12,6 @@ namespace Emby.Server.Implementations.Services
{ {
public static class ResponseHelper public static class ResponseHelper
{ {
private static async Task<bool> WriteToOutputStream(IResponse response, object result)
{
var asyncStreamWriter = result as IAsyncStreamWriter;
if (asyncStreamWriter != null)
{
await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
return true;
}
var streamWriter = result as IStreamWriter;
if (streamWriter != null)
{
streamWriter.WriteTo(response.OutputStream);
return true;
}
var stream = result as Stream;
if (stream != null)
{
using (stream)
{
await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false);
return true;
}
}
var bytes = result as byte[];
if (bytes != null)
{
response.ContentType = "application/octet-stream";
response.SetContentLength(bytes.Length);
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
return true;
}
return false;
}
public static Task WriteToResponse(IResponse httpRes, IRequest httpReq, object result) public static Task WriteToResponse(IResponse httpRes, IRequest httpReq, object result)
{ {
if (result == null) if (result == null)
@ -141,16 +102,51 @@ namespace Emby.Server.Implementations.Services
response.ContentType += "; charset=utf-8"; response.ContentType += "; charset=utf-8";
} }
var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); var asyncStreamWriter = result as IAsyncStreamWriter;
if (writeToOutputStreamResult) if (asyncStreamWriter != null)
{ {
await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
return;
}
var streamWriter = result as IStreamWriter;
if (streamWriter != null)
{
streamWriter.WriteTo(response.OutputStream);
return;
}
var fileWriter = result as FileWriter;
if (fileWriter != null)
{
await fileWriter.WriteToAsync(response, CancellationToken.None).ConfigureAwait(false);
return;
}
var stream = result as Stream;
if (stream != null)
{
using (stream)
{
await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false);
return;
}
}
var bytes = result as byte[];
if (bytes != null)
{
response.ContentType = "application/octet-stream";
response.SetContentLength(bytes.Length);
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
return; return;
} }
var responseText = result as string; var responseText = result as string;
if (responseText != null) if (responseText != null)
{ {
var bytes = Encoding.UTF8.GetBytes(responseText); bytes = Encoding.UTF8.GetBytes(responseText);
response.SetContentLength(bytes.Length); response.SetContentLength(bytes.Length);
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
return; return;

View File

@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.TV
// Avoid implicitly captured closure // Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items); var episodes = GetNextUpEpisodes(request, user, items);
return GetResult(episodes, null, request); return GetResult(episodes, request);
} }
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, List<Folder> parentsFolders) public QueryResult<BaseItem> GetNextUp(NextUpQuery request, List<Folder> parentsFolders)
@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.TV
// Avoid implicitly captured closure // Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items); var episodes = GetNextUpEpisodes(request, user, items);
return GetResult(episodes, null, request); return GetResult(episodes, request);
} }
public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys) public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys)
@ -163,8 +163,7 @@ namespace Emby.Server.Implementations.TV
return false; return false;
}) })
.Select(i => i.Item2()) .Select(i => i.Item2())
.Where(i => i != null) .Where(i => i != null);
.Take(request.Limit ?? int.MaxValue);
} }
private string GetUniqueSeriesKey(BaseItem series) private string GetUniqueSeriesKey(BaseItem series)
@ -232,24 +231,30 @@ namespace Emby.Server.Implementations.TV
return new Tuple<DateTime, Func<Episode>>(DateTime.MinValue, getEpisode); return new Tuple<DateTime, Func<Episode>>(DateTime.MinValue, getEpisode);
} }
private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, int? totalRecordLimit, NextUpQuery query) private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)
{ {
var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray(); int totalCount = 0;
var totalCount = itemsArray.Length;
if (query.EnableTotalRecordCount)
{
var list = items.ToList();
totalCount = list.Count;
items = list;
}
if (query.StartIndex.HasValue)
{
items = items.Skip(query.StartIndex.Value);
}
if (query.Limit.HasValue) if (query.Limit.HasValue)
{ {
itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray(); items = items.Take(query.Limit.Value);
}
else if (query.StartIndex.HasValue)
{
itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray();
} }
return new QueryResult<BaseItem> return new QueryResult<BaseItem>
{ {
TotalRecordCount = totalCount, TotalRecordCount = totalCount,
Items = itemsArray Items = items.ToArray()
}; };
} }
} }

View File

@ -203,19 +203,6 @@ namespace Emby.Server.Implementations.Udp
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary>
/// Stops this instance.
/// </summary>
public void Stop()
{
_isDisposed = true;
if (_udpClient != null)
{
_udpClient.Dispose();
}
}
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
@ -224,7 +211,12 @@ namespace Emby.Server.Implementations.Udp
{ {
if (dispose) if (dispose)
{ {
Stop(); _isDisposed = true;
if (_udpClient != null)
{
_udpClient.Dispose();
}
} }
} }
@ -247,9 +239,13 @@ namespace Emby.Server.Implementations.Udp
try try
{ {
await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false); await _udpClient.SendWithLockAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
_logger.Info("Udp message sent to {0}", remoteEndPoint); _logger.Info("Udp message sent to {0}", remoteEndPoint);
}
catch (OperationCanceledException)
{
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Emby.XmlTv" version="1.0.7" targetFramework="portable45-net45+win8" /> <package id="Emby.XmlTv" version="1.0.7" targetFramework="portable45-net45+win8" />
<package id="MediaBrowser.Naming" version="1.0.4" targetFramework="portable45-net45+win8" /> <package id="MediaBrowser.Naming" version="1.0.5" targetFramework="portable45-net45+win8" />
<package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" /> <package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="portable45-net45+win8" /> <package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="portable45-net45+win8" />
<package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" /> <package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" />

View File

@ -582,13 +582,13 @@ namespace MediaBrowser.Api.LiveTv
} }
[Route("/LiveTv/ListingProviders/Default", "GET")] [Route("/LiveTv/ListingProviders/Default", "GET")]
[Authenticated(AllowBeforeStartupWizard = true)] [Authenticated]
public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo> public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
{ {
} }
[Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")] [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
[Authenticated(AllowBeforeStartupWizard = true)] [Authenticated]
public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo> public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
{ {
public bool ValidateLogin { get; set; } public bool ValidateLogin { get; set; }
@ -596,7 +596,7 @@ namespace MediaBrowser.Api.LiveTv
} }
[Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")] [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")]
[Authenticated(AllowBeforeStartupWizard = true)] [Authenticated]
public class DeleteListingProvider : IReturnVoid public class DeleteListingProvider : IReturnVoid
{ {
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")] [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")]
@ -604,7 +604,7 @@ namespace MediaBrowser.Api.LiveTv
} }
[Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")] [Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")]
[Authenticated(AllowBeforeStartupWizard = true)] [Authenticated]
public class GetLineups : IReturn<List<NameIdPair>> public class GetLineups : IReturn<List<NameIdPair>>
{ {
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
@ -621,13 +621,13 @@ namespace MediaBrowser.Api.LiveTv
} }
[Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")] [Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")]
[Authenticated(AllowBeforeStartupWizard = true)] [Authenticated]
public class GetSchedulesDirectCountries public class GetSchedulesDirectCountries
{ {
} }
[Route("/LiveTv/ChannelMappingOptions")] [Route("/LiveTv/ChannelMappingOptions")]
[Authenticated(AllowBeforeStartupWizard = true)] [Authenticated]
public class GetChannelMappingOptions public class GetChannelMappingOptions
{ {
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@ -635,7 +635,7 @@ namespace MediaBrowser.Api.LiveTv
} }
[Route("/LiveTv/ChannelMappings")] [Route("/LiveTv/ChannelMappings")]
[Authenticated(AllowBeforeStartupWizard = true)] [Authenticated]
public class SetChannelMapping public class SetChannelMapping
{ {
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
@ -660,20 +660,6 @@ namespace MediaBrowser.Api.LiveTv
public string Feature { get; set; } public string Feature { get; set; }
} }
[Route("/LiveTv/TunerHosts/Satip/IniMappings", "GET", Summary = "Gets available mappings")]
[Authenticated(AllowBeforeStartupWizard = true)]
public class GetSatIniMappings : IReturn<List<NameValuePair>>
{
}
[Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")]
[Authenticated(AllowBeforeStartupWizard = true)]
public class GetSatChannnelScanResult : TunerHostInfo
{
}
[Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")] [Route("/LiveTv/LiveStreamFiles/{Id}/stream.{Container}", "GET", Summary = "Gets a live tv channel")]
public class GetLiveStreamFile public class GetLiveStreamFile
{ {
@ -687,6 +673,20 @@ namespace MediaBrowser.Api.LiveTv
public string Id { get; set; } public string Id { get; set; }
} }
[Route("/LiveTv/TunerHosts/Types", "GET")]
[Authenticated]
public class GetTunerHostTypes : IReturn<List<NameIdPair>>
{
}
[Route("/LiveTv/Tuners/Discvover", "GET")]
[Authenticated]
public class DiscoverTuners : IReturn<List<TunerHostInfo>>
{
public bool NewDevicesOnly { get; set; }
}
public class LiveTvService : BaseApiService public class LiveTvService : BaseApiService
{ {
private readonly ILiveTvManager _liveTvManager; private readonly ILiveTvManager _liveTvManager;
@ -712,6 +712,12 @@ namespace MediaBrowser.Api.LiveTv
_sessionContext = sessionContext; _sessionContext = sessionContext;
} }
public object Get(GetTunerHostTypes request)
{
var list = _liveTvManager.GetTunerHostTypes();
return ToOptimizedResult(list);
}
public object Get(GetLiveRecordingFile request) public object Get(GetLiveRecordingFile request)
{ {
var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id); var path = _liveTvManager.GetEmbyTvActiveRecordingPath(request.Id);
@ -731,6 +737,12 @@ namespace MediaBrowser.Api.LiveTv
}; };
} }
public async Task<object> Get(DiscoverTuners request)
{
var result = await _liveTvManager.DiscoverTuners(request.NewDevicesOnly, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
public async Task<object> Get(GetLiveStreamFile request) public async Task<object> Get(GetLiveStreamFile request)
{ {
var directStreamProvider = (await _liveTvManager.GetEmbyTvLiveStream(request.Id).ConfigureAwait(false)) as IDirectStreamProvider; var directStreamProvider = (await _liveTvManager.GetEmbyTvLiveStream(request.Id).ConfigureAwait(false)) as IDirectStreamProvider;
@ -749,13 +761,6 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(new ListingsProviderInfo()); return ToOptimizedResult(new ListingsProviderInfo());
} }
public async Task<object> Get(GetSatChannnelScanResult request)
{
var result = await _liveTvManager.GetSatChannelScanResult(request, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
public async Task<object> Get(GetLiveTvRegistrationInfo request) public async Task<object> Get(GetLiveTvRegistrationInfo request)
{ {
var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false); var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false);
@ -803,11 +808,6 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(result); return ToOptimizedResult(result);
} }
public object Get(GetSatIniMappings request)
{
return ToOptimizedResult(_liveTvManager.GetSatIniMappings());
}
public async Task<object> Get(GetSchedulesDirectCountries request) public async Task<object> Get(GetSchedulesDirectCountries request)
{ {
// https://json.schedulesdirect.org/20141201/available/countries // https://json.schedulesdirect.org/20141201/available/countries

View File

@ -289,8 +289,10 @@ namespace MediaBrowser.Api.Playback
// 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();
state.TranscodingJob = transcodingJob;
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream)); new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
// Wait for the file to exist before proceeeding // Wait for the file to exist before proceeeding
while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited) while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
@ -340,134 +342,6 @@ namespace MediaBrowser.Api.Playback
// string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase); // string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
} }
private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
{
try
{
using (var reader = new StreamReader(source))
{
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync().ConfigureAwait(false);
ParseLogLine(line, transcodingJob, state);
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
await target.FlushAsync().ConfigureAwait(false);
}
}
}
catch (ObjectDisposedException)
{
// Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
}
catch (Exception ex)
{
Logger.ErrorException("Error reading ffmpeg log", ex);
}
}
private void ParseLogLine(string line, TranscodingJob transcodingJob, StreamState state)
{
float? framerate = null;
double? percent = null;
TimeSpan? transcodingPosition = null;
long? bytesTranscoded = null;
int? bitRate = null;
var parts = line.Split(' ');
var totalMs = state.RunTimeTicks.HasValue
? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds
: 0;
var startMs = state.Request.StartTimeTicks.HasValue
? TimeSpan.FromTicks(state.Request.StartTimeTicks.Value).TotalMilliseconds
: 0;
for (var i = 0; i < parts.Length; i++)
{
var part = parts[i];
if (string.Equals(part, "fps=", StringComparison.OrdinalIgnoreCase) &&
(i + 1 < parts.Length))
{
var rate = parts[i + 1];
float val;
if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
{
framerate = val;
}
}
else if (state.RunTimeTicks.HasValue &&
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
{
var time = part.Split(new[] { '=' }, 2).Last();
TimeSpan val;
if (TimeSpan.TryParse(time, UsCulture, out val))
{
var currentMs = startMs + val.TotalMilliseconds;
var percentVal = currentMs / totalMs;
percent = 100 * percentVal;
transcodingPosition = val;
}
}
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
{
var size = part.Split(new[] { '=' }, 2).Last();
int? scale = null;
if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
{
scale = 1024;
size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase);
}
if (scale.HasValue)
{
long val;
if (long.TryParse(size, NumberStyles.Any, UsCulture, out val))
{
bytesTranscoded = val * scale.Value;
}
}
}
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
{
var rate = part.Split(new[] { '=' }, 2).Last();
int? scale = null;
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
{
scale = 1024;
rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
}
if (scale.HasValue)
{
float val;
if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val))
{
bitRate = (int)Math.Ceiling(val * scale.Value);
}
}
}
}
if (framerate.HasValue || percent.HasValue)
{
ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
}
}
/// <summary> /// <summary>
/// Processes the exited. /// Processes the exited.
/// </summary> /// </summary>
@ -697,6 +571,20 @@ namespace MediaBrowser.Api.Playback
{ {
request.SubtitleCodec = val; request.SubtitleCodec = val;
} }
else if (i == 31)
{
if (videoRequest != null)
{
videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
}
else if (i == 32)
{
if (videoRequest != null)
{
videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
}
} }
} }

View File

@ -41,9 +41,16 @@ namespace MediaBrowser.Api.Playback.Hls
/// <summary> /// <summary>
/// Gets the segment file extension. /// Gets the segment file extension.
/// </summary> /// </summary>
/// <param name="state">The state.</param> protected string GetSegmentFileExtension(StreamRequest request)
/// <returns>System.String.</returns> {
protected abstract string GetSegmentFileExtension(StreamState state); var segmentContainer = request.SegmentContainer;
if (!string.IsNullOrWhiteSpace(segmentContainer))
{
return "." + segmentContainer;
}
return ".ts";
}
/// <summary> /// <summary>
/// Gets the type of the transcoding job. /// Gets the type of the transcoding job.
@ -103,8 +110,11 @@ namespace MediaBrowser.Api.Playback.Hls
throw; throw;
} }
var waitForSegments = state.SegmentLength >= 10 ? 2 : 3; var minSegments = state.MinSegments;
await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false); if (minSegments > 0)
{
await WaitForMinimumSegmentCount(playlist, minSegments, cancellationTokenSource.Token).ConfigureAwait(false);
}
} }
} }
finally finally
@ -258,14 +268,22 @@ namespace MediaBrowser.Api.Playback.Hls
"hls/" + Path.GetFileNameWithoutExtension(outputPath)); "hls/" + Path.GetFileNameWithoutExtension(outputPath));
} }
var useGenericSegmenter = false; var useGenericSegmenter = true;
if (useGenericSegmenter) if (useGenericSegmenter)
{ {
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state); var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
var timeDeltaParam = String.Empty; var timeDeltaParam = String.Empty;
return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
{
segmentFormat = "mpegts";
}
baseUrlParam = string.Format("\"{0}/\"", "hls/" + Path.GetFileNameWithoutExtension(outputPath));
return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_entry_prefix {12} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
inputModifier, inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions), EncodingHelper.GetInputArgument(state, encodingOptions),
threads, threads,
@ -276,7 +294,9 @@ namespace MediaBrowser.Api.Playback.Hls
startNumberParam, startNumberParam,
outputPath, outputPath,
outputTsArg, outputTsArg,
timeDeltaParam timeDeltaParam,
segmentFormat,
baseUrlParam
).Trim(); ).Trim();
} }
@ -286,7 +306,7 @@ namespace MediaBrowser.Api.Playback.Hls
var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"", var args = string.Format("{0} {1} {2} -map_metadata -1 -map_chapters -1 -threads {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero {6} -hls_time {7} -individual_header_trailer 0 -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
itsOffset, itsOffset,
inputModifier, inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions), EncodingHelper.GetInputArgument(state, encodingOptions),
threads, threads,
EncodingHelper.GetMapArgs(state), EncodingHelper.GetMapArgs(state),
GetVideoArguments(state), GetVideoArguments(state),

View File

@ -65,7 +65,7 @@ namespace MediaBrowser.Api.Playback.Hls
{ {
} }
[Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.ts", "GET")] [Route("/Videos/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
public class GetHlsVideoSegment : VideoStreamRequest public class GetHlsVideoSegment : VideoStreamRequest
{ {
public string PlaylistId { get; set; } public string PlaylistId { get; set; }
@ -77,8 +77,7 @@ namespace MediaBrowser.Api.Playback.Hls
public string SegmentId { get; set; } public string SegmentId { get; set; }
} }
[Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.aac", "GET")] [Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
[Route("/Audio/{Id}/hls1/{PlaylistId}/{SegmentId}.ts", "GET")]
public class GetHlsAudioSegment : StreamRequest public class GetHlsAudioSegment : StreamRequest
{ {
public string PlaylistId { get; set; } public string PlaylistId { get; set; }
@ -158,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Hls
var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex); var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex);
var segmentExtension = GetSegmentFileExtension(state); var segmentExtension = GetSegmentFileExtension(state.Request);
TranscodingJob job = null; TranscodingJob job = null;
@ -420,7 +419,7 @@ namespace MediaBrowser.Api.Playback.Hls
var filename = Path.GetFileNameWithoutExtension(playlist); var filename = Path.GetFileNameWithoutExtension(playlist);
return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state)); return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state.Request));
} }
private async Task<object> GetSegmentResult(StreamState state, string playlistPath, private async Task<object> GetSegmentResult(StreamState state, string playlistPath,
@ -740,7 +739,7 @@ namespace MediaBrowser.Api.Playback.Hls
name, name,
index.ToString(UsCulture), index.ToString(UsCulture),
GetSegmentFileExtension(isOutputVideo), GetSegmentFileExtension(request),
queryString)); queryString));
index++; index++;
@ -848,7 +847,7 @@ namespace MediaBrowser.Api.Playback.Hls
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg; args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
//args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
@ -897,7 +896,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (useGenericSegmenter) if (useGenericSegmenter)
{ {
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state); var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
var timeDeltaParam = String.Empty; var timeDeltaParam = String.Empty;
@ -907,7 +906,13 @@ namespace MediaBrowser.Api.Playback.Hls
timeDeltaParam = string.Format("-segment_time_delta -{0}", startTime); timeDeltaParam = string.Format("-segment_time_delta -{0}", startTime);
} }
return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
{
segmentFormat = "mpegts";
}
return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
inputModifier, inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions), EncodingHelper.GetInputArgument(state, encodingOptions),
threads, threads,
@ -918,7 +923,8 @@ namespace MediaBrowser.Api.Playback.Hls
startNumberParam, startNumberParam,
outputPath, outputPath,
outputTsArg, outputTsArg,
timeDeltaParam timeDeltaParam,
segmentFormat
).Trim(); ).Trim();
} }
@ -935,20 +941,5 @@ namespace MediaBrowser.Api.Playback.Hls
outputPath outputPath
).Trim(); ).Trim();
} }
/// <summary>
/// Gets the segment file extension.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected override string GetSegmentFileExtension(StreamState state)
{
return GetSegmentFileExtension(state.IsOutputVideo);
}
protected string GetSegmentFileExtension(bool isOutputVideo)
{
return isOutputVideo ? ".ts" : ".ts";
}
} }
} }

View File

@ -63,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <summary> /// <summary>
/// Class GetHlsVideoSegment /// Class GetHlsVideoSegment
/// </summary> /// </summary>
[Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")] [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.{SegmentContainer}", "GET")]
public class GetHlsVideoSegmentLegacy : VideoStreamRequest public class GetHlsVideoSegmentLegacy : VideoStreamRequest
{ {
public string PlaylistId { get; set; } public string PlaylistId { get; set; }
@ -109,11 +109,13 @@ namespace MediaBrowser.Api.Playback.Hls
public Task<object> Get(GetHlsVideoSegmentLegacy request) public Task<object> Get(GetHlsVideoSegmentLegacy request)
{ {
var file = request.SegmentId + Path.GetExtension(Request.PathInfo); var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file);
var transcodeFolderPath = _config.ApplicationPaths.TranscodingTempPath;
file = Path.Combine(transcodeFolderPath, file);
var normalizedPlaylistId = request.PlaylistId; var normalizedPlaylistId = request.PlaylistId;
var playlistPath = _fileSystem.GetFilePaths(_config.ApplicationPaths.TranscodingTempPath) var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath)
.FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1);
return GetFileResult(file, playlistPath); return GetFileResult(file, playlistPath);

View File

@ -99,7 +99,7 @@ namespace MediaBrowser.Api.Playback.Hls
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg; args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
// Add resolution params, if specified // Add resolution params, if specified
if (!hasGraphicalSubs) if (!hasGraphicalSubs)
@ -118,16 +118,6 @@ namespace MediaBrowser.Api.Playback.Hls
return args; return args;
} }
/// <summary>
/// Gets the segment file extension.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected override string GetSegmentFileExtension(StreamState state)
{
return ".ts";
}
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext)
{ {
} }

View File

@ -127,7 +127,7 @@ namespace MediaBrowser.Api.Playback
SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate, SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex, request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, true, true, true); request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, true, true, true, true);
} }
else else
{ {
@ -169,7 +169,7 @@ namespace MediaBrowser.Api.Playback
{ {
var mediaSourceId = request.MediaSourceId; var mediaSourceId = request.MediaSourceId;
SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, request.EnableDirectStream, request.EnableTranscoding); SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, request.ForceDirectPlayRemoteMediaSource, request.EnableDirectStream, request.EnableTranscoding);
} }
return info; return info;
@ -253,6 +253,7 @@ namespace MediaBrowser.Api.Playback
int? maxAudioChannels, int? maxAudioChannels,
string userId, string userId,
bool enableDirectPlay, bool enableDirectPlay,
bool forceDirectPlayRemoteMediaSource,
bool enableDirectStream, bool enableDirectStream,
bool enableTranscoding) bool enableTranscoding)
{ {
@ -260,7 +261,7 @@ namespace MediaBrowser.Api.Playback
foreach (var mediaSource in result.MediaSources) foreach (var mediaSource in result.MediaSources)
{ {
SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, enableDirectStream, enableTranscoding); SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, forceDirectPlayRemoteMediaSource, enableDirectStream, enableTranscoding);
} }
SortMediaSources(result, maxBitrate); SortMediaSources(result, maxBitrate);
@ -279,6 +280,7 @@ namespace MediaBrowser.Api.Playback
string playSessionId, string playSessionId,
string userId, string userId,
bool enableDirectPlay, bool enableDirectPlay,
bool forceDirectPlayRemoteMediaSource,
bool enableDirectStream, bool enableDirectStream,
bool enableTranscoding) bool enableTranscoding)
{ {
@ -318,43 +320,49 @@ namespace MediaBrowser.Api.Playback
if (mediaSource.SupportsDirectPlay) if (mediaSource.SupportsDirectPlay)
{ {
var supportsDirectStream = mediaSource.SupportsDirectStream; if (mediaSource.IsRemote && forceDirectPlayRemoteMediaSource)
// Dummy this up to fool StreamBuilder
mediaSource.SupportsDirectStream = true;
options.MaxBitrate = maxBitrate;
if (item is Audio)
{ {
if (!user.Policy.EnableAudioPlaybackTranscoding) }
else
{
var supportsDirectStream = mediaSource.SupportsDirectStream;
// Dummy this up to fool StreamBuilder
mediaSource.SupportsDirectStream = true;
options.MaxBitrate = maxBitrate;
if (item is Audio)
{ {
options.ForceDirectPlay = true; if (!user.Policy.EnableAudioPlaybackTranscoding)
{
options.ForceDirectPlay = true;
}
} }
} else if (item is Video)
else if (item is Video)
{
if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
{ {
options.ForceDirectPlay = true; if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
{
options.ForceDirectPlay = true;
}
} }
}
// The MediaSource supports direct stream, now test to see if the client supports it // The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ? var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
streamBuilder.BuildAudioItem(options) : streamBuilder.BuildAudioItem(options) :
streamBuilder.BuildVideoItem(options); streamBuilder.BuildVideoItem(options);
if (streamInfo == null || !streamInfo.IsDirectStream) if (streamInfo == null || !streamInfo.IsDirectStream)
{ {
mediaSource.SupportsDirectPlay = false; mediaSource.SupportsDirectPlay = false;
} }
// Set this back to what it was // Set this back to what it was
mediaSource.SupportsDirectStream = supportsDirectStream; mediaSource.SupportsDirectStream = supportsDirectStream;
if (streamInfo != null) if (streamInfo != null)
{ {
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token); SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
}
} }
} }

View File

@ -15,9 +15,6 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback.Progressive namespace MediaBrowser.Api.Playback.Progressive
@ -298,7 +295,8 @@ namespace MediaBrowser.Api.Playback.Progressive
responseHeaders["Accept-Ranges"] = "none"; responseHeaders["Accept-Ranges"] = "none";
} }
if (response.ContentLength.HasValue) // Seeing cases of -1 here
if (response.ContentLength.HasValue && response.ContentLength.Value >= 0)
{ {
responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
} }

View File

@ -1,4 +1,3 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -7,15 +6,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Services; using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback.Progressive namespace MediaBrowser.Api.Playback.Progressive
@ -100,181 +92,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{ {
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
// Get the output codec name return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset());
var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
var format = string.Empty;
var keyFrame = string.Empty;
if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase))
{
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
format = " -f mp4 -movflags frag_keyframe+empty_moov";
}
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
var subtitleArguments = state.SubtitleStream != null &&
state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed
? GetSubtitleArguments(state)
: string.Empty;
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions),
keyFrame,
EncodingHelper.GetMapArgs(state),
GetVideoArguments(state, videoCodec),
threads,
GetAudioArguments(state),
subtitleArguments,
format,
outputPath
).Trim();
}
private string GetSubtitleArguments(StreamState state)
{
var format = state.SupportedSubtitleCodecs.FirstOrDefault();
string codec;
if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase))
{
codec = "copy";
}
else
{
codec = format;
}
return " -codec:s:0 " + codec;
}
/// <summary>
/// Gets video arguments to pass to ffmpeg
/// </summary>
/// <param name="state">The state.</param>
/// <param name="videoCodec">The video codec.</param>
/// <returns>System.String.</returns>
private string GetVideoArguments(StreamState state, string videoCodec)
{
var args = "-codec:v:0 " + videoCodec;
if (state.EnableMpegtsM2TsMode)
{
args += " -mpegts_m2ts_mode 1";
}
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
args += " -bsf:v h264_mp4toannexb";
}
if (state.RunTimeTicks.HasValue && state.VideoRequest.CopyTimestamps)
{
args += " -copyts -avoid_negative_ts disabled -start_at_zero";
}
if (!state.RunTimeTicks.HasValue)
{
args += " -flags -global_header -fflags +genpts";
}
return args;
}
var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
5.ToString(UsCulture));
args += keyFrameArg;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
var hasCopyTs = false;
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
var outputSizeParam = EncodingHelper.GetOutputSizeParam(state, videoCodec);
args += outputSizeParam;
hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
}
if (state.RunTimeTicks.HasValue && state.VideoRequest.CopyTimestamps)
{
if (!hasCopyTs)
{
args += " -copyts";
}
args += " -avoid_negative_ts disabled -start_at_zero";
}
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, encodingOptions, GetDefaultH264Preset());
if (!string.IsNullOrEmpty(qualityParam))
{
args += " " + qualityParam.Trim();
}
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec);
}
if (!state.RunTimeTicks.HasValue)
{
args += " -flags -global_header";
}
return args;
}
/// <summary>
/// Gets audio arguments to pass to ffmpeg
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
private string GetAudioArguments(StreamState state)
{
// If the video doesn't have an audio stream, return a default.
if (state.AudioStream == null && state.VideoStream != null)
{
return string.Empty;
}
// Get the output codec name
var codec = EncodingHelper.GetAudioEncoder(state);
var args = "-codec:a:0 " + codec;
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
return args;
}
// Add the number of audio channels
var channels = state.OutputAudioChannels;
if (channels.HasValue)
{
args += " -ac " + channels.Value;
}
var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue)
{
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), false);
return args;
} }
} }
} }

View File

@ -32,8 +32,6 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string AudioCodec { get; set; } public string AudioCodec { get; set; }
public string SubtitleCodec { get; set; }
[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; }
@ -41,6 +39,10 @@ namespace MediaBrowser.Api.Playback
public string PlaySessionId { get; set; } public string PlaySessionId { get; set; }
public string LiveStreamId { get; set; } public string LiveStreamId { get; set; }
public string Tag { get; set; } public string Tag { get; set; }
public string SegmentContainer { get; set; }
public int? SegmentLength { get; set; }
public int? MinSegments { get; set; }
} }
public class VideoStreamRequest : StreamRequest public class VideoStreamRequest : StreamRequest

View File

@ -60,6 +60,11 @@ namespace MediaBrowser.Api.Playback
{ {
get get
{ {
if (Request.SegmentLength.HasValue)
{
return Request.SegmentLength.Value;
}
if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{ {
var userAgent = UserAgent ?? string.Empty; var userAgent = UserAgent ?? string.Empty;
@ -86,6 +91,19 @@ namespace MediaBrowser.Api.Playback
} }
} }
public int MinSegments
{
get
{
if (Request.MinSegments.HasValue)
{
return Request.MinSegments.Value;
}
return SegmentLength >= 10 ? 2 : 3;
}
}
public bool IsSegmentedLiveStream public bool IsSegmentedLiveStream
{ {
get get
@ -102,7 +120,6 @@ namespace MediaBrowser.Api.Playback
} }
} }
public List<string> SupportedSubtitleCodecs { get; set; }
public string UserAgent { get; set; } public string UserAgent { get; set; }
public TranscodingJobType TranscodingType { get; set; } public TranscodingJobType TranscodingType { get; set; }
@ -111,14 +128,12 @@ namespace MediaBrowser.Api.Playback
{ {
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_logger = logger; _logger = logger;
SupportedSubtitleCodecs = new List<string>();
TranscodingType = transcodingType; TranscodingType = transcodingType;
} }
public string MimeType { get; set; } public string MimeType { get; set; }
public bool EstimateContentLength { get; set; } public bool EstimateContentLength { get; set; }
public bool EnableMpegtsM2TsMode { get; set; }
public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public long? EncodingDurationTicks { get; set; } public long? EncodingDurationTicks { get; set; }
@ -139,6 +154,8 @@ namespace MediaBrowser.Api.Playback
DisposeLiveStream(); DisposeLiveStream();
DisposeLogStream(); DisposeLogStream();
DisposeIsoMount(); DisposeIsoMount();
TranscodingJob = null;
} }
private void DisposeLogStream() private void DisposeLogStream()
@ -191,7 +208,6 @@ namespace MediaBrowser.Api.Playback
} }
public string OutputFilePath { get; set; } public string OutputFilePath { get; set; }
public int? OutputAudioBitrate;
public string ActualOutputVideoCodec public string ActualOutputVideoCodec
{ {
@ -462,5 +478,11 @@ namespace MediaBrowser.Api.Playback
return true; return true;
} }
} }
public TranscodingJob TranscodingJob;
public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
}
} }
} }

View File

@ -1,12 +1,9 @@
using MediaBrowser.Common.Configuration; using MediaBrowser.Controller;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Connect;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.LiveTv;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -52,16 +49,14 @@ namespace MediaBrowser.Api
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IConnectManager _connectManager; private readonly IConnectManager _connectManager;
private readonly ILiveTvManager _liveTvManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager, IMediaEncoder mediaEncoder) public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, IMediaEncoder mediaEncoder)
{ {
_config = config; _config = config;
_appHost = appHost; _appHost = appHost;
_userManager = userManager; _userManager = userManager;
_connectManager = connectManager; _connectManager = connectManager;
_liveTvManager = liveTvManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
} }
@ -92,20 +87,6 @@ namespace MediaBrowser.Api
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
}; };
var tvConfig = GetLiveTVConfiguration();
if (tvConfig.TunerHosts.Count > 0)
{
result.LiveTvTunerPath = tvConfig.TunerHosts[0].Url;
result.LiveTvTunerType = tvConfig.TunerHosts[0].Type;
}
if (tvConfig.ListingProviders.Count > 0)
{
result.LiveTvGuideProviderId = tvConfig.ListingProviders[0].Id;
result.LiveTvGuideProviderType = tvConfig.ListingProviders[0].Type;
}
return result; return result;
} }
@ -120,6 +101,7 @@ namespace MediaBrowser.Api
config.EnableSeriesPresentationUniqueKey = true; config.EnableSeriesPresentationUniqueKey = true;
config.EnableLocalizedGuids = true; config.EnableLocalizedGuids = true;
config.EnableSimpleArtistDetection = true; config.EnableSimpleArtistDetection = true;
config.EnableNormalizedItemByNameIds = true;
} }
public void Post(UpdateStartupConfiguration request) public void Post(UpdateStartupConfiguration request)
@ -128,9 +110,6 @@ namespace MediaBrowser.Api
_config.Configuration.MetadataCountryCode = request.MetadataCountryCode; _config.Configuration.MetadataCountryCode = request.MetadataCountryCode;
_config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage; _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
_config.SaveConfiguration(); _config.SaveConfiguration();
var task = UpdateTuners(request);
Task.WaitAll(task);
} }
public object Get(GetStartupUser request) public object Get(GetStartupUser request)
@ -165,51 +144,6 @@ namespace MediaBrowser.Api
return result; return result;
} }
private async Task UpdateTuners(UpdateStartupConfiguration request)
{
var config = GetLiveTVConfiguration();
var save = false;
if (string.IsNullOrWhiteSpace(request.LiveTvTunerPath) ||
string.IsNullOrWhiteSpace(request.LiveTvTunerType))
{
if (config.TunerHosts.Count > 0)
{
config.TunerHosts.Clear();
save = true;
}
}
else
{
if (!config.TunerHosts.Any(i => string.Equals(i.Type, request.LiveTvTunerType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.Url, request.LiveTvTunerPath, StringComparison.OrdinalIgnoreCase)))
{
// Add tuner
await _liveTvManager.SaveTunerHost(new TunerHostInfo
{
IsEnabled = true,
Type = request.LiveTvTunerType,
Url = request.LiveTvTunerPath
}).ConfigureAwait(false);
}
}
if (save)
{
SaveLiveTVConfiguration(config);
}
}
private void SaveLiveTVConfiguration(LiveTvOptions config)
{
_config.SaveConfiguration("livetv", config);
}
private LiveTvOptions GetLiveTVConfiguration()
{
return _config.GetConfiguration<LiveTvOptions>("livetv");
}
} }
public class StartupConfiguration public class StartupConfiguration
@ -217,10 +151,6 @@ namespace MediaBrowser.Api
public string UICulture { get; set; } public string UICulture { get; set; }
public string MetadataCountryCode { get; set; } public string MetadataCountryCode { get; set; }
public string PreferredMetadataLanguage { get; set; } public string PreferredMetadataLanguage { get; set; }
public string LiveTvTunerType { get; set; }
public string LiveTvTunerPath { get; set; }
public string LiveTvGuideProviderId { get; set; }
public string LiveTvGuideProviderType { get; set; }
} }
public class StartupInfo public class StartupInfo

View File

@ -72,6 +72,12 @@ namespace MediaBrowser.Api
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; } public bool? EnableUserData { get; set; }
public bool EnableTotalRecordCount { get; set; }
public GetNextUpEpisodes()
{
EnableTotalRecordCount = true;
}
} }
[Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")] [Route("/Shows/Upcoming", "GET", Summary = "Gets a list of upcoming episodes")]
@ -376,7 +382,8 @@ namespace MediaBrowser.Api
ParentId = request.ParentId, ParentId = request.ParentId,
SeriesId = request.SeriesId, SeriesId = request.SeriesId,
StartIndex = request.StartIndex, StartIndex = request.StartIndex,
UserId = request.UserId UserId = request.UserId,
EnableTotalRecordCount = request.EnableTotalRecordCount
}); });
var user = _userManager.GetUserById(request.UserId); var user = _userManager.GetUserById(request.UserId);

View File

@ -289,7 +289,12 @@ namespace MediaBrowser.Controller.Entities.Audio
} }
} }
public static string GetPath(string name, bool normalizeName = true) public static string GetPath(string name)
{
return GetPath(name, true);
}
public static string GetPath(string name, bool normalizeName)
{ {
// Trim the period at the end because windows will have a hard time with that // Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ? var validName = normalizeName ?

View File

@ -118,7 +118,12 @@ namespace MediaBrowser.Controller.Entities.Audio
return LibraryManager.GetItemList(query); return LibraryManager.GetItemList(query);
} }
public static string GetPath(string name, bool normalizeName = true) public static string GetPath(string name)
{
return GetPath(name, true);
}
public static string GetPath(string name, bool normalizeName)
{ {
// Trim the period at the end because windows will have a hard time with that // Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ? var validName = normalizeName ?

View File

@ -96,7 +96,12 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public static string GetPath(string name, bool normalizeName = true) public static string GetPath(string name)
{
return GetPath(name, true);
}
public static string GetPath(string name, bool normalizeName)
{ {
// Trim the period at the end because windows will have a hard time with that // Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ? var validName = normalizeName ?

View File

@ -108,7 +108,12 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public static string GetPath(string name, bool normalizeName = true) public static string GetPath(string name)
{
return GetPath(name, true);
}
public static string GetPath(string name, bool normalizeName)
{ {
// Trim the period at the end because windows will have a hard time with that // Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ? var validName = normalizeName ?

View File

@ -206,6 +206,8 @@ namespace MediaBrowser.Controller.Entities
void SetImage(ItemImageInfo image, int index); void SetImage(ItemImageInfo image, int index);
double? GetDefaultPrimaryImageAspectRatio(); double? GetDefaultPrimaryImageAspectRatio();
int? ProductionYear { get; set; }
} }
public static class HasImagesExtensions public static class HasImagesExtensions

View File

@ -133,7 +133,12 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public static string GetPath(string name, bool normalizeName = true) public static string GetPath(string name)
{
return GetPath(name, true);
}
public static string GetPath(string name, bool normalizeName)
{ {
// Trim the period at the end because windows will have a hard time with that // Trim the period at the end because windows will have a hard time with that
var validFilename = normalizeName ? var validFilename = normalizeName ?

View File

@ -114,7 +114,12 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public static string GetPath(string name, bool normalizeName = true) public static string GetPath(string name)
{
return GetPath(name, true);
}
public static string GetPath(string name, bool normalizeName)
{ {
// Trim the period at the end because windows will have a hard time with that // Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ? var validName = normalizeName ?

View File

@ -614,7 +614,8 @@ namespace MediaBrowser.Controller.Entities
Timestamp = i.Timestamp, Timestamp = i.Timestamp,
Type = type, Type = type,
PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(), PlayableStreamFileNames = i.PlayableStreamFileNames.ToList(),
SupportsDirectStream = i.VideoType == VideoType.VideoFile SupportsDirectStream = i.VideoType == VideoType.VideoFile,
IsRemote = i.IsShortcut
}; };
if (info.Protocol == MediaProtocol.File) if (info.Protocol == MediaProtocol.File)

View File

@ -122,7 +122,12 @@ namespace MediaBrowser.Controller.Entities
} }
} }
public static string GetPath(string name, bool normalizeName = true) public static string GetPath(string name)
{
return GetPath(name, true);
}
public static string GetPath(string name, bool normalizeName)
{ {
// Trim the period at the end because windows will have a hard time with that // Trim the period at the end because windows will have a hard time with that
var validName = normalizeName ? var validName = normalizeName ?

View File

@ -376,19 +376,14 @@ namespace MediaBrowser.Controller.LiveTv
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task OnRecordingFileDeleted(BaseItem recording); Task OnRecordingFileDeleted(BaseItem recording);
/// <summary>
/// Gets the sat ini mappings.
/// </summary>
/// <returns>List&lt;NameValuePair&gt;.</returns>
List<NameValuePair> GetSatIniMappings();
Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken);
Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
List<IListingsProvider> ListingProviders { get; } List<IListingsProvider> ListingProviders { get; }
List<NameIdPair> GetTunerHostTypes();
Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled; event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled; event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated; event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;

View File

@ -44,6 +44,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns> /// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken);
} }
public interface IConfigurableTunerHost public interface IConfigurableTunerHost
{ {

View File

@ -190,6 +190,7 @@
<Compile Include="MediaEncoding\ImageEncodingOptions.cs" /> <Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
<Compile Include="MediaEncoding\IMediaEncoder.cs" /> <Compile Include="MediaEncoding\IMediaEncoder.cs" />
<Compile Include="MediaEncoding\ISubtitleEncoder.cs" /> <Compile Include="MediaEncoding\ISubtitleEncoder.cs" />
<Compile Include="MediaEncoding\JobLogger.cs" />
<Compile Include="MediaEncoding\MediaInfoRequest.cs" /> <Compile Include="MediaEncoding\MediaInfoRequest.cs" />
<Compile Include="MediaEncoding\MediaStreamSelector.cs" /> <Compile Include="MediaEncoding\MediaStreamSelector.cs" />
<Compile Include="Net\AuthenticatedAttribute.cs" /> <Compile Include="Net\AuthenticatedAttribute.cs" />

View File

@ -154,6 +154,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return "mpegts"; return "mpegts";
} }
// For these need to find out the ffmpeg names // For these need to find out the ffmpeg names
if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase)) if (string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
{ {
@ -163,6 +164,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return null; return null;
} }
if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase)) if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase))
{ {
return null; return null;
@ -179,12 +184,23 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return null; return null;
} }
if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase))
{
return null;
}
// Seeing reported failures here, not sure yet if this is related to specfying input format // Seeing reported failures here, not sure yet if this is related to specfying input format
if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase)) if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
{ {
return null; return null;
} }
// obviously don't do this for strm files
if (string.Equals(container, "strm", StringComparison.OrdinalIgnoreCase))
{
return null;
}
return container; return container;
} }
@ -663,7 +679,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
// also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{ {
switch (level) switch (level)
@ -700,10 +715,15 @@ namespace MediaBrowser.Controller.MediaEncoding
break; break;
} }
} }
// nvenc doesn't decode with param -level set ?!
if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)){
param += "";
}
else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
{ {
param += " -level " + level; param += " -level " + level;
} }
} }
if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
@ -727,12 +747,18 @@ namespace MediaBrowser.Controller.MediaEncoding
if (videoStream.IsInterlaced) if (videoStream.IsInterlaced)
{ {
return false; if (request.DeInterlace)
{
return false;
}
} }
if (videoStream.IsAnamorphic ?? false) if (videoStream.IsAnamorphic ?? false)
{ {
return false; if (request.RequireNonAnamorphic)
{
return false;
}
} }
// Can't stream copy if we're burning in subtitles // Can't stream copy if we're burning in subtitles
@ -1561,6 +1587,15 @@ namespace MediaBrowser.Controller.MediaEncoding
MediaSourceInfo mediaSource, MediaSourceInfo mediaSource,
string requestedUrl) string requestedUrl)
{ {
if (state == null)
{
throw new ArgumentNullException("state");
}
if (mediaSource == null)
{
throw new ArgumentNullException("mediaSource");
}
state.MediaPath = mediaSource.Path; state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol; state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container; state.InputContainer = mediaSource.Container;
@ -1670,9 +1705,21 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264": case "h264":
if (_mediaEncoder.SupportsDecoder("h264_qsv")) if (_mediaEncoder.SupportsDecoder("h264_qsv"))
{ {
// qsv decoder does not support 10-bit input
if ((state.VideoStream.BitDepth ?? 8) > 8)
{
return null;
}
return "-c:v h264_qsv "; return "-c:v h264_qsv ";
} }
break; break;
//case "hevc":
//case "h265":
// if (_mediaEncoder.SupportsDecoder("hevc_qsv"))
// {
// return "-c:v hevc_qsv ";
// }
// break;
case "mpeg2video": case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_qsv")) if (_mediaEncoder.SupportsDecoder("mpeg2_qsv"))
{ {
@ -1715,5 +1762,187 @@ namespace MediaBrowser.Controller.MediaEncoding
return threads; return threads;
} }
public string GetSubtitleEmbedArguments(EncodingJobInfo state)
{
if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
{
return string.Empty;
}
var format = state.SupportedSubtitleCodecs.FirstOrDefault();
string codec;
if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase))
{
codec = "copy";
}
else
{
codec = format;
}
// Muxing in dvbsub via either copy or -codec dvbsub does not seem to work
// It doesn't throw any errors but vlc on android will not render them
// They will need to be converted to an alternative format
// TODO: This is incorrectly assuming that dvdsub will be supported by the player
// The api will need to be expanded to accomodate this.
if (string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
{
codec = "dvdsub";
}
var args = " -codec:s:0 " + codec;
args += " -disposition:s:0 default";
return args;
}
public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultH264Preset)
{
// Get the output codec name
var videoCodec = GetVideoEncoder(state, encodingOptions);
var format = string.Empty;
var keyFrame = string.Empty;
if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) &&
state.BaseRequest.Context == EncodingContext.Streaming)
{
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
format = " -f mp4 -movflags frag_keyframe+empty_moov";
}
var threads = GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
var inputModifier = GetInputModifier(state, encodingOptions);
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
inputModifier,
GetInputArgument(state, encodingOptions),
keyFrame,
GetMapArgs(state),
GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultH264Preset),
threads,
GetProgressiveVideoAudioArguments(state, encodingOptions),
GetSubtitleEmbedArguments(state),
format,
outputPath
).Trim();
}
public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset)
{
var args = "-codec:v:0 " + videoCodec;
if (state.EnableMpegtsM2TsMode)
{
args += " -mpegts_m2ts_mode 1";
}
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
args += " -bsf:v h264_mp4toannexb";
}
if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
{
args += " -copyts -avoid_negative_ts disabled -start_at_zero";
}
if (!state.RunTimeTicks.HasValue)
{
args += " -flags -global_header -fflags +genpts";
}
return args;
}
var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
5.ToString(_usCulture));
args += keyFrameArg;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.BaseRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
var hasCopyTs = false;
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
var outputSizeParam = GetOutputSizeParam(state, videoCodec);
args += outputSizeParam;
hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
}
if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
{
if (!hasCopyTs)
{
args += " -copyts";
}
args += " -avoid_negative_ts disabled -start_at_zero";
}
var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset);
if (!string.IsNullOrEmpty(qualityParam))
{
args += " " + qualityParam.Trim();
}
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += GetGraphicalSubtitleParam(state, videoCodec);
}
if (!state.RunTimeTicks.HasValue)
{
args += " -flags -global_header";
}
return args;
}
public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
{
// If the video doesn't have an audio stream, return a default.
if (state.AudioStream == null && state.VideoStream != null)
{
return string.Empty;
}
// Get the output codec name
var codec = GetAudioEncoder(state);
var args = "-codec:a:0 " + codec;
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
return args;
}
// Add the number of audio channels
var channels = state.OutputAudioChannels;
if (channels.HasValue)
{
args += " -ac " + channels.Value;
}
var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue)
{
args += " -ab " + bitrate.Value.ToString(_usCulture);
}
args += " " + GetAudioFilterParam(state, encodingOptions, false);
return args;
}
} }
} }

View File

@ -12,7 +12,7 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Controller.MediaEncoding namespace MediaBrowser.Controller.MediaEncoding
{ {
// For now, a common base class until the API and MediaEncoding classes are unified // For now, a common base class until the API and MediaEncoding classes are unified
public class EncodingJobInfo public abstract class EncodingJobInfo
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public int? OutputVideoBitrate { get; set; } public int? OutputVideoBitrate { get; set; }
public MediaStream SubtitleStream { get; set; } public MediaStream SubtitleStream { get; set; }
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
public List<string> SupportedSubtitleCodecs { get; set; }
public int InternalSubtitleStreamOffset { get; set; } public int InternalSubtitleStreamOffset { get; set; }
public MediaSourceInfo MediaSource { get; set; } public MediaSourceInfo MediaSource { get; set; }
@ -52,6 +53,8 @@ namespace MediaBrowser.Controller.MediaEncoding
public string InputContainer { get; set; } public string InputContainer { get; set; }
public IsoType? IsoType { get; set; } public IsoType? IsoType { get; set; }
public bool EnableMpegtsM2TsMode { get; set; }
public BaseEncodingJobOptions BaseRequest { get; set; } public BaseEncodingJobOptions BaseRequest { get; set; }
public long? StartTimeTicks public long? StartTimeTicks
@ -64,6 +67,7 @@ namespace MediaBrowser.Controller.MediaEncoding
get { return BaseRequest.CopyTimestamps; } get { return BaseRequest.CopyTimestamps; }
} }
public int? OutputAudioBitrate;
public int? OutputAudioChannels; public int? OutputAudioChannels;
public int? OutputAudioSampleRate; public int? OutputAudioSampleRate;
public bool DeInterlace { get; set; } public bool DeInterlace { get; set; }
@ -74,8 +78,9 @@ namespace MediaBrowser.Controller.MediaEncoding
_logger = logger; _logger = logger;
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
PlayableStreamFileNames = new List<string>(); PlayableStreamFileNames = new List<string>();
SupportedAudioCodecs = new List<string>();
SupportedVideoCodecs = new List<string>(); SupportedVideoCodecs = new List<string>();
SupportedVideoCodecs = new List<string>(); SupportedSubtitleCodecs = new List<string>();
} }
/// <summary> /// <summary>
@ -110,5 +115,7 @@ namespace MediaBrowser.Controller.MediaEncoding
IsoMount = null; IsoMount = null;
} }
} }
public abstract void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate);
} }
} }

View File

@ -14,7 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding
public string AudioCodec { get; set; } public string AudioCodec { get; set; }
public DeviceProfile DeviceProfile { get; set; } public DeviceProfile DeviceProfile { get; set; }
public EncodingContext Context { get; set; }
public bool ReadInputAtNativeFramerate { get; set; } public bool ReadInputAtNativeFramerate { get; set; }
@ -46,7 +45,7 @@ namespace MediaBrowser.Controller.MediaEncoding
AudioBitRate = info.AudioBitrate; AudioBitRate = info.AudioBitrate;
AudioSampleRate = info.TargetAudioSampleRate; AudioSampleRate = info.TargetAudioSampleRate;
DeviceProfile = deviceProfile; DeviceProfile = deviceProfile;
VideoCodec = info.VideoCodec; VideoCodec = info.TargetVideoCodec;
VideoBitRate = info.VideoBitrate; VideoBitRate = info.VideoBitrate;
AudioStreamIndex = info.AudioStreamIndex; AudioStreamIndex = info.AudioStreamIndex;
MaxRefFrames = info.MaxRefFrames; MaxRefFrames = info.MaxRefFrames;
@ -185,6 +184,8 @@ namespace MediaBrowser.Controller.MediaEncoding
[ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxVideoBitDepth { get; set; } public int? MaxVideoBitDepth { get; set; }
public bool RequireAvc { get; set; } public bool RequireAvc { get; set; }
public bool DeInterlace { get; set; }
public bool RequireNonAnamorphic { get; set; }
public int? TranscodingMaxAudioChannels { get; set; } public int? TranscodingMaxAudioChannels { get; set; }
public int? CpuCoreLimit { get; set; } public int? CpuCoreLimit { get; set; }
public string OutputContainer { get; set; } public string OutputContainer { get; set; }
@ -196,6 +197,8 @@ namespace MediaBrowser.Controller.MediaEncoding
[ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string VideoCodec { get; set; } public string VideoCodec { get; set; }
public string SubtitleCodec { get; set; }
/// <summary> /// <summary>
/// Gets or sets the index of the audio stream. /// Gets or sets the index of the audio stream.
/// </summary> /// </summary>
@ -210,9 +213,12 @@ namespace MediaBrowser.Controller.MediaEncoding
[ApiMember(Name = "VideoStreamIndex", Description = "Optional. The index of the video stream to use. If omitted the first video stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] [ApiMember(Name = "VideoStreamIndex", Description = "Optional. The index of the video stream to use. If omitted the first video stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? VideoStreamIndex { get; set; } public int? VideoStreamIndex { get; set; }
public EncodingContext Context { get; set; }
public BaseEncodingJobOptions() public BaseEncodingJobOptions()
{ {
EnableAutoStreamCopy = true; EnableAutoStreamCopy = true;
Context = EncodingContext.Streaming;
} }
} }
} }

View File

@ -6,7 +6,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace MediaBrowser.MediaEncoding.Encoder namespace MediaBrowser.Controller.MediaEncoding
{ {
public class JobLogger public class JobLogger
{ {
@ -18,7 +18,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger = logger; _logger = logger;
} }
public async void StartStreamingLog(EncodingJob transcodingJob, Stream source, Stream target) public async void StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
{ {
try try
{ {
@ -28,35 +28,41 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
var line = await reader.ReadLineAsync().ConfigureAwait(false); var line = await reader.ReadLineAsync().ConfigureAwait(false);
ParseLogLine(line, transcodingJob); ParseLogLine(line, state);
var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
await target.FlushAsync().ConfigureAwait(false);
} }
} }
} }
catch (ObjectDisposedException)
{
// Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.ErrorException("Error reading ffmpeg log", ex); _logger.ErrorException("Error reading ffmpeg log", ex);
} }
} }
private void ParseLogLine(string line, EncodingJob transcodingJob) private void ParseLogLine(string line, EncodingJobInfo state)
{ {
float? framerate = null; float? framerate = null;
double? percent = null; double? percent = null;
TimeSpan? transcodingPosition = null; TimeSpan? transcodingPosition = null;
long? bytesTranscoded = null; long? bytesTranscoded = null;
int? bitRate = null;
var parts = line.Split(' '); var parts = line.Split(' ');
var totalMs = transcodingJob.RunTimeTicks.HasValue var totalMs = state.RunTimeTicks.HasValue
? TimeSpan.FromTicks(transcodingJob.RunTimeTicks.Value).TotalMilliseconds ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds
: 0; : 0;
var startMs = transcodingJob.Options.StartTimeTicks.HasValue var startMs = state.BaseRequest.StartTimeTicks.HasValue
? TimeSpan.FromTicks(transcodingJob.Options.StartTimeTicks.Value).TotalMilliseconds ? TimeSpan.FromTicks(state.BaseRequest.StartTimeTicks.Value).TotalMilliseconds
: 0; : 0;
for (var i = 0; i < parts.Length; i++) for (var i = 0; i < parts.Length; i++)
@ -74,7 +80,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
framerate = val; framerate = val;
} }
} }
else if (transcodingJob.RunTimeTicks.HasValue && else if (state.RunTimeTicks.HasValue &&
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase)) part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
{ {
var time = part.Split(new[] { '=' }, 2).Last(); var time = part.Split(new[] { '=' }, 2).Last();
@ -111,11 +117,32 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
} }
} }
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
{
var rate = part.Split(new[] { '=' }, 2).Last();
int? scale = null;
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
{
scale = 1024;
rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase);
}
if (scale.HasValue)
{
float val;
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out val))
{
bitRate = (int)Math.Ceiling(val * scale.Value);
}
}
}
} }
if (framerate.HasValue || percent.HasValue) if (framerate.HasValue || percent.HasValue)
{ {
transcodingJob.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded); state.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
} }
} }
} }

View File

@ -23,21 +23,18 @@ namespace MediaBrowser.Controller.Net
public Action OnComplete { get; set; } public Action OnComplete { get; set; }
public Action OnError { get; set; } public Action OnError { get; set; }
public string Path { get; set; }
public FileShareMode FileShare { get; set; }
public StaticResultOptions() public StaticResultOptions()
{ {
ResponseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); ResponseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
FileShare = FileShareMode.Read;
} }
} }
public class StaticFileResultOptions : StaticResultOptions public class StaticFileResultOptions : StaticResultOptions
{ {
public string Path { get; set; }
public FileShareMode FileShare { get; set; }
public StaticFileResultOptions()
{
FileShare = FileShareMode.Read;
}
} }
} }

View File

@ -17,7 +17,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
} }
protected override Task<string> GetCommandLineArguments(EncodingJob state) protected override string GetCommandLineArguments(EncodingJob state)
{ {
var audioTranscodeParams = new List<string>(); var audioTranscodeParams = new List<string>();
@ -78,7 +78,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
mapArgs, mapArgs,
metadata).Trim(); metadata).Trim();
return Task.FromResult(result); return result;
} }
protected override string GetOutputFileExtension(EncodingJob state) protected override string GetOutputFileExtension(EncodingJob state)

View File

@ -66,7 +66,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
IProgress<double> progress, IProgress<double> progress,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager) var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager, MediaEncoder)
.CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
@ -76,7 +76,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false); await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false);
var commandLineArgs = await GetCommandLineArguments(encodingJob).ConfigureAwait(false); var commandLineArgs = GetCommandLineArguments(encodingJob);
var process = ProcessFactory.Create(new ProcessOptions var process = ProcessFactory.Create(new ProcessOptions
{ {
@ -242,7 +242,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private void OnTranscodeBeginning(EncodingJob job) private void OnTranscodeBeginning(EncodingJob job)
{ {
job.ReportTranscodingProgress(null, null, null, null); job.ReportTranscodingProgress(null, null, null, null, null);
} }
private void OnTranscodeFailedToStart(string path, EncodingJob job) private void OnTranscodeFailedToStart(string path, EncodingJob job)
@ -265,7 +265,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding"); return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
} }
protected abstract Task<string> GetCommandLineArguments(EncodingJob job); protected abstract string GetCommandLineArguments(EncodingJob job);
private string GetOutputFilePath(EncodingJob state) private string GetOutputFilePath(EncodingJob state)
{ {

View File

@ -89,6 +89,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var found = new List<string>(); var found = new List<string>();
var required = new[] var required = new[]
{ {
"mpeg2video",
"h264_qsv", "h264_qsv",
"hevc_qsv", "hevc_qsv",
"mpeg2_qsv", "mpeg2_qsv",

View File

@ -36,7 +36,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string MimeType { get; set; } public string MimeType { get; set; }
public bool EstimateContentLength { get; set; } public bool EstimateContentLength { get; set; }
public bool EnableMpegtsM2TsMode { get; set; }
public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public long? EncodingDurationTicks { get; set; } public long? EncodingDurationTicks { get; set; }
public string LiveStreamId { get; set; } public string LiveStreamId { get; set; }
@ -109,7 +108,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
public string OutputFilePath { get; set; } public string OutputFilePath { get; set; }
public int? OutputAudioBitrate;
public string ActualOutputVideoCodec public string ActualOutputVideoCodec
{ {
@ -379,7 +377,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return count; return count;
} }
public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded) public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{ {
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
@ -387,8 +385,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue) if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue)
{ {
var pct = ticks.Value/RunTimeTicks.Value; var pct = ticks.Value / RunTimeTicks.Value;
percentComplete = pct*100; percentComplete = pct * 100;
} }
if (percentComplete.HasValue) if (percentComplete.HasValue)

View File

@ -22,15 +22,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IConfigurationManager _config; private readonly IConfigurationManager _config;
private readonly IMediaEncoder _mediaEncoder;
protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config) public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder)
{ {
_logger = logger; _logger = logger;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_config = config; _config = config;
_mediaEncoder = mediaEncoder;
} }
public async Task<EncodingJob> CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken) public async Task<EncodingJob> CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken)
@ -61,6 +63,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
} }
if (!string.IsNullOrWhiteSpace(request.SubtitleCodec))
{
state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToSubtitleCodec(i))
?? state.SupportedSubtitleCodecs.FirstOrDefault();
}
var item = _libraryManager.GetItemById(request.ItemId); var item = _libraryManager.GetItemById(request.ItemId);
state.ItemType = item.GetType().Name; state.ItemType = item.GetType().Name;

View File

@ -647,9 +647,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); 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) 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 formats = (video.Container ?? string.Empty).Split(',').ToList();
var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) || 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; 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> /// <summary>
/// The us culture /// The us culture
/// </summary> /// </summary>

View File

@ -18,143 +18,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{ {
} }
protected override async Task<string> GetCommandLineArguments(EncodingJob state) protected override string GetCommandLineArguments(EncodingJob state)
{ {
// Get the output codec name // Get the output codec name
var encodingOptions = GetEncodingOptions(); var encodingOptions = GetEncodingOptions();
var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
var format = string.Empty; return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, state.OutputFilePath, "superfast");
var keyFrame = string.Empty;
if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase) &&
state.Options.Context == EncodingContext.Streaming)
{
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
format = " -f mp4 -movflags frag_keyframe+empty_moov";
}
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
var videoArguments = await GetVideoArguments(state, videoCodec).ConfigureAwait(false);
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions),
keyFrame,
EncodingHelper.GetMapArgs(state),
videoArguments,
threads,
GetAudioArguments(state),
format,
state.OutputFilePath
).Trim();
}
/// <summary>
/// Gets video arguments to pass to ffmpeg
/// </summary>
/// <param name="state">The state.</param>
/// <param name="videoCodec">The video codec.</param>
/// <returns>System.String.</returns>
private async Task<string> GetVideoArguments(EncodingJob state, string videoCodec)
{
var args = "-codec:v:0 " + videoCodec;
if (state.EnableMpegtsM2TsMode)
{
args += " -mpegts_m2ts_mode 1";
}
var isOutputMkv = string.Equals(state.Options.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
if (state.RunTimeTicks.HasValue)
{
//args += " -copyts -avoid_negative_ts disabled -start_at_zero";
}
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
args += " -bsf:v h264_mp4toannexb";
}
return args;
}
var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
5.ToString(UsCulture));
args += keyFrameArg;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode;
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
args += EncodingHelper.GetOutputSizeParam(state, videoCodec);
}
var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, GetEncodingOptions(), "superfast");
if (!string.IsNullOrEmpty(qualityParam))
{
args += " " + qualityParam.Trim();
}
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec);
}
return args;
}
/// <summary>
/// Gets audio arguments to pass to ffmpeg
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
private string GetAudioArguments(EncodingJob state)
{
// If the video doesn't have an audio stream, return a default.
if (state.AudioStream == null && state.VideoStream != null)
{
return string.Empty;
}
// Get the output codec name
var codec = EncodingHelper.GetAudioEncoder(state);
var args = "-codec:a:0 " + codec;
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
return args;
}
// Add the number of audio channels
var channels = state.OutputAudioChannels;
if (channels.HasValue)
{
args += " -ac " + channels.Value;
}
var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue)
{
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
args += " " + EncodingHelper.GetAudioFilterParam(state, GetEncodingOptions(), false);
return args;
} }
protected override string GetOutputFileExtension(EncodingJob state) protected override string GetOutputFileExtension(EncodingJob state)

View File

@ -55,7 +55,6 @@
<Compile Include="Encoder\EncodingUtils.cs" /> <Compile Include="Encoder\EncodingUtils.cs" />
<Compile Include="Encoder\EncoderValidator.cs" /> <Compile Include="Encoder\EncoderValidator.cs" />
<Compile Include="Encoder\FontConfigLoader.cs" /> <Compile Include="Encoder\FontConfigLoader.cs" />
<Compile Include="Encoder\JobLogger.cs" />
<Compile Include="Encoder\MediaEncoder.cs" /> <Compile Include="Encoder\MediaEncoder.cs" />
<Compile Include="Encoder\VideoEncoder.cs" /> <Compile Include="Encoder\VideoEncoder.cs" />
<Compile Include="Probing\FFProbeHelpers.cs" /> <Compile Include="Probing\FFProbeHelpers.cs" />

View File

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

View File

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

View File

@ -734,16 +734,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
} }
} }
var charsetFromLanguage = string.IsNullOrWhiteSpace(language)
? null
: GetSubtitleFileCharacterSetFromLanguage(language);
// This assumption should only be made for external subtitles
if (!string.IsNullOrWhiteSpace(charsetFromLanguage) && !string.Equals(charsetFromLanguage, "windows-1252", StringComparison.OrdinalIgnoreCase))
{
return charsetFromLanguage;
}
var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false); var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(charset)) if (!string.IsNullOrWhiteSpace(charset))
@ -756,7 +746,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return charset; return charset;
} }
return charsetFromLanguage; if (!string.IsNullOrWhiteSpace(language))
{
return GetSubtitleFileCharacterSetFromLanguage(language);
}
return null;
} }
public string GetSubtitleFileCharacterSetFromLanguage(string language) public string GetSubtitleFileCharacterSetFromLanguage(string language)

View File

@ -16,6 +16,8 @@
public bool EnableAutomaticSeriesGrouping { get; set; } public bool EnableAutomaticSeriesGrouping { get; set; }
public bool EnableEmbeddedTitles { get; set; } public bool EnableEmbeddedTitles { get; set; }
public int AutomaticRefreshIntervalDays { get; set; }
/// <summary> /// <summary>
/// Gets or sets the preferred metadata language. /// Gets or sets the preferred metadata language.
/// </summary> /// </summary>

View File

@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Configuration
public bool EnableHttps { get; set; } public bool EnableHttps { get; set; }
public bool EnableSeriesPresentationUniqueKey { get; set; } public bool EnableSeriesPresentationUniqueKey { get; set; }
public bool EnableLocalizedGuids { get; set; } public bool EnableLocalizedGuids { get; set; }
public bool EnableNormalizedItemByNameIds { get; set; }
/// <summary> /// <summary>
/// Gets or sets the value pointing to the file system where the ssl certiifcate is located.. /// Gets or sets the value pointing to the file system where the ssl certiifcate is located..

View File

@ -21,6 +21,7 @@
NumVideoStreams = 17, NumVideoStreams = 17,
IsSecondaryAudio = 18, IsSecondaryAudio = 18,
VideoCodecTag = 19, VideoCodecTag = 19,
IsAvc = 20 IsAvc = 20,
IsInterlaced = 21
} }
} }

View File

@ -231,6 +231,7 @@ namespace MediaBrowser.Model.Dlna
{ {
playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(','); playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
} }
playlistItem.SubProtocol = transcodingProfile.Protocol; playlistItem.SubProtocol = transcodingProfile.Protocol;
List<CodecProfile> audioCodecProfiles = new List<CodecProfile>(); List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
@ -323,7 +324,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlayProfile != null) if (directPlayProfile != null)
{ {
// While options takes the network and other factors into account. Only applies to direct stream // While options takes the network and other factors into account. Only applies to direct stream
if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true)) && options.EnableDirectStream) if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true), PlayMethod.DirectStream) && options.EnableDirectStream)
{ {
playMethods.Add(PlayMethod.DirectStream); playMethods.Add(PlayMethod.DirectStream);
} }
@ -331,7 +332,7 @@ namespace MediaBrowser.Model.Dlna
// The profile describes what the device supports // The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play // If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay && if (item.SupportsDirectPlay &&
IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true)) && options.EnableDirectPlay) IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true), PlayMethod.DirectPlay) && options.EnableDirectPlay)
{ {
playMethods.Add(PlayMethod.DirectPlay); playMethods.Add(PlayMethod.DirectPlay);
} }
@ -479,10 +480,19 @@ namespace MediaBrowser.Model.Dlna
playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(','); playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
playlistItem.VideoCodec = transcodingProfile.VideoCodec; playlistItem.VideoCodecs = transcodingProfile.VideoCodec.Split(',');
playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps; playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest; playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
if (transcodingProfile.MinSegments > 0)
{
playlistItem.MinSegments = transcodingProfile.MinSegments;
}
if (transcodingProfile.SegmentLength > 0)
{
playlistItem.SegmentLength = transcodingProfile.SegmentLength;
}
if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels)) if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels))
{ {
int transcodingMaxAudioChannels; int transcodingMaxAudioChannels;
@ -895,7 +905,7 @@ namespace MediaBrowser.Model.Dlna
} }
} }
return IsAudioEligibleForDirectPlay(item, maxBitrate); return IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
} }
public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer) public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer)
@ -1025,23 +1035,29 @@ namespace MediaBrowser.Model.Dlna
return null; return null;
} }
private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate) private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate, PlayMethod playMethod)
{ {
// Don't restrict by bitrate if coming from an external domain
if (item.IsRemote)
{
return true;
}
if (!maxBitrate.HasValue) if (!maxBitrate.HasValue)
{ {
_logger.Info("Cannot direct play due to unknown supported bitrate"); _logger.Info("Cannot "+ playMethod + " due to unknown supported bitrate");
return false; return false;
} }
if (!item.Bitrate.HasValue) if (!item.Bitrate.HasValue)
{ {
_logger.Info("Cannot direct play due to unknown content bitrate"); _logger.Info("Cannot " + playMethod + " due to unknown content bitrate");
return false; return false;
} }
if (item.Bitrate.Value > maxBitrate.Value) if (item.Bitrate.Value > maxBitrate.Value)
{ {
_logger.Info("Bitrate exceeds DirectPlay limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture)); _logger.Info("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture));
return false; return false;
} }
@ -1137,6 +1153,37 @@ namespace MediaBrowser.Model.Dlna
break; break;
} }
case ProfileConditionValue.IsAnamorphic: case ProfileConditionValue.IsAnamorphic:
{
bool isAnamorphic;
if (bool.TryParse(value, out isAnamorphic))
{
if (isAnamorphic && condition.Condition == ProfileConditionType.Equals)
{
item.RequireNonAnamorphic = true;
}
else if (!isAnamorphic && condition.Condition == ProfileConditionType.NotEquals)
{
item.RequireNonAnamorphic = true;
}
}
break;
}
case ProfileConditionValue.IsInterlaced:
{
bool isInterlaced;
if (bool.TryParse(value, out isInterlaced))
{
if (isInterlaced && condition.Condition == ProfileConditionType.Equals)
{
item.DeInterlace = true;
}
else if (!isInterlaced && condition.Condition == ProfileConditionType.NotEquals)
{
item.DeInterlace = true;
}
}
break;
}
case ProfileConditionValue.AudioProfile: case ProfileConditionValue.AudioProfile:
case ProfileConditionValue.Has64BitOffsets: case ProfileConditionValue.Has64BitOffsets:
case ProfileConditionValue.PacketLength: case ProfileConditionValue.PacketLength:

View File

@ -6,6 +6,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna
@ -18,6 +19,7 @@ namespace MediaBrowser.Model.Dlna
public StreamInfo() public StreamInfo()
{ {
AudioCodecs = new string[] { }; AudioCodecs = new string[] { };
VideoCodecs = new string[] { };
SubtitleCodecs = new string[] { }; SubtitleCodecs = new string[] { };
} }
@ -34,13 +36,18 @@ namespace MediaBrowser.Model.Dlna
public long StartPositionTicks { get; set; } public long StartPositionTicks { get; set; }
public string VideoCodec { get; set; }
public string VideoProfile { get; set; } public string VideoProfile { get; set; }
public int? SegmentLength { get; set; }
public int? MinSegments { get; set; }
public bool RequireAvc { get; set; } public bool RequireAvc { get; set; }
public bool DeInterlace { get; set; }
public bool RequireNonAnamorphic { get; set; }
public bool CopyTimestamps { get; set; } public bool CopyTimestamps { get; set; }
public bool EnableSubtitlesInManifest { get; set; } public bool EnableSubtitlesInManifest { get; set; }
public string[] AudioCodecs { get; set; } public string[] AudioCodecs { get; set; }
public string[] VideoCodecs { get; set; }
public int? AudioStreamIndex { get; set; } public int? AudioStreamIndex { get; set; }
@ -204,11 +211,15 @@ namespace MediaBrowser.Model.Dlna
string.Empty : string.Empty :
string.Join(",", item.AudioCodecs); string.Join(",", item.AudioCodecs);
string videoCodecs = item.VideoCodecs.Length == 0 ?
string.Empty :
string.Join(",", item.VideoCodecs);
list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty)); list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
list.Add(new NameValuePair("Static", item.IsDirectStream.ToString().ToLower())); list.Add(new NameValuePair("Static", item.IsDirectStream.ToString().ToLower()));
list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty)); list.Add(new NameValuePair("VideoCodec", videoCodecs));
list.Add(new NameValuePair("AudioCodec", audioCodecs)); list.Add(new NameValuePair("AudioCodec", audioCodecs));
list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty)); list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty));
list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty)); list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty));
@ -232,7 +243,9 @@ namespace MediaBrowser.Model.Dlna
// } // }
//} //}
if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls") && !forceStartPosition) var isHls = StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls");
if (isHls && !forceStartPosition)
{ {
list.Add(new NameValuePair("StartTimeTicks", string.Empty)); list.Add(new NameValuePair("StartTimeTicks", string.Empty));
} }
@ -276,6 +289,24 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty)); list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString().ToLower()));
list.Add(new NameValuePair("DeInterlace", item.DeInterlace.ToString().ToLower()));
if (!isDlna && isHls)
{
list.Add(new NameValuePair("SegmentContainer", item.Container ?? string.Empty));
if (item.SegmentLength.HasValue)
{
list.Add(new NameValuePair("SegmentLength", item.SegmentLength.Value.ToString(CultureInfo.InvariantCulture)));
}
if (item.MinSegments.HasValue)
{
list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture)));
}
}
return list; return list;
} }
@ -609,9 +640,34 @@ namespace MediaBrowser.Model.Dlna
} }
} }
public string TargetVideoCodec
{
get
{
MediaStream stream = TargetVideoStream;
string inputCodec = stream == null ? null : stream.Codec;
if (IsDirectStream)
{
return inputCodec;
}
foreach (string codec in VideoCodecs)
{
if (StringHelper.EqualsIgnoreCase(codec, inputCodec))
{
return codec;
}
}
return VideoCodecs.Length == 0 ? null : VideoCodecs[0];
}
}
/// <summary> /// <summary>
/// Predicts the audio channels that will be in the output stream /// Predicts the audio channels that will be in the output stream
/// </summary> /// </summary>
public long? TargetSize public long? TargetSize
{ {
get get

View File

@ -42,6 +42,12 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("maxAudioChannels")] [XmlAttribute("maxAudioChannels")]
public string MaxAudioChannels { get; set; } public string MaxAudioChannels { get; set; }
[XmlAttribute("minSegments")]
public int MinSegments { get; set; }
[XmlAttribute("segmentLength")]
public int SegmentLength { get; set; }
public List<string> GetAudioCodecs() public List<string> GetAudioCodecs()
{ {
List<string> list = new List<string>(); List<string> list = new List<string>();

View File

@ -10,6 +10,8 @@ namespace MediaBrowser.Model.IO
/// </summary> /// </summary>
public interface IFileSystem public interface IFileSystem
{ {
void AddShortcutHandler(IShortcutHandler handler);
/// <summary> /// <summary>
/// Determines whether the specified filename is shortcut. /// Determines whether the specified filename is shortcut.
/// </summary> /// </summary>

View File

@ -34,7 +34,7 @@ namespace MediaBrowser.Model.LiveTv
TunerHosts = new List<TunerHostInfo>(); TunerHosts = new List<TunerHostInfo>();
ListingProviders = new List<ListingsProviderInfo>(); ListingProviders = new List<ListingsProviderInfo>();
MediaLocationsCreated = new string[] { }; MediaLocationsCreated = new string[] { };
RecordingEncodingFormat = "mp4"; RecordingEncodingFormat = "mkv";
RecordingPostProcessorArguments = "\"{path}\""; RecordingPostProcessorArguments = "\"{path}\"";
EnableRecordingEncoding = true; EnableRecordingEncoding = true;
} }
@ -46,14 +46,13 @@ namespace MediaBrowser.Model.LiveTv
public string Url { get; set; } public string Url { get; set; }
public string Type { get; set; } public string Type { get; set; }
public string DeviceId { get; set; } public string DeviceId { get; set; }
public string FriendlyName { get; set; }
public bool ImportFavoritesOnly { get; set; } public bool ImportFavoritesOnly { get; set; }
public bool AllowHWTranscoding { get; set; } public bool AllowHWTranscoding { get; set; }
public bool IsEnabled { get; set; }
public bool EnableTvgId { get; set; } public bool EnableTvgId { get; set; }
public TunerHostInfo() public TunerHostInfo()
{ {
IsEnabled = true;
AllowHWTranscoding = true; AllowHWTranscoding = true;
} }
} }

View File

@ -27,9 +27,11 @@ namespace MediaBrowser.Model.MediaInfo
public bool EnableDirectPlay { get; set; } public bool EnableDirectPlay { get; set; }
public bool EnableDirectStream { get; set; } public bool EnableDirectStream { get; set; }
public bool EnableTranscoding { get; set; } public bool EnableTranscoding { get; set; }
public bool ForceDirectPlayRemoteMediaSource { get; set; }
public PlaybackInfoRequest() public PlaybackInfoRequest()
{ {
ForceDirectPlayRemoteMediaSource = true;
EnableDirectPlay = true; EnableDirectPlay = true;
EnableDirectStream = true; EnableDirectStream = true;
EnableTranscoding = true; EnableTranscoding = true;

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Model.Net namespace MediaBrowser.Model.Net
{ {
@ -13,6 +15,7 @@ namespace MediaBrowser.Model.Net
void Bind(IpEndPointInfo endpoint); void Bind(IpEndPointInfo endpoint);
void Connect(IpEndPointInfo endPoint); void Connect(IpEndPointInfo endPoint);
void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed); void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed);
Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken);
} }
public class SocketCreateException : Exception public class SocketCreateException : Exception

View File

@ -24,5 +24,6 @@ namespace MediaBrowser.Model.Net
/// Sends a UDP message to a particular end point (uni or multicast). /// Sends a UDP message to a particular end point (uni or multicast).
/// </summary> /// </summary>
Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken); Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken);
Task SendWithLockAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken);
} }
} }

View File

@ -14,6 +14,8 @@ namespace MediaBrowser.Model.Net
/// <returns>A <see cref="ISocket"/> implementation.</returns> /// <returns>A <see cref="ISocket"/> implementation.</returns>
ISocket CreateUdpSocket(int localPort); ISocket CreateUdpSocket(int localPort);
ISocket CreateUdpBroadcastSocket(int localPort);
ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort); ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort);
/// <summary> /// <summary>

View File

@ -55,9 +55,12 @@ namespace MediaBrowser.Model.Querying
/// <value>The enable image types.</value> /// <value>The enable image types.</value>
public ImageType[] EnableImageTypes { get; set; } public ImageType[] EnableImageTypes { get; set; }
public bool EnableTotalRecordCount { get; set; }
public NextUpQuery() public NextUpQuery()
{ {
EnableImageTypes = new ImageType[] {}; EnableImageTypes = new ImageType[] {};
EnableTotalRecordCount = true;
} }
} }
} }

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