Merge pull request #2445 from MediaBrowser/beta

Beta
This commit is contained in:
Luke 2017-02-06 01:08:55 -05:00 committed by GitHub
commit 2ceaa50ea7
104 changed files with 2927 additions and 5865 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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>

View file

@ -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();
}

View file

@ -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)
{

View file

@ -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))

View file

@ -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;

View file

@ -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">

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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

View file

@ -393,7 +393,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
try
{
await provider.Item1.AddMetadata(provider.Item2, enabledChannels, cancellationToken).ConfigureAwait(false);
await AddMetadata(provider.Item1, provider.Item2, enabledChannels, enableCache, cancellationToken).ConfigureAwait(false);
}
catch (NotSupportedException)
{
@ -409,6 +409,120 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return list;
}
private async Task AddMetadata(IListingsProvider provider, ListingsProviderInfo info, List<ChannelInfo> tunerChannels, bool enableCache, CancellationToken cancellationToken)
{
var epgChannels = await GetEpgChannels(provider, info, enableCache, cancellationToken).ConfigureAwait(false);
foreach (var tunerChannel in tunerChannels)
{
var epgChannel = GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels);
if (epgChannel != null)
{
if (!string.IsNullOrWhiteSpace(epgChannel.Name))
{
tunerChannel.Name = epgChannel.Name;
}
}
}
}
private readonly ConcurrentDictionary<string, List<ChannelInfo>> _epgChannels =
new ConcurrentDictionary<string, List<ChannelInfo>>(StringComparer.OrdinalIgnoreCase);
private async Task<List<ChannelInfo>> GetEpgChannels(IListingsProvider provider, ListingsProviderInfo info, bool enableCache, CancellationToken cancellationToken)
{
List<ChannelInfo> result;
if (!enableCache || !_epgChannels.TryGetValue(info.Id, out result))
{
result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
}
return result;
}
private async Task<ChannelInfo> GetEpgChannelFromTunerChannel(IListingsProvider provider, ListingsProviderInfo info, ChannelInfo tunerChannel, CancellationToken cancellationToken)
{
var epgChannels = await GetEpgChannels(provider, info, true, cancellationToken).ConfigureAwait(false);
return GetEpgChannelFromTunerChannel(info, tunerChannel, epgChannels);
}
private string GetMappedChannel(string channelId, List<NameValuePair> mappings)
{
foreach (NameValuePair mapping in mappings)
{
if (StringHelper.EqualsIgnoreCase(mapping.Name, channelId))
{
return mapping.Value;
}
}
return channelId;
}
private ChannelInfo GetEpgChannelFromTunerChannel(ListingsProviderInfo info, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
{
return GetEpgChannelFromTunerChannel(info.ChannelMappings.ToList(), tunerChannel, epgChannels);
}
public ChannelInfo GetEpgChannelFromTunerChannel(List<NameValuePair> mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
{
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
{
var tunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings);
if (string.IsNullOrWhiteSpace(tunerChannelId))
{
tunerChannelId = tunerChannel.TunerChannelId;
}
var channel = epgChannels.FirstOrDefault(i => string.Equals(tunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
if (channel != null)
{
return channel;
}
}
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
{
var tunerChannelNumber = GetMappedChannel(tunerChannel.Number, mappings);
if (string.IsNullOrWhiteSpace(tunerChannelNumber))
{
tunerChannelNumber = tunerChannel.Number;
}
var channel = epgChannels.FirstOrDefault(i => string.Equals(tunerChannelNumber, i.Number, StringComparison.OrdinalIgnoreCase));
if (channel != null)
{
return channel;
}
}
if (!string.IsNullOrWhiteSpace(tunerChannel.Name))
{
var normalizedName = NormalizeName(tunerChannel.Name);
var channel = epgChannels.FirstOrDefault(i => string.Equals(normalizedName, NormalizeName(i.Name ?? string.Empty), StringComparison.OrdinalIgnoreCase));
if (channel != null)
{
return channel;
}
}
return null;
}
private string NormalizeName(string value)
{
return value.Replace(" ", string.Empty).Replace("-", string.Empty);
}
public async Task<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken)
{
var list = new List<ChannelInfo>();
@ -663,7 +777,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
existingTimer.ProductionYear = updatedTimer.ProductionYear;
existingTimer.ProgramId = updatedTimer.ProgramId;
existingTimer.SeasonNumber = updatedTimer.SeasonNumber;
existingTimer.ShortOverview = updatedTimer.ShortOverview;
existingTimer.StartDate = updatedTimer.StartDate;
existingTimer.ShowId = updatedTimer.ShowId;
}
@ -846,49 +959,37 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.Debug("Getting programs for channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
var channelMappings = GetChannelMappings(provider.Item2);
var channelNumber = channel.Number;
string mappedChannelNumber;
if (channelMappings.TryGetValue(channelNumber, out mappedChannelNumber))
var epgChannel = await GetEpgChannelFromTunerChannel(provider.Item1, provider.Item2, channel, cancellationToken).ConfigureAwait(false);
List<ProgramInfo> programs;
if (epgChannel == null)
{
_logger.Debug("Found mapped channel on provider {0}. Tuner channel number: {1}, Mapped channel number: {2}", provider.Item1.Name, channelNumber, mappedChannelNumber);
channelNumber = mappedChannelNumber;
programs = new List<ProgramInfo>();
}
else
{
programs = (await provider.Item1.GetProgramsAsync(provider.Item2, epgChannel.Id, startDateUtc, endDateUtc, cancellationToken)
.ConfigureAwait(false)).ToList();
}
var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken)
.ConfigureAwait(false);
var list = programs.ToList();
// Replace the value that came from the provider with a normalized value
foreach (var program in list)
foreach (var program in programs)
{
program.ChannelId = channelId;
}
if (list.Count > 0)
if (programs.Count > 0)
{
SaveEpgDataForChannel(channelId, list);
SaveEpgDataForChannel(channelId, programs);
return list;
return programs;
}
}
return new List<ProgramInfo>();
}
private Dictionary<string, string> GetChannelMappings(ListingsProviderInfo info)
{
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var mapping in info.ChannelMappings)
{
dict[mapping.Name] = mapping.Value;
}
return dict;
}
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
{
return GetConfiguration().ListingProviders
@ -1755,7 +1856,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Name = timer.Name,
HomePageUrl = timer.HomePageUrl,
ShortOverview = timer.ShortOverview,
Overview = timer.Overview,
Genres = timer.Genres,
CommunityRating = timer.CommunityRating,
@ -1959,11 +2059,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteElementString("genre", genre);
}
if (!string.IsNullOrWhiteSpace(item.ShortOverview))
{
writer.WriteElementString("outline", item.ShortOverview);
}
if (!string.IsNullOrWhiteSpace(item.HomePageUrl))
{
writer.WriteElementString("website", item.HomePageUrl);

View file

@ -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)

View file

@ -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;

View file

