mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-06 19:37:55 +02:00
commit
2ceaa50ea7
104 changed files with 2927 additions and 5865 deletions
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Common.Implementations.Networking;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
@ -56,7 +57,7 @@ namespace Emby.Common.Implementations.Net
|
|||
state.TaskCompletionSource = tcs;
|
||||
|
||||
#if NETSTANDARD1_6
|
||||
_Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer),SocketFlags.None, state.RemoteEndPoint)
|
||||
_Socket.ReceiveFromAsync(new ArraySegment<Byte>(state.Buffer), SocketFlags.None, state.RemoteEndPoint)
|
||||
.ContinueWith((task, asyncState) =>
|
||||
{
|
||||
if (task.Status != TaskStatus.Faulted)
|
||||
|
@ -73,7 +74,7 @@ namespace Emby.Common.Implementations.Net
|
|||
return tcs.Task;
|
||||
}
|
||||
|
||||
public Task SendAsync(byte[] buffer, int size, IpEndPointInfo endPoint)
|
||||
public Task SendAsync(byte[] buffer, int size, IpEndPointInfo endPoint, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
|
@ -91,6 +92,8 @@ namespace Emby.Common.Implementations.Net
|
|||
buffer = copy;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_Socket.SendTo(buffer, ipEndPoint);
|
||||
return Task.FromResult(true);
|
||||
#else
|
||||
|
@ -100,6 +103,11 @@ namespace Emby.Common.Implementations.Net
|
|||
{
|
||||
_Socket.BeginSendTo(buffer, 0, size, SocketFlags.None, ipEndPoint, result =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
taskSource.TrySetCanceled();
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
_Socket.EndSend(result);
|
||||
|
|
|
@ -646,11 +646,6 @@ namespace Emby.Dlna.Didl
|
|||
{
|
||||
var desc = item.Overview;
|
||||
|
||||
if (!string.IsNullOrEmpty(item.ShortOverview))
|
||||
{
|
||||
desc = item.ShortOverview;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(desc))
|
||||
{
|
||||
AddValue(writer, "dc", "description", desc, NS_DC);
|
||||
|
|
|
@ -75,16 +75,20 @@ namespace Emby.Dlna.Ssdp
|
|||
// Enable listening for notifications (optional)
|
||||
_deviceLocator.StartListeningForNotifications();
|
||||
|
||||
await _deviceLocator.SearchAsync().ConfigureAwait(false);
|
||||
await _deviceLocator.SearchAsync(_tokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
|
||||
|
||||
await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error searching for devices", ex);
|
||||
}
|
||||
|
||||
var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
|
||||
|
||||
await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
}, CancellationToken.None, TaskCreationOptions.LongRunning);
|
||||
|
|
|
@ -413,41 +413,41 @@ namespace Emby.Server.Core
|
|||
|
||||
var result = new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer"));
|
||||
|
||||
ServiceStack.Text.JsConfig<LiveTvProgram>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<LiveTvChannel>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<LiveTvVideoRecording>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<LiveTvAudioRecording>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Audio>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<MusicAlbum>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<MusicArtist>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<MusicGenre>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<MusicVideo>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<AudioBook>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Season>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Book>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<CollectionFolder>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Folder>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Game>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<GameGenre>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<GameSystem>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Genre>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "PlaceOfBirth", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Photo>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<PhotoAlbum>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Studio>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<UserRootFolder>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<UserView>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Video>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Year>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Channel>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<AggregateFolder>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "ShortOverview", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<LiveTvProgram>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<LiveTvChannel>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<LiveTvVideoRecording>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<LiveTvAudioRecording>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Series>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Audio>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<MusicAlbum>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<MusicArtist>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<MusicGenre>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<MusicVideo>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Movie>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Playlist>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<AudioPodcast>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<AudioBook>.ExcludePropertyNames = new[] { "Artists", "AlbumArtists", "ChannelMediaSources", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Trailer>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<BoxSet>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Episode>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Season>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Book>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<CollectionFolder>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Folder>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Game>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<GameGenre>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<GameSystem>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Genre>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Person>.ExcludePropertyNames = new[] { "PlaceOfBirth", "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Photo>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<PhotoAlbum>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Studio>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<UserRootFolder>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<UserView>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Video>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Year>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<Channel>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
ServiceStack.Text.JsConfig<AggregateFolder>.ExcludePropertyNames = new[] { "ProviderIds", "ImageInfos", "ProductionLocations", "ThemeSongIds", "ThemeVideoIds", "TotalBitrate", "Taglines", "Keywords", "ExtraType" };
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -141,7 +141,6 @@ namespace Emby.Server.Core.Configuration
|
|||
{
|
||||
var newConfig = (ServerConfiguration)newConfiguration;
|
||||
|
||||
ValidatePathSubstitutions(newConfig);
|
||||
ValidateMetadataPath(newConfig);
|
||||
ValidateSslCertificate(newConfig);
|
||||
|
||||
|
@ -173,17 +172,6 @@ namespace Emby.Server.Core.Configuration
|
|||
}
|
||||
}
|
||||
|
||||
private void ValidatePathSubstitutions(ServerConfiguration newConfig)
|
||||
{
|
||||
foreach (var map in newConfig.PathSubstitutions)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(map.From) || string.IsNullOrWhiteSpace(map.To))
|
||||
{
|
||||
throw new ArgumentException("Invalid path substitution");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the metadata path.
|
||||
/// </summary>
|
||||
|
|
|
@ -282,11 +282,6 @@ namespace Emby.Server.Core.IO
|
|||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
private void StartWatchingPath(string path)
|
||||
{
|
||||
// Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_fileSystem.DirectoryExists(path))
|
||||
{
|
||||
|
@ -295,6 +290,17 @@ namespace Emby.Server.Core.IO
|
|||
return;
|
||||
}
|
||||
|
||||
// Already being watched
|
||||
if (_fileSystemWatchers.ContainsKey(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var newWatcher = new FileSystemWatcher(path, "*")
|
||||
{
|
||||
IncludeSubdirectories = true
|
||||
|
@ -326,7 +332,6 @@ namespace Emby.Server.Core.IO
|
|||
}
|
||||
else
|
||||
{
|
||||
Logger.Info("Unable to add directory watcher for {0}. It already exists in the dictionary.", path);
|
||||
newWatcher.Dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -995,7 +995,7 @@ namespace Emby.Server.Implementations.Connect
|
|||
|
||||
if (changed)
|
||||
{
|
||||
await _providerManager.SaveImage(user, imageUrl, null, ImageType.Primary, null, CancellationToken.None).ConfigureAwait(false);
|
||||
await _providerManager.SaveImage(user, imageUrl, ImageType.Primary, null, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
await user.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
|
||||
{
|
||||
|
|
|
@ -257,7 +257,6 @@ namespace Emby.Server.Implementations.Data
|
|||
AddColumn(db, "TypedBaseItems", "SeriesId", "GUID", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "SeriesSortName", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "ExternalSeriesId", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "ShortOverview", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "Tagline", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "Keywords", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "ProviderIds", "Text", existingColumnNames);
|
||||
|
@ -466,7 +465,6 @@ namespace Emby.Server.Implementations.Data
|
|||
"InheritedParentalRatingValue",
|
||||
"InheritedTags",
|
||||
"ExternalSeriesId",
|
||||
"ShortOverview",
|
||||
"Tagline",
|
||||
"Keywords",
|
||||
"ProviderIds",
|
||||
|
@ -598,7 +596,6 @@ namespace Emby.Server.Implementations.Data
|
|||
"SeriesId",
|
||||
"SeriesSortName",
|
||||
"ExternalSeriesId",
|
||||
"ShortOverview",
|
||||
"Tagline",
|
||||
"Keywords",
|
||||
"ProviderIds",
|
||||
|
@ -1038,7 +1035,6 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
|
||||
saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId);
|
||||
saveItemStatement.TryBind("@ShortOverview", item.ShortOverview);
|
||||
saveItemStatement.TryBind("@Tagline", item.Tagline);
|
||||
|
||||
if (item.Keywords.Count > 0)
|
||||
|
@ -1893,15 +1889,6 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
index++;
|
||||
|
||||
if (query.HasField(ItemFields.ShortOverview))
|
||||
{
|
||||
if (!reader.IsDBNull(index))
|
||||
{
|
||||
item.ShortOverview = reader.GetString(index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if (query.HasField(ItemFields.Taglines))
|
||||
{
|
||||
if (!reader.IsDBNull(index))
|
||||
|
|
|
@ -1052,11 +1052,6 @@ namespace Emby.Server.Implementations.Dto
|
|||
dto.OriginalTitle = item.OriginalTitle;
|
||||
}
|
||||
|
||||
if (fields.Contains(ItemFields.ShortOverview))
|
||||
{
|
||||
dto.ShortOverview = item.ShortOverview;
|
||||
}
|
||||
|
||||
if (fields.Contains(ItemFields.ParentId))
|
||||
{
|
||||
var displayParentId = item.DisplayParentId;
|
||||
|
|
|
@ -309,8 +309,8 @@
|
|||
<Project>{4f26d5d8-a7b0-42b3-ba42-7cb7d245934e}</Project>
|
||||
<Name>SocketHttpListener.Portable</Name>
|
||||
</ProjectReference>
|
||||
<Reference Include="Emby.XmlTv, Version=1.0.6193.39741, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Emby.XmlTv.1.0.3\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
|
||||
<Reference Include="Emby.XmlTv, Version=1.0.6241.4924, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Emby.XmlTv.1.0.5\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MediaBrowser.Naming, Version=1.0.6201.24431, Culture=neutral, processorArchitecture=MSIL">
|
||||
|
|
|
@ -1927,11 +1927,18 @@ namespace Emby.Server.Implementations.Library
|
|||
return ItemRepository.RetrieveItem(id);
|
||||
}
|
||||
|
||||
public IEnumerable<Folder> GetCollectionFolders(BaseItem item)
|
||||
public List<Folder> GetCollectionFolders(BaseItem item)
|
||||
{
|
||||
while (!(item.GetParent() is AggregateFolder) && item.GetParent() != null)
|
||||
while (item != null)
|
||||
{
|
||||
item = item.GetParent();
|
||||
var parent = item.GetParent();
|
||||
|
||||
if (parent == null || parent is AggregateFolder)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
item = parent;
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
|
@ -1941,7 +1948,8 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
return GetUserRootFolder().Children
|
||||
.OfType<Folder>()
|
||||
.Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase));
|
||||
.Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public LibraryOptions GetLibraryOptions(BaseItem item)
|
||||
|
@ -2619,18 +2627,6 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(map.From))
|
||||
{
|
||||
var substitutionResult = SubstitutePathInternal(path, map.From, map.To);
|
||||
if (substitutionResult.Item2)
|
||||
{
|
||||
return substitutionResult.Item1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -2764,7 +2760,6 @@ namespace Emby.Server.Implementations.Library
|
|||
return ItemRepository.UpdatePeople(item.Id, people);
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim _dynamicImageResourcePool = new SemaphoreSlim(1, 1);
|
||||
public async Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex)
|
||||
{
|
||||
foreach (var url in image.Path.Split('|'))
|
||||
|
@ -2773,7 +2768,7 @@ namespace Emby.Server.Implementations.Library
|
|||
{
|
||||
_logger.Debug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url);
|
||||
|
||||
await _providerManagerFactory().SaveImage(item, url, _dynamicImageResourcePool, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
|
||||
await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var newImage = item.GetImageInfo(image.Type, imageIndex);
|
||||
|
||||
|
|
|
@ -74,20 +74,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||
return new MusicArtist();
|
||||
}
|
||||
|
||||
if (_config.Configuration.EnableSimpleArtistDetection)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//if (_config.Configuration.EnableSimpleArtistDetection)
|
||||
//{
|
||||
// return null;
|
||||
//}
|
||||
|
||||
// Avoid mis-identifying top folders
|
||||
if (args.Parent.IsRoot) return null;
|
||||
//// Avoid mis-identifying top folders
|
||||
//if (args.Parent.IsRoot) return null;
|
||||
|
||||
var directoryService = args.DirectoryService;
|
||||
//var directoryService = args.DirectoryService;
|
||||
|
||||
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
|
||||
//var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
|
||||
|
||||
// If we contain an album assume we are an artist folder
|
||||
return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
|
||||
//// If we contain an album assume we are an artist folder
|
||||
//return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -64,6 +64,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
|||
episode.SeasonId = season.Id;
|
||||
episode.SeasonName = season.Name;
|
||||
}
|
||||
|
||||
// Assume season 1 if there's no season folder and a season number could not be determined
|
||||
if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue))
|
||||
{
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return episode;
|
||||
|
|
|
@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.Library
|
|||
return new Tuple<BaseItem, string, int>(item, index.Item1, index.Item2);
|
||||
}));
|
||||
|
||||
var returnValue = hints.Where(i => i.Item3 >= 0).OrderBy(i => i.Item3).Select(i => new SearchHintInfo
|
||||
var returnValue = hints.Where(i => i.Item3 >= 0).OrderBy(i => i.Item3).ThenBy(i => i.Item1.SortName).Select(i => new SearchHintInfo
|
||||
{
|
||||
Item = i.Item1,
|
||||
MatchedTerm = i.Item2
|
||||
|
|
|
@ -393,7 +393,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
try
|
||||
{
|
||||
await provider.Item1.AddMetadata(provider.Item2, enabledChannels, cancellationToken).ConfigureAwait(false);
|
||||
await AddMetadata(provider.Item1, provider.Item2, enabledChannels, enableCache, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
|
@ -409,6 +409,120 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
return list;
|
||||
}
|
||||
|
||||
private async Task AddMetadata(IListingsProvider provider, ListingsProviderInfo info, List<ChannelInfo> tunerChannels, bool enableCache, CancellationToken cancellationToken)
|
||||
{
|
||||
var epgChannels = await GetEpgChannels(provider, info, enableCache, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var tunerChannel in tunerChannels)
|
||||
{
|
||||
var epgChannel = GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels);
|
||||
|
||||
if (epgChannel != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(epgChannel.Name))
|
||||
{
|
||||
tunerChannel.Name = epgChannel.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, List<ChannelInfo>> _epgChannels =
|
||||
new ConcurrentDictionary<string, List<ChannelInfo>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private async Task<List<ChannelInfo>> GetEpgChannels(IListingsProvider provider, ListingsProviderInfo info, bool enableCache, CancellationToken cancellationToken)
|
||||
{
|
||||
List<ChannelInfo> result;
|
||||
if (!enableCache || !_epgChannels.TryGetValue(info.Id, out result))
|
||||
{
|
||||
result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ChannelInfo> GetEpgChannelFromTunerChannel(IListingsProvider provider, ListingsProviderInfo info, ChannelInfo tunerChannel, CancellationToken cancellationToken)
|
||||
{
|
||||
var epgChannels = await GetEpgChannels(provider, info, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels);
|
||||
}
|
||||
|
||||
private string GetMappedChannel(string channelId, List<NameValuePair> mappings)
|
||||
{
|
||||
foreach (NameValuePair mapping in mappings)
|
||||
{
|
||||
if (StringHelper.EqualsIgnoreCase(mapping.Name, channelId))
|
||||
{
|
||||
return mapping.Value;
|
||||
}
|
||||
}
|
||||
return channelId;
|
||||
}
|
||||
|
||||
private ChannelInfo GetEpgChannelFromTunerChannel(ListingsProviderInfo info, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
|
||||
{
|
||||
return GetEpgChannelFromTunerChannel(info.ChannelMappings.ToList(), tunerChannel, epgChannels);
|
||||
}
|
||||
|
||||
public ChannelInfo GetEpgChannelFromTunerChannel(List<NameValuePair> mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
|
||||
{
|
||||
var tunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tunerChannelId))
|
||||
{
|
||||
tunerChannelId = tunerChannel.TunerChannelId;
|
||||
}
|
||||
|
||||
var channel = epgChannels.FirstOrDefault(i => string.Equals(tunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
|
||||
{
|
||||
var tunerChannelNumber = GetMappedChannel(tunerChannel.Number, mappings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tunerChannelNumber))
|
||||
{
|
||||
tunerChannelNumber = tunerChannel.Number;
|
||||
}
|
||||
|
||||
var channel = epgChannels.FirstOrDefault(i => string.Equals(tunerChannelNumber, i.Number, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Name))
|
||||
{
|
||||
var normalizedName = NormalizeName(tunerChannel.Name);
|
||||
|
||||
var channel = epgChannels.FirstOrDefault(i => string.Equals(normalizedName, NormalizeName(i.Name ?? string.Empty), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string NormalizeName(string value)
|
||||
{
|
||||
return value.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
}
|
||||
|
||||
public async Task<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<ChannelInfo>();
|
||||
|
@ -663,7 +777,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
existingTimer.ProductionYear = updatedTimer.ProductionYear;
|
||||
existingTimer.ProgramId = updatedTimer.ProgramId;
|
||||
existingTimer.SeasonNumber = updatedTimer.SeasonNumber;
|
||||
existingTimer.ShortOverview = updatedTimer.ShortOverview;
|
||||
existingTimer.StartDate = updatedTimer.StartDate;
|
||||
existingTimer.ShowId = updatedTimer.ShowId;
|
||||
}
|
||||
|
@ -846,49 +959,37 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
_logger.Debug("Getting programs for channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
|
||||
|
||||
var channelMappings = GetChannelMappings(provider.Item2);
|
||||
var channelNumber = channel.Number;
|
||||
string mappedChannelNumber;
|
||||
if (channelMappings.TryGetValue(channelNumber, out mappedChannelNumber))
|
||||
var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
List<ProgramInfo> programs;
|
||||
|
||||
if (epgChannel == null)
|
||||
{
|
||||
_logger.Debug("Found mapped channel on provider {0}. Tuner channel number: {1}, Mapped channel number: {2}", provider.Item1.Name, channelNumber, mappedChannelNumber);
|
||||
channelNumber = mappedChannelNumber;
|
||||
programs = new List<ProgramInfo>();
|
||||
}
|
||||
else
|
||||
{
|
||||
programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
|
||||
.ConfigureAwait(false)).ToList();
|
||||
}
|
||||
|
||||
var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var list = programs.ToList();
|
||||
|
||||
// Replace the value that came from the provider with a normalized value
|
||||
foreach (var program in list)
|
||||
foreach (var program in programs)
|
||||
{
|
||||
program.ChannelId = channelId;
|
||||
}
|
||||
|
||||
if (list.Count > 0)
|
||||
if (programs.Count > 0)
|
||||
{
|
||||
SaveEpgDataForChannel(channelId, list);
|
||||
SaveEpgDataForChannel(channelId, programs);
|
||||
|
||||
return list;
|
||||
return programs;
|
||||
}
|
||||
}
|
||||
|
||||
return new List<ProgramInfo>();
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetChannelMappings(ListingsProviderInfo info)
|
||||
{
|
||||
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var mapping in info.ChannelMappings)
|
||||
{
|
||||
dict[mapping.Name] = mapping.Value;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
|
||||
{
|
||||
return GetConfiguration().ListingProviders
|
||||
|
@ -1755,7 +1856,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
{
|
||||
Name = timer.Name,
|
||||
HomePageUrl = timer.HomePageUrl,
|
||||
ShortOverview = timer.ShortOverview,
|
||||
Overview = timer.Overview,
|
||||
Genres = timer.Genres,
|
||||
CommunityRating = timer.CommunityRating,
|
||||
|
@ -1959,11 +2059,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
writer.WriteElementString("genre", genre);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.ShortOverview))
|
||||
{
|
||||
writer.WriteElementString("outline", item.ShortOverview);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.HomePageUrl))
|
||||
{
|
||||
writer.WriteElementString("website", item.HomePageUrl);
|
||||
|
|
|
@ -154,7 +154,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
|
||||
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
|
||||
var inputModifiers = "-fflags +genpts -async 1 -vsync -1";
|
||||
var commandLineArgs = "-i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
|
||||
var mapArgs = string.Equals(OutputFormat, "mkv", StringComparison.OrdinalIgnoreCase) ? "-map 0" : "-sn";
|
||||
// temporary
|
||||
mapArgs = "-sn";
|
||||
var commandLineArgs = "-i \"{0}\"{4} " + mapArgs + " {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
|
||||
|
||||
long startTimeTicks = 0;
|
||||
//if (mediaSource.DateLiveStreamOpened.HasValue)
|
||||
|
|
|
@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
|||
timerInfo.HomePageUrl = programInfo.HomePageUrl;
|
||||
timerInfo.CommunityRating = programInfo.CommunityRating;
|
||||
timerInfo.Overview = programInfo.Overview;
|
||||
timerInfo.ShortOverview = programInfo.ShortOverview;
|
||||
timerInfo.OfficialRating = programInfo.OfficialRating;
|
||||
timerInfo.IsRepeat = programInfo.IsRepeat;
|
||||
timerInfo.SeriesId = programInfo.SeriesId;
|
||||
|
|
|
@ -15,6 +15,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
|
@ -60,8 +61,16 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
return dates;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(channelId))
|
||||
{
|
||||
throw new ArgumentNullException("channelId");
|
||||
}
|
||||
|
||||
// Normalize incoming input
|
||||
channelId = channelId.Replace(".json.schedulesdirect.org", string.Empty, StringComparison.OrdinalIgnoreCase).TrimStart('I');
|
||||
|
||||
List<ProgramInfo> programsInfo = new List<ProgramInfo>();
|
||||
|
||||
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||
|
@ -80,15 +89,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
|
||||
var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
|
||||
|
||||
ScheduleDirect.Station station = GetStation(info.ListingsId, channelNumber, channelName);
|
||||
|
||||
if (station == null)
|
||||
{
|
||||
_logger.Info("No Schedules Direct Station found for channel {0} with name {1}", channelNumber, channelName);
|
||||
return programsInfo;
|
||||
}
|
||||
|
||||
string stationID = station.stationID;
|
||||
string stationID = channelId;
|
||||
|
||||
_logger.Info("Channel Station ID is: " + stationID);
|
||||
List<ScheduleDirect.RequestScheduleForChannel> requestList =
|
||||
|
@ -122,7 +123,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
StreamReader reader = new StreamReader(response.Content);
|
||||
string responseString = reader.ReadToEnd();
|
||||
var dailySchedules = _jsonSerializer.DeserializeFromString<List<ScheduleDirect.Day>>(responseString);
|
||||
_logger.Debug("Found " + dailySchedules.Count + " programs on " + channelNumber + " ScheduleDirect");
|
||||
_logger.Debug("Found " + dailySchedules.Count + " programs on " + stationID + " ScheduleDirect");
|
||||
|
||||
httpOptions = new HttpRequestOptions()
|
||||
{
|
||||
|
@ -180,7 +181,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
}
|
||||
}
|
||||
|
||||
programsInfo.Add(GetProgram(channelNumber, schedule, programDict[schedule.programID]));
|
||||
programsInfo.Add(GetProgram(channelId, schedule, programDict[schedule.programID]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -202,127 +203,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
return 0;
|
||||
}
|
||||
|
||||
private readonly object _channelCacheLock = new object();
|
||||
private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName)
|
||||
{
|
||||
lock (_channelCacheLock)
|
||||
{
|
||||
Dictionary<string, ScheduleDirect.Station> channelPair;
|
||||
if (_channelPairingCache.TryGetValue(listingsId, out channelPair))
|
||||
{
|
||||
ScheduleDirect.Station station;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(channelNumber) && channelPair.TryGetValue(channelNumber, out station))
|
||||
{
|
||||
return station;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(channelName))
|
||||
{
|
||||
channelName = NormalizeName(channelName);
|
||||
|
||||
var result = channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(channelNumber))
|
||||
{
|
||||
return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddToChannelPairCache(string listingsId, string channelNumber, ScheduleDirect.Station schChannel)
|
||||
{
|
||||
lock (_channelCacheLock)
|
||||
{
|
||||
Dictionary<string, ScheduleDirect.Station> cache;
|
||||
if (_channelPairingCache.TryGetValue(listingsId, out cache))
|
||||
{
|
||||
cache[channelNumber] = schChannel;
|
||||
}
|
||||
else
|
||||
{
|
||||
cache = new Dictionary<string, ScheduleDirect.Station>();
|
||||
cache[channelNumber] = schChannel;
|
||||
_channelPairingCache[listingsId] = cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearPairCache(string listingsId)
|
||||
{
|
||||
lock (_channelCacheLock)
|
||||
{
|
||||
Dictionary<string, ScheduleDirect.Station> cache;
|
||||
if (_channelPairingCache.TryGetValue(listingsId, out cache))
|
||||
{
|
||||
cache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GetChannelPairCacheCount(string listingsId)
|
||||
{
|
||||
lock (_channelCacheLock)
|
||||
{
|
||||
Dictionary<string, ScheduleDirect.Station> cache;
|
||||
if (_channelPairingCache.TryGetValue(listingsId, out cache))
|
||||
{
|
||||
return cache.Count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private string NormalizeName(string value)
|
||||
{
|
||||
return value.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
}
|
||||
|
||||
public async Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var listingsId = info.ListingsId;
|
||||
if (string.IsNullOrWhiteSpace(listingsId))
|
||||
{
|
||||
throw new Exception("ListingsId required");
|
||||
}
|
||||
|
||||
var token = await GetToken(info, cancellationToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
{
|
||||
throw new Exception("token required");
|
||||
}
|
||||
|
||||
ClearPairCache(listingsId);
|
||||
|
||||
var httpOptions = new HttpRequestOptions()
|
||||
{
|
||||
Url = ApiUrl + "/lineups/" + listingsId,
|
||||
UserAgent = UserAgent,
|
||||
CancellationToken = cancellationToken,
|
||||
LogErrorResponseBody = true,
|
||||
// The data can be large so give it some extra time
|
||||
TimeoutMs = 60000
|
||||
};
|
||||
|
||||
httpOptions.RequestHeaders["token"] = token;
|
||||
|
||||
using (var response = await Get(httpOptions, true, info).ConfigureAwait(false))
|
||||
{
|
||||
var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Channel>(response);
|
||||
|
||||
foreach (ScheduleDirect.Map map in root.map)
|
||||
private string GetChannelNumber(ScheduleDirect.Map map)
|
||||
{
|
||||
var channelNumber = map.logicalChannelNumber;
|
||||
|
||||
|
@ -336,49 +217,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
}
|
||||
channelNumber = channelNumber.TrimStart('0');
|
||||
|
||||
_logger.Debug("Found channel: " + channelNumber + " in Schedules Direct");
|
||||
|
||||
var schChannel = (root.stations ?? new List<ScheduleDirect.Station>()).FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
|
||||
if (schChannel != null)
|
||||
{
|
||||
AddToChannelPairCache(listingsId, channelNumber, schChannel);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddToChannelPairCache(listingsId, channelNumber, new ScheduleDirect.Station
|
||||
{
|
||||
stationID = map.stationID
|
||||
});
|
||||
}
|
||||
return channelNumber;
|
||||
}
|
||||
|
||||
foreach (ChannelInfo channel in channels)
|
||||
{
|
||||
var station = GetStation(listingsId, channel.Number, channel.Name);
|
||||
|
||||
if (station != null)
|
||||
{
|
||||
if (station.logo != null)
|
||||
{
|
||||
channel.ImageUrl = station.logo.URL;
|
||||
channel.HasImage = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(station.name))
|
||||
{
|
||||
channel.Name = station.name;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Schedules Direct doesnt have data for channel: " + channel.Number + " " + channel.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ProgramInfo GetProgram(string channel, ScheduleDirect.Program programInfo,
|
||||
ScheduleDirect.ProgramDetails details)
|
||||
private ProgramInfo GetProgram(string channelId, ScheduleDirect.Program programInfo, ScheduleDirect.ProgramDetails details)
|
||||
{
|
||||
//_logger.Debug("Show type is: " + (details.showType ?? "No ShowType"));
|
||||
DateTime startAt = GetDate(programInfo.airDateTime);
|
||||
|
@ -386,7 +228,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
ProgramAudio audioType = ProgramAudio.Stereo;
|
||||
|
||||
bool repeat = programInfo.@new == null;
|
||||
string newID = programInfo.programID + "T" + startAt.Ticks + "C" + channel;
|
||||
string newID = programInfo.programID + "T" + startAt.Ticks + "C" + channelId;
|
||||
|
||||
if (programInfo.audioProperties != null)
|
||||
{
|
||||
|
@ -422,7 +264,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
|
||||
var info = new ProgramInfo
|
||||
{
|
||||
ChannelId = channel,
|
||||
ChannelId = channelId,
|
||||
Id = newID,
|
||||
StartDate = startAt,
|
||||
EndDate = endAt,
|
||||
|
@ -479,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
}
|
||||
else if (details.descriptions.description100 != null)
|
||||
{
|
||||
info.ShortOverview = details.descriptions.description100[0].description;
|
||||
info.Overview = details.descriptions.description100[0].description;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -969,8 +811,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
throw new Exception("ListingsId required");
|
||||
}
|
||||
|
||||
await AddMetadata(info, new List<ChannelInfo>(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var token = await GetToken(info, cancellationToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
|
@ -997,39 +837,81 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Channel>(response);
|
||||
_logger.Info("Found " + root.map.Count + " channels on the lineup on ScheduleDirect");
|
||||
_logger.Info("Mapping Stations to Channel");
|
||||
|
||||
var allStations = root.stations ?? new List<ScheduleDirect.Station>();
|
||||
|
||||
foreach (ScheduleDirect.Map map in root.map)
|
||||
{
|
||||
var channelNumber = map.logicalChannelNumber;
|
||||
var channelNumber = GetChannelNumber(map);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(channelNumber))
|
||||
var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
|
||||
if (station == null)
|
||||
{
|
||||
channelNumber = map.channel;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(channelNumber))
|
||||
station = new ScheduleDirect.Station
|
||||
{
|
||||
channelNumber = map.atscMajor + "." + map.atscMinor;
|
||||
stationID = map.stationID
|
||||
};
|
||||
}
|
||||
channelNumber = channelNumber.TrimStart('0');
|
||||
|
||||
var name = channelNumber;
|
||||
var station = GetStation(listingsId, channelNumber, null);
|
||||
|
||||
if (station != null && !string.IsNullOrWhiteSpace(station.name))
|
||||
{
|
||||
name = station.name;
|
||||
}
|
||||
|
||||
list.Add(new ChannelInfo
|
||||
var channelInfo = new ChannelInfo
|
||||
{
|
||||
Number = channelNumber,
|
||||
Name = name
|
||||
});
|
||||
};
|
||||
|
||||
if (station != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(station.name))
|
||||
{
|
||||
channelInfo.Name = station.name;
|
||||
}
|
||||
|
||||
channelInfo.Id = station.stationID;
|
||||
channelInfo.CallSign = station.callsign;
|
||||
|
||||
if (station.logo != null)
|
||||
{
|
||||
channelInfo.ImageUrl = station.logo.URL;
|
||||
channelInfo.HasImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(channelInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private ScheduleDirect.Station GetStation(List<ScheduleDirect.Station> allStations, string channelNumber, string channelName)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(channelName))
|
||||
{
|
||||
channelName = NormalizeName(channelName);
|
||||
|
||||
var result = allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(channelNumber))
|
||||
{
|
||||
return allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string NormalizeName(string value)
|
||||
{
|
||||
return value.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
}
|
||||
|
||||
public class ScheduleDirect
|
||||
{
|
||||
public class Token
|
||||
|
|
|
@ -106,8 +106,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
return cacheFile;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(channelId))
|
||||
{
|
||||
throw new ArgumentNullException("channelId");
|
||||
}
|
||||
|
||||
if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false))
|
||||
{
|
||||
var length = endDateUtc - startDateUtc;
|
||||
|
@ -120,7 +125,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
|
||||
var reader = new XmlTvReader(path, GetLanguage());
|
||||
|
||||
var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken);
|
||||
var results = reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken);
|
||||
return results.Select(p => GetProgramInfo(p, info));
|
||||
}
|
||||
|
||||
|
@ -139,7 +144,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
StartDate = GetDate(p.StartDate),
|
||||
Name = p.Title,
|
||||
Overview = p.Description,
|
||||
ShortOverview = p.Description,
|
||||
ProductionYear = !p.CopyrightDate.HasValue ? (int?)null : p.CopyrightDate.Value.Year,
|
||||
SeasonNumber = p.Episode == null ? null : p.Episode.Series,
|
||||
IsSeries = p.Episode != null,
|
||||
|
@ -153,10 +157,29 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
HasImage = p.Icon != null && !String.IsNullOrEmpty(p.Icon.Source),
|
||||
OfficialRating = p.Rating != null && !String.IsNullOrEmpty(p.Rating.Value) ? p.Rating.Value : null,
|
||||
CommunityRating = p.StarRating.HasValue ? p.StarRating.Value : (float?)null,
|
||||
SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null,
|
||||
ShowId = ((p.Title ?? string.Empty) + (episodeTitle ?? string.Empty)).GetMD5().ToString("N")
|
||||
SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(p.ProgramId))
|
||||
{
|
||||
programInfo.ShowId = p.ProgramId;
|
||||
}
|
||||
else
|
||||
{
|
||||
var uniqueString = (p.Title ?? string.Empty) + (episodeTitle ?? string.Empty) + (p.IceTvEpisodeNumber ?? string.Empty);
|
||||
|
||||
if (programInfo.SeasonNumber.HasValue)
|
||||
{
|
||||
uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
if (programInfo.EpisodeNumber.HasValue)
|
||||
{
|
||||
uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
programInfo.ShowId = uniqueString.GetMD5().ToString("N");
|
||||
}
|
||||
|
||||
if (programInfo.IsMovie)
|
||||
{
|
||||
programInfo.IsSeries = false;
|
||||
|
@ -176,28 +199,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||
return date;
|
||||
}
|
||||
|
||||
public async Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken)
|
||||
{
|
||||
// Add the channel image url
|
||||
var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
|
||||
var reader = new XmlTvReader(path, GetLanguage());
|
||||
var results = reader.GetChannels().ToList();
|
||||
|
||||
if (channels != null)
|
||||
{
|
||||
foreach (var c in channels)
|
||||
{
|
||||
var channelNumber = info.GetMappedChannel(c.Number);
|
||||
var match = results.FirstOrDefault(r => string.Equals(r.Id, channelNumber, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (match != null && match.Icon != null && !String.IsNullOrEmpty(match.Icon.Source))
|
||||
{
|
||||
c.ImageUrl = match.Icon.Source;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings)
|
||||
{
|
||||
// Assume all urls are valid. check files for existence
|
||||
|
|
|
@ -2112,7 +2112,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
if (timer == null)
|
||||
{
|
||||
throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
|
||||
throw new ResourceNotFoundException(string.Format("SeriesTimer with Id {0} not found", id));
|
||||
}
|
||||
|
||||
var service = GetService(timer.ServiceName);
|
||||
|
@ -2884,20 +2884,20 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
|
||||
}
|
||||
|
||||
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
|
||||
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId)
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
|
||||
var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
|
||||
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelId, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.Equals(tunerChannelId, providerChannelId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var list = listingsProviderInfo.ChannelMappings.ToList();
|
||||
list.Add(new NameValuePair
|
||||
{
|
||||
Name = tunerChannelNumber,
|
||||
Value = providerChannelNumber
|
||||
Name = tunerChannelId,
|
||||
Value = providerChannelId
|
||||
});
|
||||
listingsProviderInfo.ChannelMappings = list.ToArray();
|
||||
}
|
||||
|
@ -2917,31 +2917,33 @@ namespace Emby.Server.Implementations.LiveTv
|
|||
|
||||
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
|
||||
|
||||
return tunerChannelMappings.First(i => string.Equals(i.Number, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
|
||||
return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public TunerChannelMapping GetTunerChannelMapping(ChannelInfo channel, List<NameValuePair> mappings, List<ChannelInfo> providerChannels)
|
||||
public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, List<NameValuePair> mappings, List<ChannelInfo> epgChannels)
|
||||
{
|
||||
var result = new TunerChannelMapping
|
||||
{
|
||||
Name = channel.Number + " " + channel.Name,
|
||||
Number = channel.Number
|
||||
Name = tunerChannel.Name,
|
||||
Id = tunerChannel.TunerChannelId
|
||||
};
|
||||
|
||||
var mapping = mappings.FirstOrDefault(i => string.Equals(i.Name, channel.Number, StringComparison.OrdinalIgnoreCase));
|
||||
var providerChannelNumber = channel.Number;
|
||||
|
||||
if (mapping != null)
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
|
||||
{
|
||||
providerChannelNumber = mapping.Value;
|
||||
result.Name = tunerChannel.Number + " " + result.Name;
|
||||
}
|
||||
|
||||
var providerChannel = providerChannels.FirstOrDefault(i => string.Equals(i.Number, providerChannelNumber, StringComparison.OrdinalIgnoreCase));
|
||||
if (string.IsNullOrWhiteSpace(result.Id))
|
||||
{
|
||||
result.Id = tunerChannel.Id;
|
||||
}
|
||||
|
||||
var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, epgChannels);
|
||||
|
||||
if (providerChannel != null)
|
||||
{
|
||||
result.ProviderChannelNumber = providerChannel.Number;
|
||||
result.ProviderChannelName = providerChannel.Name;
|
||||
result.ProviderChannelId = providerChannel.Id;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -76,6 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
var channels = new List<M3UChannel>();
|
||||
string line;
|
||||
string extInf = "";
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
line = line.Trim();
|
||||
|
@ -111,6 +112,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
extInf = "";
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
|
@ -134,9 +136,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
channel.Name = GetChannelName(extInf, attributes);
|
||||
channel.Number = GetChannelNumber(extInf, attributes, mediaUrl);
|
||||
|
||||
if (attributes.TryGetValue("tvg-id", out value))
|
||||
var channelId = GetTunerChannelId(attributes);
|
||||
if (!string.IsNullOrWhiteSpace(channelId))
|
||||
{
|
||||
channel.Id = value;
|
||||
channel.Id = channelId;
|
||||
channel.TunerChannelId = channelId;
|
||||
}
|
||||
|
||||
return channel;
|
||||
|
@ -172,9 +176,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(numberString) ||
|
||||
string.Equals(numberString, "-1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(numberString, "0", StringComparison.OrdinalIgnoreCase))
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
string value;
|
||||
if (attributes.TryGetValue("tvg-id", out value))
|
||||
|
@ -192,9 +194,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(numberString) ||
|
||||
string.Equals(numberString, "-1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(numberString, "0", StringComparison.OrdinalIgnoreCase))
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
string value;
|
||||
if (attributes.TryGetValue("channel-id", out value))
|
||||
|
@ -208,9 +208,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
numberString = numberString.Trim();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(numberString) ||
|
||||
string.Equals(numberString, "-1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(numberString, "0", StringComparison.OrdinalIgnoreCase))
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
numberString = null;
|
||||
}
|
||||
|
@ -225,8 +223,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
{
|
||||
numberString = Path.GetFileNameWithoutExtension(mediaUrl.Split('/').Last());
|
||||
|
||||
double value;
|
||||
if (!double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out value))
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
numberString = null;
|
||||
}
|
||||
|
@ -236,6 +233,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
return numberString;
|
||||
}
|
||||
|
||||
private bool IsValidChannelNumber(string numberString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(numberString) ||
|
||||
string.Equals(numberString, "-1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(numberString, "0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double value;
|
||||
if (!double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetChannelName(string extInf, Dictionary<string, string> attributes)
|
||||
{
|
||||
var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
@ -281,6 +296,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
return name;
|
||||
}
|
||||
|
||||
private string GetTunerChannelId(Dictionary<string, string> attributes)
|
||||
{
|
||||
string result;
|
||||
attributes.TryGetValue("tvg-id", out result);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
attributes.TryGetValue("channel-id", out result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ParseExtInf(string line, out string remaining)
|
||||
{
|
||||
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
@ -11,10 +12,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
{
|
||||
public class MulticastStream
|
||||
{
|
||||
private readonly List<QueueStream> _outputStreams = new List<QueueStream>();
|
||||
private readonly ConcurrentDictionary<Guid,QueueStream> _outputStreams = new ConcurrentDictionary<Guid, QueueStream>();
|
||||
private const int BufferSize = 81920;
|
||||
private CancellationToken _cancellationToken;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ConcurrentQueue<byte[]> _sharedBuffer = new ConcurrentQueue<byte[]>();
|
||||
|
||||
public MulticastStream(ILogger logger)
|
||||
{
|
||||
|
@ -36,16 +38,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
byte[] copy = new byte[bytesRead];
|
||||
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
|
||||
|
||||
List<QueueStream> streams = null;
|
||||
_sharedBuffer.Enqueue(copy);
|
||||
|
||||
lock (_outputStreams)
|
||||
while (_sharedBuffer.Count > 3000)
|
||||
{
|
||||
streams = _outputStreams.ToList();
|
||||
byte[] bytes;
|
||||
_sharedBuffer.TryDequeue(out bytes);
|
||||
}
|
||||
|
||||
foreach (var stream in streams)
|
||||
var allStreams = _outputStreams.ToList();
|
||||
foreach (var stream in allStreams)
|
||||
{
|
||||
stream.Queue(copy);
|
||||
stream.Value.Queue(copy);
|
||||
}
|
||||
|
||||
if (onStarted != null)
|
||||
|
@ -70,11 +74,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
OnFinished = OnFinished
|
||||
};
|
||||
|
||||
lock (_outputStreams)
|
||||
var initial = _sharedBuffer.ToList();
|
||||
var list = new List<byte>();
|
||||
|
||||
foreach (var bytes in initial)
|
||||
{
|
||||
_outputStreams.Add(result);
|
||||
list.AddRange(bytes);
|
||||
}
|
||||
|
||||
_logger.Info("QueueStream started with {0} initial bytes", list.Count);
|
||||
|
||||
result.Queue(list.ToArray());
|
||||
|
||||
_outputStreams.TryAdd(result.Id, result);
|
||||
|
||||
result.Start(_cancellationToken);
|
||||
|
||||
return result.TaskCompletion.Task;
|
||||
|
@ -82,10 +95,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
public void RemoveOutputStream(QueueStream stream)
|
||||
{
|
||||
lock (_outputStreams)
|
||||
{
|
||||
_outputStreams.Remove(stream);
|
||||
}
|
||||
QueueStream removed;
|
||||
_outputStreams.TryRemove(stream.Id, out removed);
|
||||
}
|
||||
|
||||
private void OnFinished(QueueStream queueStream)
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
public Action<QueueStream> OnFinished { get; set; }
|
||||
private readonly ILogger _logger;
|
||||
private bool _isActive;
|
||||
public Guid Id = Guid.NewGuid();
|
||||
|
||||
public QueueStream(Stream outputStream, ILogger logger)
|
||||
{
|
||||
|
@ -29,12 +29,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
}
|
||||
|
||||
public void Queue(byte[] bytes)
|
||||
{
|
||||
if (_isActive)
|
||||
{
|
||||
_queue.Enqueue(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start(CancellationToken cancellationToken)
|
||||
{
|
||||
|
@ -59,10 +56,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
while (true)
|
||||
{
|
||||
_isActive = true;
|
||||
|
||||
var bytes = Dequeue();
|
||||
if (bytes != null)
|
||||
{
|
||||
|
@ -73,9 +68,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
TaskCompletion.TrySetResult(true);
|
||||
_logger.Debug("QueueStream complete");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
@ -89,8 +81,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||
}
|
||||
finally
|
||||
{
|
||||
_isActive = false;
|
||||
|
||||
if (OnFinished != null)
|
||||
{
|
||||
OnFinished(this);
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Migrations
|
|||
|
||||
public async Task Run()
|
||||
{
|
||||
var name = "GuideRefresh2";
|
||||
var name = "GuideRefresh3";
|
||||
|
||||
if (!_config.Configuration.Migrations.Contains(name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
|
@ -144,11 +144,18 @@ namespace Emby.Server.Implementations.TV
|
|||
// If viewing all next up for all series, remove first episodes
|
||||
// But if that returns empty, keep those first episodes (avoid completely empty view)
|
||||
var alwaysEnableFirstEpisode = !string.IsNullOrWhiteSpace(request.SeriesId);
|
||||
var anyFound = false;
|
||||
|
||||
return allNextUp
|
||||
.Where(i =>
|
||||
{
|
||||
if (alwaysEnableFirstEpisode || i.Item1 != DateTime.MinValue)
|
||||
{
|
||||
anyFound = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!anyFound && i.Item1 == DateTime.MinValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
@ -246,7 +247,7 @@ namespace Emby.Server.Implementations.Udp
|
|||
|
||||
try
|
||||
{
|
||||
await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint).ConfigureAwait(false);
|
||||
await _udpClient.SendAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
_logger.Info("Udp message sent to {0}", remoteEndPoint);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Emby.XmlTv" version="1.0.3" targetFramework="portable45-net45+win8" />
|
||||
<package id="Emby.XmlTv" version="1.0.5" targetFramework="portable45-net45+win8" />
|
||||
<package id="MediaBrowser.Naming" version="1.0.4" targetFramework="portable45-net45+win8" />
|
||||
<package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.1" targetFramework="portable45-net45+win8" />
|
||||
|
|
|
@ -23,8 +23,8 @@ namespace MediaBrowser.Api
|
|||
/// <summary>
|
||||
/// The _active connections
|
||||
/// </summary>
|
||||
protected readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType, SemaphoreSlim>> ActiveConnections =
|
||||
new List<Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType, SemaphoreSlim>>();
|
||||
protected readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType>> ActiveConnections =
|
||||
new List<Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
|
@ -132,11 +132,9 @@ namespace MediaBrowser.Api
|
|||
InitialDelayMs = dueTimeMs
|
||||
};
|
||||
|
||||
var semaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
lock (ActiveConnections)
|
||||
{
|
||||
ActiveConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType, SemaphoreSlim>(message.Connection, cancellationTokenSource, timer, state, semaphore));
|
||||
ActiveConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType>(message.Connection, cancellationTokenSource, timer, state));
|
||||
}
|
||||
|
||||
if (timer != null)
|
||||
|
@ -153,7 +151,7 @@ namespace MediaBrowser.Api
|
|||
{
|
||||
var connection = (IWebSocketConnection)state;
|
||||
|
||||
Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType, SemaphoreSlim> tuple;
|
||||
Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType> tuple;
|
||||
|
||||
lock (ActiveConnections)
|
||||
{
|
||||
|
@ -176,7 +174,7 @@ namespace MediaBrowser.Api
|
|||
|
||||
protected void SendData(bool force)
|
||||
{
|
||||
List<Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType, SemaphoreSlim>> tuples;
|
||||
List<Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType>> tuples;
|
||||
|
||||
lock (ActiveConnections)
|
||||
{
|
||||
|
@ -204,14 +202,12 @@ namespace MediaBrowser.Api
|
|||
}
|
||||
}
|
||||
|
||||
private async void SendData(Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType, SemaphoreSlim> tuple)
|
||||
private async void SendData(Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType> tuple)
|
||||
{
|
||||
var connection = tuple.Item1;
|
||||
|
||||
try
|
||||
{
|
||||
await tuple.Item5.WaitAsync(tuple.Item2.Token).ConfigureAwait(false);
|
||||
|
||||
var state = tuple.Item4;
|
||||
|
||||
var data = await GetDataToSend(state).ConfigureAwait(false);
|
||||
|
@ -227,8 +223,6 @@ namespace MediaBrowser.Api
|
|||
|
||||
state.DateLastSendUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
tuple.Item5.Release();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
@ -265,7 +259,7 @@ namespace MediaBrowser.Api
|
|||
/// Disposes the connection.
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection.</param>
|
||||
private void DisposeConnection(Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType, SemaphoreSlim> connection)
|
||||
private void DisposeConnection(Tuple<IWebSocketConnection, CancellationTokenSource, ITimer, TStateType> connection)
|
||||
{
|
||||
Logger.Debug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name);
|
||||
|
||||
|
@ -293,15 +287,6 @@ namespace MediaBrowser.Api
|
|||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
connection.Item5.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ActiveConnections.Remove(connection);
|
||||
}
|
||||
|
||||
|
|
|
@ -210,7 +210,7 @@ namespace MediaBrowser.Api.Images
|
|||
/// <returns>Task.</returns>
|
||||
private async Task DownloadRemoteImage(BaseItem item, BaseDownloadRemoteImage request)
|
||||
{
|
||||
await _providerManager.SaveImage(item, request.ImageUrl, null, request.Type, null, CancellationToken.None).ConfigureAwait(false);
|
||||
await _providerManager.SaveImage(item, request.ImageUrl, request.Type, null, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
await item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
|
|
@ -276,8 +276,6 @@ namespace MediaBrowser.Api
|
|||
item.Tagline = request.Taglines.FirstOrDefault();
|
||||
}
|
||||
|
||||
item.ShortOverview = request.ShortOverview;
|
||||
|
||||
item.Keywords = request.Keywords;
|
||||
|
||||
if (request.Studios != null)
|
||||
|
|
|
@ -640,8 +640,8 @@ namespace MediaBrowser.Api.LiveTv
|
|||
{
|
||||
[ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public string ProviderId { get; set; }
|
||||
public string TunerChannelNumber { get; set; }
|
||||
public string ProviderChannelNumber { get; set; }
|
||||
public string TunerChannelId { get; set; }
|
||||
public string ProviderChannelId { get; set; }
|
||||
}
|
||||
|
||||
public class ChannelMappingOptions
|
||||
|
@ -765,7 +765,7 @@ namespace MediaBrowser.Api.LiveTv
|
|||
|
||||
public async Task<object> Post(SetChannelMapping request)
|
||||
{
|
||||
return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelNumber, request.ProviderChannelNumber).ConfigureAwait(false);
|
||||
return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelId, request.ProviderChannelId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<object> Get(GetChannelMappingOptions request)
|
||||
|
@ -791,7 +791,7 @@ namespace MediaBrowser.Api.LiveTv
|
|||
ProviderChannels = providerChannels.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = i.Number
|
||||
Id = i.TunerChannelId
|
||||
|
||||
}).ToList(),
|
||||
|
||||
|
|
|
@ -172,6 +172,10 @@
|
|||
<Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
|
||||
<Name>MediaBrowser.Controller</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj">
|
||||
<Project>{0bd82fa6-eb8a-4452-8af5-74f9c3849451}</Project>
|
||||
<Name>MediaBrowser.MediaEncoding</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
|
||||
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
|
||||
<Name>MediaBrowser.Model</Name>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -237,13 +237,15 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
|
||||
var itsOffsetMs = 0;
|
||||
|
||||
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
|
||||
|
||||
var threads = GetNumberOfThreads(state, false);
|
||||
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
|
||||
|
||||
var inputModifier = GetInputModifier(state, true);
|
||||
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
|
||||
|
||||
// If isEncoding is true we're actually starting ffmpeg
|
||||
var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
|
||||
|
@ -265,9 +267,9 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
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}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(state),
|
||||
EncodingHelper.GetInputArgument(state, encodingOptions),
|
||||
threads,
|
||||
GetMapArgs(state),
|
||||
EncodingHelper.GetMapArgs(state),
|
||||
GetVideoArguments(state),
|
||||
GetAudioArguments(state),
|
||||
state.SegmentLength.ToString(UsCulture),
|
||||
|
@ -284,9 +286,9 @@ 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}\"",
|
||||
itsOffset,
|
||||
inputModifier,
|
||||
GetInputArgument(state),
|
||||
EncodingHelper.GetInputArgument(state, encodingOptions),
|
||||
threads,
|
||||
GetMapArgs(state),
|
||||
EncodingHelper.GetMapArgs(state),
|
||||
GetVideoArguments(state),
|
||||
GetAudioArguments(state),
|
||||
state.SegmentLength.ToString(UsCulture),
|
||||
|
|
|
@ -758,7 +758,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
protected override string GetAudioArguments(StreamState state)
|
||||
{
|
||||
var codec = GetAudioEncoder(state);
|
||||
var codec = EncodingHelper.GetAudioEncoder(state);
|
||||
|
||||
if (!state.IsOutputVideo)
|
||||
{
|
||||
|
@ -811,7 +811,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
args += " -ab " + bitrate.Value.ToString(UsCulture);
|
||||
}
|
||||
|
||||
args += " " + GetAudioFilterParam(state, true);
|
||||
args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), true);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
@ -823,7 +823,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
return string.Empty;
|
||||
}
|
||||
|
||||
var codec = GetVideoEncoder(state);
|
||||
var codec = EncodingHelper.GetVideoEncoder(state, ApiEntryPoint.Instance.GetEncodingOptions());
|
||||
|
||||
var args = "-codec:v:0 " + codec;
|
||||
|
||||
|
@ -835,7 +835,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
// See if we can save come cpu cycles by avoiding encoding
|
||||
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||
if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
args += " -bsf:v h264_mp4toannexb";
|
||||
}
|
||||
|
@ -849,20 +849,22 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
|
||||
|
||||
args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
|
||||
args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
|
||||
|
||||
//args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
|
||||
|
||||
// Add resolution params, if specified
|
||||
if (!hasGraphicalSubs)
|
||||
{
|
||||
args += GetOutputSizeParam(state, codec, EnableCopyTs(state));
|
||||
args += EncodingHelper.GetOutputSizeParam(state, codec, EnableCopyTs(state));
|
||||
}
|
||||
|
||||
// This is for internal graphical subs
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
args += GetGraphicalSubtitleParam(state, codec);
|
||||
args += EncodingHelper.GetGraphicalSubtitleParam(state, codec);
|
||||
}
|
||||
|
||||
//args += " -flags -global_header";
|
||||
|
@ -884,15 +886,16 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
var threads = GetNumberOfThreads(state, false);
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
|
||||
|
||||
var inputModifier = GetInputModifier(state, false);
|
||||
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
|
||||
|
||||
// If isEncoding is true we're actually starting ffmpeg
|
||||
var startNumber = GetStartNumber(state);
|
||||
var startNumberParam = isEncoding ? startNumber.ToString(UsCulture) : "0";
|
||||
|
||||
var mapArgs = state.IsOutputVideo ? GetMapArgs(state) : string.Empty;
|
||||
var mapArgs = state.IsOutputVideo ? EncodingHelper.GetMapArgs(state) : string.Empty;
|
||||
var useGenericSegmenter = true;
|
||||
|
||||
if (useGenericSegmenter)
|
||||
|
@ -909,7 +912,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
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}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(state),
|
||||
EncodingHelper.GetInputArgument(state, encodingOptions),
|
||||
threads,
|
||||
mapArgs,
|
||||
GetVideoArguments(state),
|
||||
|
@ -924,7 +927,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {6} -individual_header_trailer 0 -start_number {7} -hls_list_size {8} -y \"{9}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(state),
|
||||
EncodingHelper.GetInputArgument(state, encodingOptions),
|
||||
threads,
|
||||
mapArgs,
|
||||
GetVideoArguments(state),
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
/// <returns>System.String.</returns>
|
||||
protected override string GetAudioArguments(StreamState state)
|
||||
{
|
||||
var codec = GetAudioEncoder(state);
|
||||
var codec = EncodingHelper.GetAudioEncoder(state);
|
||||
|
||||
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -60,7 +60,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
args += " -ab " + bitrate.Value.ToString(UsCulture);
|
||||
}
|
||||
|
||||
args += " " + GetAudioFilterParam(state, true);
|
||||
args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), true);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
/// <returns>System.String.</returns>
|
||||
protected override string GetVideoArguments(StreamState state)
|
||||
{
|
||||
var codec = GetVideoEncoder(state);
|
||||
var codec = EncodingHelper.GetVideoEncoder(state, ApiEntryPoint.Instance.GetEncodingOptions());
|
||||
|
||||
var args = "-codec:v:0 " + codec;
|
||||
|
||||
|
@ -85,7 +85,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// if h264_mp4toannexb is ever added, do not use it for live tv
|
||||
if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||
if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
args += " -bsf:v h264_mp4toannexb";
|
||||
}
|
||||
|
@ -98,18 +98,19 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
|
||||
|
||||
args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
args += " " + EncodingHelper.GetVideoQualityParam(state, EncodingHelper.GetH264Encoder(state, encodingOptions), encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
|
||||
|
||||
// Add resolution params, if specified
|
||||
if (!hasGraphicalSubs)
|
||||
{
|
||||
args += GetOutputSizeParam(state, codec);
|
||||
args += EncodingHelper.GetOutputSizeParam(state, codec);
|
||||
}
|
||||
|
||||
// This is for internal graphical subs
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
args += GetGraphicalSubtitleParam(state, codec);
|
||||
args += EncodingHelper.GetGraphicalSubtitleParam(state, codec);
|
||||
}
|
||||
|
||||
args += " -flags -global_header";
|
||||
|
|
|
@ -57,6 +57,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
|
||||
var audioTranscodeParams = new List<string>();
|
||||
|
||||
var bitrate = state.OutputAudioBitrate;
|
||||
|
@ -82,13 +84,13 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
const string vn = " -vn";
|
||||
|
||||
var threads = GetNumberOfThreads(state, false);
|
||||
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
|
||||
|
||||
var inputModifier = GetInputModifier(state);
|
||||
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
|
||||
|
||||
return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(state),
|
||||
EncodingHelper.GetInputArgument(state, encodingOptions),
|
||||
threads,
|
||||
vn,
|
||||
string.Join(" ", audioTranscodeParams.ToArray()),
|
||||
|
|
|
@ -9,6 +9,7 @@ using MediaBrowser.Model.IO;
|
|||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.IO;
|
||||
|
@ -95,8 +96,10 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
|
||||
{
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
|
||||
// Get the output codec name
|
||||
var videoCodec = GetVideoEncoder(state);
|
||||
var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
|
||||
|
||||
var format = string.Empty;
|
||||
var keyFrame = string.Empty;
|
||||
|
@ -107,23 +110,46 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
format = " -f mp4 -movflags frag_keyframe+empty_moov";
|
||||
}
|
||||
|
||||
var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
|
||||
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var inputModifier = GetInputModifier(state);
|
||||
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
|
||||
|
||||
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7} -y \"{8}\"",
|
||||
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,
|
||||
GetInputArgument(state),
|
||||
EncodingHelper.GetInputArgument(state, encodingOptions),
|
||||
keyFrame,
|
||||
GetMapArgs(state),
|
||||
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>
|
||||
|
@ -141,7 +167,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
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))
|
||||
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";
|
||||
}
|
||||
|
@ -170,7 +196,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
// Add resolution params, if specified
|
||||
if (!hasGraphicalSubs)
|
||||
{
|
||||
var outputSizeParam = GetOutputSizeParam(state, videoCodec);
|
||||
var outputSizeParam = EncodingHelper.GetOutputSizeParam(state, videoCodec);
|
||||
args += outputSizeParam;
|
||||
hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
}
|
||||
|
@ -184,7 +210,8 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
args += " -avoid_negative_ts disabled -start_at_zero";
|
||||
}
|
||||
|
||||
var qualityParam = GetVideoQualityParam(state, videoCodec);
|
||||
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
|
||||
var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, encodingOptions, GetDefaultH264Preset());
|
||||
|
||||
if (!string.IsNullOrEmpty(qualityParam))
|
||||
{
|
||||
|
@ -194,7 +221,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
// This is for internal graphical subs
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
args += GetGraphicalSubtitleParam(state, videoCodec);
|
||||
args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec);
|
||||
}
|
||||
|
||||
if (!state.RunTimeTicks.HasValue)
|
||||
|
@ -219,7 +246,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
}
|
||||
|
||||
// Get the output codec name
|
||||
var codec = GetAudioEncoder(state);
|
||||
var codec = EncodingHelper.GetAudioEncoder(state);
|
||||
|
||||
var args = "-codec:a:0 " + codec;
|
||||
|
||||
|
@ -243,7 +270,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
args += " -ab " + bitrate.Value.ToString(UsCulture);
|
||||
}
|
||||
|
||||
args += " " + GetAudioFilterParam(state, false);
|
||||
args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), false);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace MediaBrowser.Api.Playback
|
||||
|
@ -6,7 +7,7 @@ namespace MediaBrowser.Api.Playback
|
|||
/// <summary>
|
||||
/// Class StreamRequest
|
||||
/// </summary>
|
||||
public class StreamRequest
|
||||
public class StreamRequest : BaseEncodingJobOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
|
@ -28,45 +29,7 @@ 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")]
|
||||
public string AudioCodec { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start time ticks.
|
||||
/// </summary>
|
||||
/// <value>The start time ticks.</value>
|
||||
[ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public long? StartTimeTicks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio bit rate.
|
||||
/// </summary>
|
||||
/// <value>The audio bit rate.</value>
|
||||
[ApiMember(Name = "AudioBitRate", Description = "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? AudioBitRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio channels.
|
||||
/// </summary>
|
||||
/// <value>The audio channels.</value>
|
||||
[ApiMember(Name = "AudioChannels", Description = "Optional. Specify a specific number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? AudioChannels { get; set; }
|
||||
|
||||
[ApiMember(Name = "MaxAudioChannels", Description = "Optional. Specify a maximum number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxAudioChannels { get; set; }
|
||||
|
||||
public int? TranscodingMaxAudioChannels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio sample rate.
|
||||
/// </summary>
|
||||
/// <value>The audio sample rate.</value>
|
||||
[ApiMember(Name = "AudioSampleRate", Description = "Optional. Specify a specific audio sample rate, e.g. 44100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? AudioSampleRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="StreamRequest" /> is static.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if static; otherwise, <c>false</c>.</value>
|
||||
[ApiMember(Name = "Static", Description = "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool Static { 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")]
|
||||
public string DeviceProfileId { get; set; }
|
||||
|
@ -79,102 +42,6 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public class VideoStreamRequest : StreamRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the video codec.
|
||||
/// </summary>
|
||||
/// <value>The video codec.</value>
|
||||
[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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the video bit rate.
|
||||
/// </summary>
|
||||
/// <value>The video bit rate.</value>
|
||||
[ApiMember(Name = "VideoBitRate", Description = "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? VideoBitRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the audio stream.
|
||||
/// </summary>
|
||||
/// <value>The index of the audio stream.</value>
|
||||
[ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the video stream.
|
||||
/// </summary>
|
||||
/// <value>The index of the video stream.</value>
|
||||
[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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the subtitle stream.
|
||||
/// </summary>
|
||||
/// <value>The index of the subtitle stream.</value>
|
||||
[ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width.
|
||||
/// </summary>
|
||||
/// <value>The width.</value>
|
||||
[ApiMember(Name = "Width", Description = "Optional. The fixed horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height.
|
||||
/// </summary>
|
||||
/// <value>The height.</value>
|
||||
[ApiMember(Name = "Height", Description = "Optional. The fixed vertical resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the max.
|
||||
/// </summary>
|
||||
/// <value>The width of the max.</value>
|
||||
[ApiMember(Name = "MaxWidth", Description = "Optional. The maximum horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the max.
|
||||
/// </summary>
|
||||
/// <value>The height of the max.</value>
|
||||
[ApiMember(Name = "MaxHeight", Description = "Optional. The maximum vertical resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxHeight { get; set; }
|
||||
|
||||
[ApiMember(Name = "MaxRefFrames", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxRefFrames { get; set; }
|
||||
|
||||
[ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxVideoBitDepth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the framerate.
|
||||
/// </summary>
|
||||
/// <value>The framerate.</value>
|
||||
[ApiMember(Name = "Framerate", Description = "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", IsRequired = false, DataType = "double", ParameterType = "query", Verb = "GET")]
|
||||
public float? Framerate { get; set; }
|
||||
|
||||
[ApiMember(Name = "MaxFramerate", Description = "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", IsRequired = false, DataType = "double", ParameterType = "query", Verb = "GET")]
|
||||
public float? MaxFramerate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the profile.
|
||||
/// </summary>
|
||||
/// <value>The profile.</value>
|
||||
[ApiMember(Name = "Profile", Description = "Optional. Specify a specific h264 profile, e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public string Profile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the level.
|
||||
/// </summary>
|
||||
/// <value>The level.</value>
|
||||
[ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public string Level { get; set; }
|
||||
|
||||
[ApiMember(Name = "SubtitleDeliveryMethod", Description = "Optional. Specify the subtitle delivery method.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public SubtitleDeliveryMethod SubtitleMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has fixed resolution.
|
||||
/// </summary>
|
||||
|
@ -187,18 +54,6 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
[ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool EnableAutoStreamCopy { get; set; }
|
||||
|
||||
[ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool CopyTimestamps { get; set; }
|
||||
|
||||
public bool EnableSubtitlesInManifest { get; set; }
|
||||
public bool RequireAvc { get; set; }
|
||||
|
||||
public VideoStreamRequest()
|
||||
{
|
||||
EnableAutoStreamCopy = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,17 +13,28 @@ using System.Globalization;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MediaBrowser.MediaEncoding.Encoder;
|
||||
|
||||
namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
public class StreamState : IDisposable
|
||||
public class StreamState : EncodingJobInfo, IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public string RequestedUrl { get; set; }
|
||||
|
||||
public StreamRequest Request { get; set; }
|
||||
public StreamRequest Request
|
||||
{
|
||||
get { return (StreamRequest)BaseRequest; }
|
||||
set
|
||||
{
|
||||
BaseRequest = value;
|
||||
|
||||
IsVideoRequest = VideoRequest != null;
|
||||
}
|
||||
}
|
||||
|
||||
public TranscodingThrottler TranscodingThrottler { get; set; }
|
||||
|
||||
public VideoStreamRequest VideoRequest
|
||||
|
@ -31,8 +42,6 @@ namespace MediaBrowser.Api.Playback
|
|||
get { return Request as VideoStreamRequest; }
|
||||
}
|
||||
|
||||
public Dictionary<string, string> RemoteHttpHeaders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log file stream.
|
||||
/// </summary>
|
||||
|
@ -40,35 +49,12 @@ namespace MediaBrowser.Api.Playback
|
|||
public Stream LogFileStream { get; set; }
|
||||
public IDirectStreamProvider DirectStreamProvider { get; set; }
|
||||
|
||||
public string InputContainer { get; set; }
|
||||
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
|
||||
public MediaStream AudioStream { get; set; }
|
||||
public MediaStream VideoStream { get; set; }
|
||||
public MediaStream SubtitleStream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the iso mount.
|
||||
/// </summary>
|
||||
/// <value>The iso mount.</value>
|
||||
public IIsoMount IsoMount { get; set; }
|
||||
|
||||
public string MediaPath { get; set; }
|
||||
public string WaitForPath { get; set; }
|
||||
|
||||
public MediaProtocol InputProtocol { get; set; }
|
||||
|
||||
public bool IsOutputVideo
|
||||
{
|
||||
get { return Request is VideoStreamRequest; }
|
||||
}
|
||||
public bool IsInputVideo { get; set; }
|
||||
|
||||
public VideoType VideoType { get; set; }
|
||||
public IsoType? IsoType { get; set; }
|
||||
|
||||
public List<string> PlayableStreamFileNames { get; set; }
|
||||
|
||||
public int SegmentLength
|
||||
{
|
||||
|
@ -116,39 +102,19 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
public long? RunTimeTicks;
|
||||
|
||||
public long? InputBitrate { get; set; }
|
||||
public long? InputFileSize { get; set; }
|
||||
|
||||
public string OutputAudioSync = "1";
|
||||
public string OutputVideoSync = "-1";
|
||||
|
||||
public List<string> SupportedAudioCodecs { get; set; }
|
||||
public List<string> SupportedVideoCodecs { get; set; }
|
||||
public List<string> SupportedSubtitleCodecs { get; set; }
|
||||
public string UserAgent { get; set; }
|
||||
public TranscodingJobType TranscodingType { get; set; }
|
||||
|
||||
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType)
|
||||
: base(logger)
|
||||
{
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_logger = logger;
|
||||
SupportedAudioCodecs = new List<string>();
|
||||
SupportedVideoCodecs = new List<string>();
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
SupportedSubtitleCodecs = new List<string>();
|
||||
TranscodingType = transcodingType;
|
||||
}
|
||||
|
||||
public string InputAudioSync { get; set; }
|
||||
public string InputVideoSync { get; set; }
|
||||
|
||||
public bool DeInterlace { get; set; }
|
||||
|
||||
public bool ReadInputAtNativeFramerate { get; set; }
|
||||
|
||||
public TransportStreamTimestamp InputTimestamp { get; set; }
|
||||
|
||||
public string MimeType { get; set; }
|
||||
|
||||
public bool EstimateContentLength { get; set; }
|
||||
|
@ -209,23 +175,6 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
private void DisposeIsoMount()
|
||||
{
|
||||
if (IsoMount != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsoMount.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error disposing iso mount", ex);
|
||||
}
|
||||
|
||||
IsoMount = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async void DisposeLiveStream()
|
||||
{
|
||||
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
|
||||
|
@ -241,15 +190,8 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
public int InternalSubtitleStreamOffset { get; set; }
|
||||
|
||||
public string OutputFilePath { get; set; }
|
||||
public string OutputVideoCodec { get; set; }
|
||||
public string OutputAudioCodec { get; set; }
|
||||
public int? OutputAudioChannels;
|
||||
public int? OutputAudioSampleRate;
|
||||
public int? OutputAudioBitrate;
|
||||
public int? OutputVideoBitrate;
|
||||
|
||||
public string ActualOutputVideoCodec
|
||||
{
|
||||
|
@ -295,8 +237,6 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
public string OutputContainer { get; set; }
|
||||
|
||||
public DeviceProfile DeviceProfile { get; set; }
|
||||
|
||||
public int? TotalOutputBitrate
|
||||
|
@ -444,20 +384,6 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predicts the audio sample rate that will be in the output stream
|
||||
/// </summary>
|
||||
public double? TargetVideoLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
var stream = VideoStream;
|
||||
return !string.IsNullOrEmpty(VideoRequest.Level) && !Request.Static
|
||||
? double.Parse(VideoRequest.Level, CultureInfo.InvariantCulture)
|
||||
: stream == null ? null : stream.Level;
|
||||
}
|
||||
}
|
||||
|
||||
public TransportStreamTimestamp TargetTimestamp
|
||||
{
|
||||
get
|
||||
|
|
|
@ -189,24 +189,10 @@ namespace MediaBrowser.Api
|
|||
result.Series = hasSeries.SeriesName;
|
||||
}
|
||||
|
||||
var season = item as Season;
|
||||
if (season != null)
|
||||
{
|
||||
result.EpisodeCount = season.GetRecursiveChildren(i => i is Episode).Count;
|
||||
}
|
||||
|
||||
var series = item as Series;
|
||||
if (series != null)
|
||||
{
|
||||
result.EpisodeCount = series.GetRecursiveChildren(i => i is Episode).Count;
|
||||
}
|
||||
|
||||
var album = item as MusicAlbum;
|
||||
|
||||
if (album != null)
|
||||
{
|
||||
result.SongCount = album.Tracks.Count();
|
||||
|
||||
result.Artists = album.Artists.ToArray();
|
||||
result.AlbumArtist = album.AlbumArtist;
|
||||
}
|
||||
|
|
|
@ -111,15 +111,14 @@ namespace MediaBrowser.Api
|
|||
|
||||
private void SetWizardFinishValues(ServerConfiguration config)
|
||||
{
|
||||
config.EnableLocalizedGuids = true;
|
||||
config.EnableStandaloneMusicKeys = true;
|
||||
config.EnableCaseSensitiveItemIds = true;
|
||||
config.EnableFolderView = true;
|
||||
config.EnableSimpleArtistDetection = true;
|
||||
config.SkipDeserializationForBasicTypes = true;
|
||||
config.SkipDeserializationForPrograms = true;
|
||||
config.SkipDeserializationForAudio = true;
|
||||
config.EnableSeriesPresentationUniqueKey = true;
|
||||
config.EnableLocalizedGuids = true;
|
||||
}
|
||||
|
||||
public void Post(UpdateStartupConfiguration request)
|
||||
|
|
|
@ -213,6 +213,9 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
[ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
[ApiMember(Name = "NextMediaType", Description = "The next media type that will play", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||
public string NextMediaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position ticks.
|
||||
/// </summary>
|
||||
|
@ -363,7 +366,8 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
PositionTicks = request.PositionTicks,
|
||||
MediaSourceId = request.MediaSourceId,
|
||||
PlaySessionId = request.PlaySessionId,
|
||||
LiveStreamId = request.LiveStreamId
|
||||
LiveStreamId = request.LiveStreamId,
|
||||
NextMediaType = request.NextMediaType
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
public long? Size { get; set; }
|
||||
public string Container { get; set; }
|
||||
public string ShortOverview { get; set; }
|
||||
[IgnoreDataMember]
|
||||
public string Tagline { get; set; }
|
||||
|
||||
|
@ -2263,11 +2262,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
ownedItem.Overview = item.Overview;
|
||||
newOptions.ForceSave = true;
|
||||
}
|
||||
if (!string.Equals(item.ShortOverview, ownedItem.ShortOverview, StringComparison.Ordinal))
|
||||
{
|
||||
ownedItem.ShortOverview = item.ShortOverview;
|
||||
newOptions.ForceSave = true;
|
||||
}
|
||||
if (!string.Equals(item.OfficialRating, ownedItem.OfficialRating, StringComparison.Ordinal))
|
||||
{
|
||||
ownedItem.OfficialRating = item.OfficialRating;
|
||||
|
|
|
@ -172,7 +172,6 @@ namespace MediaBrowser.Controller.Entities
|
|||
case ItemFields.ProductionLocations:
|
||||
case ItemFields.Keywords:
|
||||
case ItemFields.Taglines:
|
||||
case ItemFields.ShortOverview:
|
||||
case ItemFields.CustomRating:
|
||||
case ItemFields.DateCreated:
|
||||
case ItemFields.SortName:
|
||||
|
|
|
@ -788,7 +788,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
query.IsVirtualUnaired,
|
||||
query.IsUnaired);
|
||||
|
||||
if (collapseBoxSetItems)
|
||||
if (collapseBoxSetItems && user != null)
|
||||
{
|
||||
items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
|
||||
}
|
||||
|
@ -1119,13 +1119,11 @@ namespace MediaBrowser.Controller.Entities
|
|||
InternalItemsQuery query,
|
||||
ILibraryManager libraryManager, bool enableSorting)
|
||||
{
|
||||
var user = query.User;
|
||||
|
||||
items = items.DistinctBy(i => i.GetPresentationUniqueKey(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (query.SortBy.Length > 0)
|
||||
{
|
||||
items = libraryManager.Sort(items, user, query.SortBy, query.SortOrder);
|
||||
items = libraryManager.Sort(items, query.User, query.SortBy, query.SortOrder);
|
||||
}
|
||||
|
||||
var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray();
|
||||
|
|
|
@ -456,7 +456,7 @@ namespace MediaBrowser.Controller.Library
|
|||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>IEnumerable<Folder>.</returns>
|
||||
IEnumerable<Folder> GetCollectionFolders(BaseItem item);
|
||||
List<Folder> GetCollectionFolders(BaseItem item);
|
||||
|
||||
LibraryOptions GetLibraryOptions(BaseItem item);
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// <value>The id of the channel.</value>
|
||||
public string Id { get; set; }
|
||||
|
||||
public string TunerChannelId { get; set; }
|
||||
|
||||
public string CallSign { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tuner host identifier.
|
||||
/// </summary>
|
||||
|
|
|
@ -11,8 +11,7 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
{
|
||||
string Name { get; }
|
||||
string Type { get; }
|
||||
Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken);
|
||||
Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken);
|
||||
Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken);
|
||||
Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings);
|
||||
Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location);
|
||||
Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken);
|
||||
|
|
|
@ -114,7 +114,6 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
public bool IsRepeat { get; set; }
|
||||
public string HomePageUrl { get; set; }
|
||||
public float? CommunityRating { get; set; }
|
||||
public string ShortOverview { get; set; }
|
||||
public string OfficialRating { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public string RecordingPath { get; set; }
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
public class TunerChannelMapping
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Number { get; set; }
|
||||
public string ProviderChannelNumber { get; set; }
|
||||
public string ProviderChannelName { get; set; }
|
||||
public string ProviderChannelId { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,51 +1,22 @@
|
|||
using MediaBrowser.Model.Dlna;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
public class EncodingJobOptions
|
||||
public class EncodingJobOptions : BaseEncodingJobOptions
|
||||
{
|
||||
public string OutputContainer { get; set; }
|
||||
public string OutputDirectory { get; set; }
|
||||
|
||||
public long? StartTimeTicks { get; set; }
|
||||
public int? Width { get; set; }
|
||||
public int? Height { get; set; }
|
||||
public int? MaxWidth { get; set; }
|
||||
public int? MaxHeight { get; set; }
|
||||
public bool Static = false;
|
||||
public float? Framerate { get; set; }
|
||||
public float? MaxFramerate { get; set; }
|
||||
public string Profile { get; set; }
|
||||
public int? Level { get; set; }
|
||||
|
||||
public string DeviceId { get; set; }
|
||||
public string ItemId { get; set; }
|
||||
public string MediaSourceId { get; set; }
|
||||
public string AudioCodec { get; set; }
|
||||
|
||||
public bool EnableAutoStreamCopy { get; set; }
|
||||
|
||||
public int? MaxAudioChannels { get; set; }
|
||||
public int? AudioChannels { get; set; }
|
||||
public int? AudioBitRate { get; set; }
|
||||
public int? AudioSampleRate { get; set; }
|
||||
|
||||
public DeviceProfile DeviceProfile { get; set; }
|
||||
public EncodingContext Context { get; set; }
|
||||
|
||||
public string VideoCodec { get; set; }
|
||||
|
||||
public int? TranscodingMaxAudioChannels { get; set; }
|
||||
public int? VideoBitRate { get; set; }
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
public int? VideoStreamIndex { get; set; }
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
public int? MaxRefFrames { get; set; }
|
||||
public int? MaxVideoBitDepth { get; set; }
|
||||
public int? CpuCoreLimit { get; set; }
|
||||
public bool ReadInputAtNativeFramerate { get; set; }
|
||||
public SubtitleDeliveryMethod SubtitleMethod { get; set; }
|
||||
public bool CopyTimestamps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has fixed resolution.
|
||||
|
@ -59,11 +30,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
}
|
||||
}
|
||||
|
||||
public EncodingJobOptions()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile)
|
||||
{
|
||||
OutputContainer = info.Container;
|
||||
|
@ -72,7 +39,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
MaxHeight = info.MaxHeight;
|
||||
MaxFramerate = info.MaxFramerate;
|
||||
Profile = info.VideoProfile;
|
||||
Level = info.VideoLevel;
|
||||
ItemId = info.ItemId;
|
||||
MediaSourceId = info.MediaSourceId;
|
||||
AudioCodec = info.TargetAudioCodec;
|
||||
|
@ -93,6 +59,160 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
{
|
||||
SubtitleStreamIndex = info.SubtitleStreamIndex;
|
||||
}
|
||||
|
||||
if (info.VideoLevel.HasValue)
|
||||
{
|
||||
Level = info.VideoLevel.Value.ToString(_usCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For now until api and media encoding layers are unified
|
||||
public class BaseEncodingJobOptions
|
||||
{
|
||||
[ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool EnableAutoStreamCopy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio sample rate.
|
||||
/// </summary>
|
||||
/// <value>The audio sample rate.</value>
|
||||
[ApiMember(Name = "AudioSampleRate", Description = "Optional. Specify a specific audio sample rate, e.g. 44100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? AudioSampleRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio bit rate.
|
||||
/// </summary>
|
||||
/// <value>The audio bit rate.</value>
|
||||
[ApiMember(Name = "AudioBitRate", Description = "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? AudioBitRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio channels.
|
||||
/// </summary>
|
||||
/// <value>The audio channels.</value>
|
||||
[ApiMember(Name = "AudioChannels", Description = "Optional. Specify a specific number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? AudioChannels { get; set; }
|
||||
|
||||
[ApiMember(Name = "MaxAudioChannels", Description = "Optional. Specify a maximum number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxAudioChannels { get; set; }
|
||||
|
||||
[ApiMember(Name = "Static", Description = "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool Static { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the profile.
|
||||
/// </summary>
|
||||
/// <value>The profile.</value>
|
||||
[ApiMember(Name = "Profile", Description = "Optional. Specify a specific h264 profile, e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public string Profile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the level.
|
||||
/// </summary>
|
||||
/// <value>The level.</value>
|
||||
[ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public string Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the framerate.
|
||||
/// </summary>
|
||||
/// <value>The framerate.</value>
|
||||
[ApiMember(Name = "Framerate", Description = "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", IsRequired = false, DataType = "double", ParameterType = "query", Verb = "GET")]
|
||||
public float? Framerate { get; set; }
|
||||
|
||||
[ApiMember(Name = "MaxFramerate", Description = "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", IsRequired = false, DataType = "double", ParameterType = "query", Verb = "GET")]
|
||||
public float? MaxFramerate { get; set; }
|
||||
|
||||
[ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool CopyTimestamps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start time ticks.
|
||||
/// </summary>
|
||||
/// <value>The start time ticks.</value>
|
||||
[ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public long? StartTimeTicks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width.
|
||||
/// </summary>
|
||||
/// <value>The width.</value>
|
||||
[ApiMember(Name = "Width", Description = "Optional. The fixed horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height.
|
||||
/// </summary>
|
||||
/// <value>The height.</value>
|
||||
[ApiMember(Name = "Height", Description = "Optional. The fixed vertical resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the max.
|
||||
/// </summary>
|
||||
/// <value>The width of the max.</value>
|
||||
[ApiMember(Name = "MaxWidth", Description = "Optional. The maximum horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the max.
|
||||
/// </summary>
|
||||
/// <value>The height of the max.</value>
|
||||
[ApiMember(Name = "MaxHeight", Description = "Optional. The maximum vertical resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the video bit rate.
|
||||
/// </summary>
|
||||
/// <value>The video bit rate.</value>
|
||||
[ApiMember(Name = "VideoBitRate", Description = "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? VideoBitRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the subtitle stream.
|
||||
/// </summary>
|
||||
/// <value>The index of the subtitle stream.</value>
|
||||
[ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
|
||||
[ApiMember(Name = "SubtitleMethod", Description = "Optional. Specify the subtitle delivery method.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public SubtitleDeliveryMethod SubtitleMethod { get; set; }
|
||||
|
||||
[ApiMember(Name = "MaxRefFrames", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxRefFrames { get; set; }
|
||||
|
||||
[ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? MaxVideoBitDepth { get; set; }
|
||||
public bool RequireAvc { get; set; }
|
||||
public int? TranscodingMaxAudioChannels { get; set; }
|
||||
public int? CpuCoreLimit { get; set; }
|
||||
public string OutputContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the video codec.
|
||||
/// </summary>
|
||||
/// <value>The video codec.</value>
|
||||
[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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the audio stream.
|
||||
/// </summary>
|
||||
/// <value>The index of the audio stream.</value>
|
||||
[ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the video stream.
|
||||
/// </summary>
|
||||
/// <value>The index of the video stream.</value>
|
||||
[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 BaseEncodingJobOptions()
|
||||
{
|
||||
EnableAutoStreamCopy = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,12 +47,11 @@ namespace MediaBrowser.Controller.Providers
|
|||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="resourcePool">The resource pool.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="imageIndex">Index of the image.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SaveImage(IHasImages item, string url, SemaphoreSlim resourcePool, ImageType type, int? imageIndex, CancellationToken cancellationToken);
|
||||
Task SaveImage(IHasImages item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the image.
|
||||
|
|
|
@ -294,18 +294,6 @@ namespace MediaBrowser.LocalMetadata.Parsers
|
|||
break;
|
||||
}
|
||||
|
||||
case "ShortOverview":
|
||||
{
|
||||
var val = reader.ReadElementContentAsString();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(val))
|
||||
{
|
||||
item.ShortOverview = val;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "CriticRatingSummary":
|
||||
{
|
||||
var val = reader.ReadElementContentAsString();
|
||||
|
|
|
@ -85,7 +85,6 @@ namespace MediaBrowser.LocalMetadata.Savers
|
|||
"MusicbrainzId",
|
||||
|
||||
"Overview",
|
||||
"ShortOverview",
|
||||
"Persons",
|
||||
"PlotKeywords",
|
||||
"PremiereDate",
|
||||
|
@ -351,10 +350,6 @@ namespace MediaBrowser.LocalMetadata.Savers
|
|||
{
|
||||
writer.WriteElementString("OriginalTitle", item.OriginalTitle);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(item.ShortOverview))
|
||||
{
|
||||
writer.WriteElementString("ShortOverview", item.ShortOverview);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(item.CustomRating))
|
||||
{
|
||||
writer.WriteElementString("CustomRating", item.CustomRating);
|
||||
|
|
|
@ -42,9 +42,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
var threads = GetNumberOfThreads(state, false);
|
||||
var encodingOptions = GetEncodingOptions();
|
||||
|
||||
var inputModifier = GetInputModifier(state);
|
||||
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
|
||||
|
||||
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
|
||||
|
||||
var albumCoverInput = string.Empty;
|
||||
var mapArgs = string.Empty;
|
||||
|
@ -67,7 +69,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
var result = string.Format("{0} {1}{6}{7} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{8} -y \"{5}\"",
|
||||
inputModifier,
|
||||
GetInputArgument(state),
|
||||
EncodingHelper.GetInputArgument(state, GetEncodingOptions()),
|
||||
threads,
|
||||
vn,
|
||||
string.Join(" ", audioTranscodeParams.ToArray()),
|
||||
|
|
|
@ -36,6 +36,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
protected EncodingHelper EncodingHelper;
|
||||
|
||||
protected BaseEncoder(MediaEncoder mediaEncoder,
|
||||
ILogger logger,
|
||||
IServerConfigurationManager configurationManager,
|
||||
|
@ -56,6 +58,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
SubtitleEncoder = subtitleEncoder;
|
||||
MediaSourceManager = mediaSourceManager;
|
||||
ProcessFactory = processFactory;
|
||||
|
||||
EncodingHelper = new EncodingHelper(MediaEncoder, ConfigurationManager, FileSystem, SubtitleEncoder);
|
||||
}
|
||||
|
||||
public async Task<EncodingJob> Start(EncodingJobOptions options,
|
||||
|
@ -63,7 +67,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
CancellationToken cancellationToken)
|
||||
{
|
||||
var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager)
|
||||
.CreateJob(options, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
|
||||
.CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
|
||||
FileSystem.CreateDirectory(Path.GetDirectoryName(encodingJob.OutputFilePath));
|
||||
|
@ -285,72 +289,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of threads.
|
||||
/// </summary>
|
||||
/// <returns>System.Int32.</returns>
|
||||
protected int GetNumberOfThreads(EncodingJob job, bool isWebm)
|
||||
{
|
||||
return job.Options.CpuCoreLimit ?? 0;
|
||||
}
|
||||
|
||||
protected string GetInputModifier(EncodingJob state, bool genPts = true)
|
||||
{
|
||||
var inputModifier = string.Empty;
|
||||
|
||||
var probeSize = GetProbeSizeArgument(state);
|
||||
inputModifier += " " + probeSize;
|
||||
inputModifier = inputModifier.Trim();
|
||||
|
||||
var userAgentParam = GetUserAgentParam(state);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(userAgentParam))
|
||||
{
|
||||
inputModifier += " " + userAgentParam;
|
||||
}
|
||||
|
||||
inputModifier = inputModifier.Trim();
|
||||
|
||||
inputModifier += " " + GetFastSeekCommandLineParameter(state.Options);
|
||||
inputModifier = inputModifier.Trim();
|
||||
|
||||
if (state.IsVideoRequest && genPts)
|
||||
{
|
||||
inputModifier += " -fflags +genpts";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(state.InputAudioSync))
|
||||
{
|
||||
inputModifier += " -async " + state.InputAudioSync;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(state.InputVideoSync))
|
||||
{
|
||||
inputModifier += " -vsync " + state.InputVideoSync;
|
||||
}
|
||||
|
||||
if (state.ReadInputAtNativeFramerate)
|
||||
{
|
||||
inputModifier += " -re";
|
||||
}
|
||||
|
||||
var videoDecoder = GetVideoDecoder(state);
|
||||
if (!string.IsNullOrWhiteSpace(videoDecoder))
|
||||
{
|
||||
inputModifier += " " + videoDecoder;
|
||||
}
|
||||
|
||||
//if (state.IsVideoRequest)
|
||||
//{
|
||||
// if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// //inputModifier += " -noaccurate_seek";
|
||||
// }
|
||||
//}
|
||||
|
||||
return inputModifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the output video codec
|
||||
/// </summary>
|
||||
|
@ -405,130 +343,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
return null;
|
||||
}
|
||||
|
||||
private string GetUserAgentParam(EncodingJob state)
|
||||
{
|
||||
string useragent = null;
|
||||
|
||||
state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(useragent))
|
||||
{
|
||||
return "-user-agent \"" + useragent + "\"";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the probe size argument.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private string GetProbeSizeArgument(EncodingJob state)
|
||||
{
|
||||
if (state.PlayableStreamFileNames.Count > 0)
|
||||
{
|
||||
return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
|
||||
}
|
||||
|
||||
return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(new[] { state.MediaPath }, state.InputProtocol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fast seek command line parameter.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <value>The fast seek command line parameter.</value>
|
||||
protected string GetFastSeekCommandLineParameter(EncodingJobOptions request)
|
||||
{
|
||||
var time = request.StartTimeTicks ?? 0;
|
||||
|
||||
if (time > 0)
|
||||
{
|
||||
return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time));
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input argument.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected string GetInputArgument(EncodingJob state)
|
||||
{
|
||||
var arg = string.Format("-i {0}", GetInputPathArgument(state));
|
||||
|
||||
if (state.SubtitleStream != null && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode)
|
||||
{
|
||||
if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
|
||||
{
|
||||
if (state.VideoStream != null && state.VideoStream.Width.HasValue)
|
||||
{
|
||||
// This is hacky but not sure how to get the exact subtitle resolution
|
||||
double height = state.VideoStream.Width.Value;
|
||||
height /= 16;
|
||||
height *= 9;
|
||||
|
||||
arg += string.Format(" -canvas_size {0}:{1}", state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture), Convert.ToInt32(height).ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
var subtitlePath = state.SubtitleStream.Path;
|
||||
|
||||
if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
|
||||
if (FileSystem.FileExists(idxFile))
|
||||
{
|
||||
subtitlePath = idxFile;
|
||||
}
|
||||
}
|
||||
|
||||
arg += " -i \"" + subtitlePath + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
if (state.IsVideoRequest)
|
||||
{
|
||||
var encodingOptions = GetEncodingOptions();
|
||||
var videoEncoder = EncodingJobFactory.GetVideoEncoder(MediaEncoder, state, encodingOptions);
|
||||
if (videoEncoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode;
|
||||
var hwOutputFormat = "vaapi";
|
||||
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
hwOutputFormat = "yuv420p";
|
||||
}
|
||||
|
||||
arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
|
||||
}
|
||||
}
|
||||
|
||||
return arg.Trim();
|
||||
}
|
||||
|
||||
private string GetInputPathArgument(EncodingJob state)
|
||||
{
|
||||
var protocol = state.InputProtocol;
|
||||
var mediaPath = state.MediaPath ?? string.Empty;
|
||||
|
||||
var inputPath = new[] { mediaPath };
|
||||
|
||||
if (state.IsInputVideo)
|
||||
{
|
||||
if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
|
||||
{
|
||||
inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
|
||||
}
|
||||
}
|
||||
|
||||
return MediaEncoder.GetInputArgument(inputPath, protocol);
|
||||
}
|
||||
|
||||
private async Task AcquireResources(EncodingJob state, CancellationToken cancellationToken)
|
||||
{
|
||||
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
|
||||
|
@ -544,11 +358,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
}, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.Options);
|
||||
EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, null);
|
||||
|
||||
if (state.IsVideoRequest)
|
||||
{
|
||||
EncodingJobFactory.TryStreamCopy(state, state.Options);
|
||||
EncodingHelper.TryStreamCopy(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -557,603 +371,5 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachMediaSourceInfo(EncodingJob state,
|
||||
MediaSourceInfo mediaSource,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
EncodingJobFactory.AttachMediaSourceInfo(state, mediaSource, videoRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal graphical subtitle param.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="outputVideoCodec">The output video codec.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected async Task<string> GetGraphicalSubtitleParam(EncodingJob state, string outputVideoCodec)
|
||||
{
|
||||
var outputSizeParam = string.Empty;
|
||||
|
||||
var request = state.Options;
|
||||
|
||||
// Add resolution params, if specified
|
||||
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
|
||||
{
|
||||
outputSizeParam = await GetOutputSizeParam(state, outputVideoCodec).ConfigureAwait(false);
|
||||
outputSizeParam = outputSizeParam.TrimEnd('"');
|
||||
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else
|
||||
{
|
||||
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0)
|
||||
{
|
||||
outputSizeParam = ",format=nv12|vaapi,hwupload";
|
||||
}
|
||||
|
||||
var videoSizeParam = string.Empty;
|
||||
|
||||
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
|
||||
{
|
||||
videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
|
||||
}
|
||||
|
||||
var mapPrefix = state.SubtitleStream.IsExternal ?
|
||||
1 :
|
||||
0;
|
||||
|
||||
var subtitleStreamIndex = state.SubtitleStream.IsExternal
|
||||
? 0
|
||||
: state.SubtitleStream.Index;
|
||||
|
||||
return string.Format(" -filter_complex \"[{0}:{1}]format=yuva444p{4},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{2}] [sub] overlay{3}\"",
|
||||
mapPrefix.ToString(UsCulture),
|
||||
subtitleStreamIndex.ToString(UsCulture),
|
||||
state.VideoStream.Index.ToString(UsCulture),
|
||||
outputSizeParam,
|
||||
videoSizeParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the video bitrate to specify on the command line
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="videoEncoder">The video codec.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected string GetVideoQualityParam(EncodingJob state, string videoEncoder)
|
||||
{
|
||||
var param = string.Empty;
|
||||
|
||||
var isVc1 = state.VideoStream != null &&
|
||||
string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-preset superfast";
|
||||
|
||||
param += " -crf 23";
|
||||
}
|
||||
|
||||
else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-preset fast";
|
||||
|
||||
param += " -crf 28";
|
||||
}
|
||||
|
||||
// h264 (h264_qsv)
|
||||
else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-preset 7 -look_ahead 0";
|
||||
|
||||
}
|
||||
|
||||
// h264 (h264_nvenc)
|
||||
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-preset llhq";
|
||||
}
|
||||
|
||||
// webm
|
||||
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Values 0-3, 0 being highest quality but slower
|
||||
var profileScore = 0;
|
||||
|
||||
string crf;
|
||||
var qmin = "0";
|
||||
var qmax = "50";
|
||||
|
||||
crf = "10";
|
||||
|
||||
if (isVc1)
|
||||
{
|
||||
profileScore++;
|
||||
}
|
||||
|
||||
// Max of 2
|
||||
profileScore = Math.Min(profileScore, 2);
|
||||
|
||||
// http://www.webmproject.org/docs/encoder-parameters/
|
||||
param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
|
||||
profileScore.ToString(UsCulture),
|
||||
crf,
|
||||
qmin,
|
||||
qmax);
|
||||
}
|
||||
|
||||
else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
|
||||
}
|
||||
|
||||
// asf/wmv
|
||||
else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-qmin 2";
|
||||
}
|
||||
|
||||
else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-mbd 2";
|
||||
}
|
||||
|
||||
param += GetVideoBitrateParam(state, videoEncoder);
|
||||
|
||||
var framerate = GetFramerateParam(state);
|
||||
if (framerate.HasValue)
|
||||
{
|
||||
param += string.Format(" -r {0}", framerate.Value.ToString(UsCulture));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(state.OutputVideoSync))
|
||||
{
|
||||
param += " -vsync " + state.OutputVideoSync;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(state.Options.Profile))
|
||||
{
|
||||
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// not supported by h264_omx
|
||||
param += " -profile:v " + state.Options.Profile;
|
||||
}
|
||||
}
|
||||
|
||||
var levelString = state.Options.Level.HasValue ? state.Options.Level.Value.ToString(CultureInfo.InvariantCulture) : null;
|
||||
|
||||
if (!string.IsNullOrEmpty(levelString))
|
||||
{
|
||||
levelString = NormalizeTranscodingLevel(state.OutputVideoCodec, levelString);
|
||||
|
||||
// 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
|
||||
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
switch (levelString)
|
||||
{
|
||||
case "30":
|
||||
param += " -level 3.0";
|
||||
break;
|
||||
case "31":
|
||||
param += " -level 3.1";
|
||||
break;
|
||||
case "32":
|
||||
param += " -level 3.2";
|
||||
break;
|
||||
case "40":
|
||||
param += " -level 4.0";
|
||||
break;
|
||||
case "41":
|
||||
param += " -level 4.1";
|
||||
break;
|
||||
case "42":
|
||||
param += " -level 4.2";
|
||||
break;
|
||||
case "50":
|
||||
param += " -level 5.0";
|
||||
break;
|
||||
case "51":
|
||||
param += " -level 5.1";
|
||||
break;
|
||||
case "52":
|
||||
param += " -level 5.2";
|
||||
break;
|
||||
default:
|
||||
param += " -level " + levelString;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param += " -level " + levelString;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
|
||||
!string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param = "-pix_fmt yuv420p " + param;
|
||||
}
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
private string NormalizeTranscodingLevel(string videoCodec, string level)
|
||||
{
|
||||
double requestLevel;
|
||||
|
||||
// Clients may direct play higher than level 41, but there's no reason to transcode higher
|
||||
if (double.TryParse(level, NumberStyles.Any, UsCulture, out requestLevel))
|
||||
{
|
||||
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (requestLevel > 41)
|
||||
{
|
||||
return "41";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
protected string GetVideoBitrateParam(EncodingJob state, string videoCodec)
|
||||
{
|
||||
var bitrate = state.OutputVideoBitrate;
|
||||
|
||||
if (bitrate.HasValue)
|
||||
{
|
||||
if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// With vpx when crf is used, b:v becomes a max rate
|
||||
// https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
|
||||
return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
|
||||
}
|
||||
|
||||
if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
|
||||
}
|
||||
|
||||
if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// h264
|
||||
return string.Format(" -maxrate {0} -bufsize {1}",
|
||||
bitrate.Value.ToString(UsCulture),
|
||||
(bitrate.Value * 2).ToString(UsCulture));
|
||||
}
|
||||
|
||||
// h264
|
||||
return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
|
||||
bitrate.Value.ToString(UsCulture),
|
||||
(bitrate.Value * 2).ToString(UsCulture));
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected double? GetFramerateParam(EncodingJob state)
|
||||
{
|
||||
if (state.Options != null)
|
||||
{
|
||||
if (state.Options.Framerate.HasValue)
|
||||
{
|
||||
return state.Options.Framerate.Value;
|
||||
}
|
||||
|
||||
var maxrate = state.Options.MaxFramerate;
|
||||
|
||||
if (maxrate.HasValue && state.VideoStream != null)
|
||||
{
|
||||
var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
|
||||
|
||||
if (contentRate.HasValue && contentRate.Value > maxrate.Value)
|
||||
{
|
||||
return maxrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the map args.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected virtual string GetMapArgs(EncodingJob state)
|
||||
{
|
||||
// If we don't have known media info
|
||||
// If input is video, use -sn to drop subtitles
|
||||
// Otherwise just return empty
|
||||
if (state.VideoStream == null && state.AudioStream == null)
|
||||
{
|
||||
return state.IsInputVideo ? "-sn" : string.Empty;
|
||||
}
|
||||
|
||||
// We have media info, but we don't know the stream indexes
|
||||
if (state.VideoStream != null && state.VideoStream.Index == -1)
|
||||
{
|
||||
return "-sn";
|
||||
}
|
||||
|
||||
// We have media info, but we don't know the stream indexes
|
||||
if (state.AudioStream != null && state.AudioStream.Index == -1)
|
||||
{
|
||||
return state.IsInputVideo ? "-sn" : string.Empty;
|
||||
}
|
||||
|
||||
var args = string.Empty;
|
||||
|
||||
if (state.VideoStream != null)
|
||||
{
|
||||
args += string.Format("-map 0:{0}", state.VideoStream.Index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No known video stream
|
||||
args += "-vn";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null)
|
||||
{
|
||||
args += string.Format(" -map 0:{0}", state.AudioStream.Index);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
args += " -map -0:a";
|
||||
}
|
||||
|
||||
if (state.SubtitleStream == null || state.Options.SubtitleMethod == SubtitleDeliveryMethod.Hls)
|
||||
{
|
||||
args += " -map -0:s";
|
||||
}
|
||||
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
|
||||
{
|
||||
args += " -map 1:0 -sn";
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified stream is H264.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
|
||||
protected bool IsH264(MediaStream stream)
|
||||
{
|
||||
var codec = stream.Codec ?? string.Empty;
|
||||
|
||||
return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we're going to put a fixed size on the command line, this will calculate it
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="outputVideoCodec">The output video codec.</param>
|
||||
/// <param name="allowTimeStampCopy">if set to <c>true</c> [allow time stamp copy].</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected async Task<string> GetOutputSizeParam(EncodingJob state,
|
||||
string outputVideoCodec,
|
||||
bool allowTimeStampCopy = true)
|
||||
{
|
||||
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
|
||||
|
||||
var request = state.Options;
|
||||
|
||||
var filters = new List<string>();
|
||||
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filters.Add("format=nv12|vaapi");
|
||||
filters.Add("hwupload");
|
||||
}
|
||||
else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filters.Add("yadif=0:-1:0");
|
||||
}
|
||||
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Work around vaapi's reduced scaling features
|
||||
var scaler = "scale_vaapi";
|
||||
|
||||
// Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
|
||||
// (outputWidth, outputHeight). The user may request precise output dimensions or maximum
|
||||
// output dimensions. Output dimensions are guaranteed to be even.
|
||||
decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
|
||||
decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
|
||||
decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
|
||||
decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
|
||||
decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
|
||||
decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
|
||||
|
||||
if (outputWidth > maximumWidth || outputHeight > maximumHeight)
|
||||
{
|
||||
var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
|
||||
outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
|
||||
outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
|
||||
}
|
||||
|
||||
outputWidth = 2 * Math.Truncate(outputWidth / 2);
|
||||
outputHeight = 2 * Math.Truncate(outputHeight / 2);
|
||||
|
||||
if (outputWidth != inputWidth || outputHeight != inputHeight)
|
||||
{
|
||||
filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If fixed dimensions were supplied
|
||||
if (request.Width.HasValue && request.Height.HasValue)
|
||||
{
|
||||
var widthParam = request.Width.Value.ToString(UsCulture);
|
||||
var heightParam = request.Height.Value.ToString(UsCulture);
|
||||
|
||||
filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
|
||||
}
|
||||
|
||||
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
|
||||
else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
|
||||
{
|
||||
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
|
||||
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
|
||||
|
||||
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
|
||||
}
|
||||
|
||||
// If a fixed width was requested
|
||||
else if (request.Width.HasValue)
|
||||
{
|
||||
var widthParam = request.Width.Value.ToString(UsCulture);
|
||||
|
||||
filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
|
||||
}
|
||||
|
||||
// If a fixed height was requested
|
||||
else if (request.Height.HasValue)
|
||||
{
|
||||
var heightParam = request.Height.Value.ToString(UsCulture);
|
||||
|
||||
filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
|
||||
}
|
||||
|
||||
// If a max width was requested
|
||||
else if (request.MaxWidth.HasValue)
|
||||
{
|
||||
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
|
||||
|
||||
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
|
||||
}
|
||||
|
||||
// If a max height was requested
|
||||
else if (request.MaxHeight.HasValue)
|
||||
{
|
||||
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
|
||||
|
||||
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
|
||||
}
|
||||
}
|
||||
|
||||
var output = string.Empty;
|
||||
|
||||
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode)
|
||||
{
|
||||
var subParam = await GetTextSubtitleParam(state).ConfigureAwait(false);
|
||||
|
||||
filters.Add(subParam);
|
||||
|
||||
if (allowTimeStampCopy)
|
||||
{
|
||||
output += " -copyts";
|
||||
}
|
||||
}
|
||||
|
||||
if (filters.Count > 0)
|
||||
{
|
||||
output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray()));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text subtitle param.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
protected async Task<string> GetTextSubtitleParam(EncodingJob state)
|
||||
{
|
||||
var seconds = Math.Round(TimeSpan.FromTicks(state.Options.StartTimeTicks ?? 0).TotalSeconds);
|
||||
|
||||
if (state.SubtitleStream.IsExternal)
|
||||
{
|
||||
var subtitlePath = state.SubtitleStream.Path;
|
||||
|
||||
var charsetParam = string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
||||
{
|
||||
var charenc = await SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(charenc))
|
||||
{
|
||||
charsetParam = ":charenc=" + charenc;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Perhaps also use original_size=1920x800 ??
|
||||
return string.Format("subtitles=filename='{0}'{1},setpts=PTS -{2}/TB",
|
||||
MediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
|
||||
charsetParam,
|
||||
seconds.ToString(UsCulture));
|
||||
}
|
||||
|
||||
var mediaPath = state.MediaPath ?? string.Empty;
|
||||
|
||||
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
|
||||
MediaEncoder.EscapeSubtitleFilterPath(mediaPath),
|
||||
state.InternalSubtitleStreamOffset.ToString(UsCulture),
|
||||
seconds.ToString(UsCulture));
|
||||
}
|
||||
|
||||
protected string GetAudioFilterParam(EncodingJob state, bool isHls)
|
||||
{
|
||||
var volParam = string.Empty;
|
||||
var audioSampleRate = string.Empty;
|
||||
|
||||
var channels = state.OutputAudioChannels;
|
||||
|
||||
// Boost volume to 200% when downsampling from 6ch to 2ch
|
||||
if (channels.HasValue && channels.Value <= 2)
|
||||
{
|
||||
if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !GetEncodingOptions().DownMixAudioBoost.Equals(1))
|
||||
{
|
||||
volParam = ",volume=" + GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.OutputAudioSampleRate.HasValue)
|
||||
{
|
||||
audioSampleRate = state.OutputAudioSampleRate.Value + ":";
|
||||
}
|
||||
|
||||
var adelay = isHls ? "adelay=1," : string.Empty;
|
||||
|
||||
var pts = string.Empty;
|
||||
|
||||
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode && !state.Options.CopyTimestamps)
|
||||
{
|
||||
var seconds = TimeSpan.FromTicks(state.Options.StartTimeTicks ?? 0).TotalSeconds;
|
||||
|
||||
pts = string.Format(",asetpts=PTS-{0}/TB", Math.Round(seconds).ToString(UsCulture));
|
||||
}
|
||||
|
||||
return string.Format("-af \"{0}aresample={1}async={4}{2}{3}\"",
|
||||
|
||||
adelay,
|
||||
audioSampleRate,
|
||||
volParam,
|
||||
pts,
|
||||
state.OutputAudioSync);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1681
MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs
Normal file
1681
MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,7 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
public class EncodingJob : IDisposable
|
||||
public class EncodingJob : EncodingJobInfo, IDisposable
|
||||
{
|
||||
public bool HasExited { get; internal set; }
|
||||
public bool IsCancelled { get; internal set; }
|
||||
|
@ -25,46 +25,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
public Stream LogFileStream { get; set; }
|
||||
public IProgress<double> Progress { get; set; }
|
||||
public TaskCompletionSource<bool> TaskCompletionSource;
|
||||
public EncodingJobOptions Options { get; set; }
|
||||
public string InputContainer { get; set; }
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
public MediaStream AudioStream { get; set; }
|
||||
public MediaStream VideoStream { get; set; }
|
||||
public MediaStream SubtitleStream { get; set; }
|
||||
public IIsoMount IsoMount { get; set; }
|
||||
|
||||
public bool ReadInputAtNativeFramerate { get; set; }
|
||||
public bool IsVideoRequest { get; set; }
|
||||
public string InputAudioSync { get; set; }
|
||||
public string InputVideoSync { get; set; }
|
||||
public EncodingJobOptions Options
|
||||
{
|
||||
get { return (EncodingJobOptions) BaseRequest; }
|
||||
set { BaseRequest = value; }
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
|
||||
public string MediaPath { get; set; }
|
||||
public MediaProtocol InputProtocol { get; set; }
|
||||
public bool IsInputVideo { get; set; }
|
||||
public VideoType VideoType { get; set; }
|
||||
public IsoType? IsoType { get; set; }
|
||||
public List<string> PlayableStreamFileNames { get; set; }
|
||||
|
||||
public List<string> SupportedAudioCodecs { get; set; }
|
||||
public Dictionary<string, string> RemoteHttpHeaders { get; set; }
|
||||
public TransportStreamTimestamp InputTimestamp { get; set; }
|
||||
|
||||
public bool DeInterlace { get; set; }
|
||||
public string MimeType { get; set; }
|
||||
public bool EstimateContentLength { get; set; }
|
||||
public bool EnableMpegtsM2TsMode { get; set; }
|
||||
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||
public long? EncodingDurationTicks { get; set; }
|
||||
public string LiveStreamId { get; set; }
|
||||
public long? RunTimeTicks;
|
||||
|
||||
public string ItemType { get; set; }
|
||||
|
||||
public long? InputBitrate { get; set; }
|
||||
public long? InputFileSize { get; set; }
|
||||
public string OutputAudioSync = "1";
|
||||
public string OutputVideoSync = "vfr";
|
||||
public string AlbumCoverPath { get; set; }
|
||||
|
||||
public string GetMimeType(string outputPath)
|
||||
|
@ -80,17 +58,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
private readonly ILogger _logger;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager)
|
||||
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) :
|
||||
base(logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
Id = Guid.NewGuid().ToString("N");
|
||||
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
_logger = logger;
|
||||
SupportedAudioCodecs = new List<string>();
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
TaskCompletionSource = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
|
@ -118,23 +93,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
private void DisposeIsoMount()
|
||||
{
|
||||
if (IsoMount != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsoMount.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error disposing iso mount", ex);
|
||||
}
|
||||
|
||||
IsoMount = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async void DisposeLiveStream()
|
||||
{
|
||||
if (MediaSource.RequiresClosing)
|
||||
|
@ -150,15 +108,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
public int InternalSubtitleStreamOffset { get; set; }
|
||||
|
||||
public string OutputFilePath { get; set; }
|
||||
public string OutputVideoCodec { get; set; }
|
||||
public string OutputAudioCodec { get; set; }
|
||||
public int? OutputAudioChannels;
|
||||
public int? OutputAudioSampleRate;
|
||||
public int? OutputAudioBitrate;
|
||||
public int? OutputVideoBitrate;
|
||||
|
||||
public string ActualOutputVideoCodec
|
||||
{
|
||||
|
@ -313,25 +264,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predicts the audio sample rate that will be in the output stream
|
||||
/// </summary>
|
||||
public double? TargetVideoLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
var stream = VideoStream;
|
||||
return Options.Level.HasValue && !Options.Static
|
||||
? Options.Level.Value
|
||||
: stream == null ? null : stream.Level;
|
||||
}
|
||||
}
|
||||
|
||||
public TransportStreamTimestamp TargetTimestamp
|
||||
{
|
||||
get
|
||||
{
|
||||
var defaultValue = string.Equals(Options.OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
|
||||
var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
|
||||
TransportStreamTimestamp.Valid :
|
||||
TransportStreamTimestamp.None;
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<EncodingJob> CreateJob(EncodingJobOptions options, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
public async Task<EncodingJob> CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = options;
|
||||
|
||||
|
@ -49,6 +49,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
Progress = progress
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.VideoCodec))
|
||||
{
|
||||
state.SupportedVideoCodecs = request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
|
||||
{
|
||||
state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
|
@ -76,7 +82,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
var videoRequest = state.Options;
|
||||
|
||||
AttachMediaSourceInfo(state, mediaSource, videoRequest);
|
||||
encodingHelper.AttachMediaSourceInfo(state, mediaSource, null);
|
||||
|
||||
//var container = Path.GetExtension(state.RequestedUrl);
|
||||
|
||||
|
@ -89,17 +95,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
//state.OutputContainer = (container ?? string.Empty).TrimStart('.');
|
||||
|
||||
state.OutputAudioBitrate = GetAudioBitrateParam(state.Options, state.AudioStream);
|
||||
state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.Options, state.AudioStream);
|
||||
state.OutputAudioSampleRate = request.AudioSampleRate;
|
||||
|
||||
state.OutputAudioCodec = state.Options.AudioCodec;
|
||||
|
||||
state.OutputAudioChannels = GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec);
|
||||
state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec);
|
||||
|
||||
if (videoRequest != null)
|
||||
{
|
||||
state.OutputVideoCodec = state.Options.VideoCodec;
|
||||
state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec);
|
||||
state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec);
|
||||
|
||||
if (state.OutputVideoBitrate.HasValue)
|
||||
{
|
||||
|
@ -120,7 +126,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
if (videoRequest != null)
|
||||
{
|
||||
TryStreamCopy(state, videoRequest);
|
||||
encodingHelper.TryStreamCopy(state);
|
||||
}
|
||||
|
||||
//state.OutputFilePath = GetOutputFilePath(state);
|
||||
|
@ -128,104 +134,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
return state;
|
||||
}
|
||||
|
||||
internal static void TryStreamCopy(EncodingJob state,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
if (state.IsVideoRequest)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||
{
|
||||
state.OutputVideoCodec = "copy";
|
||||
}
|
||||
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
state.OutputAudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void AttachMediaSourceInfo(EncodingJob state,
|
||||
MediaSourceInfo mediaSource,
|
||||
EncodingJobOptions videoRequest)
|
||||
{
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.InputContainer = mediaSource.Container;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
|
||||
if (mediaSource.VideoType.HasValue)
|
||||
{
|
||||
state.VideoType = mediaSource.VideoType.Value;
|
||||
}
|
||||
|
||||
state.IsoType = mediaSource.IsoType;
|
||||
|
||||
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
|
||||
|
||||
if (mediaSource.Timestamp.HasValue)
|
||||
{
|
||||
state.InputTimestamp = mediaSource.Timestamp.Value;
|
||||
}
|
||||
|
||||
state.InputProtocol = mediaSource.Protocol;
|
||||
state.MediaPath = mediaSource.Path;
|
||||
state.RunTimeTicks = mediaSource.RunTimeTicks;
|
||||
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
|
||||
state.InputBitrate = mediaSource.Bitrate;
|
||||
state.InputFileSize = mediaSource.Size;
|
||||
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
|
||||
|
||||
if (state.ReadInputAtNativeFramerate ||
|
||||
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
state.OutputAudioSync = "1000";
|
||||
state.InputVideoSync = "-1";
|
||||
state.InputAudioSync = "1";
|
||||
}
|
||||
|
||||
if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Seeing some stuttering when transcoding wma to audio-only HLS
|
||||
state.InputAudioSync = "1";
|
||||
}
|
||||
|
||||
var mediaStreams = mediaSource.MediaStreams;
|
||||
|
||||
if (videoRequest != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoRequest.VideoCodec))
|
||||
{
|
||||
videoRequest.VideoCodec = InferVideoCodec(videoRequest.OutputContainer);
|
||||
}
|
||||
|
||||
state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
|
||||
state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
|
||||
state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
|
||||
|
||||
if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal)
|
||||
{
|
||||
state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream);
|
||||
}
|
||||
|
||||
if (state.VideoStream != null && state.VideoStream.IsInterlaced)
|
||||
{
|
||||
state.DeInterlace = true;
|
||||
}
|
||||
|
||||
EnforceResolutionLimit(state, videoRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||
}
|
||||
|
||||
state.MediaSource = mediaSource;
|
||||
}
|
||||
|
||||
protected EncodingOptions GetEncodingOptions()
|
||||
{
|
||||
return _config.GetConfiguration<EncodingOptions>("encoding");
|
||||
|
@ -300,203 +208,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
return "copy";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines which stream will be used for playback
|
||||
/// </summary>
|
||||
/// <param name="allStream">All stream.</param>
|
||||
/// <param name="desiredIndex">Index of the desired.</param>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
|
||||
/// <returns>MediaStream.</returns>
|
||||
private static MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
|
||||
{
|
||||
var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
|
||||
|
||||
if (desiredIndex.HasValue)
|
||||
{
|
||||
var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
|
||||
|
||||
if (stream != null)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == MediaStreamType.Video)
|
||||
{
|
||||
streams = streams.Where(i => !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
|
||||
if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
|
||||
{
|
||||
return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
|
||||
streams.FirstOrDefault();
|
||||
}
|
||||
|
||||
// Just return the first one
|
||||
return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enforces the resolution limit.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="videoRequest">The video request.</param>
|
||||
private static void EnforceResolutionLimit(EncodingJob state, EncodingJobOptions videoRequest)
|
||||
{
|
||||
// Switch the incoming params to be ceilings rather than fixed values
|
||||
videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
|
||||
videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
|
||||
|
||||
videoRequest.Width = null;
|
||||
videoRequest.Height = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of audio channels to specify on the command line
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="audioStream">The audio stream.</param>
|
||||
/// <param name="outputAudioCodec">The output audio codec.</param>
|
||||
/// <returns>System.Nullable{System.Int32}.</returns>
|
||||
private int? GetNumAudioChannelsParam(EncodingJobOptions request, MediaStream audioStream, string outputAudioCodec)
|
||||
{
|
||||
var inputChannels = audioStream == null
|
||||
? null
|
||||
: audioStream.Channels;
|
||||
|
||||
if (inputChannels <= 0)
|
||||
{
|
||||
inputChannels = null;
|
||||
}
|
||||
|
||||
int? transcoderChannelLimit = null;
|
||||
var codec = outputAudioCodec ?? string.Empty;
|
||||
|
||||
if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
// wmav2 currently only supports two channel output
|
||||
transcoderChannelLimit = 2;
|
||||
}
|
||||
|
||||
else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
// libmp3lame currently only supports two channel output
|
||||
transcoderChannelLimit = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels
|
||||
transcoderChannelLimit = 6;
|
||||
}
|
||||
|
||||
var isTranscodingAudio = !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
int? resultChannels = null;
|
||||
if (isTranscodingAudio)
|
||||
{
|
||||
resultChannels = request.TranscodingMaxAudioChannels;
|
||||
}
|
||||
resultChannels = resultChannels ?? request.MaxAudioChannels ?? request.AudioChannels;
|
||||
|
||||
if (inputChannels.HasValue)
|
||||
{
|
||||
resultChannels = resultChannels.HasValue
|
||||
? Math.Min(resultChannels.Value, inputChannels.Value)
|
||||
: inputChannels.Value;
|
||||
}
|
||||
|
||||
if (isTranscodingAudio && transcoderChannelLimit.HasValue)
|
||||
{
|
||||
resultChannels = resultChannels.HasValue
|
||||
? Math.Min(resultChannels.Value, transcoderChannelLimit.Value)
|
||||
: transcoderChannelLimit.Value;
|
||||
}
|
||||
|
||||
return resultChannels ?? request.AudioChannels;
|
||||
}
|
||||
|
||||
private int? GetVideoBitrateParamValue(EncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
|
||||
{
|
||||
var bitrate = request.VideoBitRate;
|
||||
|
||||
if (videoStream != null)
|
||||
{
|
||||
var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue &&
|
||||
request.Height.Value > videoStream.Height.Value;
|
||||
|
||||
if (request.Width.HasValue && videoStream.Width.HasValue &&
|
||||
request.Width.Value > videoStream.Width.Value)
|
||||
{
|
||||
isUpscaling = true;
|
||||
}
|
||||
|
||||
// Don't allow bitrate increases unless upscaling
|
||||
if (!isUpscaling)
|
||||
{
|
||||
if (bitrate.HasValue && videoStream.BitRate.HasValue)
|
||||
{
|
||||
bitrate = Math.Min(bitrate.Value, videoStream.BitRate.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bitrate.HasValue)
|
||||
{
|
||||
var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
|
||||
bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
|
||||
|
||||
// If a max bitrate was requested, don't let the scaled bitrate exceed it
|
||||
if (request.VideoBitRate.HasValue)
|
||||
{
|
||||
bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
protected string GetVideoBitrateParam(EncodingJob state, string videoCodec, bool isHls)
|
||||
{
|
||||
var bitrate = state.OutputVideoBitrate;
|
||||
|
||||
if (bitrate.HasValue)
|
||||
{
|
||||
if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// With vpx when crf is used, b:v becomes a max rate
|
||||
// https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
|
||||
return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
|
||||
}
|
||||
|
||||
if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
|
||||
}
|
||||
|
||||
// h264
|
||||
return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
|
||||
bitrate.Value.ToString(UsCulture),
|
||||
(bitrate.Value * 2).ToString(UsCulture));
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private int? GetAudioBitrateParam(EncodingJobOptions request, MediaStream audioStream)
|
||||
{
|
||||
if (request.AudioBitRate.HasValue)
|
||||
{
|
||||
// Make sure we don't request a bitrate higher than the source
|
||||
var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value;
|
||||
|
||||
return request.AudioBitRate.Value;
|
||||
//return Math.Min(currentBitrate, request.AudioBitRate.Value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified stream is H264.
|
||||
/// </summary>
|
||||
|
@ -510,260 +221,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the output audio codec
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
internal static string GetAudioEncoder(EncodingJob state)
|
||||
{
|
||||
var codec = state.OutputAudioCodec;
|
||||
|
||||
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "aac -strict experimental";
|
||||
}
|
||||
if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "libmp3lame";
|
||||
}
|
||||
if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "libvorbis";
|
||||
}
|
||||
if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "wmav2";
|
||||
}
|
||||
|
||||
return codec.ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the output video codec
|
||||
/// </summary>
|
||||
/// <returns>System.String.</returns>
|
||||
internal static string GetVideoEncoder(IMediaEncoder mediaEncoder, EncodingJob state, EncodingOptions options)
|
||||
{
|
||||
var codec = state.OutputVideoCodec;
|
||||
|
||||
if (!string.IsNullOrEmpty(codec))
|
||||
{
|
||||
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetH264Encoder(mediaEncoder, state, options);
|
||||
}
|
||||
if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "libvpx";
|
||||
}
|
||||
if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "wmv2";
|
||||
}
|
||||
if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "libtheora";
|
||||
}
|
||||
|
||||
return codec.ToLower();
|
||||
}
|
||||
|
||||
return "copy";
|
||||
}
|
||||
|
||||
private static string GetAvailableEncoder(IMediaEncoder mediaEncoder, string preferredEncoder, string defaultEncoder)
|
||||
{
|
||||
if (mediaEncoder.SupportsEncoder(preferredEncoder))
|
||||
{
|
||||
return preferredEncoder;
|
||||
}
|
||||
return defaultEncoder;
|
||||
}
|
||||
|
||||
internal static string GetH264Encoder(IMediaEncoder mediaEncoder, EncodingJob state, EncodingOptions options)
|
||||
{
|
||||
var defaultEncoder = "libx264";
|
||||
|
||||
// Only use alternative encoders for video files.
|
||||
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
||||
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
||||
if (state.VideoType == VideoType.VideoFile)
|
||||
{
|
||||
var hwType = options.HardwareAccelerationType;
|
||||
|
||||
if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetAvailableEncoder(mediaEncoder, "h264_qsv", defaultEncoder);
|
||||
}
|
||||
|
||||
if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetAvailableEncoder(mediaEncoder, "h264_nvenc", defaultEncoder);
|
||||
}
|
||||
if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetAvailableEncoder(mediaEncoder, "h264_omx", defaultEncoder);
|
||||
}
|
||||
if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(options.VaapiDevice))
|
||||
{
|
||||
if (IsVaapiSupported(state))
|
||||
{
|
||||
return GetAvailableEncoder(mediaEncoder, "h264_vaapi", defaultEncoder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultEncoder;
|
||||
}
|
||||
|
||||
private static bool IsVaapiSupported(EncodingJob state)
|
||||
{
|
||||
var videoStream = state.VideoStream;
|
||||
|
||||
if (videoStream != null)
|
||||
{
|
||||
// vaapi will throw an error with this input
|
||||
// [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
|
||||
if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (videoStream.Level == -99 || videoStream.Level == 15)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool CanStreamCopyVideo(EncodingJobOptions request, MediaStream videoStream)
|
||||
{
|
||||
if (videoStream.IsInterlaced)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (videoStream.IsAnamorphic ?? false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't stream copy if we're burning in subtitles
|
||||
if (request.SubtitleStreamIndex.HasValue)
|
||||
{
|
||||
if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Source and target codecs must match
|
||||
if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If client is requesting a specific video profile, it must match the source
|
||||
if (!string.IsNullOrEmpty(request.Profile))
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoStream.Profile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var currentScore = GetVideoProfileScore(videoStream.Profile);
|
||||
var requestedScore = GetVideoProfileScore(request.Profile);
|
||||
|
||||
if (currentScore == -1 || currentScore > requestedScore)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Video width must fall within requested value
|
||||
if (request.MaxWidth.HasValue)
|
||||
{
|
||||
if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Video height must fall within requested value
|
||||
if (request.MaxHeight.HasValue)
|
||||
{
|
||||
if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Video framerate must fall within requested value
|
||||
var requestedFramerate = request.MaxFramerate ?? request.Framerate;
|
||||
if (requestedFramerate.HasValue)
|
||||
{
|
||||
var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate;
|
||||
|
||||
if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Video bitrate must fall within requested value
|
||||
if (request.VideoBitRate.HasValue)
|
||||
{
|
||||
if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.MaxVideoBitDepth.HasValue)
|
||||
{
|
||||
if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > request.MaxVideoBitDepth.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.MaxRefFrames.HasValue)
|
||||
{
|
||||
if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > request.MaxRefFrames.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If a specific level was requested, the source must match or be less than
|
||||
if (request.Level.HasValue)
|
||||
{
|
||||
if (!videoStream.Level.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (videoStream.Level.Value > request.Level.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return request.EnableAutoStreamCopy;
|
||||
}
|
||||
|
||||
private static int GetVideoProfileScore(string profile)
|
||||
{
|
||||
var list = new List<string>
|
||||
|
@ -780,57 +237,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
internal static bool CanStreamCopyAudio(EncodingJobOptions request, MediaStream audioStream, List<string> supportedAudioCodecs)
|
||||
{
|
||||
// Source and target codecs must match
|
||||
if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Video bitrate must fall within requested value
|
||||
if (request.AudioBitRate.HasValue)
|
||||
{
|
||||
if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (audioStream.BitRate.Value > request.AudioBitRate.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Channels must fall within requested value
|
||||
var channels = request.AudioChannels ?? request.MaxAudioChannels;
|
||||
if (channels.HasValue)
|
||||
{
|
||||
if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (audioStream.Channels.Value > channels.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Sample rate must fall within requested value
|
||||
if (request.AudioSampleRate.HasValue)
|
||||
{
|
||||
if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return request.EnableAutoStreamCopy;
|
||||
}
|
||||
|
||||
private void ApplyDeviceProfileSettings(EncodingJob state)
|
||||
{
|
||||
var profile = state.Options.DeviceProfile;
|
||||
|
|
118
MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs
Normal file
118
MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs
Normal file
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
// For now, a common base class until the API and MediaEncoding classes are unified
|
||||
public class EncodingJobInfo
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MediaStream VideoStream { get; set; }
|
||||
public VideoType VideoType { get; set; }
|
||||
public Dictionary<string, string> RemoteHttpHeaders { get; set; }
|
||||
public string OutputVideoCodec { get; set; }
|
||||
public MediaProtocol InputProtocol { get; set; }
|
||||
public string MediaPath { get; set; }
|
||||
public bool IsInputVideo { get; set; }
|
||||
public IIsoMount IsoMount { get; set; }
|
||||
public List<string> PlayableStreamFileNames { get; set; }
|
||||
public string OutputAudioCodec { get; set; }
|
||||
public int? OutputVideoBitrate { get; set; }
|
||||
public MediaStream SubtitleStream { get; set; }
|
||||
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
|
||||
|
||||
public int InternalSubtitleStreamOffset { get; set; }
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
public User User { get; set; }
|
||||
|
||||
public long? RunTimeTicks { get; set; }
|
||||
|
||||
public bool ReadInputAtNativeFramerate { get; set; }
|
||||
|
||||
public string OutputContainer { get; set; }
|
||||
|
||||
public string OutputVideoSync = "-1";
|
||||
public string OutputAudioSync = "1";
|
||||
public string InputAudioSync { get; set; }
|
||||
public string InputVideoSync { get; set; }
|
||||
public TransportStreamTimestamp InputTimestamp { get; set; }
|
||||
|
||||
public MediaStream AudioStream { get; set; }
|
||||
public List<string> SupportedAudioCodecs { get; set; }
|
||||
public List<string> SupportedVideoCodecs { get; set; }
|
||||
public string InputContainer { get; set; }
|
||||
public IsoType? IsoType { get; set; }
|
||||
|
||||
public BaseEncodingJobOptions BaseRequest { get; set; }
|
||||
|
||||
public long? StartTimeTicks
|
||||
{
|
||||
get { return BaseRequest.StartTimeTicks; }
|
||||
}
|
||||
|
||||
public bool CopyTimestamps
|
||||
{
|
||||
get { return BaseRequest.CopyTimestamps; }
|
||||
}
|
||||
|
||||
public int? OutputAudioChannels;
|
||||
public int? OutputAudioSampleRate;
|
||||
public bool DeInterlace { get; set; }
|
||||
public bool IsVideoRequest { get; set; }
|
||||
|
||||
public EncodingJobInfo(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
SupportedVideoCodecs = new List<string>();
|
||||
SupportedVideoCodecs = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predicts the audio sample rate that will be in the output stream
|
||||
/// </summary>
|
||||
public double? TargetVideoLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
var stream = VideoStream;
|
||||
var request = BaseRequest;
|
||||
|
||||
return !string.IsNullOrEmpty(request.Level) && !request.Static
|
||||
? double.Parse(request.Level, CultureInfo.InvariantCulture)
|
||||
: stream == null ? null : stream.Level;
|
||||
}
|
||||
}
|
||||
|
||||
protected void DisposeIsoMount()
|
||||
{
|
||||
if (IsoMount != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsoMount.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error disposing iso mount", ex);
|
||||
}
|
||||
|
||||
IsoMount = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -496,6 +496,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
return SupportsEncoder(codec);
|
||||
}
|
||||
|
||||
public bool CanEncodeToSubtitleCodec(string codec)
|
||||
{
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the encoder path.
|
||||
/// </summary>
|
||||
|
@ -693,16 +699,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
private async Task<bool> DetectInterlaced(MediaSourceInfo video, MediaStream videoStream, string inputPath, string probeSizeArgument)
|
||||
{
|
||||
if (video.Protocol != MediaProtocol.File)
|
||||
{
|
||||
// If it's mpeg based, assume true
|
||||
if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var formats = (video.Container ?? string.Empty).Split(',').ToList();
|
||||
var enableInterlacedDection = formats.Contains("vob", StringComparer.OrdinalIgnoreCase) ||
|
||||
formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) ||
|
||||
|
@ -727,6 +723,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
}
|
||||
|
||||
if (video.Protocol != MediaProtocol.File)
|
||||
{
|
||||
// If it's mpeg based, assume true
|
||||
if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
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
|
||||
|
|
|
@ -21,7 +21,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
protected override async Task<string> GetCommandLineArguments(EncodingJob state)
|
||||
{
|
||||
// Get the output codec name
|
||||
var videoCodec = EncodingJobFactory.GetVideoEncoder(MediaEncoder, state, GetEncodingOptions());
|
||||
var encodingOptions = GetEncodingOptions();
|
||||
var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
|
||||
|
||||
var format = string.Empty;
|
||||
var keyFrame = string.Empty;
|
||||
|
@ -33,17 +34,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
format = " -f mp4 -movflags frag_keyframe+empty_moov";
|
||||
}
|
||||
|
||||
var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
|
||||
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var inputModifier = GetInputModifier(state);
|
||||
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,
|
||||
GetInputArgument(state),
|
||||
EncodingHelper.GetInputArgument(state, encodingOptions),
|
||||
keyFrame,
|
||||
GetMapArgs(state),
|
||||
EncodingHelper.GetMapArgs(state),
|
||||
videoArguments,
|
||||
threads,
|
||||
GetAudioArguments(state),
|
||||
|
@ -76,7 +77,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", 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";
|
||||
}
|
||||
|
@ -94,10 +95,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
// Add resolution params, if specified
|
||||
if (!hasGraphicalSubs)
|
||||
{
|
||||
args += await GetOutputSizeParam(state, videoCodec).ConfigureAwait(false);
|
||||
args += EncodingHelper.GetOutputSizeParam(state, videoCodec);
|
||||
}
|
||||
|
||||
var qualityParam = GetVideoQualityParam(state, videoCodec);
|
||||
var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, GetEncodingOptions(), "superfast");
|
||||
|
||||
if (!string.IsNullOrEmpty(qualityParam))
|
||||
{
|
||||
|
@ -107,7 +108,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
// This is for internal graphical subs
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
args += await GetGraphicalSubtitleParam(state, videoCodec).ConfigureAwait(false);
|
||||
args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec);
|
||||
}
|
||||
|
||||
return args;
|
||||
|
@ -127,7 +128,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
}
|
||||
|
||||
// Get the output codec name
|
||||
var codec = EncodingJobFactory.GetAudioEncoder(state);
|
||||
var codec = EncodingHelper.GetAudioEncoder(state);
|
||||
|
||||
var args = "-codec:a:0 " + codec;
|
||||
|
||||
|
@ -151,7 +152,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
args += " -ab " + bitrate.Value.ToString(UsCulture);
|
||||
}
|
||||
|
||||
args += " " + GetAudioFilterParam(state, false);
|
||||
args += " " + EncodingHelper.GetAudioFilterParam(state, GetEncodingOptions(), false);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
|
|
@ -50,8 +50,10 @@
|
|||
<Compile Include="Configuration\EncodingConfigurationFactory.cs" />
|
||||
<Compile Include="Encoder\AudioEncoder.cs" />
|
||||
<Compile Include="Encoder\BaseEncoder.cs" />
|
||||
<Compile Include="Encoder\EncodingHelper.cs" />
|
||||
<Compile Include="Encoder\EncodingJob.cs" />
|
||||
<Compile Include="Encoder\EncodingJobFactory.cs" />
|
||||
<Compile Include="Encoder\EncodingJobInfo.cs" />
|
||||
<Compile Include="Encoder\EncodingUtils.cs" />
|
||||
<Compile Include="Encoder\EncoderValidator.cs" />
|
||||
<Compile Include="Encoder\FontConfigLoader.cs" />
|
||||
|
|
|
@ -90,13 +90,11 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||
}
|
||||
|
||||
FetchGenres(info, tags);
|
||||
var shortOverview = FFProbeHelpers.GetDictionaryValue(tags, "description");
|
||||
var overview = FFProbeHelpers.GetDictionaryValue(tags, "synopsis");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(overview))
|
||||
{
|
||||
overview = shortOverview;
|
||||
shortOverview = null;
|
||||
overview = FFProbeHelpers.GetDictionaryValue(tags, "description");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(overview))
|
||||
{
|
||||
|
@ -108,11 +106,6 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||
info.Overview = overview;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(shortOverview))
|
||||
{
|
||||
info.ShortOverview = shortOverview;
|
||||
}
|
||||
|
||||
var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
namespace MediaBrowser.Model.Configuration
|
||||
{
|
||||
public class PathSubstitution
|
||||
{
|
||||
public string From { get; set; }
|
||||
public string To { get; set; }
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ namespace MediaBrowser.Model.Configuration
|
|||
/// <value><c>true</c> if [use HTTPS]; otherwise, <c>false</c>.</value>
|
||||
public bool EnableHttps { get; set; }
|
||||
public bool EnableSeriesPresentationUniqueKey { get; set; }
|
||||
public bool EnableLocalizedGuids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
|
||||
|
@ -163,8 +164,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
public bool SkipDeserializationForPrograms { get; set; }
|
||||
public bool SkipDeserializationForAudio { get; set; }
|
||||
|
||||
public PathSubstitution[] PathSubstitutions { get; set; }
|
||||
|
||||
public string ServerName { get; set; }
|
||||
public string WanDdns { get; set; }
|
||||
|
||||
|
@ -182,7 +181,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
public bool EnableAnonymousUsageReporting { get; set; }
|
||||
public bool EnableStandaloneMusicKeys { get; set; }
|
||||
public bool EnableLocalizedGuids { get; set; }
|
||||
public bool EnableFolderView { get; set; }
|
||||
public bool EnableGroupingIntoCollections { get; set; }
|
||||
public bool DisplaySpecialsWithinSeasons { get; set; }
|
||||
|
@ -192,7 +190,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
public string[] Migrations { get; set; }
|
||||
public bool EnableChannelView { get; set; }
|
||||
public bool EnableExternalContentInSuggestions { get; set; }
|
||||
public bool EnableSimpleArtistDetection { get; set; }
|
||||
|
||||
public int ImageExtractionTimeoutMs { get; set; }
|
||||
/// <summary>
|
||||
|
@ -204,8 +201,8 @@ namespace MediaBrowser.Model.Configuration
|
|||
CodecsUsed = new string[] { };
|
||||
Migrations = new string[] { };
|
||||
ImageExtractionTimeoutMs = 0;
|
||||
|
||||
EnableLocalizedGuids = true;
|
||||
|
||||
DisplaySpecialsWithinSeasons = true;
|
||||
EnableExternalContentInSuggestions = true;
|
||||
|
||||
|
@ -231,7 +228,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
LibraryMonitorDelay = 60;
|
||||
|
||||
PathSubstitutions = new PathSubstitution[] { };
|
||||
ContentTypes = new NameValuePair[] { };
|
||||
|
||||
PreferredMetadataLanguage = "en";
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
public interface ITranscoderSupport
|
||||
{
|
||||
bool CanEncodeToAudioCodec(string codec);
|
||||
bool CanEncodeToSubtitleCodec(string codec);
|
||||
}
|
||||
|
||||
public class FullTranscoderSupport : ITranscoderSupport
|
||||
|
@ -11,5 +12,9 @@
|
|||
{
|
||||
return true;
|
||||
}
|
||||
public bool CanEncodeToSubtitleCodec(string codec)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -435,7 +435,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
if (subtitleStream != null)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value);
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, null, null);
|
||||
|
||||
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
|
||||
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
||||
|
@ -465,10 +465,11 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
if (subtitleStream != null)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode);
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, transcodingProfile.Protocol, transcodingProfile.Container);
|
||||
|
||||
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
|
||||
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
||||
playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format };
|
||||
}
|
||||
|
||||
playlistItem.PlayMethod = PlayMethod.Transcode;
|
||||
|
@ -874,7 +875,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
if (subtitleStream != null)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, playMethod);
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, playMethod, null, null);
|
||||
|
||||
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
|
||||
{
|
||||
|
@ -886,11 +887,11 @@ namespace MediaBrowser.Model.Dlna
|
|||
return IsAudioEligibleForDirectPlay(item, maxBitrate);
|
||||
}
|
||||
|
||||
public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod)
|
||||
public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer)
|
||||
{
|
||||
if (playMethod != PlayMethod.Transcode && !subtitleStream.IsExternal)
|
||||
if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || !string.Equals(transcodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// Look for supported embedded subs
|
||||
// Look for supported embedded subs of the same format
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
{
|
||||
if (!profile.SupportsLanguage(subtitleStream.Language))
|
||||
|
@ -903,11 +904,40 @@ namespace MediaBrowser.Model.Dlna
|
|||
continue;
|
||||
}
|
||||
|
||||
if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(subtitleStream, profile, transcodingSubProtocol, transcodingContainer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format) && StringHelper.EqualsIgnoreCase(profile.Format, subtitleStream.Codec))
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for supported embedded subs of a convertible format
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
{
|
||||
if (!profile.SupportsLanguage(subtitleStream.Language))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile.Method != SubtitleDeliveryMethod.Embed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(subtitleStream, profile, transcodingSubProtocol, transcodingContainer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsSubtitleConversionTo(profile.Format))
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for an external or hls profile that matches the stream type (text/graphical) and doesn't require conversion
|
||||
|
@ -918,6 +948,28 @@ namespace MediaBrowser.Model.Dlna
|
|||
};
|
||||
}
|
||||
|
||||
private static bool IsSubtitleEmbedSupported(MediaStream subtitleStream, SubtitleProfile subtitleProfile, string transcodingSubProtocol, string transcodingContainer)
|
||||
{
|
||||
if (string.Equals(transcodingContainer, "ts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(transcodingContainer, "mpegts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(transcodingContainer, "mp4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(transcodingContainer, "mkv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static SubtitleProfile GetExternalSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, bool allowConversion)
|
||||
{
|
||||
foreach (SubtitleProfile profile in subtitleProfiles)
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
public StreamInfo()
|
||||
{
|
||||
AudioCodecs = new string[] { };
|
||||
SubtitleCodecs = new string[] { };
|
||||
}
|
||||
|
||||
public string ItemId { get; set; }
|
||||
|
@ -74,6 +75,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
|
||||
public string[] SubtitleCodecs { get; set; }
|
||||
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
|
||||
public string SubtitleFormat { get; set; }
|
||||
|
||||
|
@ -268,6 +270,12 @@ namespace MediaBrowser.Model.Dlna
|
|||
list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty));
|
||||
list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString().ToLower()));
|
||||
|
||||
string subtitleCodecs = item.SubtitleCodecs.Length == 0 ?
|
||||
string.Empty :
|
||||
string.Join(",", item.SubtitleCodecs);
|
||||
|
||||
list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -354,7 +362,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, PlayMethod);
|
||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, PlayMethod, SubProtocol, Container);
|
||||
SubtitleStreamInfo info = new SubtitleStreamInfo
|
||||
{
|
||||
IsForced = stream.IsForced,
|
||||
|
|
|
@ -211,12 +211,6 @@ namespace MediaBrowser.Model.Dto
|
|||
/// <value>The overview.</value>
|
||||
public string Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the short overview.
|
||||
/// </summary>
|
||||
/// <value>The short overview.</value>
|
||||
public string ShortOverview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the taglines.
|
||||
/// </summary>
|
||||
|
|
|
@ -311,29 +311,31 @@ namespace MediaBrowser.Model.Entities
|
|||
!StringHelper.EqualsIgnoreCase(codec, "dvb_subtitle");
|
||||
}
|
||||
|
||||
public bool SupportsSubtitleConversionTo(string codec)
|
||||
public bool SupportsSubtitleConversionTo(string toCodec)
|
||||
{
|
||||
if (!IsTextSubtitleStream)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fromCodec = Codec;
|
||||
|
||||
// Can't convert from this
|
||||
if (StringHelper.EqualsIgnoreCase(Codec, "ass"))
|
||||
if (StringHelper.EqualsIgnoreCase(fromCodec, "ass"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (StringHelper.EqualsIgnoreCase(Codec, "ssa"))
|
||||
if (StringHelper.EqualsIgnoreCase(fromCodec, "ssa"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't convert to this
|
||||
if (StringHelper.EqualsIgnoreCase(codec, "ass"))
|
||||
if (StringHelper.EqualsIgnoreCase(toCodec, "ass"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (StringHelper.EqualsIgnoreCase(codec, "ssa"))
|
||||
if (StringHelper.EqualsIgnoreCase(toCodec, "ssa"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -96,17 +96,5 @@ namespace MediaBrowser.Model.LiveTv
|
|||
EnableAllTuners = true;
|
||||
ChannelMappings = new NameValuePair[] {};
|
||||
}
|
||||
|
||||
public string GetMappedChannel(string channelNumber)
|
||||
{
|
||||
foreach (NameValuePair mapping in ChannelMappings)
|
||||
{
|
||||
if (StringHelper.EqualsIgnoreCase(mapping.Name, channelNumber))
|
||||
{
|
||||
return mapping.Value;
|
||||
}
|
||||
}
|
||||
return channelNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,7 +198,6 @@
|
|||
<Compile Include="Notifications\NotificationOption.cs" />
|
||||
<Compile Include="Notifications\NotificationOptions.cs" />
|
||||
<Compile Include="Notifications\NotificationType.cs" />
|
||||
<Compile Include="Configuration\PathSubstitution.cs" />
|
||||
<Compile Include="Notifications\SendToUserType.cs" />
|
||||
<Compile Include="Configuration\ServerConfiguration.cs" />
|
||||
<Compile Include="Playlists\PlaylistCreationRequest.cs" />
|
||||
|
|
|
@ -51,11 +51,6 @@ namespace MediaBrowser.Model.MediaInfo
|
|||
/// </summary>
|
||||
/// <value>The overview.</value>
|
||||
public string Overview { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the short overview.
|
||||
/// </summary>
|
||||
/// <value>The short overview.</value>
|
||||
public string ShortOverview { get; set; }
|
||||
|
||||
public MediaInfo()
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Model.Net
|
||||
|
@ -22,6 +23,6 @@ namespace MediaBrowser.Model.Net
|
|||
/// <summary>
|
||||
/// Sends a UDP message to a particular end point (uni or multicast).
|
||||
/// </summary>
|
||||
Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint);
|
||||
Task SendAsync(byte[] buffer, int bytes, IpEndPointInfo endPoint, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -188,11 +188,6 @@
|
|||
/// </summary>
|
||||
Settings,
|
||||
|
||||
/// <summary>
|
||||
/// The short overview
|
||||
/// </summary>
|
||||
ShortOverview,
|
||||
|
||||
/// <summary>
|
||||
/// The screenshot image tags
|
||||
/// </summary>
|
||||
|
|
|
@ -47,5 +47,7 @@ namespace MediaBrowser.Model.Session
|
|||
/// </summary>
|
||||
/// <value><c>true</c> if failed; otherwise, <c>false</c>.</value>
|
||||
public bool Failed { get; set; }
|
||||
|
||||
public string NextMediaType { get; set; }
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace MediaBrowser.Providers.Manager
|
||||
|
@ -234,6 +233,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
return retryPath;
|
||||
}
|
||||
|
||||
private SemaphoreSlim _imageSaveSemaphore = new SemaphoreSlim(1, 1);
|
||||
/// <summary>
|
||||
/// Saves the image to location.
|
||||
/// </summary>
|
||||
|
@ -247,11 +247,13 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
var parentFolder = Path.GetDirectoryName(path);
|
||||
|
||||
_libraryMonitor.ReportFileSystemChangeBeginning(path);
|
||||
_libraryMonitor.ReportFileSystemChangeBeginning(parentFolder);
|
||||
await _imageSaveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
_libraryMonitor.ReportFileSystemChangeBeginning(path);
|
||||
_libraryMonitor.ReportFileSystemChangeBeginning(parentFolder);
|
||||
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
// If the file is currently hidden we'll have to remove that or the save will fail
|
||||
|
@ -283,6 +285,8 @@ namespace MediaBrowser.Providers.Manager
|
|||
}
|
||||
finally
|
||||
{
|
||||
_imageSaveSemaphore.Release();
|
||||
|
||||
_libraryMonitor.ReportFileSystemChangeComplete(path, false);
|
||||
_libraryMonitor.ReportFileSystemChangeComplete(parentFolder, false);
|
||||
}
|
||||
|
|
|
@ -253,7 +253,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
{
|
||||
try
|
||||
{
|
||||
await ProviderManager.SaveImage(personEntity, imageUrl, null, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
|
||||
await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -123,12 +123,11 @@ namespace MediaBrowser.Providers.Manager
|
|||
return Task.FromResult(ItemUpdateType.None);
|
||||
}
|
||||
|
||||
public async Task SaveImage(IHasImages item, string url, SemaphoreSlim resourcePool, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
||||
public async Task SaveImage(IHasImages item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
ResourcePool = resourcePool,
|
||||
Url = url,
|
||||
BufferContent = false
|
||||
|
||||
|
|
|
@ -200,7 +200,6 @@ namespace MediaBrowser.Providers.Manager
|
|||
MergeCriticRating(source, target, lockedFields, replaceData);
|
||||
MergeAwards(source, target, lockedFields, replaceData);
|
||||
MergeTrailers(source, target, lockedFields, replaceData);
|
||||
MergeShortOverview(source, target, lockedFields, replaceData);
|
||||
|
||||
if (mergeMetadataSettings)
|
||||
{
|
||||
|
@ -234,14 +233,6 @@ namespace MediaBrowser.Providers.Manager
|
|||
}
|
||||
}
|
||||
|
||||
private static void MergeShortOverview(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
||||
{
|
||||
if (replaceData || string.IsNullOrEmpty(target.ShortOverview))
|
||||
{
|
||||
target.ShortOverview = source.ShortOverview;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MergeAlbumArtist(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
|
||||
{
|
||||
var sourceHasAlbumArtist = source as IHasAlbumArtist;
|
||||
|
|
|
@ -440,11 +440,6 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
video.Overview = data.Overview;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(video.ShortOverview) || isFullRefresh)
|
||||
{
|
||||
video.ShortOverview = data.ShortOverview;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FetchPeople(Video video, Model.MediaInfo.MediaInfo data, MetadataRefreshOptions options)
|
||||
|
|
|
@ -124,7 +124,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
{
|
||||
get
|
||||
{
|
||||
return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami", ".txt" };
|
||||
return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami" };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -271,7 +271,12 @@ namespace MediaBrowser.Providers.Movies
|
|||
//and the rest from crew
|
||||
if (movieData.casts != null && movieData.casts.crew != null)
|
||||
{
|
||||
var keepTypes = new[] { PersonType.Director, PersonType.Writer, PersonType.Producer };
|
||||
var keepTypes = new[]
|
||||
{
|
||||
PersonType.Director,
|
||||
PersonType.Writer,
|
||||
//PersonType.Producer
|
||||
};
|
||||
|
||||
foreach (var person in movieData.casts.crew)
|
||||
{
|
||||
|
|
|
@ -127,14 +127,7 @@ namespace MediaBrowser.Providers.Omdb
|
|||
}
|
||||
}
|
||||
|
||||
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
ResourcePool = OmdbProvider.ResourcePool,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = true
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
using (var stream = await OmdbProvider.GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var resultList = new List<SearchResult>();
|
||||
|
||||
|
|
|
@ -294,16 +294,9 @@ namespace MediaBrowser.Providers.Omdb
|
|||
}
|
||||
}
|
||||
|
||||
var url = string.Format("https://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
|
||||
var url = string.Format("https://www.omdbapi.com/?i={0}&plot=full&tomatoes=true&r=json", imdbParam);
|
||||
|
||||
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
ResourcePool = ResourcePool,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = true
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
using (var stream = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var rootObject = _jsonSerializer.DeserializeFromStream<RootObject>(stream);
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
@ -337,14 +330,7 @@ namespace MediaBrowser.Providers.Omdb
|
|||
|
||||
var url = string.Format("https://www.omdbapi.com/?i={0}&season={1}&detail=full", imdbParam, seasonId);
|
||||
|
||||
using (var stream = await _httpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
ResourcePool = ResourcePool,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = true
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
using (var stream = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
var rootObject = _jsonSerializer.DeserializeFromStream<SeasonRootObject>(stream);
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
@ -354,6 +340,17 @@ namespace MediaBrowser.Providers.Omdb
|
|||
return path;
|
||||
}
|
||||
|
||||
public static Task<Stream> GetOmdbResponse(IHttpClient httpClient, string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return httpClient.Get(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
ResourcePool = ResourcePool,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = true
|
||||
});
|
||||
}
|
||||
|
||||
internal string GetDataFilePath(string imdbId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(imdbId))
|
||||
|
@ -421,7 +418,7 @@ namespace MediaBrowser.Providers.Omdb
|
|||
}
|
||||
|
||||
// Imdb plots are usually pretty short
|
||||
item.ShortOverview = result.Plot;
|
||||
item.Overview = result.Plot;
|
||||
|
||||
//if (!string.IsNullOrWhiteSpace(result.Director))
|
||||
//{
|
||||
|
|
|
@ -167,7 +167,12 @@ namespace MediaBrowser.Providers.TV
|
|||
//and the rest from crew
|
||||
if (credits.crew != null)
|
||||
{
|
||||
var keepTypes = new[] { PersonType.Director, PersonType.Writer, PersonType.Producer };
|
||||
var keepTypes = new[]
|
||||
{
|
||||
PersonType.Director,
|
||||
//PersonType.Writer,
|
||||
//PersonType.Producer
|
||||
};
|
||||
|
||||
foreach (var person in credits.crew)
|
||||
{
|
||||
|
|
|
@ -76,6 +76,11 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
public string V { get; set; }
|
||||
}
|
||||
|
||||
[Route("/favicon.ico", "GET")]
|
||||
public class GetFavIcon
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class DashboardService
|
||||
/// </summary>
|
||||
|
@ -134,6 +139,14 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
_memoryStreamFactory = memoryStreamFactory;
|
||||
}
|
||||
|
||||
public object Get(GetFavIcon request)
|
||||
{
|
||||
return Get(new GetDashboardResource
|
||||
{
|
||||
ResourceName = "favicon.ico"
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
|
@ -323,48 +336,11 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
return new PackageCreator(_fileSystem, _logger, _serverConfigurationManager, _memoryStreamFactory);
|
||||
}
|
||||
|
||||
private List<string> GetDeployIgnoreExtensions()
|
||||
{
|
||||
var list = new List<string>();
|
||||
|
||||
list.Add(".log");
|
||||
list.Add(".txt");
|
||||
list.Add(".map");
|
||||
list.Add(".md");
|
||||
list.Add(".gz");
|
||||
list.Add(".bat");
|
||||
list.Add(".sh");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<Tuple<string, bool>> GetDeployIgnoreFilenames()
|
||||
{
|
||||
var list = new List<Tuple<string, bool>>();
|
||||
|
||||
list.Add(new Tuple<string, bool>("copying", true));
|
||||
list.Add(new Tuple<string, bool>("license", true));
|
||||
list.Add(new Tuple<string, bool>("license-mit", true));
|
||||
list.Add(new Tuple<string, bool>("gitignore", false));
|
||||
list.Add(new Tuple<string, bool>("npmignore", false));
|
||||
list.Add(new Tuple<string, bool>("jshintrc", false));
|
||||
list.Add(new Tuple<string, bool>("gruntfile", false));
|
||||
list.Add(new Tuple<string, bool>("bowerrc", false));
|
||||
list.Add(new Tuple<string, bool>("jscsrc", false));
|
||||
list.Add(new Tuple<string, bool>("hero.svg", false));
|
||||
list.Add(new Tuple<string, bool>("travis.yml", false));
|
||||
list.Add(new Tuple<string, bool>("build.js", false));
|
||||
list.Add(new Tuple<string, bool>("editorconfig", false));
|
||||
list.Add(new Tuple<string, bool>("gitattributes", false));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public async Task<object> Get(GetDashboardPackage request)
|
||||
{
|
||||
var mode = request.Mode;
|
||||
|
||||
var path = string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ?
|
||||
var path = !string.IsNullOrWhiteSpace(mode) ?
|
||||
Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump")
|
||||
: "C:\\dev\\emby-web-mobile\\src";
|
||||
|
||||
|
@ -388,101 +364,19 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
// Try to trim the output size a bit
|
||||
var bowerPath = Path.Combine(path, "bower_components");
|
||||
|
||||
foreach (var ext in GetDeployIgnoreExtensions())
|
||||
{
|
||||
DeleteFilesByExtension(bowerPath, ext);
|
||||
}
|
||||
|
||||
DeleteFilesByExtension(bowerPath, ".json", "strings\\");
|
||||
|
||||
foreach (var ignore in GetDeployIgnoreFilenames())
|
||||
{
|
||||
DeleteFilesByName(bowerPath, ignore.Item1, ignore.Item2);
|
||||
}
|
||||
|
||||
DeleteFoldersByName(bowerPath, "demo");
|
||||
DeleteFoldersByName(bowerPath, "test");
|
||||
DeleteFoldersByName(bowerPath, "guides");
|
||||
DeleteFoldersByName(bowerPath, "grunt");
|
||||
DeleteFoldersByName(bowerPath, "rollups");
|
||||
|
||||
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "montserrat");
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "opensans");
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto");
|
||||
}
|
||||
|
||||
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
|
||||
|
||||
DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
|
||||
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "jquery"), "src");
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
|
||||
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
|
||||
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
|
||||
//DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
|
||||
|
||||
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.IsNullOrWhiteSpace(mode))
|
||||
{
|
||||
// Delete things that are unneeded in an attempt to keep the output as trim as possible
|
||||
|
||||
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto");
|
||||
_fileSystem.DeleteDirectory(Path.Combine(path, "css", "images", "tour"), true);
|
||||
}
|
||||
|
||||
await DumpHtml(creator.DashboardUIPath, path, mode, culture, appVersion);
|
||||
|
||||
await DumpFile("css/all.css", Path.Combine(path, "css", "all.css"), mode, culture, appVersion).ConfigureAwait(false);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private void DeleteCryptoFiles(string path)
|
||||
{
|
||||
var files = _fileSystem.GetFiles(path)
|
||||
.ToList();
|
||||
|
||||
var keepFiles = new[] { "core-min.js", "md5-min.js", "sha1-min.js" };
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!keepFiles.Contains(file.Name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
_fileSystem.DeleteFile(file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteFilesByExtension(string path, string extension, string exclude = null)
|
||||
{
|
||||
var files = _fileSystem.GetFiles(path, true)
|
||||
.Where(i => string.Equals(i.Extension, extension, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(exclude))
|
||||
{
|
||||
if (file.FullName.IndexOf(exclude, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_fileSystem.DeleteFile(file.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteFilesByName(string path, string name, bool exact = false)
|
||||
{
|
||||
var files = _fileSystem.GetFiles(path, true)
|
||||
.Where(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase) || (!exact && i.Name.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1))
|
||||
.ToList();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
_fileSystem.DeleteFile(file.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteFoldersByName(string path, string name)
|
||||
{
|
||||
var directories = _fileSystem.GetDirectories(path, true)
|
||||
|
|
|
@ -33,16 +33,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
string localizationCulture,
|
||||
string appVersion)
|
||||
{
|
||||
Stream resourceStream;
|
||||
|
||||
if (path.Equals("css/all.css", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
resourceStream = await GetAllCss().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceStream = GetRawResourceStream(path);
|
||||
}
|
||||
var resourceStream = GetRawResourceStream(path);
|
||||
|
||||
if (resourceStream != null)
|
||||
{
|
||||
|
@ -151,7 +142,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
|
||||
html = Encoding.UTF8.GetString(originalBytes, 0, originalBytes.Length);
|
||||
|
||||
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.IsNullOrWhiteSpace(mode))
|
||||
{
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(path) && !string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -160,10 +151,13 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
if (index != -1)
|
||||
{
|
||||
html = html.Substring(index);
|
||||
|
||||
html = html.Substring(html.IndexOf('>') + 1);
|
||||
|
||||
index = html.IndexOf("</body>", StringComparison.OrdinalIgnoreCase);
|
||||
if (index != -1)
|
||||
{
|
||||
html = html.Substring(0, index+7);
|
||||
html = html.Substring(0, index);
|
||||
}
|
||||
}
|
||||
var mainFile = _fileSystem.ReadAllText(GetDashboardResourcePath("index.html"));
|
||||
|
@ -214,7 +208,8 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem: ws: wss:;\">");
|
||||
}
|
||||
|
@ -263,11 +258,14 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
/// <returns>System.String.</returns>
|
||||
private string GetCommonCss(string mode, string version)
|
||||
{
|
||||
var versionString = !string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? "?v=" + version : string.Empty;
|
||||
var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty;
|
||||
|
||||
var files = new[]
|
||||
{
|
||||
"css/all.css" + versionString
|
||||
"css/site.css" + versionString,
|
||||
"css/librarymenu.css" + versionString,
|
||||
"css/librarybrowser.css" + versionString,
|
||||
"thirdparty/paper-button-style.css" + versionString
|
||||
};
|
||||
|
||||
var tags = files.Select(s => string.Format("<link rel=\"stylesheet\" href=\"{0}\" async />", s)).ToArray();
|
||||
|
@ -291,14 +289,14 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
builder.AppendFormat("window.appMode='{0}';", mode);
|
||||
}
|
||||
|
||||
if (!string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.IsNullOrWhiteSpace(mode))
|
||||
{
|
||||
builder.AppendFormat("window.dashboardVersion='{0}';", version);
|
||||
}
|
||||
|
||||
builder.Append("</script>");
|
||||
|
||||
var versionString = !string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? "?v=" + version : string.Empty;
|
||||
var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty;
|
||||
|
||||
var files = new List<string>();
|
||||
|
||||
|
@ -318,48 +316,6 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all CSS.
|
||||
/// </summary>
|
||||
/// <returns>Task{Stream}.</returns>
|
||||
private async Task<Stream> GetAllCss()
|
||||
{
|
||||
var memoryStream = _memoryStreamFactory.CreateNew();
|
||||
|
||||
var files = new[]
|
||||
{
|
||||
"css/site.css",
|
||||
"css/librarymenu.css",
|
||||
"css/librarybrowser.css",
|
||||
"thirdparty/paper-button-style.css"
|
||||
};
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var path = GetDashboardResourcePath(file);
|
||||
|
||||
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true))
|
||||
{
|
||||
using (var streamReader = new StreamReader(fs))
|
||||
{
|
||||
var text = await streamReader.ReadToEndAsync().ConfigureAwait(false);
|
||||
builder.Append(text);
|
||||
builder.Append(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var css = builder.ToString();
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(css);
|
||||
memoryStream.Write(bytes, 0, bytes.Length);
|
||||
|
||||
memoryStream.Position = 0;
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw resource stream.
|
||||
/// </summary>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -394,17 +394,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
|||
break;
|
||||
}
|
||||
|
||||
case "outline":
|
||||
{
|
||||
var val = reader.ReadElementContentAsString();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(val))
|
||||
{
|
||||
item.ShortOverview = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "biography":
|
||||
case "plot":
|
||||
case "review":
|
||||
|
|
|
@ -85,15 +85,15 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
}
|
||||
}
|
||||
|
||||
protected override List<string> GetTagsUsed()
|
||||
protected override List<string> GetTagsUsed(IHasMetadata item)
|
||||
{
|
||||
var list = new List<string>
|
||||
var list = base.GetTagsUsed(item);
|
||||
list.AddRange(new string[]
|
||||
{
|
||||
"track",
|
||||
"artist",
|
||||
"albumartist"
|
||||
};
|
||||
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
|
@ -79,14 +79,14 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
}
|
||||
}
|
||||
|
||||
protected override List<string> GetTagsUsed()
|
||||
protected override List<string> GetTagsUsed(IHasMetadata item)
|
||||
{
|
||||
var list = new List<string>
|
||||
var list = base.GetTagsUsed(item);
|
||||
list.AddRange(new string[]
|
||||
{
|
||||
"album",
|
||||
"disbanded"
|
||||
};
|
||||
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
|
@ -183,9 +183,18 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
/// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
|
||||
public abstract bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType);
|
||||
|
||||
protected virtual List<string> GetTagsUsed()
|
||||
protected virtual List<string> GetTagsUsed(IHasMetadata item)
|
||||
{
|
||||
return new List<string>();
|
||||
var list = new List<string>();
|
||||
foreach (var providerKey in item.ProviderIds.Keys)
|
||||
{
|
||||
var providerIdTagName = GetTagForProviderKey(providerKey);
|
||||
if (!CommonTags.ContainsKey(providerIdTagName))
|
||||
{
|
||||
list.Add(providerIdTagName);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void Save(IHasMetadata item, CancellationToken cancellationToken)
|
||||
|
@ -271,7 +280,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
AddMediaInfo(hasMediaSources, writer);
|
||||
}
|
||||
|
||||
var tagsUsed = GetTagsUsed();
|
||||
var tagsUsed = GetTagsUsed(item);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -457,7 +466,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
|
||||
if (item is Video)
|
||||
{
|
||||
var outline = (item.ShortOverview ?? string.Empty)
|
||||
var outline = (item.Tagline ?? string.Empty)
|
||||
.StripHtml()
|
||||
.Replace(""", "'");
|
||||
|
||||
|
@ -834,7 +843,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
var providerId = item.ProviderIds[providerKey];
|
||||
if (!string.IsNullOrEmpty(providerId) && !writtenProviderIds.Contains(providerKey))
|
||||
{
|
||||
writer.WriteElementString(providerKey.ToLower() + "id", providerId);
|
||||
writer.WriteElementString(GetTagForProviderKey(providerKey), providerId);
|
||||
writtenProviderIds.Add(providerKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1093,5 +1103,10 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTagForProviderKey(string providerKey)
|
||||
{
|
||||
return providerKey.ToLower() + "id";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,9 +108,10 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
protected override List<string> GetTagsUsed()
|
||||
protected override List<string> GetTagsUsed(IHasMetadata item)
|
||||
{
|
||||
var list = new List<string>
|
||||
var list = base.GetTagsUsed(item);
|
||||
list.AddRange(new string[]
|
||||
{
|
||||
"aired",
|
||||
"season",
|
||||
|
@ -124,8 +125,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
"absolute_number",
|
||||
"displayseason",
|
||||
"displayepisode"
|
||||
};
|
||||
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
|
@ -113,16 +113,16 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
}
|
||||
}
|
||||
|
||||
protected override List<string> GetTagsUsed()
|
||||
protected override List<string> GetTagsUsed(IHasMetadata item)
|
||||
{
|
||||
var list = new List<string>
|
||||
var list = base.GetTagsUsed(item);
|
||||
list.AddRange(new string[]
|
||||
{
|
||||
"album",
|
||||
"artist",
|
||||
"set",
|
||||
"id"
|
||||
};
|
||||
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,11 +51,13 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
}
|
||||
}
|
||||
|
||||
protected override List<string> GetTagsUsed()
|
||||
protected override List<string> GetTagsUsed(IHasMetadata item)
|
||||
{
|
||||
var list = base.GetTagsUsed();
|
||||
|
||||
list.Add("seasonnumber");
|
||||
var list = base.GetTagsUsed(item);
|
||||
list.AddRange(new string[]
|
||||
{
|
||||
"seasonnumber"
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
|
|
@ -90,9 +90,10 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
}
|
||||
}
|
||||
|
||||
protected override List<string> GetTagsUsed()
|
||||
protected override List<string> GetTagsUsed(IHasMetadata item)
|
||||
{
|
||||
var list = new List<string>
|
||||
var list = base.GetTagsUsed(item);
|
||||
list.AddRange(new string[]
|
||||
{
|
||||
"id",
|
||||
"episodeguide",
|
||||
|
@ -102,8 +103,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||
"airs_time",
|
||||
"airs_dayofweek",
|
||||
"animeseriesindex"
|
||||
};
|
||||
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
||||
|
@ -44,15 +45,12 @@ namespace Rssdp.Infrastructure
|
|||
/// <summary>
|
||||
/// Sends a message to a particular address (uni or multicast) and port.
|
||||
/// </summary>
|
||||
/// <param name="messageData">A byte array containing the data to send.</param>
|
||||
/// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
|
||||
/// <param name="fromLocalIpAddress">A <see cref="IpEndPointInfo"/> The local ip address to send from, or .Any if sending from all available</param>
|
||||
Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress);
|
||||
Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the SSDP multicast address and port.
|
||||
/// </summary>
|
||||
Task SendMulticastMessage(string message);
|
||||
Task SendMulticastMessage(string message, CancellationToken cancellationToken);
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue