aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Common.Implementations/Net/UdpSocket.cs12
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs5
-rw-r--r--Emby.Dlna/Ssdp/DeviceDiscovery.cs14
-rw-r--r--Emby.Server.Core/ApplicationHost.cs70
-rw-r--r--Emby.Server.Core/Configuration/ServerConfigurationManager.cs12
-rw-r--r--Emby.Server.Core/IO/LibraryMonitor.cs21
-rw-r--r--Emby.Server.Implementations/Connect/ConnectManager.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs13
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs5
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj4
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs31
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs21
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs6
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs165
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs286
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs55
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs34
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs54
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs37
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs16
-rw-r--r--Emby.Server.Implementations/Migrations/GuideMigration.cs2
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs7
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs3
-rw-r--r--Emby.Server.Implementations/packages.config2
-rw-r--r--MediaBrowser.Api/BasePeriodicWebSocketListener.cs29
-rw-r--r--MediaBrowser.Api/Images/RemoteImageService.cs2
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs2
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs8
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj4
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs1674
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs14
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs27
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs15
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs8
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs51
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs153
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs108
-rw-r--r--MediaBrowser.Api/SearchService.cs14
-rw-r--r--MediaBrowser.Api/StartupWizardService.cs3
-rw-r--r--MediaBrowser.Api/UserLibrary/PlaystateService.cs6
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs6
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs1
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs6
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/ChannelInfo.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/IListingsProvider.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerInfo.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs198
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs3
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs12
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs798
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs1681
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs83
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs618
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs118
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs26
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs23
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs9
-rw-r--r--MediaBrowser.Model/Configuration/PathSubstitution.cs8
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs8
-rw-r--r--MediaBrowser.Model/Dlna/ITranscoderSupport.cs5
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs64
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs10
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs6
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs12
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvOptions.cs12
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
-rw-r--r--MediaBrowser.Model/MediaInfo/MediaInfo.cs5
-rw-r--r--MediaBrowser.Model/Net/IUdpSocket.cs3
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs5
-rw-r--r--MediaBrowser.Model/Session/PlaybackStopInfo.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs10
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs3
-rw-r--r--MediaBrowser.Providers/Manager/ProviderUtils.cs9
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs5
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs2
-rw-r--r--MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs7
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbItemProvider.cs9
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbProvider.cs33
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs7
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs138
-rw-r--r--MediaBrowser.WebDashboard/Api/PackageCreator.cs74
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj1430
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs11
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs14
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs12
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs25
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs32
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs16
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs10
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs24
-rw-r--r--RSSDP/ISsdpCommunicationsServer.cs8
-rw-r--r--RSSDP/ISsdpDeviceLocator.cs5
-rw-r--r--RSSDP/SsdpCommunicationsServer.cs28
-rw-r--r--RSSDP/SsdpDeviceLocatorBase.cs48
-rw-r--r--RSSDP/SsdpDevicePublisherBase.cs63
104 files changed, 2921 insertions, 5859 deletions
diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs
index b2af9d162..e4b0c7d6d 100644
--- a/Emby.Common.Implementations/Net/UdpSocket.cs
+++ b/Emby.Common.Implementations/Net/UdpSocket.cs
@@ -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);
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index ca4c3b912..d048dcde1 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -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);
diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
index 3f0e070ca..7852669c9 100644
--- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
@@ -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);
diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs
index b9412d0fb..a3c228a58 100644
--- a/Emby.Server.Core/ApplicationHost.cs
+++ b/Emby.Server.Core/ApplicationHost.cs
@@ -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;
}
diff --git a/Emby.Server.Core/Configuration/ServerConfigurationManager.cs b/Emby.Server.Core/Configuration/ServerConfigurationManager.cs
index f98c096ca..eb3d8b9f9 100644
--- a/Emby.Server.Core/Configuration/ServerConfigurationManager.cs
+++ b/Emby.Server.Core/Configuration/ServerConfigurationManager.cs
@@ -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>
diff --git a/Emby.Server.Core/IO/LibraryMonitor.cs b/Emby.Server.Core/IO/LibraryMonitor.cs
index f0ecb9d89..baa705111 100644
--- a/Emby.Server.Core/IO/LibraryMonitor.cs
+++ b/Emby.Server.Core/IO/LibraryMonitor.cs
@@ -283,18 +283,24 @@ namespace Emby.Server.Core.IO
/// <param name="path">The path.</param>
private void StartWatchingPath(string path)
{
+ if (!_fileSystem.DirectoryExists(path))
+ {
+ // Seeing a crash in the mono runtime due to an exception being thrown on a different thread
+ Logger.Info("Skipping realtime monitor for {0} because the path does not exist", path);
+ 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
{
- if (!_fileSystem.DirectoryExists(path))
- {
- // Seeing a crash in the mono runtime due to an exception being thrown on a different thread
- Logger.Info("Skipping realtime monitor for {0} because the path does not exist", path);
- return;
- }
-
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();
}
diff --git a/Emby.Server.Implementations/Connect/ConnectManager.cs b/Emby.Server.Implementations/Connect/ConnectManager.cs
index 7e6755f6a..8aac2a8c4 100644
--- a/Emby.Server.Implementations/Connect/ConnectManager.cs
+++ b/Emby.Server.Implementations/Connect/ConnectManager.cs
@@ -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)
{
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index c158f2e51..4a4a1a6bf 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -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))
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index f866c34de..9c50ad5da 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -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;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index e3cd96894..195d24b21 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -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">
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index c59a22884..de3a1664e 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -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);
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 2971405b9..6cf201990 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -74,20 +74,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return new MusicArtist();
}
- if (_config.Configuration.EnableSimpleArtistDetection)
- {
- return null;
- }
+ 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;
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index 2a4cc49b7..cf37366fb 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -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;
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index e6c88aa1a..a47a3322e 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -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
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 5e0b4ff34..89ef87c8e 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -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))
- {
- _logger.Debug("Found mapped channel on provider {0}. Tuner channel number: {1}, Mapped channel number: {2}", provider.Item1.Name, channelNumber, mappedChannelNumber);
- channelNumber = mappedChannelNumber;
- }
+ var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
- var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken)
- .ConfigureAwait(false);
+ List<ProgramInfo> programs;
- var list = programs.ToList();
+ if (epgChannel == null)
+ {
+ programs = new List<ProgramInfo>();
+ }
+ else
+ {
+ programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
+ .ConfigureAwait(false)).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);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index beb08cc25..1f739b3c6 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -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)
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 84f802d76..17de93a3c 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -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;
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 46b914232..1b7a1c8c6 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -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,183 +203,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return 0;
}
- private readonly object _channelCacheLock = new object();
- private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName)
+ private string GetChannelNumber(ScheduleDirect.Map map)
{
- 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;
- }
- }
+ var channelNumber = map.logicalChannelNumber;
- 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)
+ if (string.IsNullOrWhiteSpace(channelNumber))
{
- Dictionary<string, ScheduleDirect.Station> cache;
- if (_channelPairingCache.TryGetValue(listingsId, out cache))
- {
- cache.Clear();
- }
+ channelNumber = map.channel;
}
- }
-
- private int GetChannelPairCacheCount(string listingsId)
- {
- lock (_channelCacheLock)
+ if (string.IsNullOrWhiteSpace(channelNumber))
{
- Dictionary<string, ScheduleDirect.Station> cache;
- if (_channelPairingCache.TryGetValue(listingsId, out cache))
- {
- return cache.Count;
- }
-
- return 0;
+ channelNumber = map.atscMajor + "." + map.atscMinor;
}
- }
+ channelNumber = channelNumber.TrimStart('0');
- private string NormalizeName(string value)
- {
- return value.Replace(" ", string.Empty).Replace("-", string.Empty);
+ return channelNumber;
}
- 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)
- {
- var channelNumber = map.logicalChannelNumber;
-
- if (string.IsNullOrWhiteSpace(channelNumber))
- {
- channelNumber = map.channel;
- }
- if (string.IsNullOrWhiteSpace(channelNumber))
- {
- channelNumber = map.atscMajor + "." + map.atscMinor;
- }
- 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
- });
- }
- }
-
- 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;
-
- if (string.IsNullOrWhiteSpace(channelNumber))
+ var channelNumber = GetChannelNumber(map);
+
+ var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
+ if (station == null)
{
- channelNumber = map.channel;
- }
- if (string.IsNullOrWhiteSpace(channelNumber))
- {
- channelNumber = map.atscMajor + "." + map.atscMinor;
+ station = new ScheduleDirect.Station
+ {
+ 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
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index 57307aa73..d7803f9e3 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -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
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index ff76f6bef..e59a8f93c 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -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 (!string.IsNullOrWhiteSpace(tunerChannel.Number))
+ {
+ result.Name = tunerChannel.Number + " " + result.Name;
+ }
- if (mapping != null)
+ if (string.IsNullOrWhiteSpace(result.Id))
{
- providerChannelNumber = mapping.Value;
+ result.Id = tunerChannel.Id;
}
- var providerChannel = providerChannels.FirstOrDefault(i => string.Equals(i.Number, providerChannelNumber, StringComparison.OrdinalIgnoreCase));
+ 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;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 5e191ada9..34d0dd853 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -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);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
index 7b88be19c..a7e1b3cf3 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs
@@ -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)
{
@@ -35,17 +37,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
byte[] copy = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
-
- List<QueueStream> streams = null;
- lock (_outputStreams)
+ _sharedBuffer.Enqueue(copy);
+
+ 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)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
index bd6f31906..7b48ce21a 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs
@@ -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)
{
@@ -30,10 +30,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public void Queue(byte[] bytes)
{
- if (_isActive)
- {
- _queue.Enqueue(bytes);
- }
+ _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);
diff --git a/Emby.Server.Implementations/Migrations/GuideMigration.cs b/Emby.Server.Implementations/Migrations/GuideMigration.cs
index 71286b282..99b2942dc 100644
--- a/Emby.Server.Implementations/Migrations/GuideMigration.cs
+++ b/Emby.Server.Implementations/Migrations/GuideMigration.cs
@@ -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))
{
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 6bf412525..9dfaa102a 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -144,12 +144,19 @@ 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;
}
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index 79a1e4640..691ff0699 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -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);
}
diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config
index fcce67b33..5249577e6 100644
--- a/Emby.Server.Implementations/packages.config
+++ b/Emby.Server.Implementations/packages.config
@@ -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" />
diff --git a/MediaBrowser.Api/BasePeriodicWebSocketListener.cs b/MediaBrowser.Api/BasePeriodicWebSocketListener.cs
index fe7de387f..8004d7e9b 100644
--- a/MediaBrowser.Api/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Api/BasePeriodicWebSocketListener.cs
@@ -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);
}
diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs
index eb871746d..d7ccf8f6d 100644
--- a/MediaBrowser.Api/Images/RemoteImageService.cs
+++ b/MediaBrowser.Api/Images/RemoteImageService.cs
@@ -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);
}
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 5c99d98c2..a3d43a3f9 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -276,8 +276,6 @@ namespace MediaBrowser.Api
item.Tagline = request.Taglines.FirstOrDefault();
}
- item.ShortOverview = request.ShortOverview;
-
item.Keywords = request.Keywords;
if (request.Studios != null)
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 85a66080d..04983553b 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -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(),
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index c1a7347b5..7be04d892 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -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>
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 120c00a14..d1c3de427 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -22,6 +22,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Net;
+using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Diagnostics;
namespace MediaBrowser.Api.Playback
@@ -74,6 +75,8 @@ namespace MediaBrowser.Api.Playback
public static IHttpClient HttpClient;
protected IAuthorizationContext AuthorizationContext { get; private set; }
+ protected EncodingHelper EncodingHelper { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
@@ -92,6 +95,7 @@ namespace MediaBrowser.Api.Playback
LibraryManager = libraryManager;
IsoManager = isoManager;
MediaEncoder = mediaEncoder;
+ EncodingHelper = new EncodingHelper(MediaEncoder, serverConfig, FileSystem, SubtitleEncoder);
}
/// <summary>
@@ -152,980 +156,11 @@ namespace MediaBrowser.Api.Playback
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
- /// <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(StreamRequest request)
- {
- var time = request.StartTimeTicks ?? 0;
-
- if (time > 0)
- {
- return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time));
- }
-
- return string.Empty;
- }
-
- /// <summary>
- /// Gets the map args.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected virtual string GetMapArgs(StreamState 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.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Hls)
- {
- args += " -map -0:s";
- }
- else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
- {
- args += " -map 1:0 -sn";
- }
-
- return args;
- }
-
- /// <summary>
- /// Determines which stream will be used for playback
- /// </summary>
- /// <param name="allStream">All stream.</param>
- /// <param name="desiredIndex">Index of the desired.</param>
- /// <param name="type">The type.</param>
- /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
- /// <returns>MediaStream.</returns>
- private MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
- {
- var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
-
- if (desiredIndex.HasValue)
- {
- var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
-
- if (stream != null)
- {
- return stream;
- }
- }
-
- if (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>
- /// Gets the number of threads.
- /// </summary>
- /// <returns>System.Int32.</returns>
- protected int GetNumberOfThreads(StreamState state, bool isWebm)
- {
- var threads = ApiEntryPoint.Instance.GetEncodingOptions().EncodingThreadCount;
-
- if (isWebm)
- {
- // Recommended per docs
- return Math.Max(Environment.ProcessorCount - 1, 2);
- }
-
- // Automatic
- if (threads == -1)
- {
- return 0;
- }
-
- return threads;
- }
-
- protected string GetH264Encoder(StreamState state)
- {
- 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 encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
- var hwType = encodingOptions.HardwareAccelerationType;
-
- if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- {
- return GetAvailableEncoder("h264_qsv", defaultEncoder);
- }
-
- if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
- {
- return GetAvailableEncoder("h264_nvenc", defaultEncoder);
- }
- if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
- {
- return GetAvailableEncoder("h264_omx", defaultEncoder);
- }
- if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice))
- {
- if (IsVaapiSupported(state))
- {
- return GetAvailableEncoder("h264_vaapi", defaultEncoder);
- }
- }
- }
-
- return defaultEncoder;
- }
-
- private bool IsVaapiSupported(StreamState 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;
- }
-
- private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder)
- {
- if (MediaEncoder.SupportsEncoder(preferredEncoder))
- {
- return preferredEncoder;
- }
- return defaultEncoder;
- }
-
protected virtual string GetDefaultH264Preset()
{
return "superfast";
}
- /// <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(StreamState state, string videoEncoder)
- {
- var param = string.Empty;
-
- var isVc1 = state.VideoStream != null &&
- string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
-
- var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
-
- if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
- {
- if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset))
- {
- param += "-preset " + encodingOptions.H264Preset;
- }
- else
- {
- param += "-preset " + GetDefaultH264Preset();
- }
-
- if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51)
- {
- param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture);
- }
- else
- {
- 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 default";
- }
-
- // 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.VideoRequest.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.VideoRequest.Profile;
- }
- }
-
- if (!string.IsNullOrEmpty(state.VideoRequest.Level))
- {
- var level = NormalizeTranscodingLevel(state.OutputVideoCodec, state.VideoRequest.Level);
-
- // 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 (level)
- {
- 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 " + level;
- break;
- }
- }
- else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
- {
- param += " -level " + level;
- }
- }
-
- if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
- {
- param += " -x264opts:0 subme=0:rc_lookahead=10:me_range=4:me=dia:no_chroma_me:8x8dct=0:partitions=none";
- }
-
- 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 GetAudioFilterParam(StreamState 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 && !ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.Equals(1))
- {
- volParam = ",volume=" + ApiEntryPoint.Instance.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.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode && !state.VideoRequest.CopyTimestamps)
- {
- var seconds = TimeSpan.FromTicks(state.Request.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);
- }
-
- /// <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 string GetOutputSizeParam(StreamState 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.VideoRequest;
-
- 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.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
- {
- var subParam = GetTextSubtitleParam(state);
-
- 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 string GetTextSubtitleParam(StreamState state)
- {
- var seconds = Math.Round(TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds);
-
- var setPtsParam = state.VideoRequest.CopyTimestamps
- ? string.Empty
- : string.Format(",setpts=PTS -{0}/TB", seconds.ToString(UsCulture));
-
- if (state.SubtitleStream.IsExternal)
- {
- var subtitlePath = state.SubtitleStream.Path;
-
- var charsetParam = string.Empty;
-
- if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
- {
- var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
-
- if (!string.IsNullOrEmpty(charenc))
- {
- charsetParam = ":charenc=" + charenc;
- }
- }
-
- // TODO: Perhaps also use original_size=1920x800 ??
- return string.Format("subtitles=filename='{0}'{1}{2}",
- MediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
- charsetParam,
- setPtsParam);
- }
-
- var mediaPath = state.MediaPath ?? string.Empty;
-
- return string.Format("subtitles='{0}:si={1}'{2}",
- MediaEncoder.EscapeSubtitleFilterPath(mediaPath),
- state.InternalSubtitleStreamOffset.ToString(UsCulture),
- setPtsParam);
- }
-
- /// <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 string GetGraphicalSubtitleParam(StreamState state, string outputVideoCodec)
- {
- var outputSizeParam = string.Empty;
-
- var request = state.VideoRequest;
-
- // Add resolution params, if specified
- if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
- {
- outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).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}]{4}[sub] ; [0:{2}] [sub] overlay{3}\"",
- mapPrefix.ToString(UsCulture),
- subtitleStreamIndex.ToString(UsCulture),
- state.VideoStream.Index.ToString(UsCulture),
- outputSizeParam,
- videoSizeParam);
- }
-
- /// <summary>
- /// Gets the probe size argument.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- private string GetProbeSizeArgument(StreamState 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 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(StreamRequest 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;
- }
-
- /// <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>
- /// Gets the audio encoder.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected string GetAudioEncoder(StreamState 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>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected string GetVideoEncoder(StreamState state)
- {
- var codec = state.OutputVideoCodec;
-
- if (!string.IsNullOrEmpty(codec))
- {
- if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
- {
- return GetH264Encoder(state);
- }
- 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";
- }
-
- /// <summary>
- /// Gets the name of the output video codec
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected string GetVideoDecoder(StreamState state)
- {
- if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return null;
- }
-
- // 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)
- {
- return null;
- }
-
- if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
- {
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
- {
- switch (state.MediaSource.VideoStream.Codec.ToLower())
- {
- case "avc":
- case "h264":
- if (MediaEncoder.SupportsDecoder("h264_qsv"))
- {
- // Seeing stalls and failures with decoding. Not worth it compared to encoding.
- return "-c:v h264_qsv ";
- }
- break;
- case "mpeg2video":
- if (MediaEncoder.SupportsDecoder("mpeg2_qsv"))
- {
- return "-c:v mpeg2_qsv ";
- }
- break;
- case "vc1":
- if (MediaEncoder.SupportsDecoder("vc1_qsv"))
- {
- return "-c:v vc1_qsv ";
- }
- break;
- }
- }
- }
-
- // leave blank so ffmpeg will decide
- return null;
- }
-
- /// <summary>
- /// Gets the input argument.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected string GetInputArgument(StreamState state)
- {
- var arg = string.Format("-i {0}", GetInputPathArgument(state));
-
- if (state.SubtitleStream != null && state.VideoRequest.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.VideoRequest != null)
- {
- var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
- if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
- {
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.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(StreamState 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(StreamState state, CancellationTokenSource cancellationTokenSource)
{
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
@@ -1141,11 +176,11 @@ namespace MediaBrowser.Api.Playback
}, false, cancellationTokenSource.Token).ConfigureAwait(false);
- AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.VideoRequest, state.RequestedUrl);
+ EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl);
if (state.VideoRequest != null)
{
- TryStreamCopy(state, state.VideoRequest);
+ EncodingHelper.TryStreamCopy(state);
}
}
@@ -1300,14 +335,14 @@ namespace MediaBrowser.Api.Playback
private bool EnableThrottling(StreamState state)
{
return false;
- // do not use throttling with hardware encoders
- return state.InputProtocol == MediaProtocol.File &&
- state.RunTimeTicks.HasValue &&
- state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
- state.IsInputVideo &&
- state.VideoType == VideoType.VideoFile &&
- !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) &&
- string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
+ //// do not use throttling with hardware encoders
+ //return state.InputProtocol == MediaProtocol.File &&
+ // state.RunTimeTicks.HasValue &&
+ // state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
+ // state.IsInputVideo &&
+ // state.VideoType == VideoType.VideoFile &&
+ // !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) &&
+ // string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
}
private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
@@ -1438,115 +473,6 @@ namespace MediaBrowser.Api.Playback
}
}
- private int? GetVideoBitrateParamValue(VideoStreamRequest 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(StreamState 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;
- }
-
- private int? GetAudioBitrateParam(StreamRequest 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;
-
- // Don't encode any higher than this
- return Math.Min(384000, request.AudioBitRate.Value);
- //return Math.Min(currentBitrate, request.AudioBitRate.Value);
- }
-
- return null;
- }
-
- /// <summary>
- /// Gets the user agent param.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- private string GetUserAgentParam(StreamState state)
- {
- string useragent = null;
-
- state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
-
- if (!string.IsNullOrWhiteSpace(useragent))
- {
- return "-user-agent \"" + useragent + "\"";
- }
-
- return string.Empty;
- }
-
/// <summary>
/// Processes the exited.
/// </summary>
@@ -1584,31 +510,6 @@ namespace MediaBrowser.Api.Playback
//}
}
- protected double? GetFramerateParam(StreamState state)
- {
- if (state.VideoRequest != null)
- {
- if (state.VideoRequest.Framerate.HasValue)
- {
- return state.VideoRequest.Framerate.Value;
- }
-
- var maxrate = state.VideoRequest.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>
/// Parses the parameters.
/// </summary>
@@ -1797,6 +698,10 @@ namespace MediaBrowser.Api.Playback
videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
}
+ else if (i == 30)
+ {
+ request.SubtitleCodec = val;
+ }
}
}
@@ -1882,7 +787,7 @@ namespace MediaBrowser.Api.Playback
if (string.IsNullOrEmpty(request.AudioCodec))
{
- request.AudioCodec = InferAudioCodec(url);
+ request.AudioCodec = EncodingHelper.InferAudioCodec(url);
}
var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType)
@@ -1892,6 +797,12 @@ namespace MediaBrowser.Api.Playback
UserAgent = Request.UserAgent
};
+ var auth = AuthorizationContext.GetAuthorizationInfo(Request);
+ if (!string.IsNullOrWhiteSpace(auth.UserId))
+ {
+ state.User = UserManager.GetUserById(auth.UserId);
+ }
+
//if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
// (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
// (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
@@ -1915,6 +826,13 @@ namespace MediaBrowser.Api.Playback
?? state.SupportedAudioCodecs.FirstOrDefault();
}
+ if (!string.IsNullOrWhiteSpace(request.SubtitleCodec))
+ {
+ state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i))
+ ?? state.SupportedSubtitleCodecs.FirstOrDefault();
+ }
+
var item = LibraryManager.GetItemById(request.Id);
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
@@ -1954,7 +872,7 @@ namespace MediaBrowser.Api.Playback
var videoRequest = request as VideoStreamRequest;
- AttachMediaSourceInfo(state, mediaSource, videoRequest, url);
+ EncodingHelper.AttachMediaSourceInfo(state, mediaSource, url);
var container = Path.GetExtension(state.RequestedUrl);
@@ -1967,21 +885,21 @@ namespace MediaBrowser.Api.Playback
state.OutputContainer = (container ?? string.Empty).TrimStart('.');
- state.OutputAudioBitrate = GetAudioBitrateParam(state.Request, state.AudioStream);
+ state.OutputAudioBitrate = EncodingHelper.GetAudioBitrateParam(state.Request, state.AudioStream);
state.OutputAudioSampleRate = request.AudioSampleRate;
state.OutputAudioCodec = state.Request.AudioCodec;
- state.OutputAudioChannels = GetNumAudioChannelsParam(state.Request, state.AudioStream, state.OutputAudioCodec);
+ state.OutputAudioChannels = EncodingHelper.GetNumAudioChannelsParam(state.Request, state.AudioStream, state.OutputAudioCodec);
if (videoRequest != null)
{
state.OutputVideoCodec = state.VideoRequest.VideoCodec;
- state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
+ state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
if (videoRequest != null)
{
- TryStreamCopy(state, videoRequest);
+ EncodingHelper.TryStreamCopy(state);
}
if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
@@ -2010,333 +928,6 @@ namespace MediaBrowser.Api.Playback
return state;
}
- private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
- {
- if (state.VideoStream != null && CanStreamCopyVideo(state))
- {
- state.OutputVideoCodec = "copy";
- }
- else
- {
- // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
- var auth = AuthorizationContext.GetAuthorizationInfo(Request);
- if (!string.IsNullOrWhiteSpace(auth.UserId))
- {
- var user = UserManager.GetUserById(auth.UserId);
- if (!user.Policy.EnableVideoPlaybackTranscoding)
- {
- state.OutputVideoCodec = "copy";
- }
- }
- }
-
- if (state.AudioStream != null && CanStreamCopyAudio(state, state.SupportedAudioCodecs))
- {
- state.OutputAudioCodec = "copy";
- }
- else
- {
- // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
- var auth = AuthorizationContext.GetAuthorizationInfo(Request);
- if (!string.IsNullOrWhiteSpace(auth.UserId))
- {
- var user = UserManager.GetUserById(auth.UserId);
- if (!user.Policy.EnableAudioPlaybackTranscoding)
- {
- state.OutputAudioCodec = "copy";
- }
- }
- }
- }
-
- private void AttachMediaSourceInfo(StreamState state,
- MediaSourceInfo mediaSource,
- VideoStreamRequest videoRequest,
- string requestedUrl)
- {
- 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(requestedUrl);
- }
-
- 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 bool CanStreamCopyVideo(StreamState state)
- {
- var request = state.VideoRequest;
- var videoStream = state.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;
- }
- }
-
- if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
- {
- if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value && request.RequireAvc)
- {
- Logger.Debug("Cannot stream copy video. Stream is marked as not AVC");
- return false;
- }
- }
-
- // Source and target codecs must match
- if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase))
- {
- 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.IsNullOrEmpty(videoStream.Profile) && !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 (!string.IsNullOrEmpty(request.Level))
- {
- double requestLevel;
-
- if (double.TryParse(request.Level, NumberStyles.Any, UsCulture, out requestLevel))
- {
- if (!videoStream.Level.HasValue)
- {
- //return false;
- }
-
- if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
- {
- return false;
- }
- }
- }
-
- return request.EnableAutoStreamCopy;
- }
-
- private int GetVideoProfileScore(string profile)
- {
- var list = new List<string>
- {
- "Constrained Baseline",
- "Baseline",
- "Extended",
- "Main",
- "High",
- "Progressive High",
- "Constrained High"
- };
-
- return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
- }
-
- protected virtual bool CanStreamCopyAudio(StreamState state, List<string> supportedAudioCodecs)
- {
- var request = state.VideoRequest;
- var audioStream = state.AudioStream;
-
- // 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(StreamState state)
{
var headers = Request.Headers.ToDictionary();
@@ -2633,196 +1224,5 @@ namespace MediaBrowser.Api.Playback
responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);
}
-
- /// <summary>
- /// Enforces the resolution limit.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="videoRequest">The video request.</param>
- private void EnforceResolutionLimit(StreamState state, VideoStreamRequest 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;
- }
-
- protected string GetInputModifier(StreamState 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.Request);
- inputModifier = inputModifier.Trim();
-
- //inputModifier += " -fflags +genpts+ignidx+igndts";
- if (state.VideoRequest != null && 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.VideoRequest != null)
- {
- // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
- if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
- {
- //inputModifier += " -noaccurate_seek";
- }
-
- foreach (var stream in state.MediaSource.MediaStreams)
- {
- if (!stream.IsExternal && stream.Type != MediaStreamType.Subtitle)
- {
- if (!string.IsNullOrWhiteSpace(stream.Codec) && stream.Index != -1)
- {
- var decoder = GetDecoderFromCodec(stream.Codec);
-
- if (!string.IsNullOrWhiteSpace(decoder))
- {
- inputModifier += " -codec:" + stream.Index.ToString(UsCulture) + " " + decoder;
- }
- }
- }
- }
- //var videoStream = state.VideoStream;
- //if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
- //{
- // inputModifier += " -codec:0 " + videoStream.Codec;
-
- // var audioStream = state.AudioStream;
- // if (audioStream != null && !string.IsNullOrWhiteSpace(audioStream.Codec))
- // {
- // inputModifier += " -codec:1 " + audioStream.Codec;
- // }
- //}
- }
-
- return inputModifier;
- }
-
- private string GetDecoderFromCodec(string codec)
- {
- return null;
-
- //if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
- //{
- // return null;
- //}
- //if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
- //{
- // return null;
- //}
-
- //return codec;
- }
-
- /// <summary>
- /// Infers the audio codec based on the url
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <returns>System.Nullable{AudioCodecs}.</returns>
- private string InferAudioCodec(string url)
- {
- var ext = Path.GetExtension(url);
-
- if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
- {
- return "mp3";
- }
- if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
- {
- return "aac";
- }
- if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
- {
- return "wma";
- }
- if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
- if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
- if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
- if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
- if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
-
- return "copy";
- }
-
- /// <summary>
- /// Infers the video codec.
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <returns>System.Nullable{VideoCodecs}.</returns>
- private string InferVideoCodec(string url)
- {
- var ext = Path.GetExtension(url);
-
- if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
- {
- return "wmv";
- }
- if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
- {
- return "vpx";
- }
- if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
- {
- return "theora";
- }
- if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
- {
- return "h264";
- }
-
- return "copy";
- }
}
}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 97072115d..aaca1793c 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -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),
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index e922094ad..cfd7471c4 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -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),
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index 67edd3f00..c9c6acc1b 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -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";
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index 082d6b2f4..a0ab90664 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -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()),
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 7ae64b834..3c614567b 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -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 = EncodingHelper.GetInputModifier(state, encodingOptions);
- var inputModifier = GetInputModifier(state);
+ 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} -y \"{8}\"",
+ 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;
}
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 2a2ad92cd..6bdb30890 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -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; }
@@ -80,102 +43,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>
/// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
@@ -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;
- }
}
}
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index ab0bfd502..4fb936340 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -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)
+ 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
diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs
index 68fa7f92a..d6fa4030d 100644
--- a/MediaBrowser.Api/SearchService.cs
+++ b/MediaBrowser.Api/SearchService.cs
@@ -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;
}
diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs
index 25a460935..f5fd94d0e 100644
--- a/MediaBrowser.Api/StartupWizardService.cs
+++ b/MediaBrowser.Api/StartupWizardService.cs
@@ -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)
diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs
index 5fe6c1771..504dd29a7 100644
--- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs
+++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs
@@ -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
});
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 4cfea4c70..cab7588f0 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -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;
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 9c5730d05..f03531270 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -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:
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index a880b6d77..6e0f4ada9 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -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();
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index bf9a07626..33cd4f3d1 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -456,7 +456,7 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="item">The item.</param>
/// <returns>IEnumerable&lt;Folder&gt;.</returns>
- IEnumerable<Folder> GetCollectionFolders(BaseItem item);
+ List<Folder> GetCollectionFolders(BaseItem item);
LibraryOptions GetLibraryOptions(BaseItem item);
diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
index 372b095fd..6682942ad 100644
--- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
@@ -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>
diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs
index 5ecd70cc5..faf4a34df 100644
--- a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs
+++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs
@@ -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);
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
index 3c935f924..ea12db7f9 100644
--- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -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; }
diff --git a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
index c1d1c413e..3b2df0471 100644
--- a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
+++ b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
@@ -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; }
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
index d5fe790b9..4bb180d4b 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -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;
}
}
}
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 428651ed5..f4d45c7e0 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -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.
diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
index d66cdf801..9e36f2f95 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
@@ -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();
diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
index b52bae2ce..e1bf4613b 100644
--- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
@@ -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);
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
index 5a554d26f..a4db8f3b8 100644
--- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
@@ -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()),
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
index 7654ec1d7..b0b37f2d6 100644
--- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
@@ -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));
@@ -286,72 +290,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <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>
/// <param name="state">The state.</param>
@@ -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);
- }
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs
new file mode 100644
index 000000000..83057dba4
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingHelper.cs
@@ -0,0 +1,1681 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Configuration;
+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
+{
+ public class EncodingHelper
+ {
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IServerConfigurationManager _config;
+ private readonly IFileSystem _fileSystem;
+ private readonly ISubtitleEncoder _subtitleEncoder;
+
+ public EncodingHelper(IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder)
+ {
+ _mediaEncoder = mediaEncoder;
+ _config = config;
+ _fileSystem = fileSystem;
+ _subtitleEncoder = subtitleEncoder;
+ }
+
+ public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
+ {
+ 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 = encodingOptions.HardwareAccelerationType;
+
+ if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetAvailableEncoder("h264_qsv", defaultEncoder);
+ }
+
+ if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetAvailableEncoder("h264_nvenc", defaultEncoder);
+ }
+ if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetAvailableEncoder("h264_omx", defaultEncoder);
+ }
+ if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice))
+ {
+ if (IsVaapiSupported(state))
+ {
+ return GetAvailableEncoder("h264_vaapi", defaultEncoder);
+ }
+ }
+ }
+
+ return defaultEncoder;
+ }
+
+ private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder)
+ {
+ if (_mediaEncoder.SupportsEncoder(preferredEncoder))
+ {
+ return preferredEncoder;
+ }
+ return defaultEncoder;
+ }
+
+ private bool IsVaapiSupported(EncodingJobInfo 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;
+ }
+
+ /// <summary>
+ /// Gets the name of the output video codec
+ /// </summary>
+ public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
+ {
+ var codec = state.OutputVideoCodec;
+
+ if (!string.IsNullOrEmpty(codec))
+ {
+ if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetH264Encoder(state, encodingOptions);
+ }
+ 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";
+ }
+
+ /// <summary>
+ /// Gets the user agent param.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ public string GetUserAgentParam(EncodingJobInfo state)
+ {
+ string useragent = null;
+
+ state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
+
+ if (!string.IsNullOrWhiteSpace(useragent))
+ {
+ return "-user-agent \"" + useragent + "\"";
+ }
+
+ return string.Empty;
+ }
+
+ public string GetInputFormat(string container)
+ {
+ if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "matroska";
+ }
+ if (string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
+ {
+ return "mpegts";
+ }
+
+ return container;
+ }
+
+ public string GetDecoderFromCodec(string codec)
+ {
+ if (string.Equals(codec, "mp2", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+ if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ return codec;
+ }
+
+ /// <summary>
+ /// Infers the audio codec based on the url
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <returns>System.Nullable{AudioCodecs}.</returns>
+ public string InferAudioCodec(string url)
+ {
+ var ext = Path.GetExtension(url);
+
+ if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
+ {
+ return "mp3";
+ }
+ if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
+ {
+ return "aac";
+ }
+ if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
+ {
+ return "wma";
+ }
+ if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+ if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+ if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+ if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+ if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vorbis";
+ }
+
+ return "copy";
+ }
+
+ /// <summary>
+ /// Infers the video codec.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <returns>System.Nullable{VideoCodecs}.</returns>
+ public string InferVideoCodec(string url)
+ {
+ var ext = Path.GetExtension(url);
+
+ if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
+ {
+ return "wmv";
+ }
+ if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
+ {
+ return "vpx";
+ }
+ if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "theora";
+ }
+ if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264";
+ }
+
+ return "copy";
+ }
+
+ public int GetVideoProfileScore(string profile)
+ {
+ var list = new List<string>
+ {
+ "Constrained Baseline",
+ "Baseline",
+ "Extended",
+ "Main",
+ "High",
+ "Progressive High",
+ "Constrained High"
+ };
+
+ return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
+ }
+
+ public string GetInputPathArgument(EncodingJobInfo 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);
+ }
+
+ /// <summary>
+ /// Gets the audio encoder.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ public string GetAudioEncoder(EncodingJobInfo 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 input argument.
+ /// </summary>
+ public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
+ {
+ var request = state.BaseRequest;
+
+ var arg = string.Format("-i {0}", GetInputPathArgument(state));
+
+ if (state.SubtitleStream != null && request.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)
+ {
+ if (GetVideoEncoder(state, encodingOptions).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && request.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();
+ }
+
+ /// <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>
+ public bool IsH264(MediaStream stream)
+ {
+ var codec = stream.Codec ?? string.Empty;
+
+ return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 ||
+ codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
+ public string GetVideoBitrateParam(EncodingJobInfo 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;
+ }
+
+ public 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;
+ }
+
+ /// <summary>
+ /// Gets the probe size argument.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ public string GetProbeSizeArgument(EncodingJobInfo 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 text subtitle param.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ public string GetTextSubtitleParam(EncodingJobInfo state)
+ {
+ var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
+
+ var setPtsParam = state.CopyTimestamps
+ ? string.Empty
+ : string.Format(",setpts=PTS -{0}/TB", seconds.ToString(_usCulture));
+
+ if (state.SubtitleStream.IsExternal)
+ {
+ var subtitlePath = state.SubtitleStream.Path;
+
+ var charsetParam = string.Empty;
+
+ if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
+ {
+ var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
+
+ if (!string.IsNullOrEmpty(charenc))
+ {
+ charsetParam = ":charenc=" + charenc;
+ }
+ }
+
+ // TODO: Perhaps also use original_size=1920x800 ??
+ return string.Format("subtitles=filename='{0}'{1}{2}",
+ _mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
+ charsetParam,
+ setPtsParam);
+ }
+
+ var mediaPath = state.MediaPath ?? string.Empty;
+
+ return string.Format("subtitles='{0}:si={1}'{2}",
+ _mediaEncoder.EscapeSubtitleFilterPath(mediaPath),
+ state.InternalSubtitleStreamOffset.ToString(_usCulture),
+ setPtsParam);
+ }
+
+ public double? GetFramerateParam(EncodingJobInfo state)
+ {
+ var request = state.BaseRequest;
+
+ if (request.Framerate.HasValue)
+ {
+ return request.Framerate.Value;
+ }
+
+ var maxrate = request.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 video bitrate to specify on the command line
+ /// </summary>
+ public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultH264Preset)
+ {
+ var param = string.Empty;
+
+ var isVc1 = state.VideoStream != null &&
+ string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
+
+ if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset))
+ {
+ param += "-preset " + encodingOptions.H264Preset;
+ }
+ else
+ {
+ param += "-preset " + defaultH264Preset;
+ }
+
+ if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51)
+ {
+ param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ 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 default";
+ }
+
+ // 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;
+ }
+
+ var request = state.BaseRequest;
+
+ if (!string.IsNullOrEmpty(request.Profile))
+ {
+ if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ // not supported by h264_omx
+ param += " -profile:v " + request.Profile;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(request.Level))
+ {
+ var level = NormalizeTranscodingLevel(state.OutputVideoCodec, request.Level);
+
+ // 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 (level)
+ {
+ 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 " + level;
+ break;
+ }
+ }
+ else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -level " + level;
+ }
+ }
+
+ if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -x264opts:0 subme=0:rc_lookahead=10:me_range=4:me=dia:no_chroma_me:8x8dct=0:partitions=none";
+ }
+
+ 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;
+ }
+
+ public bool CanStreamCopyVideo(EncodingJobInfo state, MediaStream videoStream)
+ {
+ var request = state.BaseRequest;
+
+ 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;
+ }
+ }
+
+ if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value && request.RequireAvc)
+ {
+ return false;
+ }
+ }
+
+ // Source and target codecs must match
+ if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase))
+ {
+ 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.IsNullOrEmpty(videoStream.Profile) && !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 (!string.IsNullOrEmpty(request.Level))
+ {
+ double requestLevel;
+
+ if (double.TryParse(request.Level, NumberStyles.Any, _usCulture, out requestLevel))
+ {
+ if (!videoStream.Level.HasValue)
+ {
+ //return false;
+ }
+
+ if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
+ {
+ return false;
+ }
+ }
+ }
+
+ return request.EnableAutoStreamCopy;
+ }
+
+ public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, List<string> supportedAudioCodecs)
+ {
+ var request = state.BaseRequest;
+
+ // 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;
+ }
+
+ public int? GetVideoBitrateParamValue(BaseEncodingJobOptions 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;
+ }
+
+ public int? GetAudioBitrateParam(BaseEncodingJobOptions 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;
+
+ // Don't encode any higher than this
+ return Math.Min(384000, request.AudioBitRate.Value);
+ //return Math.Min(currentBitrate, request.AudioBitRate.Value);
+ }
+
+ return null;
+ }
+
+ public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions, 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 && !encodingOptions.DownMixAudioBoost.Equals(1))
+ {
+ volParam = ",volume=" + encodingOptions.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.BaseRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode && !state.CopyTimestamps)
+ {
+ var seconds = TimeSpan.FromTicks(state.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);
+ }
+
+ /// <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>
+ public int? GetNumAudioChannelsParam(BaseEncodingJobOptions 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;
+ }
+
+ /// <summary>
+ /// Enforces the resolution limit.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ public void EnforceResolutionLimit(EncodingJobInfo state)
+ {
+ var videoRequest = state.BaseRequest;
+
+ // 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 fast seek command line parameter.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>System.String.</returns>
+ /// <value>The fast seek command line parameter.</value>
+ public string GetFastSeekCommandLineParameter(BaseEncodingJobOptions request)
+ {
+ var time = request.StartTimeTicks ?? 0;
+
+ if (time > 0)
+ {
+ return string.Format("-ss {0}", _mediaEncoder.GetTimeParameter(time));
+ }
+
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Gets the map args.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <returns>System.String.</returns>
+ public string GetMapArgs(EncodingJobInfo 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";
+ }
+
+ var subtitleMethod = state.BaseRequest.SubtitleMethod;
+ if (state.SubtitleStream == null || subtitleMethod == SubtitleDeliveryMethod.Hls)
+ {
+ args += " -map -0:s";
+ }
+ else if (subtitleMethod == SubtitleDeliveryMethod.Embed)
+ {
+ args += string.Format(" -map 0:{0}", state.SubtitleStream.Index);
+ }
+ else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
+ {
+ args += " -map 1:0 -sn";
+ }
+
+ return args;
+ }
+
+ /// <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>
+ public 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>
+ /// 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>
+ public string GetGraphicalSubtitleParam(EncodingJobInfo state, string outputVideoCodec)
+ {
+ var outputSizeParam = string.Empty;
+
+ var request = state.BaseRequest;
+
+ // Add resolution params, if specified
+ if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
+ {
+ outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).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}]{4}[sub] ; [0:{2}] [sub] overlay{3}\"",
+ mapPrefix.ToString(_usCulture),
+ subtitleStreamIndex.ToString(_usCulture),
+ state.VideoStream.Index.ToString(_usCulture),
+ outputSizeParam,
+ videoSizeParam);
+ }
+
+ /// <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>
+ public string GetOutputSizeParam(EncodingJobInfo 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.BaseRequest;
+
+ 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 && request.SubtitleMethod == SubtitleDeliveryMethod.Encode)
+ {
+ var subParam = GetTextSubtitleParam(state);
+
+ 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 number of threads.
+ /// </summary>
+ /// <returns>System.Int32.</returns>
+ public int GetNumberOfThreads(EncodingJobInfo state, EncodingOptions encodingOptions, bool isWebm)
+ {
+ var threads = GetNumberOfThreadsInternal(state, encodingOptions, isWebm);
+
+ if (state.BaseRequest.CpuCoreLimit.HasValue && state.BaseRequest.CpuCoreLimit.Value > 0)
+ {
+ threads = Math.Min(threads, state.BaseRequest.CpuCoreLimit.Value);
+ }
+
+ return threads;
+ }
+
+ public void TryStreamCopy(EncodingJobInfo state)
+ {
+ if (state.VideoStream != null && CanStreamCopyVideo(state, state.VideoStream))
+ {
+ state.OutputVideoCodec = "copy";
+ }
+ else
+ {
+ var user = state.User;
+
+ // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
+ if (user != null && !user.Policy.EnableVideoPlaybackTranscoding)
+ {
+ state.OutputVideoCodec = "copy";
+ }
+ }
+
+ if (state.AudioStream != null && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
+ {
+ state.OutputAudioCodec = "copy";
+ }
+ else
+ {
+ var user = state.User;
+
+ // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
+ if (user != null && !user.Policy.EnableAudioPlaybackTranscoding)
+ {
+ state.OutputAudioCodec = "copy";
+ }
+ }
+ }
+
+ public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions)
+ {
+ 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.BaseRequest);
+ inputModifier = inputModifier.Trim();
+
+ //inputModifier += " -fflags +genpts+ignidx+igndts";
+ //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, encodingOptions);
+ if (!string.IsNullOrWhiteSpace(videoDecoder))
+ {
+ inputModifier += " " + videoDecoder;
+ }
+
+ if (state.IsVideoRequest)
+ {
+ // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
+ if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.CopyTimestamps)
+ {
+ //inputModifier += " -noaccurate_seek";
+ }
+
+ //if (!string.IsNullOrWhiteSpace(state.InputContainer))
+ //{
+ // var inputFormat = GetInputFormat(state.InputContainer);
+ // if (!string.IsNullOrWhiteSpace(inputFormat))
+ // {
+ // inputModifier += " -f " + inputFormat;
+ // }
+ //}
+
+ //if (state.RunTimeTicks.HasValue)
+ //{
+ // foreach (var stream in state.MediaSource.MediaStreams)
+ // {
+ // if (!stream.IsExternal && stream.Type != MediaStreamType.Subtitle)
+ // {
+ // if (!string.IsNullOrWhiteSpace(stream.Codec) && stream.Index != -1)
+ // {
+ // var decoder = GetDecoderFromCodec(stream.Codec);
+
+ // if (!string.IsNullOrWhiteSpace(decoder))
+ // {
+ // inputModifier += " -codec:" + stream.Index.ToString(_usCulture) + " " + decoder;
+ // }
+ // }
+ // }
+ // }
+ //}
+ }
+
+ return inputModifier;
+ }
+
+
+ public void AttachMediaSourceInfo(EncodingJobInfo state,
+ MediaSourceInfo mediaSource,
+ string requestedUrl)
+ {
+ state.MediaPath = mediaSource.Path;
+ state.InputProtocol = mediaSource.Protocol;
+ state.InputContainer = mediaSource.Container;
+ 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.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 (state.IsVideoRequest)
+ {
+ var videoRequest = state.BaseRequest;
+
+ if (string.IsNullOrEmpty(videoRequest.VideoCodec))
+ {
+ if (string.IsNullOrWhiteSpace(requestedUrl))
+ {
+ requestedUrl = "test." + videoRequest.OutputContainer;
+ }
+
+ videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
+ }
+
+ state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
+ state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
+ state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
+ 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);
+ }
+ else
+ {
+ state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
+ }
+
+ state.MediaSource = mediaSource;
+ }
+
+ /// <summary>
+ /// Gets the name of the output video codec
+ /// </summary>
+ protected string GetVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
+ {
+ if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ // 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)
+ {
+ return null;
+ }
+
+ if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
+ {
+ if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ switch (state.MediaSource.VideoStream.Codec.ToLower())
+ {
+ case "avc":
+ case "h264":
+ if (_mediaEncoder.SupportsDecoder("h264_qsv"))
+ {
+ return "-c:v h264_qsv ";
+ }
+ break;
+ case "mpeg2video":
+ if (_mediaEncoder.SupportsDecoder("mpeg2_qsv"))
+ {
+ return "-c:v mpeg2_qsv ";
+ }
+ break;
+ case "vc1":
+ if (_mediaEncoder.SupportsDecoder("vc1_qsv"))
+ {
+ return "-c:v vc1_qsv ";
+ }
+ break;
+ }
+ }
+ }
+
+ // leave blank so ffmpeg will decide
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the number of threads.
+ /// </summary>
+ /// <returns>System.Int32.</returns>
+ private int GetNumberOfThreadsInternal(EncodingJobInfo state, EncodingOptions encodingOptions, bool isWebm)
+ {
+ var threads = encodingOptions.EncodingThreadCount;
+
+ if (isWebm)
+ {
+ // Recommended per docs
+ return Math.Max(Environment.ProcessorCount - 1, 2);
+ }
+
+ // Automatic
+ if (threads == -1)
+ {
+ return 0;
+ }
+
+ return threads;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
index b5ff5cbb6..f6895696a 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
@@ -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 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 EncodingJobOptions Options
+ {
+ get { return (EncodingJobOptions) BaseRequest; }
+ set { BaseRequest = value; }
+ }
- public List<string> SupportedAudioCodecs { get; set; }
- public Dictionary<string, string> RemoteHttpHeaders { get; set; }
- public TransportStreamTimestamp InputTimestamp { get; set; }
+ public string Id { 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;
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
index d6340d4ab..4b336e671 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
@@ -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");
@@ -301,203 +209,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
/// <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>
/// <param name="stream">The stream.</param>
@@ -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;
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs
new file mode 100644
index 000000000..20a9817a3
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobInfo.cs
@@ -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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 6a5945b76..ee3482a70 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -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
diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
index cbbca479a..52ef4d834 100644
--- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
@@ -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;
}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 63e789a59..5eac1a16d 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -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" />
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 2f8ecaece..256c38597 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.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))
{
diff --git a/MediaBrowser.Model/Configuration/PathSubstitution.cs b/MediaBrowser.Model/Configuration/PathSubstitution.cs
deleted file mode 100644
index 576dd2d5a..000000000
--- a/MediaBrowser.Model/Configuration/PathSubstitution.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace MediaBrowser.Model.Configuration
-{
- public class PathSubstitution
- {
- public string From { get; set; }
- public string To { get; set; }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index bd0a53cc0..f9df776df 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -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";
diff --git a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs
index 0dac23403..fd615733d 100644
--- a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs
+++ b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs
@@ -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;
+ }
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 262964404..129b49cf6 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -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)
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 5b8d40dfb..a85e6085b 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -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,
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 864123bb9..c69bbb581 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -212,12 +212,6 @@ namespace MediaBrowser.Model.Dto
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>
/// <value>The taglines.</value>
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 0eb9e2730..3cd3e7dde 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -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;
}
diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
index 3c2ace896..5cf52e0ef 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
@@ -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;
- }
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 4457c6dc2..c50ee984e 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -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" />
diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
index de082635d..126710197 100644
--- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs
+++ b/MediaBrowser.Model/MediaInfo/MediaInfo.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()
{
diff --git a/MediaBrowser.Model/Net/IUdpSocket.cs b/MediaBrowser.Model/Net/IUdpSocket.cs
index c70510726..76793f4a6 100644
--- a/MediaBrowser.Model/Net/IUdpSocket.cs
+++ b/MediaBrowser.Model/Net/IUdpSocket.cs
@@ -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);
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index e36abf16e..5e1c17d1b 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -189,11 +189,6 @@
Settings,
/// <summary>
- /// The short overview
- /// </summary>
- ShortOverview,
-
- /// <summary>
/// The screenshot image tags
/// </summary>
ScreenshotImageTags,
diff --git a/MediaBrowser.Model/Session/PlaybackStopInfo.cs b/MediaBrowser.Model/Session/PlaybackStopInfo.cs
index 3b4ac36a7..74347f894 100644
--- a/MediaBrowser.Model/Session/PlaybackStopInfo.cs
+++ b/MediaBrowser.Model/Session/PlaybackStopInfo.cs
@@ -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; }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index 5146df6e6..a65453a78 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -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);
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 9c6d6a482..bdfe13c1d 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -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)
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 003e7b9fa..0b8dca2eb 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -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
diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs
index 178e861a3..5b53cd257 100644
--- a/MediaBrowser.Providers/Manager/ProviderUtils.cs
+++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs
@@ -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;
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index c4ea13063..1bb715866 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -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)
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index 313feda52..f39786669 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -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" };
}
}
diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
index 6788bdc9c..ee634f157 100644
--- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
+++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
@@ -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)
{
diff --git a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
index db551b763..73668b73c 100644
--- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
@@ -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>();
diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
index c1668c4cc..07b35c45a 100644
--- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
@@ -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))
//{
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
index fcd753264..d2191e1a6 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
@@ -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)
{
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index 7fcfbfb13..91c23f871 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -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())
+ if (!string.IsNullOrWhiteSpace(mode))
{
- DeleteFilesByName(bowerPath, ignore.Item1, ignore.Item2);
- }
-
- DeleteFoldersByName(bowerPath, "demo");
- DeleteFoldersByName(bowerPath, "test");
- DeleteFoldersByName(bowerPath, "guides");
- DeleteFoldersByName(bowerPath, "grunt");
- DeleteFoldersByName(bowerPath, "rollups");
+ // Delete things that are unneeded in an attempt to keep the output as trim as possible
- 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))
- {
- // Delete things that are unneeded in an attempt to keep the output as trim as possible
_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)
diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
index f2df01976..453f46edd 100644
--- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs
+++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
@@ -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>();
@@ -319,48 +317,6 @@ namespace MediaBrowser.WebDashboard.Api
}
/// <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>
/// <param name="path">The path.</param>
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 5b0ef735e..c9361a46e 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -70,1439 +70,11 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
- <None Include="dashboard-ui\bower_components\**\*.*">
+ <None Include="dashboard-ui\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\autoorganizesmart.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\camerauploadsettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\accessschedule\accessschedule.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\accessschedule\accessschedule.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\appfooter\appfooter.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\appfooter\appfooter.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\apphost.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\categorysyncbuttons.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\channelmapper\channelmapper.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\chromecasthelpers.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\directorybrowser\directorybrowser.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\dockedtabs\dockedtabs.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\dockedtabs\dockedtabs.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\favoriteitems.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\filterdialog\filterdialog.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\filterdialog\filterdialog.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\filterdialog\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\groupedcards.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\guestinviter\connectlink.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\guestinviter\connectlink.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\guestinviter\guestinviter.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\guestinviter\guestinviter.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\iap.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\libraryoptionseditor\libraryoptionseditor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\libraryoptionseditor\libraryoptionseditor.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\navdrawer\navdrawer.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\navdrawer\navdrawer.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\remotecontrol.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\tvproviders\xmltv.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\tvproviders\xmltv.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\throbber.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\camerauploadsettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvschedule.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvseriestimers.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\userpasswordpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\viewcontainer-lite.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\dashboard.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\autoorganizetable.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\logo.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\home.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\legacy\fnchecked.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\legacy\buttonenabled.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\directorybrowser\directorybrowser.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\fileorganizer\fileorganizer.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\fileorganizer\fileorganizer.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\humanedate.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\imagedownloader\imagedownloader.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\imagedownloader\imagedownloader.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\imageuploader\imageuploader.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\imageuploader\imageuploader.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\medialibrarycreator\medialibrarycreator.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\medialibrarycreator\medialibrarycreator.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\medialibraryeditor\medialibraryeditor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\medialibraryeditor\medialibraryeditor.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\ani_equalizer_black.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\ani_equalizer_white.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\androidtv-tile.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\chromecast.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\empty.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\videoosd.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\touchicon144.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\help.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\sync.png">
- <CopyToOutputDirectory>Always</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourmysync.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\nowplayingbar.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\favorites.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\legacy\dashboard.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\legacy\selectmenu.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\librarydisplay.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\livetvguideprovider.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\livetvtunerprovider-hdhomerun.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\livetvtunerprovider-m3u.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\livetvtunerprovider-satip.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\mypreferenceshome.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\mypreferencesmenu.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\mysyncsettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\robots.txt">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\autobackdrops.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\homefavorites.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\homenextup.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\homeupcoming.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\librarydisplay.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvguideprovider.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvtunerprovider-m3u.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\livetvtunerprovider-satip.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\localsync.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\musicfolders.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\mypreferenceshome.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\mysync.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\mysyncsettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\autoorganizesmart.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\searchpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\secondaryitems.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\serversecurity.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\shared.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\supporterkeypage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\tvlatest.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\wizardcomponents.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\wizardcontroller.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\wizardlivetvguide.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\wizardlivetvtuner.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\search.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\secondaryitems.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\serversecurity.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\serviceworker.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\shared.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\devices\android\android.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\themes\holiday\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\themes\holiday\theme.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\themes\halloween\bg.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\themes\halloween\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\themes\halloween\theme.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\devices\ios\ios.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\sections.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.panel.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.panel.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.slider.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.table.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.slider.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.widget.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\paper-button-style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\tvproviders\schedulesdirect.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\tvproviders\schedulesdirect.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboardhosting.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\forgotpasswordpin.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\livetvitems.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\mysync.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\mysyncjob.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\photos.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\dashboardhosting.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\forgotpassword.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\forgotpasswordpin.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvcomponents.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvitems.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\mypreferencescommon.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\photos.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\reports.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\selectserver.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\streamingsettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\appservices.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\syncsettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\taskbutton.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\usernew.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\userpassword.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\wizardagreement.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\selectserver.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\streamingsettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\appservices.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\syncsettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\channelitems.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\channels.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\cinemamodeconfiguration.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\connectlogin.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\chromecast.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\amazon.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\playstore.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\kodi.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\favicon.ico">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\items\detail\tv.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\media\pause.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\media\play.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\chapters.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\cinemamode.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\dashboard.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\mobile.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\notifications.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\plugins.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\scheduledtasks.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\subtitles.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\admin\users.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\enjoy.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourcollections.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourcontent.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\toureditor.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourmobile1.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourmobile2.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourmouseover.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourmovies.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourplaylist.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourtaphold.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourusersettings1.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourusersettings2.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourusersettings3.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\tour\web\tourusersettings4.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\librarymenu.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\livetv.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\nowplaying.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboardgeneral.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\syncactivity.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\device.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\devices.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\devicesupload.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dlnaprofile.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dlnaprofiles.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dlnasettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\encodingsettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\autoorganizetv.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\autoorganizelog.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\files\dummy.mp4">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\metadatanfo.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\mypreferencesdisplay.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\mypreferenceslanguages.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\notificationlist.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\playbackconfiguration.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\playlists.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\reports.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\livetvsettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\livetvstatus.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\metadatasubtitles.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\editor\missing.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\editor\missingbackdrop.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\editor\missinglogo.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\editor\missingprimaryimage.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\metadataeditor.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\notifications.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\detailtable.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\chrome.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\mbc.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\roku.jpg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\fresh.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\media\chapterflyout.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\rotten.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\items\detail\person.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\items\list\chapter.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\items\list\person.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\supporter\nonsupporterbadge.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\administrator.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\password.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\librarybrowser.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\edititemmetadata.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\gamegenres.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\games.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\gamesrecommended.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\gamestudios.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\gamesystems.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\notificationsetting.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\notificationsettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\nowplaying.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\channelitems.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\channels.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\channelslatest.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\cinemamodeconfiguration.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\connectlogin.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\dashboardgeneral.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\syncactivity.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\device.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\devices.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\devicesupload.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\dlnaprofile.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\dlnaprofiles.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\dlnasettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\encodingsettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\autoorganizetv.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\autoorganizelog.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\favorites.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\librarymenu.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\metadatanfo.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\mypreferencesdisplay.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\mypreferenceslanguages.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\notificationlist.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\playbackconfiguration.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\playlistedit.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\playlists.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvchannel.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvsettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvstatus.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\editorsidebar.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\metadatasubtitles.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvchannels.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvguide.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvrecordings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\notificationsetting.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\notificationsettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\nowplayingbar.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\nowplayingpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\episodes.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvsuggested.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\tvupcoming.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\userlibraryaccess.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\userparentalcontrol.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\wizardsettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\livetv.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree\themes\default\32px.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree\themes\default\40px.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree\themes\default\style.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree\themes\default\style.min.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jstree\themes\default\throbber.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\userlibraryaccess.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\usernew.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\userparentalcontrol.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\userpassword.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\videoosd.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\wizardagreement.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\wizardcomponents.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\wizardlivetvguide.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\wizardlivetvtuner.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\index.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\forgotpassword.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\dashboard.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\librarysettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\movies.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\music.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\edititemmetadata.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\librarysettings.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\musicrecommended.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\notifications.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\moviecollections.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\gamegenrepage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\gamespage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\gamesrecommendedpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\gamestudiospage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\gamesystemspage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\itembynamedetailpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\moviegenres.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\moviestudios.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\movietrailers.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\musicalbums.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\musicartists.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\musicgenres.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\songs.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\librarybrowser.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\movies.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\moviesrecommended.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\site.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\site.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\library.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\tvgenres.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\tvrecommended.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\tvshows.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\tvstudios.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\tv.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\userprofiles.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\plugins.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\login.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\logindefault.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\useredit.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\favicon.ico">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\touchicon.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\touchicon114.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\touchicon72.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\iossplash.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\myprofile.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\indexpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\items\list\collection.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\rightarrow.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\userflyoutdefault.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\medialibrarypage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\pluginspage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\loginpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\useredit.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\myprofile.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\userprofilespage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\addplugin.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\addpluginpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\plugincatalog.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\plugincatalogpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scheduledtasks.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\scheduledtaskspage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scheduledtask.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\scheduledtaskpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\wizardsettings.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\wizardstart.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\wizardfinish.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\wizarduser.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\wizardlibrary.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\wizardstartpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\wizarduserpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\log.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\dashboard\logpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\metadataimages.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\metadataimagespage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\dashboardpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\checkmarkgreen.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\clients\html5.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\clients\android.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\ios.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\clients\windowsrt.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\clients\windowsphone.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\supporter\supporterbadge.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\supporter\premiumflag.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\supporterkey.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\itemdetails.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\scripts\itemdetailpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\dashboard\aboutpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\supporter\supporterflag.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\itemlistpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\itemlist.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\dashboard\wizardfinishpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\items\detail\video.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\items\detail\audio.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\items\detail\game.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\mblogoicon.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\clients\dlna.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <None Include="dashboard-ui\manifest.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.table.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <None Include="dashboard-ui\strings\ar.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\be-by.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\bg-bg.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\ca.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\cs.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\da.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\de-de.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\de.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\el.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\en-gb.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\en-us.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\es-ar.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\es-es.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\es-mx.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\es.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\fi.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\fr-ca.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\fr-fr.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\fr.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\gsw.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\he.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\hr.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\hu.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\id.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\it.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\kk.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\ko.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\lt-lt.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\ms.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\nb.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\nl.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\pl.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\pt-br.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\pt-pt.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\ro.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\ru.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\sk.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\sl-si.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\sv.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\tr.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\uk.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\vi.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\zh-cn.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\zh-hk.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\strings\zh-tw.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index e4037f2ef..8a8560711 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -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":
diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
index 83539bbf4..9122d3046 100644
--- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
@@ -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"
- };
-
+ "track",
+ "artist",
+ "albumartist"
+ });
return list;
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
index 14a56c33a..38738470e 100644
--- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
@@ -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"
- };
-
+ "album",
+ "disbanded"
+ });
return list;
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index 057522e9b..4f7715874 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -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("&quot;", "'");
@@ -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";
+ }
}
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
index 3346b43d3..e734b0640 100644
--- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
@@ -108,24 +108,24 @@ 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",
- "episode",
- "episodenumberend",
- "airsafter_season",
- "airsbefore_episode",
- "airsbefore_season",
- "DVD_episodenumber",
- "DVD_season",
- "absolute_number",
- "displayseason",
- "displayepisode"
- };
-
+ "aired",
+ "season",
+ "episode",
+ "episodenumberend",
+ "airsafter_season",
+ "airsbefore_episode",
+ "airsbefore_season",
+ "DVD_episodenumber",
+ "DVD_season",
+ "absolute_number",
+ "displayseason",
+ "displayepisode"
+ });
return list;
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
index 3bd3a1555..cdada7446 100644
--- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
@@ -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"
- };
-
+ "album",
+ "artist",
+ "set",
+ "id"
+ });
return list;
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
index bbf4b5900..5c6b02360 100644
--- a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
@@ -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;
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
index b512939a7..9e48b0c8b 100644
--- a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
@@ -90,20 +90,20 @@ 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",
- "season",
- "episode",
- "status",
- "airs_time",
- "airs_dayofweek",
- "animeseriesindex"
- };
-
+ "id",
+ "episodeguide",
+ "season",
+ "episode",
+ "status",
+ "airs_time",
+ "airs_dayofweek",
+ "animeseriesindex"
+ });
return list;
}
diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs
index eea5e0ed6..0e47974e2 100644
--- a/RSSDP/ISsdpCommunicationsServer.cs
+++ b/RSSDP/ISsdpCommunicationsServer.cs
@@ -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
diff --git a/RSSDP/ISsdpDeviceLocator.cs b/RSSDP/ISsdpDeviceLocator.cs
index 4b7d10796..3ab271836 100644
--- a/RSSDP/ISsdpDeviceLocator.cs
+++ b/RSSDP/ISsdpDeviceLocator.cs
@@ -58,11 +58,6 @@ namespace Rssdp.Infrastructure
set;
}
- /// <summary>
- /// Returns a boolean indicating whether or not a search is currently active.
- /// </summary>
- bool IsSearching { get; }
-
#endregion
#region Methods
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index c4959c1f2..1f00d5d7c 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
@@ -149,12 +150,7 @@ 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>
- /// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="messageData"/> argument is null.</exception>
- /// <exception cref="System.ObjectDisposedException">Thrown if the <see cref="DisposableManagedObjectBase.IsDisposed"/> property is true (because <seealso cref="DisposableManagedObjectBase.Dispose()" /> has been called previously).</exception>
- public async Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress)
+ public async Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken)
{
if (messageData == null) throw new ArgumentNullException("messageData");
@@ -170,18 +166,18 @@ namespace Rssdp.Infrastructure
// SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP.
for (var i = 0; i < SsdpConstants.UdpResendCount; i++)
{
- var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination)).ToArray();
+ var tasks = sockets.Select(s => SendFromSocket(s, messageData, destination, cancellationToken)).ToArray();
await Task.WhenAll(tasks).ConfigureAwait(false);
- await Task.Delay(100).ConfigureAwait(false);
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
}
- private async Task SendFromSocket(IUdpSocket socket, byte[] messageData, IpEndPointInfo destination)
+ private async Task SendFromSocket(IUdpSocket socket, byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken)
{
try
{
- await socket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false);
+ await socket.SendAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
@@ -230,7 +226,7 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Sends a message to the SSDP multicast address and port.
/// </summary>
- public async Task SendMulticastMessage(string message)
+ public async Task SendMulticastMessage(string message, CancellationToken cancellationToken)
{
if (message == null) throw new ArgumentNullException("messageData");
@@ -238,6 +234,8 @@ namespace Rssdp.Infrastructure
ThrowIfDisposed();
+ cancellationToken.ThrowIfCancellationRequested();
+
EnsureSendSocketCreated();
// SSDP spec recommends sending messages multiple times (not more than 3) to account for possible packet loss over UDP.
@@ -251,9 +249,9 @@ namespace Rssdp.Infrastructure
},
Port = SsdpConstants.MulticastPort
- }).ConfigureAwait(false);
+ }, cancellationToken).ConfigureAwait(false);
- await Task.Delay(100).ConfigureAwait(false);
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
}
@@ -334,7 +332,7 @@ namespace Rssdp.Infrastructure
#region Private Methods
- private async Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination)
+ private async Task SendMessageIfSocketNotDisposed(byte[] messageData, IpEndPointInfo destination, CancellationToken cancellationToken)
{
var sockets = _sendSockets;
if (sockets != null)
@@ -343,7 +341,7 @@ namespace Rssdp.Infrastructure
foreach (var socket in sockets)
{
- await socket.SendAsync(messageData, messageData.Length, destination).ConfigureAwait(false);
+ await socket.SendAsync(messageData, messageData.Length, destination, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/RSSDP/SsdpDeviceLocatorBase.cs b/RSSDP/SsdpDeviceLocatorBase.cs
index 60a792425..4f5eae616 100644
--- a/RSSDP/SsdpDeviceLocatorBase.cs
+++ b/RSSDP/SsdpDeviceLocatorBase.cs
@@ -16,7 +16,7 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Allows you to search the network for a particular device, device types, or UPnP service types. Also listenings for broadcast notifications of device availability and raises events to indicate changes in status.
/// </summary>
- public abstract class SsdpDeviceLocatorBase : DisposableManagedObjectBase, ISsdpDeviceLocator
+ public abstract class SsdpDeviceLocatorBase : DisposableManagedObjectBase
{
#region Fields & Constants
@@ -96,9 +96,9 @@ namespace Rssdp.Infrastructure
/// Performs a search for all devices using the default search timeout.
/// </summary>
/// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
- public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync()
+ public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(CancellationToken cancellationToken)
{
- return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, DefaultSearchWaitTime);
+ return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, DefaultSearchWaitTime, cancellationToken);
}
/// <summary>
@@ -114,7 +114,7 @@ namespace Rssdp.Infrastructure
/// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget)
{
- return SearchAsync(searchTarget, DefaultSearchWaitTime);
+ return SearchAsync(searchTarget, DefaultSearchWaitTime, CancellationToken.None);
}
/// <summary>
@@ -124,27 +124,10 @@ namespace Rssdp.Infrastructure
/// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
public Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(TimeSpan searchWaitTime)
{
- return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, searchWaitTime);
+ return SearchAsync(SsdpConstants.SsdpDiscoverAllSTHeader, searchWaitTime, CancellationToken.None);
}
- /// <summary>
- /// Performs a search for the specified search target (criteria) and search timeout.
- /// </summary>
- /// <param name="searchTarget">The criteria for the search. Value can be;
- /// <list type="table">
- /// <item><term>Root devices</term><description>upnp:rootdevice</description></item>
- /// <item><term>Specific device by UUID</term><description>uuid:&lt;device uuid&gt;</description></item>
- /// <item><term>Device type</term><description>A device namespace and type in format of urn:&lt;device namespace&gt;:device:&lt;device type&gt;:&lt;device version&gt; i.e urn:schemas-upnp-org:device:Basic:1</description></item>
- /// <item><term>Service type</term><description>A service namespace and type in format of urn:&lt;service namespace&gt;:service:&lt;servicetype&gt;:&lt;service version&gt; i.e urn:my-namespace:service:MyCustomService:1</description></item>
- /// </list>
- /// </param>
- /// <param name="searchWaitTime">The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 seconds is recommended by the UPnP 1.1 specification, this method requires the value be greater 1 second if it is not zero. Specify TimeSpan.Zero to return only devices already in the cache.</param>
- /// <remarks>
- /// <para>By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implemetning these services if you know the service type.</para>
- /// </remarks>
- /// <returns>A task whose result is an <see cref="IEnumerable{T}"/> of <see cref="DiscoveredSsdpDevice" /> instances, representing all found devices.</returns>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "expireTask", Justification = "Task is not actually required, but capturing to local variable suppresses compiler warning")]
- public async Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget, TimeSpan searchWaitTime)
+ public async Task<IEnumerable<DiscoveredSsdpDevice>> SearchAsync(string searchTarget, TimeSpan searchWaitTime, CancellationToken cancellationToken)
{
if (searchTarget == null) throw new ArgumentNullException("searchTarget");
if (searchTarget.Length == 0) throw new ArgumentException("searchTarget cannot be an empty string.", "searchTarget");
@@ -158,7 +141,7 @@ namespace Rssdp.Infrastructure
// If searchWaitTime == 0 then we are only going to report unexpired cached items, not actually do a search.
if (searchWaitTime > TimeSpan.Zero)
- await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime)).ConfigureAwait(false);
+ await BroadcastDiscoverMessage(searchTarget, SearchTimeToMXValue(searchWaitTime), cancellationToken).ConfigureAwait(false);
lock (_SearchResultsSynchroniser)
{
@@ -169,7 +152,7 @@ namespace Rssdp.Infrastructure
}
if (searchWaitTime != TimeSpan.Zero)
- await Task.Delay(searchWaitTime).ConfigureAwait(false);
+ await Task.Delay(searchWaitTime, cancellationToken).ConfigureAwait(false);
IEnumerable<DiscoveredSsdpDevice> retVal = null;
@@ -271,17 +254,6 @@ namespace Rssdp.Infrastructure
#region Public Properties
/// <summary>
- /// Returns a boolean indicating whether or not a search is currently in progress.
- /// </summary>
- /// <remarks>
- /// <para>Only one search can be performed at a time, per <see cref="SsdpDeviceLocatorBase"/> instance.</para>
- /// </remarks>
- public bool IsSearching
- {
- get { return _SearchResults != null; }
- }
-
- /// <summary>
/// Sets or returns a string containing the filter for notifications. Notifications not matching the filter will not raise the <see cref="ISsdpDeviceLocator.DeviceAvailable"/> or <see cref="ISsdpDeviceLocator.DeviceUnavailable"/> events.
/// </summary>
/// <remarks>
@@ -407,7 +379,7 @@ namespace Rssdp.Infrastructure
#region Network Message Processing
- private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue)
+ private Task BroadcastDiscoverMessage(string serviceType, TimeSpan mxValue, CancellationToken cancellationToken)
{
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -427,7 +399,7 @@ namespace Rssdp.Infrastructure
var message = SsdpHelper.BuildMessage(header, values);
- return _CommunicationsServer.SendMulticastMessage(message);
+ return _CommunicationsServer.SendMulticastMessage(message, cancellationToken);
}
private void ProcessSearchResponseMessage(HttpResponseMessage message, IpAddressInfo localIpAddress)
diff --git a/RSSDP/SsdpDevicePublisherBase.cs b/RSSDP/SsdpDevicePublisherBase.cs
index c0ae3955d..eda769da6 100644
--- a/RSSDP/SsdpDevicePublisherBase.cs
+++ b/RSSDP/SsdpDevicePublisherBase.cs
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Net.Http;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Threading;
@@ -122,7 +123,7 @@ namespace Rssdp.Infrastructure
SetRebroadcastAliveNotificationsTimer(minCacheTime);
- SendAliveNotifications(device, true);
+ SendAliveNotifications(device, true, CancellationToken.None);
}
}
@@ -161,7 +162,7 @@ namespace Rssdp.Infrastructure
WriteTrace("Device Removed", device);
- await SendByeByeNotifications(device, true).ConfigureAwait(false);
+ await SendByeByeNotifications(device, true, CancellationToken.None).ConfigureAwait(false);
SetRebroadcastAliveNotificationsTimer(minCacheTime);
}
@@ -237,7 +238,7 @@ namespace Rssdp.Infrastructure
#region Search Related Methods
- private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnlocalIpAddress)
+ private void ProcessSearchRequest(string mx, string searchTarget, IpEndPointInfo remoteEndPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken)
{
if (String.IsNullOrEmpty(searchTarget))
{
@@ -295,7 +296,7 @@ namespace Rssdp.Infrastructure
foreach (var device in deviceList)
{
- SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress);
+ SendDeviceSearchResponses(device, remoteEndPoint, receivedOnlocalIpAddress, cancellationToken);
}
}
else
@@ -310,19 +311,19 @@ namespace Rssdp.Infrastructure
return _Devices.Union(_Devices.SelectManyRecursive<SsdpDevice>((d) => d.Devices));
}
- private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress)
+ private void SendDeviceSearchResponses(SsdpDevice device, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken)
{
bool isRootDevice = (device as SsdpRootDevice) != null;
if (isRootDevice)
{
- SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress);
+ SendSearchResponse(SsdpConstants.UpnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress, cancellationToken);
if (this.SupportPnpRootDevice)
- SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress);
+ SendSearchResponse(SsdpConstants.PnpDeviceTypeRootDevice, device, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), endPoint, receivedOnlocalIpAddress, cancellationToken);
}
- SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIpAddress);
+ SendSearchResponse(device.Udn, device, device.Udn, endPoint, receivedOnlocalIpAddress, cancellationToken);
- SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint, receivedOnlocalIpAddress);
+ SendSearchResponse(device.FullDeviceType, device, GetUsn(device.Udn, device.FullDeviceType), endPoint, receivedOnlocalIpAddress, cancellationToken);
}
private static string GetUsn(string udn, string fullDeviceType)
@@ -330,7 +331,7 @@ namespace Rssdp.Infrastructure
return String.Format("{0}::{1}", udn, fullDeviceType);
}
- private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress)
+ private async void SendSearchResponse(string searchTarget, SsdpDevice device, string uniqueServiceName, IpEndPointInfo endPoint, IpAddressInfo receivedOnlocalIpAddress, CancellationToken cancellationToken)
{
var rootDevice = device.ToRootDevice();
@@ -352,7 +353,7 @@ namespace Rssdp.Infrastructure
try
{
- await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint, receivedOnlocalIpAddress).ConfigureAwait(false);
+ await _CommsServer.SendMessage(System.Text.Encoding.UTF8.GetBytes(message), endPoint, receivedOnlocalIpAddress, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -427,7 +428,7 @@ namespace Rssdp.Infrastructure
{
if (IsDisposed) return;
- SendAliveNotifications(device, true);
+ SendAliveNotifications(device, true, CancellationToken.None);
}
//WriteTrace("Completed Sending Alive Notifications For All Devices");
@@ -445,25 +446,25 @@ namespace Rssdp.Infrastructure
//}
}
- private void SendAliveNotifications(SsdpDevice device, bool isRoot)
+ private void SendAliveNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken)
{
if (isRoot)
{
- SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice));
+ SendAliveNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken);
if (this.SupportPnpRootDevice)
- SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice));
+ SendAliveNotification(device, SsdpConstants.PnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.PnpDeviceTypeRootDevice), cancellationToken);
}
- SendAliveNotification(device, device.Udn, device.Udn);
- SendAliveNotification(device, device.FullDeviceType, GetUsn(device.Udn, device.FullDeviceType));
+ SendAliveNotification(device, device.Udn, device.Udn, cancellationToken);
+ SendAliveNotification(device, device.FullDeviceType, GetUsn(device.Udn, device.FullDeviceType), cancellationToken);
foreach (var childDevice in device.Devices)
{
- SendAliveNotifications(childDevice, false);
+ SendAliveNotifications(childDevice, false, cancellationToken);
}
}
- private void SendAliveNotification(SsdpDevice device, string notificationType, string uniqueServiceName)
+ private void SendAliveNotification(SsdpDevice device, string notificationType, string uniqueServiceName, CancellationToken cancellationToken)
{
var rootDevice = device.ToRootDevice();
@@ -483,7 +484,7 @@ namespace Rssdp.Infrastructure
var message = SsdpHelper.BuildMessage(header, values);
- _CommsServer.SendMulticastMessage(message);
+ _CommsServer.SendMulticastMessage(message, cancellationToken);
//WriteTrace(String.Format("Sent alive notification"), device);
}
@@ -492,26 +493,26 @@ namespace Rssdp.Infrastructure
#region ByeBye
- private async Task SendByeByeNotifications(SsdpDevice device, bool isRoot)
+ private async Task SendByeByeNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken)
{
if (isRoot)
{
- await SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice)).ConfigureAwait(false);
+ await SendByeByeNotification(device, SsdpConstants.UpnpDeviceTypeRootDevice, GetUsn(device.Udn, SsdpConstants.UpnpDeviceTypeRootDevice), cancellationToken).ConfigureAwait(false);
if (this.SupportPnpRootDevice)
- await SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice")).ConfigureAwait(false); ;
+ await SendByeByeNotification(device, "pnp:rootdevice", GetUsn(device.Udn, "pnp:rootdevice"), cancellationToken).ConfigureAwait(false); ;
}
- await SendByeByeNotification(device, device.Udn, device.Udn).ConfigureAwait(false); ;
- await SendByeByeNotification(device, String.Format("urn:{0}", device.FullDeviceType), GetUsn(device.Udn, device.FullDeviceType)).ConfigureAwait(false); ;
+ await SendByeByeNotification(device, device.Udn, device.Udn, cancellationToken).ConfigureAwait(false); ;
+ await SendByeByeNotification(device, String.Format("urn:{0}", device.FullDeviceType), GetUsn(device.Udn, device.FullDeviceType), cancellationToken).ConfigureAwait(false); ;
foreach (var childDevice in device.Devices)
{
- await SendByeByeNotifications(childDevice, false).ConfigureAwait(false); ;
+ await SendByeByeNotifications(childDevice, false, cancellationToken).ConfigureAwait(false); ;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "byebye", Justification = "Correct value for this type of notification in SSDP.")]
- private Task SendByeByeNotification(SsdpDevice device, string notificationType, string uniqueServiceName)
+ private Task SendByeByeNotification(SsdpDevice device, string notificationType, string uniqueServiceName, CancellationToken cancellationToken)
{
const string header = "NOTIFY * HTTP/1.1";
@@ -527,7 +528,7 @@ namespace Rssdp.Infrastructure
var message = SsdpHelper.BuildMessage(header, values);
- return _CommsServer.SendMulticastMessage(message);
+ return _CommsServer.SendMulticastMessage(message, cancellationToken);
//WriteTrace(String.Format("Sent byebye notification"), device);
}
@@ -653,13 +654,13 @@ namespace Rssdp.Infrastructure
private void device_DeviceAdded(object sender, DeviceEventArgs e)
{
- SendAliveNotifications(e.Device, false);
+ SendAliveNotifications(e.Device, false, CancellationToken.None);
ConnectToDeviceEvents(e.Device);
}
private void device_DeviceRemoved(object sender, DeviceEventArgs e)
{
- var task = SendByeByeNotifications(e.Device, false);
+ var task = SendByeByeNotifications(e.Device, false, CancellationToken.None);
Task.WaitAll(task);
DisconnectFromDeviceEvents(e.Device);
}
@@ -677,7 +678,7 @@ namespace Rssdp.Infrastructure
//else if (!e.Message.Headers.Contains("MAN"))
// WriteTrace("Ignoring search request - missing MAN header.");
//else
- ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress);
+ ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress, CancellationToken.None);
}
}