@ -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)
var channelNumber = map.logicalChannelNumber;
if (string.IsNullOrWhiteSpace(channelNumber))
{
Dictionary<string, ScheduleDirect.Station> channelPair;
if (_channelPairingCache.TryGetValue(listingsId, out channelPair))
{
ScheduleDirect.Station station;
if (!string.IsNullOrWhiteSpace(channelNumber) && channelPair.TryGetValue(channelNumber, out station))
{
return station;
}
if (!string.IsNullOrWhiteSpace(channelName))
{
channelName = NormalizeName(channelName);
var result = channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase));
if (result != null)
{
return result;
}
}
if (!string.IsNullOrWhiteSpace(channelNumber))
{
return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase));
}
}
return null;
channelNumber = map.channel;
}
if (string.IsNullOrWhiteSpace(channelNumber))
{
channelNumber = map.atscMajor + "." + map.atscMinor;
}
channelNumber = channelNumber.TrimStart('0');
return channelNumber;
}
private void AddToChannelPairCache(string listingsId, string channelNumber, ScheduleDirect.Station schChannel)
{
lock (_channelCacheLock)
{
Dictionary<string, ScheduleDirect.Station> cache;
if (_channelPairingCache.TryGetValue(listingsId, out cache))
{
cache[channelNumber] = schChannel;
}
else
{
cache = new Dictionary<string, ScheduleDirect.Station>();
cache[channelNumber] = schChannel;
_channelPairingCache[listingsId] = cache;
}
}
}
private void ClearPairCache(string listingsId)
{
lock (_channelCacheLock)
{
Dictionary<string, ScheduleDirect.Station> cache;
if (_channelPairingCache.TryGetValue(listingsId, out cache))
{
cache.Clear();
}
}
}
private int GetChannelPairCacheCount(string listingsId)
{
lock (_channelCacheLock)
{
Dictionary<string, ScheduleDirect.Station> cache;
if (_channelPairingCache.TryGetValue(listingsId, out cache))
{
return cache.Count;
}
return 0;
}
}
private string NormalizeName(string value)
{
return value.Replace(" ", string.Empty).Replace("-", string.Empty);
}
public async Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels,
CancellationToken cancellationToken)
{
var listingsId = info.ListingsId;
if (string.IsNullOrWhiteSpace(listingsId))
{
throw new Exception("ListingsId required");
}
var token = await GetToken(info, cancellationToken);
if (string.IsNullOrWhiteSpace(token))
{
throw new Exception("token required");
}
ClearPairCache(listingsId);
var httpOptions = new HttpRequestOptions()
{
Url = ApiUrl + "/lineups/" + listingsId,
UserAgent = UserAgent,
CancellationToken = cancellationToken,
LogErrorResponseBody = true,
// The data can be large so give it some extra time
TimeoutMs = 60000
};
httpOptions.RequestHeaders["token"] = token;
using (var response = await Get(httpOptions, true, info).ConfigureAwait(false))
{
var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Channel>(response);
foreach (ScheduleDirect.Map map in root.map)
{
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;
station = new ScheduleDirect.Station
{
stationID = map.stationID
};
}
if (string.IsNullOrWhiteSpace(channelNumber))
{
channelNumber = map.atscMajor + "." + map.atscMinor;
}
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

View file

@ -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

View file

@ -2112,7 +2112,7 @@ namespace Emby.Server.Implementations.LiveTv
if (timer == null)
{
throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
throw new ResourceNotFoundException(string.Format("SeriesTimer with Id {0} not found", id));
}
var service = GetService(timer.ServiceName);
@ -2884,20 +2884,20 @@ namespace Emby.Server.Implementations.LiveTv
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
}
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId)
{
var config = GetConfiguration();
var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelId, StringComparison.OrdinalIgnoreCase)).ToArray();
if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
if (!string.Equals(tunerChannelId, providerChannelId, StringComparison.OrdinalIgnoreCase))
{
var list = listingsProviderInfo.ChannelMappings.ToList();
list.Add(new NameValuePair
{
Name = tunerChannelNumber,
Value = providerChannelNumber
Name = tunerChannelId,
Value = providerChannelId
});
listingsProviderInfo.ChannelMappings = list.ToArray();
}
@ -2917,31 +2917,33 @@ namespace Emby.Server.Implementations.LiveTv
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
return tunerChannelMappings.First(i => string.Equals(i.Number, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase));
}
public TunerChannelMapping GetTunerChannelMapping(ChannelInfo channel, List<NameValuePair> mappings, List<ChannelInfo> providerChannels)
public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, List<NameValuePair> mappings, List<ChannelInfo> epgChannels)
{
var result = new TunerChannelMapping
{
Name = channel.Number + " " + channel.Name,
Number = channel.Number
Name = tunerChannel.Name,
Id = tunerChannel.TunerChannelId
};
var mapping = mappings.FirstOrDefault(i => string.Equals(i.Name, channel.Number, StringComparison.OrdinalIgnoreCase));
var providerChannelNumber = channel.Number;
if (mapping != null)
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
{
providerChannelNumber = mapping.Value;
result.Name = tunerChannel.Number + " " + result.Name;
}
var providerChannel = providerChannels.FirstOrDefault(i => string.Equals(i.Number, providerChannelNumber, StringComparison.OrdinalIgnoreCase));
if (string.IsNullOrWhiteSpace(result.Id))
{
result.Id = tunerChannel.Id;
}
var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, epgChannels);
if (providerChannel != null)
{
result.ProviderChannelNumber = providerChannel.Number;
result.ProviderChannelName = providerChannel.Name;
result.ProviderChannelId = providerChannel.Id;
}
return result;

View file

@ -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);

View file

@ -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)

View file

@ -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);

View file

@ -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))
{

View file

@ -144,11 +144,18 @@ namespace Emby.Server.Implementations.TV
// If viewing all next up for all series, remove first episodes
// But if that returns empty, keep those first episodes (avoid completely empty view)
var alwaysEnableFirstEpisode = !string.IsNullOrWhiteSpace(request.SeriesId);
var anyFound = false;
return allNextUp
.Where(i =>
{
if (alwaysEnableFirstEpisode || i.Item1 != DateTime.MinValue)
{
anyFound = true;
return true;
}
if (!anyFound && i.Item1 == DateTime.MinValue)
{
return true;
}

View file

@ -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);
}

View file

@ -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" />

View file

@ -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);
}

View file

@ -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);
}

View file

@ -276,8 +276,6 @@ namespace MediaBrowser.Api
item.Tagline = request.Taglines.FirstOrDefault();
}
item.ShortOverview = request.ShortOverview;
item.Keywords = request.Keywords;
if (request.Studios != null)

View file

@ -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(),

View file

@ -172,6 +172,10 @@
<Project>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</Project>
<Name>MediaBrowser.Controller</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj">
<Project>{0bd82fa6-eb8a-4452-8af5-74f9c3849451}</Project>
<Name>MediaBrowser.MediaEncoding</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name>

File diff suppressed because it is too large Load diff

View file

@ -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),

View file

@ -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),

View file

@ -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";

View file

@ -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()),

View file

@ -9,6 +9,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
@ -95,8 +96,10 @@ namespace MediaBrowser.Api.Playback.Progressive
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
// Get the output codec name
var videoCodec = GetVideoEncoder(state);
var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
var format = string.Empty;
var keyFrame = string.Empty;
@ -107,23 +110,46 @@ namespace MediaBrowser.Api.Playback.Progressive
format = " -f mp4 -movflags frag_keyframe+empty_moov";
}
var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase));
var inputModifier = GetInputModifier(state);
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7} -y \"{8}\"",
var subtitleArguments = state.SubtitleStream != null &&
state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed
? GetSubtitleArguments(state)
: string.Empty;
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"",
inputModifier,
GetInputArgument(state),
EncodingHelper.GetInputArgument(state, encodingOptions),
keyFrame,
GetMapArgs(state),
EncodingHelper.GetMapArgs(state),
GetVideoArguments(state, videoCodec),
threads,
GetAudioArguments(state),
subtitleArguments,
format,
outputPath
).Trim();
}
private string GetSubtitleArguments(StreamState state)
{
var format = state.SupportedSubtitleCodecs.FirstOrDefault();
string codec;
if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase))
{
codec = "copy";
}
else
{
codec = format;
}
return " -codec:s:0 " + codec;
}
/// <summary>
/// Gets video arguments to pass to ffmpeg
/// </summary>
@ -141,7 +167,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
args += " -bsf:v h264_mp4toannexb";
}
@ -170,7 +196,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
var outputSizeParam = GetOutputSizeParam(state, videoCodec);
var outputSizeParam = EncodingHelper.GetOutputSizeParam(state, videoCodec);
args += outputSizeParam;
hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
}
@ -184,7 +210,8 @@ namespace MediaBrowser.Api.Playback.Progressive
args += " -avoid_negative_ts disabled -start_at_zero";
}
var qualityParam = GetVideoQualityParam(state, videoCodec);
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
var qualityParam = EncodingHelper.GetVideoQualityParam(state, videoCodec, encodingOptions, GetDefaultH264Preset());
if (!string.IsNullOrEmpty(qualityParam))
{
@ -194,7 +221,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// This is for internal graphical subs
if (hasGraphicalSubs)
{
args += GetGraphicalSubtitleParam(state, videoCodec);
args += EncodingHelper.GetGraphicalSubtitleParam(state, videoCodec);
}
if (!state.RunTimeTicks.HasValue)
@ -219,7 +246,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
// Get the output codec name
var codec = GetAudioEncoder(state);
var codec = EncodingHelper.GetAudioEncoder(state);
var args = "-codec:a:0 " + codec;
@ -243,7 +270,7 @@ namespace MediaBrowser.Api.Playback.Progressive
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
args += " " + GetAudioFilterParam(state, false);
args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), false);
return args;
}

View file

@ -1,4 +1,5 @@
using MediaBrowser.Model.Dlna;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Playback
@ -6,7 +7,7 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Class StreamRequest
/// </summary>
public class StreamRequest
public class StreamRequest : BaseEncodingJobOptions
{
/// <summary>
/// Gets or sets the id.
@ -28,45 +29,7 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string AudioCodec { get; set; }
/// <summary>
/// Gets or sets the start time ticks.
/// </summary>
/// <value>The start time ticks.</value>
[ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public long? StartTimeTicks { get; set; }
/// <summary>
/// Gets or sets the audio bit rate.
/// </summary>
/// <value>The audio bit rate.</value>
[ApiMember(Name = "AudioBitRate", Description = "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? AudioBitRate { get; set; }
/// <summary>
/// Gets or sets the audio channels.
/// </summary>
/// <value>The audio channels.</value>
[ApiMember(Name = "AudioChannels", Description = "Optional. Specify a specific number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? AudioChannels { get; set; }
[ApiMember(Name = "MaxAudioChannels", Description = "Optional. Specify a maximum number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxAudioChannels { get; set; }
public int? TranscodingMaxAudioChannels { get; set; }
/// <summary>
/// Gets or sets the audio sample rate.
/// </summary>
/// <value>The audio sample rate.</value>
[ApiMember(Name = "AudioSampleRate", Description = "Optional. Specify a specific audio sample rate, e.g. 44100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? AudioSampleRate { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this <see cref="StreamRequest" /> is static.
/// </summary>
/// <value><c>true</c> if static; otherwise, <c>false</c>.</value>
[ApiMember(Name = "Static", Description = "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool Static { get; set; }
public string SubtitleCodec { get; set; }
[ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string DeviceProfileId { get; set; }
@ -79,102 +42,6 @@ namespace MediaBrowser.Api.Playback
public class VideoStreamRequest : StreamRequest
{
/// <summary>
/// Gets or sets the video codec.
/// </summary>
/// <value>The video codec.</value>
[ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string VideoCodec { get; set; }
/// <summary>
/// Gets or sets the video bit rate.
/// </summary>
/// <value>The video bit rate.</value>
[ApiMember(Name = "VideoBitRate", Description = "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? VideoBitRate { get; set; }
/// <summary>
/// Gets or sets the index of the audio stream.
/// </summary>
/// <value>The index of the audio stream.</value>
[ApiMember(Name = "AudioStreamIndex", Description = "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? AudioStreamIndex { get; set; }
/// <summary>
/// Gets or sets the index of the video stream.
/// </summary>
/// <value>The index of the video stream.</value>
[ApiMember(Name = "VideoStreamIndex", Description = "Optional. The index of the video stream to use. If omitted the first video stream will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? VideoStreamIndex { get; set; }
/// <summary>
/// Gets or sets the index of the subtitle stream.
/// </summary>
/// <value>The index of the subtitle stream.</value>
[ApiMember(Name = "SubtitleStreamIndex", Description = "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? SubtitleStreamIndex { get; set; }
/// <summary>
/// Gets or sets the width.
/// </summary>
/// <value>The width.</value>
[ApiMember(Name = "Width", Description = "Optional. The fixed horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Width { get; set; }
/// <summary>
/// Gets or sets the height.
/// </summary>
/// <value>The height.</value>
[ApiMember(Name = "Height", Description = "Optional. The fixed vertical resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Height { get; set; }
/// <summary>
/// Gets or sets the width of the max.
/// </summary>
/// <value>The width of the max.</value>
[ApiMember(Name = "MaxWidth", Description = "Optional. The maximum horizontal resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxWidth { get; set; }
/// <summary>
/// Gets or sets the height of the max.
/// </summary>
/// <value>The height of the max.</value>
[ApiMember(Name = "MaxHeight", Description = "Optional. The maximum vertical resolution of the encoded video.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxHeight { get; set; }
[ApiMember(Name = "MaxRefFrames", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxRefFrames { get; set; }
[ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxVideoBitDepth { get; set; }
/// <summary>
/// Gets or sets the framerate.
/// </summary>
/// <value>The framerate.</value>
[ApiMember(Name = "Framerate", Description = "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", IsRequired = false, DataType = "double", ParameterType = "query", Verb = "GET")]
public float? Framerate { get; set; }
[ApiMember(Name = "MaxFramerate", Description = "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", IsRequired = false, DataType = "double", ParameterType = "query", Verb = "GET")]
public float? MaxFramerate { get; set; }
/// <summary>
/// Gets or sets the profile.
/// </summary>
/// <value>The profile.</value>
[ApiMember(Name = "Profile", Description = "Optional. Specify a specific h264 profile, e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Profile { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>
/// <value>The level.</value>
[ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Level { get; set; }
[ApiMember(Name = "SubtitleDeliveryMethod", Description = "Optional. Specify the subtitle delivery method.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public SubtitleDeliveryMethod SubtitleMethod { get; set; }
/// <summary>
/// Gets a value indicating whether this instance has fixed resolution.
/// </summary>
@ -187,18 +54,6 @@ namespace MediaBrowser.Api.Playback
}
}
[ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool EnableAutoStreamCopy { get; set; }
[ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool CopyTimestamps { get; set; }
public bool EnableSubtitlesInManifest { get; set; }
public bool RequireAvc { get; set; }
public VideoStreamRequest()
{
EnableAutoStreamCopy = true;
}
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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)

View file

@ -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
});
}

View file

@ -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;

View file

@ -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:

View file

@ -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();

View file

@ -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);

View file

@ -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>

View file

@ -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);

View file

@ -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; }

View file

@ -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; }
}
}

View file

@ -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;
}
}
}

View file

@ -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.

View file

@ -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();

View file

@ -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);

View file

@ -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()),

View file

@ -36,6 +36,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
protected EncodingHelper EncodingHelper;
protected BaseEncoder(MediaEncoder mediaEncoder,
ILogger logger,
IServerConfigurationManager configurationManager,
@ -56,6 +58,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
SubtitleEncoder = subtitleEncoder;
MediaSourceManager = mediaSourceManager;
ProcessFactory = processFactory;
EncodingHelper = new EncodingHelper(MediaEncoder, ConfigurationManager, FileSystem, SubtitleEncoder);
}
public async Task<EncodingJob> Start(EncodingJobOptions options,
@ -63,7 +67,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
CancellationToken cancellationToken)
{
var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager)
.CreateJob(options, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
.CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
FileSystem.CreateDirectory(Path.GetDirectoryName(encodingJob.OutputFilePath));
@ -285,72 +289,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return null;
}
/// <summary>
/// Gets the number of threads.
/// </summary>
/// <returns>System.Int32.</returns>
protected int GetNumberOfThreads(EncodingJob job, bool isWebm)
{
return job.Options.CpuCoreLimit ?? 0;
}
protected string GetInputModifier(EncodingJob state, bool genPts = true)
{
var inputModifier = string.Empty;
var probeSize = GetProbeSizeArgument(state);
inputModifier += " " + probeSize;
inputModifier = inputModifier.Trim();
var userAgentParam = GetUserAgentParam(state);
if (!string.IsNullOrWhiteSpace(userAgentParam))
{
inputModifier += " " + userAgentParam;
}
inputModifier = inputModifier.Trim();
inputModifier += " " + GetFastSeekCommandLineParameter(state.Options);
inputModifier = inputModifier.Trim();
if (state.IsVideoRequest && genPts)
{
inputModifier += " -fflags +genpts";
}
if (!string.IsNullOrEmpty(state.InputAudioSync))
{
inputModifier += " -async " + state.InputAudioSync;
}
if (!string.IsNullOrEmpty(state.InputVideoSync))
{
inputModifier += " -vsync " + state.InputVideoSync;
}
if (state.ReadInputAtNativeFramerate)
{
inputModifier += " -re";
}
var videoDecoder = GetVideoDecoder(state);
if (!string.IsNullOrWhiteSpace(videoDecoder))
{
inputModifier += " " + videoDecoder;
}
//if (state.IsVideoRequest)
//{
// if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
// {
// //inputModifier += " -noaccurate_seek";
// }
//}
return inputModifier;
}
/// <summary>
/// Gets the name of the output video codec
/// </summary>
@ -405,130 +343,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return null;
}
private string GetUserAgentParam(EncodingJob state)
{
string useragent = null;
state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
if (!string.IsNullOrWhiteSpace(useragent))
{
return "-user-agent \"" + useragent + "\"";
}
return string.Empty;
}
/// <summary>
/// Gets the probe size argument.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
private string GetProbeSizeArgument(EncodingJob state)
{
if (state.PlayableStreamFileNames.Count > 0)
{
return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
}
return MediaEncoder.GetProbeSizeAndAnalyzeDurationArgument(new[] { state.MediaPath }, state.InputProtocol);
}
/// <summary>
/// Gets the fast seek command line parameter.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.String.</returns>
/// <value>The fast seek command line parameter.</value>
protected string GetFastSeekCommandLineParameter(EncodingJobOptions request)
{
var time = request.StartTimeTicks ?? 0;
if (time > 0)
{
return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time));
}
return string.Empty;
}
/// <summary>
/// Gets the input argument.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected string GetInputArgument(EncodingJob state)
{
var arg = string.Format("-i {0}", GetInputPathArgument(state));
if (state.SubtitleStream != null && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode)
{
if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
if (state.VideoStream != null && state.VideoStream.Width.HasValue)
{
// This is hacky but not sure how to get the exact subtitle resolution
double height = state.VideoStream.Width.Value;
height /= 16;
height *= 9;
arg += string.Format(" -canvas_size {0}:{1}", state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture), Convert.ToInt32(height).ToString(CultureInfo.InvariantCulture));
}
var subtitlePath = state.SubtitleStream.Path;
if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase))
{
var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
if (FileSystem.FileExists(idxFile))
{
subtitlePath = idxFile;
}
}
arg += " -i \"" + subtitlePath + "\"";
}
}
if (state.IsVideoRequest)
{
var encodingOptions = GetEncodingOptions();
var videoEncoder = EncodingJobFactory.GetVideoEncoder(MediaEncoder, state, encodingOptions);
if (videoEncoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
{
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode;
var hwOutputFormat = "vaapi";
if (hasGraphicalSubs)
{
hwOutputFormat = "yuv420p";
}
arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
}
}
return arg.Trim();
}
private string GetInputPathArgument(EncodingJob state)
{
var protocol = state.InputProtocol;
var mediaPath = state.MediaPath ?? string.Empty;
var inputPath = new[] { mediaPath };
if (state.IsInputVideo)
{
if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
{
inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
}
}
return MediaEncoder.GetInputArgument(inputPath, protocol);
}
private async Task AcquireResources(EncodingJob state, CancellationToken cancellationToken)
{
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
@ -544,11 +358,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
}, false, cancellationToken).ConfigureAwait(false);
AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.Options);
EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, null);
if (state.IsVideoRequest)
{
EncodingJobFactory.TryStreamCopy(state, state.Options);
EncodingHelper.TryStreamCopy(state);
}
}
@ -557,603 +371,5 @@ namespace MediaBrowser.MediaEncoding.Encoder
await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false);
}
}
private void AttachMediaSourceInfo(EncodingJob state,
MediaSourceInfo mediaSource,
EncodingJobOptions videoRequest)
{
EncodingJobFactory.AttachMediaSourceInfo(state, mediaSource, videoRequest);
}
/// <summary>
/// Gets the internal graphical subtitle param.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</param>
/// <returns>System.String.</returns>
protected async Task<string> GetGraphicalSubtitleParam(EncodingJob state, string outputVideoCodec)
{
var outputSizeParam = string.Empty;
var request = state.Options;
// Add resolution params, if specified
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
{
outputSizeParam = await GetOutputSizeParam(state, outputVideoCodec).ConfigureAwait(false);
outputSizeParam = outputSizeParam.TrimEnd('"');
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase));
}
else
{
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
}
}
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0)
{
outputSizeParam = ",format=nv12|vaapi,hwupload";
}
var videoSizeParam = string.Empty;
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
{
videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
}
var mapPrefix = state.SubtitleStream.IsExternal ?
1 :
0;
var subtitleStreamIndex = state.SubtitleStream.IsExternal
? 0
: state.SubtitleStream.Index;
return string.Format(" -filter_complex \"[{0}:{1}]format=yuva444p{4},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{2}] [sub] overlay{3}\"",
mapPrefix.ToString(UsCulture),
subtitleStreamIndex.ToString(UsCulture),
state.VideoStream.Index.ToString(UsCulture),
outputSizeParam,
videoSizeParam);
}
/// <summary>
/// Gets the video bitrate to specify on the command line
/// </summary>
/// <param name="state">The state.</param>
/// <param name="videoEncoder">The video codec.</param>
/// <returns>System.String.</returns>
protected string GetVideoQualityParam(EncodingJob state, string videoEncoder)
{
var param = string.Empty;
var isVc1 = state.VideoStream != null &&
string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
param = "-preset superfast";
param += " -crf 23";
}
else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
{
param = "-preset fast";
param += " -crf 28";
}
// h264 (h264_qsv)
else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
param = "-preset 7 -look_ahead 0";
}
// h264 (h264_nvenc)
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
param = "-preset llhq";
}
// webm
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase))
{
// Values 0-3, 0 being highest quality but slower
var profileScore = 0;
string crf;
var qmin = "0";
var qmax = "50";
crf = "10";
if (isVc1)
{
profileScore++;
}
// Max of 2
profileScore = Math.Min(profileScore, 2);
// http://www.webmproject.org/docs/encoder-parameters/
param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
profileScore.ToString(UsCulture),
crf,
qmin,
qmax);
}
else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
}
// asf/wmv
else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase))
{
param = "-qmin 2";
}
else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
param = "-mbd 2";
}
param += GetVideoBitrateParam(state, videoEncoder);
var framerate = GetFramerateParam(state);
if (framerate.HasValue)
{
param += string.Format(" -r {0}", framerate.Value.ToString(UsCulture));
}
if (!string.IsNullOrEmpty(state.OutputVideoSync))
{
param += " -vsync " + state.OutputVideoSync;
}
if (!string.IsNullOrEmpty(state.Options.Profile))
{
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
// not supported by h264_omx
param += " -profile:v " + state.Options.Profile;
}
}
var levelString = state.Options.Level.HasValue ? state.Options.Level.Value.ToString(CultureInfo.InvariantCulture) : null;
if (!string.IsNullOrEmpty(levelString))
{
levelString = NormalizeTranscodingLevel(state.OutputVideoCodec, levelString);
// h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
// also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) ||
string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
switch (levelString)
{
case "30":
param += " -level 3.0";
break;
case "31":
param += " -level 3.1";
break;
case "32":
param += " -level 3.2";
break;
case "40":
param += " -level 4.0";
break;
case "41":
param += " -level 4.1";
break;
case "42":
param += " -level 4.2";
break;
case "50":
param += " -level 5.0";
break;
case "51":
param += " -level 5.1";
break;
case "52":
param += " -level 5.2";
break;
default:
param += " -level " + levelString;
break;
}
}
else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
param += " -level " + levelString;
}
}
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt yuv420p " + param;
}
return param;
}
private string NormalizeTranscodingLevel(string videoCodec, string level)
{
double requestLevel;
// Clients may direct play higher than level 41, but there's no reason to transcode higher
if (double.TryParse(level, NumberStyles.Any, UsCulture, out requestLevel))
{
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
if (requestLevel > 41)
{
return "41";
}
}
}
return level;
}
protected string GetVideoBitrateParam(EncodingJob state, string videoCodec)
{
var bitrate = state.OutputVideoBitrate;
if (bitrate.HasValue)
{
if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
{
// With vpx when crf is used, b:v becomes a max rate
// https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
}
if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
}
if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
// h264
return string.Format(" -maxrate {0} -bufsize {1}",
bitrate.Value.ToString(UsCulture),
(bitrate.Value * 2).ToString(UsCulture));
}
// h264
return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
bitrate.Value.ToString(UsCulture),
(bitrate.Value * 2).ToString(UsCulture));
}
return string.Empty;
}
protected double? GetFramerateParam(EncodingJob state)
{
if (state.Options != null)
{
if (state.Options.Framerate.HasValue)
{
return state.Options.Framerate.Value;
}
var maxrate = state.Options.MaxFramerate;
if (maxrate.HasValue && state.VideoStream != null)
{
var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
if (contentRate.HasValue && contentRate.Value > maxrate.Value)
{
return maxrate;
}
}
}
return null;
}
/// <summary>
/// Gets the map args.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected virtual string GetMapArgs(EncodingJob state)
{
// If we don't have known media info
// If input is video, use -sn to drop subtitles
// Otherwise just return empty
if (state.VideoStream == null && state.AudioStream == null)
{
return state.IsInputVideo ? "-sn" : string.Empty;
}
// We have media info, but we don't know the stream indexes
if (state.VideoStream != null && state.VideoStream.Index == -1)
{
return "-sn";
}
// We have media info, but we don't know the stream indexes
if (state.AudioStream != null && state.AudioStream.Index == -1)
{
return state.IsInputVideo ? "-sn" : string.Empty;
}
var args = string.Empty;
if (state.VideoStream != null)
{
args += string.Format("-map 0:{0}", state.VideoStream.Index);
}
else
{
// No known video stream
args += "-vn";
}
if (state.AudioStream != null)
{
args += string.Format(" -map 0:{0}", state.AudioStream.Index);
}
else
{
args += " -map -0:a";
}
if (state.SubtitleStream == null || state.Options.SubtitleMethod == SubtitleDeliveryMethod.Hls)
{
args += " -map -0:s";
}
else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
args += " -map 1:0 -sn";
}
return args;
}
/// <summary>
/// Determines whether the specified stream is H264.
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
protected bool IsH264(MediaStream stream)
{
var codec = stream.Codec ?? string.Empty;
return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 ||
codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
}
/// <summary>
/// If we're going to put a fixed size on the command line, this will calculate it
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</param>
/// <param name="allowTimeStampCopy">if set to <c>true</c> [allow time stamp copy].</param>
/// <returns>System.String.</returns>
protected async Task<string> GetOutputSizeParam(EncodingJob state,
string outputVideoCodec,
bool allowTimeStampCopy = true)
{
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
var request = state.Options;
var filters = new List<string>();
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
filters.Add("yadif=0:-1:0");
}
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
// Work around vaapi's reduced scaling features
var scaler = "scale_vaapi";
// Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
// (outputWidth, outputHeight). The user may request precise output dimensions or maximum
// output dimensions. Output dimensions are guaranteed to be even.
decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
if (outputWidth > maximumWidth || outputHeight > maximumHeight)
{
var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
}
outputWidth = 2 * Math.Truncate(outputWidth / 2);
outputHeight = 2 * Math.Truncate(outputHeight / 2);
if (outputWidth != inputWidth || outputHeight != inputHeight)
{
filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
}
}
else
{
// If fixed dimensions were supplied
if (request.Width.HasValue && request.Height.HasValue)
{
var widthParam = request.Width.Value.ToString(UsCulture);
var heightParam = request.Height.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
}
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
{
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
}
// If a fixed width was requested
else if (request.Width.HasValue)
{
var widthParam = request.Width.Value.ToString(UsCulture);
filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
}
// If a fixed height was requested
else if (request.Height.HasValue)
{
var heightParam = request.Height.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
}
// If a max width was requested
else if (request.MaxWidth.HasValue)
{
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
}
// If a max height was requested
else if (request.MaxHeight.HasValue)
{
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
}
}
var output = string.Empty;
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode)
{
var subParam = await GetTextSubtitleParam(state).ConfigureAwait(false);
filters.Add(subParam);
if (allowTimeStampCopy)
{
output += " -copyts";
}
}
if (filters.Count > 0)
{
output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray()));
}
return output;
}
/// <summary>
/// Gets the text subtitle param.
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
protected async Task<string> GetTextSubtitleParam(EncodingJob state)
{
var seconds = Math.Round(TimeSpan.FromTicks(state.Options.StartTimeTicks ?? 0).TotalSeconds);
if (state.SubtitleStream.IsExternal)
{
var subtitlePath = state.SubtitleStream.Path;
var charsetParam = string.Empty;
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
{
var charenc = await SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).ConfigureAwait(false);
if (!string.IsNullOrEmpty(charenc))
{
charsetParam = ":charenc=" + charenc;
}
}
// TODO: Perhaps also use original_size=1920x800 ??
return string.Format("subtitles=filename='{0}'{1},setpts=PTS -{2}/TB",
MediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
charsetParam,
seconds.ToString(UsCulture));
}
var mediaPath = state.MediaPath ?? string.Empty;
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
MediaEncoder.EscapeSubtitleFilterPath(mediaPath),
state.InternalSubtitleStreamOffset.ToString(UsCulture),
seconds.ToString(UsCulture));
}
protected string GetAudioFilterParam(EncodingJob state, bool isHls)
{
var volParam = string.Empty;
var audioSampleRate = string.Empty;
var channels = state.OutputAudioChannels;
// Boost volume to 200% when downsampling from 6ch to 2ch
if (channels.HasValue && channels.Value <= 2)
{
if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !GetEncodingOptions().DownMixAudioBoost.Equals(1))
{
volParam = ",volume=" + GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
}
}
if (state.OutputAudioSampleRate.HasValue)
{
audioSampleRate = state.OutputAudioSampleRate.Value + ":";
}
var adelay = isHls ? "adelay=1," : string.Empty;
var pts = string.Empty;
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode && !state.Options.CopyTimestamps)
{
var seconds = TimeSpan.FromTicks(state.Options.StartTimeTicks ?? 0).TotalSeconds;
pts = string.Format(",asetpts=PTS-{0}/TB", Math.Round(seconds).ToString(UsCulture));
}
return string.Format("-af \"{0}aresample={1}async={4}{2}{3}\"",
adelay,
audioSampleRate,
volParam,
pts,
state.OutputAudioSync);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.MediaEncoding.Encoder
{
public class EncodingJob : IDisposable
public class EncodingJob : EncodingJobInfo, IDisposable
{
public bool HasExited { get; internal set; }
public bool IsCancelled { get; internal set; }
@ -25,46 +25,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
public Stream LogFileStream { get; set; }
public IProgress<double> Progress { get; set; }
public TaskCompletionSource<bool> TaskCompletionSource;
public EncodingJobOptions Options { get; set; }
public string InputContainer { get; set; }
public MediaSourceInfo MediaSource { get; set; }
public MediaStream AudioStream { get; set; }
public MediaStream VideoStream { get; set; }
public MediaStream SubtitleStream { get; set; }
public IIsoMount IsoMount { get; set; }
public bool ReadInputAtNativeFramerate { get; set; }
public bool IsVideoRequest { get; set; }
public string InputAudioSync { get; set; }
public string InputVideoSync { get; set; }
public EncodingJobOptions Options
{
get { return (EncodingJobOptions) BaseRequest; }
set { BaseRequest = value; }
}
public string Id { get; set; }
public string MediaPath { get; set; }
public MediaProtocol InputProtocol { get; set; }
public bool IsInputVideo { get; set; }
public VideoType VideoType { get; set; }
public IsoType? IsoType { get; set; }
public List<string> PlayableStreamFileNames { get; set; }
public List<string> SupportedAudioCodecs { get; set; }
public Dictionary<string, string> RemoteHttpHeaders { get; set; }
public TransportStreamTimestamp InputTimestamp { get; set; }
public bool DeInterlace { get; set; }
public string MimeType { get; set; }
public bool EstimateContentLength { get; set; }
public bool EnableMpegtsM2TsMode { get; set; }
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public long? EncodingDurationTicks { get; set; }
public string LiveStreamId { get; set; }
public long? RunTimeTicks;
public string ItemType { get; set; }
public long? InputBitrate { get; set; }
public long? InputFileSize { get; set; }
public string OutputAudioSync = "1";
public string OutputVideoSync = "vfr";
public string AlbumCoverPath { get; set; }
public string GetMimeType(string outputPath)
@ -80,17 +58,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly ILogger _logger;
private readonly IMediaSourceManager _mediaSourceManager;
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager)
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) :
base(logger)
{
_logger = logger;
_mediaSourceManager = mediaSourceManager;
Id = Guid.NewGuid().ToString("N");
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
_logger = logger;
SupportedAudioCodecs = new List<string>();
PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
TaskCompletionSource = new TaskCompletionSource<bool>();
}
@ -118,23 +93,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
private void DisposeIsoMount()
{
if (IsoMount != null)
{
try
{
IsoMount.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing iso mount", ex);
}
IsoMount = null;
}
}
private async void DisposeLiveStream()
{
if (MediaSource.RequiresClosing)
@ -150,15 +108,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
public int InternalSubtitleStreamOffset { get; set; }
public string OutputFilePath { get; set; }
public string OutputVideoCodec { get; set; }
public string OutputAudioCodec { get; set; }
public int? OutputAudioChannels;
public int? OutputAudioSampleRate;
public int? OutputAudioBitrate;
public int? OutputVideoBitrate;
public string ActualOutputVideoCodec
{
@ -313,25 +264,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
/// <summary>
/// Predicts the audio sample rate that will be in the output stream
/// </summary>
public double? TargetVideoLevel
{
get
{
var stream = VideoStream;
return Options.Level.HasValue && !Options.Static
? Options.Level.Value
: stream == null ? null : stream.Level;
}
}
public TransportStreamTimestamp TargetTimestamp
{
get
{
var defaultValue = string.Equals(Options.OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
TransportStreamTimestamp.Valid :
TransportStreamTimestamp.None;

View file

@ -33,7 +33,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_config = config;
}
public async Task<EncodingJob> CreateJob(EncodingJobOptions options, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken)
public async Task<EncodingJob> CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken)
{
var request = options;
@ -49,6 +49,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
Progress = progress
};
if (!string.IsNullOrWhiteSpace(request.VideoCodec))
{
state.SupportedVideoCodecs = request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
}
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
{
state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
@ -76,7 +82,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var videoRequest = state.Options;
AttachMediaSourceInfo(state, mediaSource, videoRequest);
encodingHelper.AttachMediaSourceInfo(state, mediaSource, null);
//var container = Path.GetExtension(state.RequestedUrl);
@ -89,17 +95,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
//state.OutputContainer = (container ?? string.Empty).TrimStart('.');
state.OutputAudioBitrate = GetAudioBitrateParam(state.Options, state.AudioStream);
state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.Options, state.AudioStream);
state.OutputAudioSampleRate = request.AudioSampleRate;
state.OutputAudioCodec = state.Options.AudioCodec;
state.OutputAudioChannels = GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec);
state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec);
if (videoRequest != null)
{
state.OutputVideoCodec = state.Options.VideoCodec;
state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec);
state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec);
if (state.OutputVideoBitrate.HasValue)
{
@ -120,7 +126,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (videoRequest != null)
{
TryStreamCopy(state, videoRequest);
encodingHelper.TryStreamCopy(state);
}
//state.OutputFilePath = GetOutputFilePath(state);
@ -128,104 +134,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return state;
}
internal static void TryStreamCopy(EncodingJob state,
EncodingJobOptions videoRequest)
{
if (state.IsVideoRequest)
{
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
}
}
internal static void AttachMediaSourceInfo(EncodingJob state,
MediaSourceInfo mediaSource,
EncodingJobOptions videoRequest)
{
state.MediaPath = mediaSource.Path;
state.InputProtocol = mediaSource.Protocol;
state.InputContainer = mediaSource.Container;
state.InputFileSize = mediaSource.Size;
state.InputBitrate = mediaSource.Bitrate;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
if (mediaSource.VideoType.HasValue)
{
state.VideoType = mediaSource.VideoType.Value;
}
state.IsoType = mediaSource.IsoType;
state.PlayableStreamFileNames = mediaSource.PlayableStreamFileNames.ToList();
if (mediaSource.Timestamp.HasValue)
{
state.InputTimestamp = mediaSource.Timestamp.Value;
}
state.InputProtocol = mediaSource.Protocol;
state.MediaPath = mediaSource.Path;
state.RunTimeTicks = mediaSource.RunTimeTicks;
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
state.InputBitrate = mediaSource.Bitrate;
state.InputFileSize = mediaSource.Size;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
if (state.ReadInputAtNativeFramerate ||
mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
{
state.OutputAudioSync = "1000";
state.InputVideoSync = "-1";
state.InputAudioSync = "1";
}
if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase))
{
// Seeing some stuttering when transcoding wma to audio-only HLS
state.InputAudioSync = "1";
}
var mediaStreams = mediaSource.MediaStreams;
if (videoRequest != null)
{
if (string.IsNullOrEmpty(videoRequest.VideoCodec))
{
videoRequest.VideoCodec = InferVideoCodec(videoRequest.OutputContainer);
}
state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal)
{
state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream);
}
if (state.VideoStream != null && state.VideoStream.IsInterlaced)
{
state.DeInterlace = true;
}
EnforceResolutionLimit(state, videoRequest);
}
else
{
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
}
state.MediaSource = mediaSource;
}
protected EncodingOptions GetEncodingOptions()
{
return _config.GetConfiguration<EncodingOptions>("encoding");
@ -300,203 +208,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return "copy";
}
/// <summary>
/// Determines which stream will be used for playback
/// </summary>
/// <param name="allStream">All stream.</param>
/// <param name="desiredIndex">Index of the desired.</param>
/// <param name="type">The type.</param>
/// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
/// <returns>MediaStream.</returns>
private static MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
{
var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
if (desiredIndex.HasValue)
{
var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
if (stream != null)
{
return stream;
}
}
if (type == MediaStreamType.Video)
{
streams = streams.Where(i => !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)).ToList();
}
if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
{
return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
streams.FirstOrDefault();
}
// Just return the first one
return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
}
/// <summary>
/// Enforces the resolution limit.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="videoRequest">The video request.</param>
private static void EnforceResolutionLimit(EncodingJob state, EncodingJobOptions videoRequest)
{
// Switch the incoming params to be ceilings rather than fixed values
videoRequest.MaxWidth = videoRequest.MaxWidth ?? videoRequest.Width;
videoRequest.MaxHeight = videoRequest.MaxHeight ?? videoRequest.Height;
videoRequest.Width = null;
videoRequest.Height = null;
}
/// <summary>
/// Gets the number of audio channels to specify on the command line
/// </summary>
/// <param name="request">The request.</param>
/// <param name="audioStream">The audio stream.</param>
/// <param name="outputAudioCodec">The output audio codec.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
private int? GetNumAudioChannelsParam(EncodingJobOptions request, MediaStream audioStream, string outputAudioCodec)
{
var inputChannels = audioStream == null
? null
: audioStream.Channels;
if (inputChannels <= 0)
{
inputChannels = null;
}
int? transcoderChannelLimit = null;
var codec = outputAudioCodec ?? string.Empty;
if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
{
// wmav2 currently only supports two channel output
transcoderChannelLimit = 2;
}
else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
{
// libmp3lame currently only supports two channel output
transcoderChannelLimit = 2;
}
else
{
// If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels
transcoderChannelLimit = 6;
}
var isTranscodingAudio = !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
int? resultChannels = null;
if (isTranscodingAudio)
{
resultChannels = request.TranscodingMaxAudioChannels;
}
resultChannels = resultChannels ?? request.MaxAudioChannels ?? request.AudioChannels;
if (inputChannels.HasValue)
{
resultChannels = resultChannels.HasValue
? Math.Min(resultChannels.Value, inputChannels.Value)
: inputChannels.Value;
}
if (isTranscodingAudio && transcoderChannelLimit.HasValue)
{
resultChannels = resultChannels.HasValue
? Math.Min(resultChannels.Value, transcoderChannelLimit.Value)
: transcoderChannelLimit.Value;
}
return resultChannels ?? request.AudioChannels;
}
private int? GetVideoBitrateParamValue(EncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
{
var bitrate = request.VideoBitRate;
if (videoStream != null)
{
var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue &&
request.Height.Value > videoStream.Height.Value;
if (request.Width.HasValue && videoStream.Width.HasValue &&
request.Width.Value > videoStream.Width.Value)
{
isUpscaling = true;
}
// Don't allow bitrate increases unless upscaling
if (!isUpscaling)
{
if (bitrate.HasValue && videoStream.BitRate.HasValue)
{
bitrate = Math.Min(bitrate.Value, videoStream.BitRate.Value);
}
}
}
if (bitrate.HasValue)
{
var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
// If a max bitrate was requested, don't let the scaled bitrate exceed it
if (request.VideoBitRate.HasValue)
{
bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
}
}
return bitrate;
}
protected string GetVideoBitrateParam(EncodingJob state, string videoCodec, bool isHls)
{
var bitrate = state.OutputVideoBitrate;
if (bitrate.HasValue)
{
if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
{
// With vpx when crf is used, b:v becomes a max rate
// https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
}
if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
}
// h264
return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
bitrate.Value.ToString(UsCulture),
(bitrate.Value * 2).ToString(UsCulture));
}
return string.Empty;
}
private int? GetAudioBitrateParam(EncodingJobOptions request, MediaStream audioStream)
{
if (request.AudioBitRate.HasValue)
{
// Make sure we don't request a bitrate higher than the source
var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value;
return request.AudioBitRate.Value;
//return Math.Min(currentBitrate, request.AudioBitRate.Value);
}
return null;
}
/// <summary>
/// Determines whether the specified stream is H264.
/// </summary>
@ -510,260 +221,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
}
/// <summary>
/// Gets the name of the output audio codec
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
internal static string GetAudioEncoder(EncodingJob state)
{
var codec = state.OutputAudioCodec;
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{
return "aac -strict experimental";
}
if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
{
return "libmp3lame";
}
if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase))
{
return "libvorbis";
}
if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase))
{
return "wmav2";
}
return codec.ToLower();
}
/// <summary>
/// Gets the name of the output video codec
/// </summary>
/// <returns>System.String.</returns>
internal static string GetVideoEncoder(IMediaEncoder mediaEncoder, EncodingJob state, EncodingOptions options)
{
var codec = state.OutputVideoCodec;
if (!string.IsNullOrEmpty(codec))
{
if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
{
return GetH264Encoder(mediaEncoder, state, options);
}
if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
{
return "libvpx";
}
if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
{
return "wmv2";
}
if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
{
return "libtheora";
}
return codec.ToLower();
}
return "copy";
}
private static string GetAvailableEncoder(IMediaEncoder mediaEncoder, string preferredEncoder, string defaultEncoder)
{
if (mediaEncoder.SupportsEncoder(preferredEncoder))
{
return preferredEncoder;
}
return defaultEncoder;
}
internal static string GetH264Encoder(IMediaEncoder mediaEncoder, EncodingJob state, EncodingOptions options)
{
var defaultEncoder = "libx264";
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
if (state.VideoType == VideoType.VideoFile)
{
var hwType = options.HardwareAccelerationType;
if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
return GetAvailableEncoder(mediaEncoder, "h264_qsv", defaultEncoder);
}
if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
{
return GetAvailableEncoder(mediaEncoder, "h264_nvenc", defaultEncoder);
}
if (string.Equals(hwType, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
return GetAvailableEncoder(mediaEncoder, "h264_omx", defaultEncoder);
}
if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(options.VaapiDevice))
{
if (IsVaapiSupported(state))
{
return GetAvailableEncoder(mediaEncoder, "h264_vaapi", defaultEncoder);
}
}
}
return defaultEncoder;
}
private static bool IsVaapiSupported(EncodingJob state)
{
var videoStream = state.VideoStream;
if (videoStream != null)
{
// vaapi will throw an error with this input
// [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
if (videoStream.Level == -99 || videoStream.Level == 15)
{
return false;
}
}
}
return true;
}
internal static bool CanStreamCopyVideo(EncodingJobOptions request, MediaStream videoStream)
{
if (videoStream.IsInterlaced)
{
return false;
}
if (videoStream.IsAnamorphic ?? false)
{
return false;
}
// Can't stream copy if we're burning in subtitles
if (request.SubtitleStreamIndex.HasValue)
{
if (request.SubtitleMethod == SubtitleDeliveryMethod.Encode)
{
return false;
}
}
// Source and target codecs must match
if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value)
{
return false;
}
}
// If client is requesting a specific video profile, it must match the source
if (!string.IsNullOrEmpty(request.Profile))
{
if (string.IsNullOrEmpty(videoStream.Profile))
{
return false;
}
if (!string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
{
var currentScore = GetVideoProfileScore(videoStream.Profile);
var requestedScore = GetVideoProfileScore(request.Profile);
if (currentScore == -1 || currentScore > requestedScore)
{
return false;
}
}
}
// Video width must fall within requested value
if (request.MaxWidth.HasValue)
{
if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)
{
return false;
}
}
// Video height must fall within requested value
if (request.MaxHeight.HasValue)
{
if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value)
{
return false;
}
}
// Video framerate must fall within requested value
var requestedFramerate = request.MaxFramerate ?? request.Framerate;
if (requestedFramerate.HasValue)
{
var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate;
if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
{
return false;
}
}
// Video bitrate must fall within requested value
if (request.VideoBitRate.HasValue)
{
if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)
{
return false;
}
}
if (request.MaxVideoBitDepth.HasValue)
{
if (videoStream.BitDepth.HasValue && videoStream.BitDepth.Value > request.MaxVideoBitDepth.Value)
{
return false;
}
}
if (request.MaxRefFrames.HasValue)
{
if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > request.MaxRefFrames.Value)
{
return false;
}
}
// If a specific level was requested, the source must match or be less than
if (request.Level.HasValue)
{
if (!videoStream.Level.HasValue)
{
return false;
}
if (videoStream.Level.Value > request.Level.Value)
{
return false;
}
}
return request.EnableAutoStreamCopy;
}
private static int GetVideoProfileScore(string profile)
{
var list = new List<string>
@ -780,57 +237,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
}
internal static bool CanStreamCopyAudio(EncodingJobOptions request, MediaStream audioStream, List<string> supportedAudioCodecs)
{
// Source and target codecs must match
if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
{
return false;
}
// Video bitrate must fall within requested value
if (request.AudioBitRate.HasValue)
{
if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value <= 0)
{
return false;
}
if (audioStream.BitRate.Value > request.AudioBitRate.Value)
{
return false;
}
}
// Channels must fall within requested value
var channels = request.AudioChannels ?? request.MaxAudioChannels;
if (channels.HasValue)
{
if (!audioStream.Channels.HasValue || audioStream.Channels.Value <= 0)
{
return false;
}
if (audioStream.Channels.Value > channels.Value)
{
return false;
}
}
// Sample rate must fall within requested value
if (request.AudioSampleRate.HasValue)
{
if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value <= 0)
{
return false;
}
if (audioStream.SampleRate.Value > request.AudioSampleRate.Value)
{
return false;
}
}
return request.EnableAutoStreamCopy;
}
private void ApplyDeviceProfileSettings(EncodingJob state)
{
var profile = state.Options.DeviceProfile;

View file

@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Encoder
{
// For now, a common base class until the API and MediaEncoding classes are unified
public class EncodingJobInfo
{
private readonly ILogger _logger;
public MediaStream VideoStream { get; set; }
public VideoType VideoType { get; set; }
public Dictionary<string, string> RemoteHttpHeaders { get; set; }
public string OutputVideoCodec { get; set; }
public MediaProtocol InputProtocol { get; set; }
public string MediaPath { get; set; }
public bool IsInputVideo { get; set; }
public IIsoMount IsoMount { get; set; }
public List<string> PlayableStreamFileNames { get; set; }
public string OutputAudioCodec { get; set; }
public int? OutputVideoBitrate { get; set; }
public MediaStream SubtitleStream { get; set; }
public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; }
public int InternalSubtitleStreamOffset { get; set; }
public MediaSourceInfo MediaSource { get; set; }
public User User { get; set; }
public long? RunTimeTicks { get; set; }
public bool ReadInputAtNativeFramerate { get; set; }
public string OutputContainer { get; set; }
public string OutputVideoSync = "-1";
public string OutputAudioSync = "1";
public string InputAudioSync { get; set; }
public string InputVideoSync { get; set; }
public TransportStreamTimestamp InputTimestamp { get; set; }
public MediaStream AudioStream { get; set; }
public List<string> SupportedAudioCodecs { get; set; }
public List<string> SupportedVideoCodecs { get; set; }
public string InputContainer { get; set; }
public IsoType? IsoType { get; set; }
public BaseEncodingJobOptions BaseRequest { get; set; }
public long? StartTimeTicks
{
get { return BaseRequest.StartTimeTicks; }
}
public bool CopyTimestamps
{
get { return BaseRequest.CopyTimestamps; }
}
public int? OutputAudioChannels;
public int? OutputAudioSampleRate;
public bool DeInterlace { get; set; }
public bool IsVideoRequest { get; set; }
public EncodingJobInfo(ILogger logger)
{
_logger = logger;
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
PlayableStreamFileNames = new List<string>();
SupportedVideoCodecs = new List<string>();
SupportedVideoCodecs = new List<string>();
}
/// <summary>
/// Predicts the audio sample rate that will be in the output stream
/// </summary>
public double? TargetVideoLevel
{
get
{
var stream = VideoStream;
var request = BaseRequest;
return !string.IsNullOrEmpty(request.Level) && !request.Static
? double.Parse(request.Level, CultureInfo.InvariantCulture)
: stream == null ? null : stream.Level;
}
}
protected void DisposeIsoMount()
{
if (IsoMount != null)
{
try
{
IsoMount.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing iso mount", ex);
}
IsoMount = null;
}
}
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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" />

View file

@ -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))
{

View file

@ -1,8 +0,0 @@
namespace MediaBrowser.Model.Configuration
{
public class PathSubstitution
{
public string From { get; set; }
public string To { get; set; }
}
}

View file

@ -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";

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -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,

View file

@ -211,12 +211,6 @@ namespace MediaBrowser.Model.Dto
/// <value>The overview.</value>
public string Overview { get; set; }
/// <summary>
/// Gets or sets the short overview.
/// </summary>
/// <value>The short overview.</value>
public string ShortOverview { get; set; }
/// <summary>
/// Gets or sets the taglines.
/// </summary>

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -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" />

View file

@ -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()
{

View file

@ -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);
}
}

View file

@ -188,11 +188,6 @@
/// </summary>
Settings,
/// <summary>
/// The short overview
/// </summary>
ShortOverview,
/// <summary>
/// The screenshot image tags
/// </summary>

View file

@ -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; }
}
}

View file

@ -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);
}

View file

@ -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)

View file

@ -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

View file

@ -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;

View file

@ -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)

View file

@ -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" };
}
}

View file

@ -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)
{

View file

@ -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>();

View file

@ -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))
//{

View file

@ -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)
{

View file

@ -76,6 +76,11 @@ namespace MediaBrowser.WebDashboard.Api
public string V { get; set; }
}
[Route("/favicon.ico", "GET")]
public class GetFavIcon
{
}
/// <summary>
/// Class DashboardService
/// </summary>
@ -134,6 +139,14 @@ namespace MediaBrowser.WebDashboard.Api
_memoryStreamFactory = memoryStreamFactory;
}
public object Get(GetFavIcon request)
{
return Get(new GetDashboardResource
{
ResourceName = "favicon.ico"
});
}
/// <summary>
/// Gets the specified request.
/// </summary>
@ -323,48 +336,11 @@ namespace MediaBrowser.WebDashboard.Api
return new PackageCreator(_fileSystem, _logger, _serverConfigurationManager, _memoryStreamFactory);
}
private List<string> GetDeployIgnoreExtensions()
{
var list = new List<string>();
list.Add(".log");
list.Add(".txt");
list.Add(".map");
list.Add(".md");
list.Add(".gz");
list.Add(".bat");
list.Add(".sh");
return list;
}
private List<Tuple<string, bool>> GetDeployIgnoreFilenames()
{
var list = new List<Tuple<string, bool>>();
list.Add(new Tuple<string, bool>("copying", true));
list.Add(new Tuple<string, bool>("license", true));
list.Add(new Tuple<string, bool>("license-mit", true));
list.Add(new Tuple<string, bool>("gitignore", false));
list.Add(new Tuple<string, bool>("npmignore", false));
list.Add(new Tuple<string, bool>("jshintrc", false));
list.Add(new Tuple<string, bool>("gruntfile", false));
list.Add(new Tuple<string, bool>("bowerrc", false));
list.Add(new Tuple<string, bool>("jscsrc", false));
list.Add(new Tuple<string, bool>("hero.svg", false));
list.Add(new Tuple<string, bool>("travis.yml", false));
list.Add(new Tuple<string, bool>("build.js", false));
list.Add(new Tuple<string, bool>("editorconfig", false));
list.Add(new Tuple<string, bool>("gitattributes", false));
return list;
}
public async Task<object> Get(GetDashboardPackage request)
{
var mode = request.Mode;
var path = string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ?
var path = !string.IsNullOrWhiteSpace(mode) ?
Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath, "webclient-dump")
: "C:\\dev\\emby-web-mobile\\src";
@ -388,101 +364,19 @@ namespace MediaBrowser.WebDashboard.Api
// Try to trim the output size a bit
var bowerPath = Path.Combine(path, "bower_components");
foreach (var ext in GetDeployIgnoreExtensions())
{
DeleteFilesByExtension(bowerPath, ext);
}
DeleteFilesByExtension(bowerPath, ".json", "strings\\");
foreach (var ignore in GetDeployIgnoreFilenames())
{
DeleteFilesByName(bowerPath, ignore.Item1, ignore.Item2);
}
DeleteFoldersByName(bowerPath, "demo");
DeleteFoldersByName(bowerPath, "test");
DeleteFoldersByName(bowerPath, "guides");
DeleteFoldersByName(bowerPath, "grunt");
DeleteFoldersByName(bowerPath, "rollups");
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "montserrat");
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "opensans");
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto");
}
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
DeleteFoldersByName(Path.Combine(bowerPath, "jquery"), "src");
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
//DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
//DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(mode))
{
// Delete things that are unneeded in an attempt to keep the output as trim as possible
DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto");
_fileSystem.DeleteDirectory(Path.Combine(path, "css", "images", "tour"), true);
}
await DumpHtml(creator.DashboardUIPath, path, mode, culture, appVersion);
await DumpFile("css/all.css", Path.Combine(path, "css", "all.css"), mode, culture, appVersion).ConfigureAwait(false);
return "";
}
private void DeleteCryptoFiles(string path)
{
var files = _fileSystem.GetFiles(path)
.ToList();
var keepFiles = new[] { "core-min.js", "md5-min.js", "sha1-min.js" };
foreach (var file in files)
{
if (!keepFiles.Contains(file.Name, StringComparer.OrdinalIgnoreCase))
{
_fileSystem.DeleteFile(file.FullName);
}
}
}
private void DeleteFilesByExtension(string path, string extension, string exclude = null)
{
var files = _fileSystem.GetFiles(path, true)
.Where(i => string.Equals(i.Extension, extension, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach (var file in files)
{
if (!string.IsNullOrWhiteSpace(exclude))
{
if (file.FullName.IndexOf(exclude, StringComparison.OrdinalIgnoreCase) != -1)
{
continue;
}
}
_fileSystem.DeleteFile(file.FullName);
}
}
private void DeleteFilesByName(string path, string name, bool exact = false)
{
var files = _fileSystem.GetFiles(path, true)
.Where(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase) || (!exact && i.Name.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1))
.ToList();
foreach (var file in files)
{
_fileSystem.DeleteFile(file.FullName);
}
}
private void DeleteFoldersByName(string path, string name)
{
var directories = _fileSystem.GetDirectories(path, true)

View file

@ -33,16 +33,7 @@ namespace MediaBrowser.WebDashboard.Api
string localizationCulture,
string appVersion)
{
Stream resourceStream;
if (path.Equals("css/all.css", StringComparison.OrdinalIgnoreCase))
{
resourceStream = await GetAllCss().ConfigureAwait(false);
}
else
{
resourceStream = GetRawResourceStream(path);
}
var resourceStream = GetRawResourceStream(path);
if (resourceStream != null)
{
@ -151,7 +142,7 @@ namespace MediaBrowser.WebDashboard.Api
html = Encoding.UTF8.GetString(originalBytes, 0, originalBytes.Length);
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrWhiteSpace(mode))
{
}
else if (!string.IsNullOrWhiteSpace(path) && !string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase))
@ -160,10 +151,13 @@ namespace MediaBrowser.WebDashboard.Api
if (index != -1)
{
html = html.Substring(index);
html = html.Substring(html.IndexOf('>') + 1);
index = html.IndexOf("</body>", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
html = html.Substring(0, index+7);
html = html.Substring(0, index);
}
}
var mainFile = _fileSystem.ReadAllText(GetDashboardResourcePath("index.html"));
@ -214,7 +208,8 @@ namespace MediaBrowser.WebDashboard.Api
{
var sb = new StringBuilder();
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ||
string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase))
{
sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem: ws: wss:;\">");
}
@ -263,11 +258,14 @@ namespace MediaBrowser.WebDashboard.Api
/// <returns>System.String.</returns>
private string GetCommonCss(string mode, string version)
{
var versionString = !string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? "?v=" + version : string.Empty;
var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty;
var files = new[]
{
"css/all.css" + versionString
"css/site.css" + versionString,
"css/librarymenu.css" + versionString,
"css/librarybrowser.css" + versionString,
"thirdparty/paper-button-style.css" + versionString
};
var tags = files.Select(s => string.Format("<link rel=\"stylesheet\" href=\"{0}\" async />", s)).ToArray();
@ -291,14 +289,14 @@ namespace MediaBrowser.WebDashboard.Api
builder.AppendFormat("window.appMode='{0}';", mode);
}
if (!string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
if (string.IsNullOrWhiteSpace(mode))
{
builder.AppendFormat("window.dashboardVersion='{0}';", version);
}
builder.Append("</script>");
var versionString = !string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? "?v=" + version : string.Empty;
var versionString = string.IsNullOrWhiteSpace(mode) ? "?v=" + version : string.Empty;
var files = new List<string>();
@ -318,48 +316,6 @@ namespace MediaBrowser.WebDashboard.Api
return builder.ToString();
}
/// <summary>
/// Gets all CSS.
/// </summary>
/// <returns>Task{Stream}.</returns>
private async Task<Stream> GetAllCss()
{
var memoryStream = _memoryStreamFactory.CreateNew();
var files = new[]
{
"css/site.css",
"css/librarymenu.css",
"css/librarybrowser.css",
"thirdparty/paper-button-style.css"
};
var builder = new StringBuilder();
foreach (var file in files)
{
var path = GetDashboardResourcePath(file);
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true))
{
using (var streamReader = new StreamReader(fs))
{
var text = await streamReader.ReadToEndAsync().ConfigureAwait(false);
builder.Append(text);
builder.Append(Environment.NewLine);
}
}
}
var css = builder.ToString();
var bytes = Encoding.UTF8.GetBytes(css);
memoryStream.Write(bytes, 0, bytes.Length);
memoryStream.Position = 0;
return memoryStream;
}
/// <summary>
/// Gets the raw resource stream.
/// </summary>

File diff suppressed because it is too large Load diff

View file

@ -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":

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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";
}
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
@ -44,15 +45,12 @@ namespace Rssdp.Infrastructure
/// <summary>
/// Sends a message to a particular address (uni or multicast) and port.
/// </summary>
/// <param name="messageData">A byte array containing the data to send.</param>
/// <param name="destination">A <see cref="IpEndPointInfo"/> representing the destination address for the data. Can be either a multicast or unicast destination.</param>
/// <param name="fromLocalIpAddress">A <see cref="IpEndPointInfo"/> The local ip address to send from, or .Any if sending from all available</param>
Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress);
Task SendMessage(byte[] messageData, IpEndPointInfo destination, IpAddressInfo fromLocalIpAddress, CancellationToken cancellationToken);
/// <summary>
/// Sends a message to the SSDP multicast address and port.
/// </summary>
Task SendMulticastMessage(string message);
Task SendMulticastMessage(string message, CancellationToken cancellationToken);
#endregion

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