commit
50ff08310e
|
@ -829,18 +829,7 @@ namespace Emby.Drawing
|
||||||
// Run the enhancers sequentially in order of priority
|
// Run the enhancers sequentially in order of priority
|
||||||
foreach (var enhancer in imageEnhancers)
|
foreach (var enhancer in imageEnhancers)
|
||||||
{
|
{
|
||||||
var typeName = enhancer.GetType().Name;
|
await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("{0} failed enhancing {1}", ex, typeName, item.Name);
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feed the output into the next enhancer as input
|
// Feed the output into the next enhancer as input
|
||||||
inputPath = outputPath;
|
inputPath = outputPath;
|
||||||
|
|
|
@ -197,6 +197,10 @@
|
||||||
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
|
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
|
||||||
<Name>MediaBrowser.Model</Name>
|
<Name>MediaBrowser.Model</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj">
|
||||||
|
<Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
|
||||||
|
<Name>MediaBrowser.Server.Implementations</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|
|
@ -116,6 +116,7 @@ namespace MediaBrowser.Api
|
||||||
config.EnableCaseSensitiveItemIds = true;
|
config.EnableCaseSensitiveItemIds = true;
|
||||||
//config.EnableFolderView = true;
|
//config.EnableFolderView = true;
|
||||||
config.SchemaVersion = 109;
|
config.SchemaVersion = 109;
|
||||||
|
config.EnableSimpleArtistDetection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Post(UpdateStartupConfiguration request)
|
public void Post(UpdateStartupConfiguration request)
|
||||||
|
|
|
@ -115,6 +115,22 @@ namespace MediaBrowser.Controller.Entities
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public bool IsInMixedFolder { get; set; }
|
public bool IsInMixedFolder { get; set; }
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
protected virtual bool SupportsIsInMixedFolderDetection
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DetectIsInMixedFolder()
|
||||||
|
{
|
||||||
|
if (SupportsIsInMixedFolderDetection)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsInMixedFolder;
|
||||||
|
}
|
||||||
|
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public virtual bool SupportsRemoteImageDownloading
|
public virtual bool SupportsRemoteImageDownloading
|
||||||
{
|
{
|
||||||
|
@ -1116,7 +1132,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
var hasThemeMedia = this as IHasThemeMedia;
|
var hasThemeMedia = this as IHasThemeMedia;
|
||||||
if (hasThemeMedia != null)
|
if (hasThemeMedia != null)
|
||||||
{
|
{
|
||||||
if (!IsInMixedFolder)
|
if (!DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -1266,7 +1282,15 @@ namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
var current = this;
|
var current = this;
|
||||||
|
|
||||||
return current.IsInMixedFolder == newItem.IsInMixedFolder;
|
if (!SupportsIsInMixedFolderDetection)
|
||||||
|
{
|
||||||
|
if (current.IsInMixedFolder != newItem.IsInMixedFolder)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AfterMetadataRefresh()
|
public void AfterMetadataRefresh()
|
||||||
|
|
|
@ -98,7 +98,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
public override IEnumerable<string> GetDeletePaths()
|
public override IEnumerable<string> GetDeletePaths()
|
||||||
{
|
{
|
||||||
if (!IsInMixedFolder)
|
if (!DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
return new[] { System.IO.Path.GetDirectoryName(Path) };
|
return new[] { System.IO.Path.GetDirectoryName(Path) };
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,11 +150,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
/// <value><c>true</c> if [supports local metadata]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [supports local metadata]; otherwise, <c>false</c>.</value>
|
||||||
bool SupportsLocalMetadata { get; }
|
bool SupportsLocalMetadata { get; }
|
||||||
|
|
||||||
/// <summary>
|
bool DetectIsInMixedFolder();
|
||||||
/// Gets a value indicating whether this instance is in mixed folder.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
|
|
||||||
bool IsInMixedFolder { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance is locked.
|
/// Gets a value indicating whether this instance is locked.
|
||||||
|
|
|
@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||||
|
|
||||||
// Must have a parent to have special features
|
// Must have a parent to have special features
|
||||||
// In other words, it must be part of the Parent/Child tree
|
// In other words, it must be part of the Parent/Child tree
|
||||||
if (LocationType == LocationType.FileSystem && GetParent() != null && !IsInMixedFolder)
|
if (LocationType == LocationType.FileSystem && GetParent() != null && !DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||||
{
|
{
|
||||||
var info = GetItemLookupInfo<MovieInfo>();
|
var info = GetItemLookupInfo<MovieInfo>();
|
||||||
|
|
||||||
if (!IsInMixedFolder)
|
if (!DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
|
info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Try to get the year from the folder name
|
// Try to get the year from the folder name
|
||||||
if (!IsInMixedFolder)
|
if (!DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
|
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,15 @@ namespace MediaBrowser.Controller.Entities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[IgnoreDataMember]
|
||||||
|
protected override bool SupportsIsInMixedFolderDetection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override UnratedItem GetBlockUnratedType()
|
public override UnratedItem GetBlockUnratedType()
|
||||||
{
|
{
|
||||||
return UnratedItem.Music;
|
return UnratedItem.Music;
|
||||||
|
@ -65,7 +74,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Try to get the year from the folder name
|
// Try to get the year from the folder name
|
||||||
if (!IsInMixedFolder)
|
if (!DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
|
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
info.IsLocalTrailer = TrailerTypes.Contains(TrailerType.LocalTrailer);
|
info.IsLocalTrailer = TrailerTypes.Contains(TrailerType.LocalTrailer);
|
||||||
|
|
||||||
if (!IsInMixedFolder && LocationType == LocationType.FileSystem)
|
if (!DetectIsInMixedFolder() && LocationType == LocationType.FileSystem)
|
||||||
{
|
{
|
||||||
info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
|
info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Try to get the year from the folder name
|
// Try to get the year from the folder name
|
||||||
if (!IsInMixedFolder)
|
if (!DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
|
info = LibraryManager.ParseName(System.IO.Path.GetFileName(ContainingFolderPath));
|
||||||
|
|
||||||
|
|
|
@ -480,7 +480,7 @@ namespace MediaBrowser.Controller.Entities
|
||||||
|
|
||||||
public override IEnumerable<string> GetDeletePaths()
|
public override IEnumerable<string> GetDeletePaths()
|
||||||
{
|
{
|
||||||
if (!IsInMixedFolder)
|
if (!DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
return new[] { ContainingFolderPath };
|
return new[] { ContainingFolderPath };
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||||
public ITunerHost TunerHost { get; set; }
|
public ITunerHost TunerHost { get; set; }
|
||||||
public string OriginalStreamId { get; set; }
|
public string OriginalStreamId { get; set; }
|
||||||
public bool EnableStreamSharing { get; set; }
|
public bool EnableStreamSharing { get; set; }
|
||||||
|
public string UniqueId = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
public LiveStream(MediaSourceInfo mediaSource)
|
public LiveStream(MediaSourceInfo mediaSource)
|
||||||
{
|
{
|
||||||
|
|
|
@ -287,9 +287,7 @@
|
||||||
<Compile Include="Providers\IHasItemChangeMonitor.cs" />
|
<Compile Include="Providers\IHasItemChangeMonitor.cs" />
|
||||||
<Compile Include="Providers\IHasLookupInfo.cs" />
|
<Compile Include="Providers\IHasLookupInfo.cs" />
|
||||||
<Compile Include="Providers\IHasOrder.cs" />
|
<Compile Include="Providers\IHasOrder.cs" />
|
||||||
<Compile Include="Providers\IImageFileSaver.cs" />
|
|
||||||
<Compile Include="Providers\IImageProvider.cs" />
|
<Compile Include="Providers\IImageProvider.cs" />
|
||||||
<Compile Include="Providers\IImageSaver.cs" />
|
|
||||||
<Compile Include="Providers\ILocalImageFileProvider.cs" />
|
<Compile Include="Providers\ILocalImageFileProvider.cs" />
|
||||||
<Compile Include="Providers\ILocalMetadataProvider.cs" />
|
<Compile Include="Providers\ILocalMetadataProvider.cs" />
|
||||||
<Compile Include="Providers\ImageRefreshMode.cs" />
|
<Compile Include="Providers\ImageRefreshMode.cs" />
|
||||||
|
|
|
@ -11,14 +11,6 @@ namespace MediaBrowser.Controller.Net
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IHttpResultFactory
|
public interface IHttpResultFactory
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Throws the error.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="statusCode">The status code.</param>
|
|
||||||
/// <param name="errorMessage">The error message.</param>
|
|
||||||
/// <param name="responseHeaders">The response headers.</param>
|
|
||||||
void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the result.
|
/// Gets the result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Model.Drawing;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Providers
|
|
||||||
{
|
|
||||||
public interface IImageFileSaver : IImageSaver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the save paths.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
/// <param name="format">The format.</param>
|
|
||||||
/// <param name="index">The index.</param>
|
|
||||||
/// <returns>IEnumerable{System.String}.</returns>
|
|
||||||
IEnumerable<string> GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
namespace MediaBrowser.Controller.Providers
|
|
||||||
{
|
|
||||||
public interface IImageSaver
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
string Name { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -95,15 +95,8 @@ namespace MediaBrowser.Controller.Providers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the metadata providers.
|
/// Adds the metadata providers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="imageProviders">The image providers.</param>
|
|
||||||
/// <param name="metadataServices">The metadata services.</param>
|
|
||||||
/// <param name="metadataProviders">The metadata providers.</param>
|
|
||||||
/// <param name="savers">The savers.</param>
|
|
||||||
/// <param name="imageSavers">The image savers.</param>
|
|
||||||
/// <param name="externalIds">The external ids.</param>
|
|
||||||
void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
|
void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
|
||||||
IEnumerable<IMetadataSaver> savers,
|
IEnumerable<IMetadataSaver> savers,
|
||||||
IEnumerable<IImageSaver> imageSavers,
|
|
||||||
IEnumerable<IExternalId> externalIds);
|
IEnumerable<IExternalId> externalIds);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Providers
|
||||||
{
|
{
|
||||||
Path = item.Path;
|
Path = item.Path;
|
||||||
ContainingFolderPath = item.ContainingFolderPath;
|
ContainingFolderPath = item.ContainingFolderPath;
|
||||||
IsInMixedFolder = item.IsInMixedFolder;
|
IsInMixedFolder = item.DetectIsInMixedFolder();
|
||||||
|
|
||||||
var video = item as Video;
|
var video = item as Video;
|
||||||
if (video != null)
|
if (video != null)
|
||||||
|
|
|
@ -156,7 +156,6 @@ namespace MediaBrowser.Dlna.Eventing
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
|
@ -132,7 +132,7 @@ namespace MediaBrowser.LocalMetadata.Images
|
||||||
}
|
}
|
||||||
|
|
||||||
var imagePrefix = item.FileNameWithoutExtension + "-";
|
var imagePrefix = item.FileNameWithoutExtension + "-";
|
||||||
var isInMixedFolder = item.IsInMixedFolder;
|
var isInMixedFolder = item.DetectIsInMixedFolder();
|
||||||
|
|
||||||
PopulatePrimaryImages(item, images, files, imagePrefix, isInMixedFolder);
|
PopulatePrimaryImages(item, images, files, imagePrefix, isInMixedFolder);
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ namespace MediaBrowser.LocalMetadata.Savers
|
||||||
|
|
||||||
public static string GetGameSavePath(Game item)
|
public static string GetGameSavePath(Game item)
|
||||||
{
|
{
|
||||||
if (item.IsInMixedFolder)
|
if (item.DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
return Path.ChangeExtension(item.Path, ".xml");
|
return Path.ChangeExtension(item.Path, ".xml");
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
public static string GetProbeSizeArgument(bool isDvd)
|
public static string GetProbeSizeArgument(bool isDvd)
|
||||||
{
|
{
|
||||||
return isDvd ? "-probesize 1G -analyzeduration 200M" : " -analyzeduration 2M";
|
return isDvd ? "-probesize 1G -analyzeduration 200M" : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -426,8 +426,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames);
|
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames);
|
||||||
|
|
||||||
|
var probeSizeArgument = GetProbeSizeArgument(inputFiles, request.Protocol);
|
||||||
|
|
||||||
return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters,
|
return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters,
|
||||||
GetProbeSizeArgument(inputFiles, request.Protocol), request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken);
|
probeSizeArgument, request.MediaType == DlnaProfileType.Audio, request.VideoType, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -203,6 +203,7 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public string[] CodecsUsed { get; set; }
|
public string[] CodecsUsed { get; set; }
|
||||||
public bool EnableChannelView { get; set; }
|
public bool EnableChannelView { get; set; }
|
||||||
public bool EnableExternalContentInSuggestions { get; set; }
|
public bool EnableExternalContentInSuggestions { get; set; }
|
||||||
|
public bool EnableSimpleArtistDetection { get; set; }
|
||||||
|
|
||||||
public int ImageExtractionTimeoutMs { get; set; }
|
public int ImageExtractionTimeoutMs { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -16,7 +16,6 @@ namespace MediaBrowser.Model.Notifications
|
||||||
PluginUpdateInstalled,
|
PluginUpdateInstalled,
|
||||||
PluginUninstalled,
|
PluginUninstalled,
|
||||||
NewLibraryContent,
|
NewLibraryContent,
|
||||||
NewLibraryContentMultiple,
|
|
||||||
ServerRestartRequired,
|
ServerRestartRequired,
|
||||||
TaskFailed,
|
TaskFailed,
|
||||||
CameraImageUploaded,
|
CameraImageUploaded,
|
||||||
|
|
|
@ -371,7 +371,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
return Path.Combine(seriesFolder, imageFilename);
|
return Path.Combine(seriesFolder, imageFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.IsInMixedFolder)
|
if (item.DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
return GetSavePathForItemInMixedFolder(item, type, "landscape", extension);
|
return GetSavePathForItemInMixedFolder(item, type, "landscape", extension);
|
||||||
}
|
}
|
||||||
|
@ -447,7 +447,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
|
path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (item.IsInMixedFolder)
|
else if (item.DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
|
path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
|
||||||
}
|
}
|
||||||
|
@ -514,7 +514,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
|
|
||||||
if (imageIndex.Value == 0)
|
if (imageIndex.Value == 0)
|
||||||
{
|
{
|
||||||
if (item.IsInMixedFolder)
|
if (item.DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) };
|
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) };
|
||||||
}
|
}
|
||||||
|
@ -540,7 +540,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
|
|
||||||
var outputIndex = imageIndex.Value;
|
var outputIndex = imageIndex.Value;
|
||||||
|
|
||||||
if (item.IsInMixedFolder)
|
if (item.DetectIsInMixedFolder())
|
||||||
{
|
{
|
||||||
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) };
|
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) };
|
||||||
}
|
}
|
||||||
|
@ -583,7 +583,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
return new[] { Path.Combine(seasonFolder, imageFilename) };
|
return new[] { Path.Combine(seasonFolder, imageFilename) };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.IsInMixedFolder || item is MusicVideo)
|
if (item.DetectIsInMixedFolder() || item is MusicVideo)
|
||||||
{
|
{
|
||||||
return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) };
|
return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) };
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,6 @@ namespace MediaBrowser.Providers.Manager
|
||||||
private IMetadataService[] _metadataServices = { };
|
private IMetadataService[] _metadataServices = { };
|
||||||
private IMetadataProvider[] _metadataProviders = { };
|
private IMetadataProvider[] _metadataProviders = { };
|
||||||
private IEnumerable<IMetadataSaver> _savers;
|
private IEnumerable<IMetadataSaver> _savers;
|
||||||
private IImageSaver[] _imageSavers;
|
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
|
|
||||||
|
@ -91,21 +90,14 @@ namespace MediaBrowser.Providers.Manager
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the metadata providers.
|
/// Adds the metadata providers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="imageProviders">The image providers.</param>
|
|
||||||
/// <param name="metadataServices">The metadata services.</param>
|
|
||||||
/// <param name="metadataProviders">The metadata providers.</param>
|
|
||||||
/// <param name="metadataSavers">The metadata savers.</param>
|
|
||||||
/// <param name="imageSavers">The image savers.</param>
|
|
||||||
/// <param name="externalIds">The external ids.</param>
|
|
||||||
public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
|
public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
|
||||||
IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
|
IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
|
||||||
IEnumerable<IImageSaver> imageSavers, IEnumerable<IExternalId> externalIds)
|
IEnumerable<IExternalId> externalIds)
|
||||||
{
|
{
|
||||||
ImageProviders = imageProviders.ToArray();
|
ImageProviders = imageProviders.ToArray();
|
||||||
|
|
||||||
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
|
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
|
||||||
_metadataProviders = metadataProviders.ToArray();
|
_metadataProviders = metadataProviders.ToArray();
|
||||||
_imageSavers = imageSavers.ToArray();
|
|
||||||
_externalIds = externalIds.OrderBy(i => i.Name).ToArray();
|
_externalIds = externalIds.OrderBy(i => i.Name).ToArray();
|
||||||
|
|
||||||
_savers = metadataSavers.Where(i =>
|
_savers = metadataSavers.Where(i =>
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace MediaBrowser.Providers.TV
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IMemoryStreamProvider _memoryStreamProvider;
|
private readonly IMemoryStreamProvider _memoryStreamProvider;
|
||||||
|
|
||||||
public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager)
|
public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager, IMemoryStreamProvider memoryStreamProvider)
|
||||||
{
|
{
|
||||||
_zipClient = zipClient;
|
_zipClient = zipClient;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
@ -49,6 +49,7 @@ namespace MediaBrowser.Providers.TV
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_memoryStreamProvider = memoryStreamProvider;
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -377,10 +377,10 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
|
||||||
DisposeLibraryUpdateTimer();
|
DisposeLibraryUpdateTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.Count == 1)
|
items = items.Take(10).ToList();
|
||||||
{
|
|
||||||
var item = items.First();
|
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
var notification = new NotificationRequest
|
var notification = new NotificationRequest
|
||||||
{
|
{
|
||||||
NotificationType = NotificationType.NewLibraryContent.ToString()
|
NotificationType = NotificationType.NewLibraryContent.ToString()
|
||||||
|
@ -388,17 +388,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
|
||||||
|
|
||||||
notification.Variables["Name"] = GetItemName(item);
|
notification.Variables["Name"] = GetItemName(item);
|
||||||
|
|
||||||
await SendNotification(notification).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var notification = new NotificationRequest
|
|
||||||
{
|
|
||||||
NotificationType = NotificationType.NewLibraryContentMultiple.ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
notification.Variables["ItemCount"] = items.Count.ToString(CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
await SendNotification(notification).ConfigureAwait(false);
|
await SendNotification(notification).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,12 +94,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
|
|
||||||
// The Markdown feature causes slow startup times (5 mins+) on cold boots for some users
|
// The Markdown feature causes slow startup times (5 mins+) on cold boots for some users
|
||||||
// Custom format allows images
|
// Custom format allows images
|
||||||
HostConfig.Instance.EnableFeatures = Feature.Csv | Feature.Html | Feature.Json | Feature.Jsv | Feature.Metadata | Feature.Xml | Feature.CustomFormat;
|
HostConfig.Instance.EnableFeatures = Feature.Html | Feature.Json | Feature.CustomFormat;
|
||||||
|
|
||||||
container.Adapter = _containerAdapter;
|
container.Adapter = _containerAdapter;
|
||||||
|
|
||||||
Plugins.RemoveAll(x => x is NativeTypesFeature);
|
Plugins.RemoveAll(x => x is NativeTypesFeature);
|
||||||
Plugins.Add(new SwaggerFeature());
|
//Plugins.Add(new SwaggerFeature());
|
||||||
Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"));
|
Plugins.Add(new CorsFeature(allowedHeaders: "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"));
|
||||||
|
|
||||||
//Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
|
//Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
|
||||||
|
@ -546,8 +546,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
|
{
|
||||||
|
httpRes.Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -683,29 +683,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the error result.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="statusCode">The status code.</param>
|
|
||||||
/// <param name="errorMessage">The error message.</param>
|
|
||||||
/// <param name="responseHeaders">The response headers.</param>
|
|
||||||
/// <returns>System.Object.</returns>
|
|
||||||
public void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null)
|
|
||||||
{
|
|
||||||
var error = new HttpError
|
|
||||||
{
|
|
||||||
Status = statusCode,
|
|
||||||
ErrorCode = errorMessage
|
|
||||||
};
|
|
||||||
|
|
||||||
if (responseHeaders != null)
|
|
||||||
{
|
|
||||||
AddResponseHeaders(error, responseHeaders);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object GetAsyncStreamWriter(IAsyncStreamSource streamSource)
|
public object GetAsyncStreamWriter(IAsyncStreamSource streamSource)
|
||||||
{
|
{
|
||||||
return new AsyncStreamWriter(streamSource);
|
return new AsyncStreamWriter(streamSource);
|
||||||
|
|
|
@ -1,240 +0,0 @@
|
||||||
using MediaBrowser.Common.Events;
|
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using System;
|
|
||||||
using System.Net.WebSockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType;
|
|
||||||
using WebSocketState = MediaBrowser.Model.Net.WebSocketState;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.HttpServer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class NativeWebSocket
|
|
||||||
/// </summary>
|
|
||||||
public class NativeWebSocket : IWebSocket
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The logger
|
|
||||||
/// </summary>
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public event EventHandler<EventArgs> Closed;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the web socket.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The web socket.</value>
|
|
||||||
private System.Net.WebSockets.WebSocket WebSocket { get; set; }
|
|
||||||
|
|
||||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="NativeWebSocket" /> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="socket">The socket.</param>
|
|
||||||
/// <param name="logger">The logger.</param>
|
|
||||||
/// <exception cref="System.ArgumentNullException">socket</exception>
|
|
||||||
public NativeWebSocket(WebSocket socket, ILogger logger)
|
|
||||||
{
|
|
||||||
if (socket == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("socket");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logger == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("logger");
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger = logger;
|
|
||||||
WebSocket = socket;
|
|
||||||
|
|
||||||
Receive();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the state.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The state.</value>
|
|
||||||
public WebSocketState State
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
WebSocketState commonState;
|
|
||||||
|
|
||||||
if (!Enum.TryParse(WebSocket.State.ToString(), true, out commonState))
|
|
||||||
{
|
|
||||||
_logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.State.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return commonState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receives this instance.
|
|
||||||
/// </summary>
|
|
||||||
private async void Receive()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
byte[] bytes;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bytes = await ReceiveBytesAsync(_cancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (WebSocketException ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error receiving web socket message", ex);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes == null)
|
|
||||||
{
|
|
||||||
// Connection closed
|
|
||||||
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OnReceiveBytes != null)
|
|
||||||
{
|
|
||||||
OnReceiveBytes(bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Receives the async.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <returns>Task{WebSocketMessageInfo}.</returns>
|
|
||||||
/// <exception cref="System.Net.WebSockets.WebSocketException">Connection closed</exception>
|
|
||||||
private async Task<byte[]> ReceiveBytesAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var bytes = new byte[4096];
|
|
||||||
var buffer = new ArraySegment<byte>(bytes);
|
|
||||||
|
|
||||||
var result = await WebSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (result.CloseStatus.HasValue)
|
|
||||||
{
|
|
||||||
_logger.Info("Web socket connection closed by client. Reason: {0}", result.CloseStatus.Value);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sends the async.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bytes">The bytes.</param>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
/// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
System.Net.WebSockets.WebSocketMessageType nativeType;
|
|
||||||
|
|
||||||
if (!Enum.TryParse(type.ToString(), true, out nativeType))
|
|
||||||
{
|
|
||||||
_logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
return WebSocket.SendAsync(new ArraySegment<byte>(bytes), nativeType, true, linkedTokenSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
return WebSocket.SendAsync(new ArraySegment<byte>(bytes), System.Net.WebSockets.WebSocketMessageType.Binary, true, linkedTokenSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(text);
|
|
||||||
|
|
||||||
return WebSocket.SendAsync(new ArraySegment<byte>(bytes), System.Net.WebSockets.WebSocketMessageType.Text, true, linkedTokenSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases unmanaged and - optionally - managed resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
|
||||||
protected virtual void Dispose(bool dispose)
|
|
||||||
{
|
|
||||||
if (dispose)
|
|
||||||
{
|
|
||||||
_cancellationTokenSource.Cancel();
|
|
||||||
|
|
||||||
WebSocket.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the receive action.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The receive action.</value>
|
|
||||||
public Action<byte[]> OnReceiveBytes { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the on receive.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The on receive.</value>
|
|
||||||
public Action<string> OnReceive { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _supports native web socket
|
|
||||||
/// </summary>
|
|
||||||
private static bool? _supportsNativeWebSocket;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether [supports web sockets].
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value>
|
|
||||||
public static bool IsSupported
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!_supportsNativeWebSocket.HasValue)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
new ClientWebSocket();
|
|
||||||
|
|
||||||
_supportsNativeWebSocket = true;
|
|
||||||
}
|
|
||||||
catch (PlatformNotSupportedException)
|
|
||||||
{
|
|
||||||
_supportsNativeWebSocket = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _supportsNativeWebSocket.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -191,15 +191,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error in range request writer", ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (OnComplete != null)
|
if (OnComplete != null)
|
||||||
|
@ -251,15 +242,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error in range request writer", ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (OnComplete != null)
|
if (OnComplete != null)
|
||||||
|
|
|
@ -81,20 +81,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
|
||||||
|
|
||||||
public void Write(string text)
|
public void Write(string text)
|
||||||
{
|
{
|
||||||
try
|
var bOutput = System.Text.Encoding.UTF8.GetBytes(text);
|
||||||
{
|
response.ContentLength64 = bOutput.Length;
|
||||||
var bOutput = System.Text.Encoding.UTF8.GetBytes(text);
|
|
||||||
response.ContentLength64 = bOutput.Length;
|
|
||||||
|
|
||||||
var outputStream = response.OutputStream;
|
var outputStream = response.OutputStream;
|
||||||
outputStream.Write(bOutput, 0, bOutput.Length);
|
outputStream.Write(bOutput, 0, bOutput.Length);
|
||||||
Close();
|
Close();
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Could not WriteTextToResponse: " + ex.Message, ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
public void Close()
|
||||||
|
|
|
@ -2463,7 +2463,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
var files = owner.DetectIsInMixedFolder() ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||||
.Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase))
|
.Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase))
|
||||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
|
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommonIO;
|
using CommonIO;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
|
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
|
||||||
{
|
{
|
||||||
|
@ -18,12 +19,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager)
|
public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -67,6 +70,19 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.IsDirectory)
|
||||||
|
{
|
||||||
|
if (args.ContainsFileSystemEntryByName("artist.nfo"))
|
||||||
|
{
|
||||||
|
return new MusicArtist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.Configuration.EnableSimpleArtistDetection)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var directoryService = args.DirectoryService;
|
var directoryService = args.DirectoryService;
|
||||||
|
|
||||||
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
|
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
|
||||||
|
|
|
@ -48,12 +48,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
|
||||||
string collectionType,
|
string collectionType,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
if (parent != null && parent.Path != null && parent.Path.IndexOf("disney", StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
|
||||||
var b = true;
|
|
||||||
var a = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
|
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
|
@ -213,26 +207,22 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
|
||||||
// Find movies with their own folders
|
// Find movies with their own folders
|
||||||
if (args.IsDirectory)
|
if (args.IsDirectory)
|
||||||
{
|
{
|
||||||
var files = args.FileSystemChildren
|
|
||||||
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return FindMovie<MusicVideo>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(collectionType))
|
if (string.IsNullOrEmpty(collectionType))
|
||||||
{
|
{
|
||||||
// Owned items should just use the plain video type
|
// Owned items will be caught by the plain video resolver
|
||||||
if (args.Parent == null)
|
if (args.Parent == null)
|
||||||
{
|
{
|
||||||
return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.HasParent<Series>())
|
if (args.HasParent<Series>())
|
||||||
|
@ -240,11 +230,21 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
{
|
||||||
|
var files = args.FileSystemChildren
|
||||||
|
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
var files = args.FileSystemChildren
|
||||||
|
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,16 +61,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
foreach (var key in keys)
|
foreach (var key in keys)
|
||||||
{
|
{
|
||||||
try
|
await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error saving user data", ex);
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cacheKey = GetCacheKey(userId, item.Id);
|
var cacheKey = GetCacheKey(userId, item.Id);
|
||||||
|
@ -107,18 +98,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
try
|
await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
await Repository.SaveAllUserData(userId, userData, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error saving user data", ex);
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -841,23 +841,39 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(result.Item2, result.Item1 as IDirectStreamProvider);
|
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(result.Item2, result.Item1 as IDirectStreamProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, int consumerId, bool enableStreamSharing)
|
private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing)
|
||||||
{
|
{
|
||||||
var json = _jsonSerializer.SerializeToString(mediaSource);
|
var json = _jsonSerializer.SerializeToString(mediaSource);
|
||||||
mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
||||||
|
|
||||||
mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id;
|
mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id;
|
||||||
|
|
||||||
if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing)
|
//if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing)
|
||||||
{
|
//{
|
||||||
var ticks = (DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value).Ticks - TimeSpan.FromSeconds(10).Ticks;
|
// var ticks = (DateTime.UtcNow - mediaSource.DateLiveStreamOpened.Value).Ticks - TimeSpan.FromSeconds(10).Ticks;
|
||||||
ticks = Math.Max(0, ticks);
|
// ticks = Math.Max(0, ticks);
|
||||||
mediaSource.Path += "?t=" + ticks.ToString(CultureInfo.InvariantCulture) + "&s=" + mediaSource.DateLiveStreamOpened.Value.Ticks.ToString(CultureInfo.InvariantCulture);
|
// mediaSource.Path += "?t=" + ticks.ToString(CultureInfo.InvariantCulture) + "&s=" + mediaSource.DateLiveStreamOpened.Value.Ticks.ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
//}
|
||||||
|
|
||||||
return mediaSource;
|
return mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<LiveStream> GetLiveStream(string uniqueId)
|
||||||
|
{
|
||||||
|
await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _liveStreams.Values
|
||||||
|
.FirstOrDefault(i => string.Equals(i.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_liveStreamsSemaphore.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Tuple<LiveStream, MediaSourceInfo, ITunerHost>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
|
private async Task<Tuple<LiveStream, MediaSourceInfo, ITunerHost>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.Info("Streaming Channel " + channelId);
|
_logger.Info("Streaming Channel " + channelId);
|
||||||
|
@ -872,7 +888,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
_logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount);
|
_logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount);
|
||||||
|
|
||||||
var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.ConsumerCount - 1, result.EnableStreamSharing);
|
var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing);
|
||||||
_liveStreamsSemaphore.Release();
|
_liveStreamsSemaphore.Release();
|
||||||
return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, result.TunerHost);
|
return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, result.TunerHost);
|
||||||
}
|
}
|
||||||
|
@ -885,7 +901,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
|
result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, 0, result.EnableStreamSharing);
|
var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing);
|
||||||
|
|
||||||
_liveStreams[openedMediaSource.Id] = result;
|
_liveStreams[openedMediaSource.Id] = result;
|
||||||
|
|
||||||
|
@ -1542,6 +1558,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||||
if (timer.IsKids)
|
if (timer.IsKids)
|
||||||
{
|
{
|
||||||
AddGenre(timer.Genres, "Kids");
|
AddGenre(timer.Genres, "Kids");
|
||||||
|
AddGenre(timer.Genres, "Children");
|
||||||
}
|
}
|
||||||
if (timer.IsNews)
|
if (timer.IsNews)
|
||||||
{
|
{
|
||||||
|
|
|
@ -223,8 +223,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
return result.Items.FirstOrDefault();
|
return result.Items.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
|
public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var info = await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
|
var info = await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -284,80 +282,65 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
|
private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
mediaSourceId = null;
|
mediaSourceId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
MediaSourceInfo info;
|
||||||
|
bool isVideo;
|
||||||
|
ILiveTvService service;
|
||||||
|
IDirectStreamProvider directStreamProvider = null;
|
||||||
|
|
||||||
|
if (isChannel)
|
||||||
{
|
{
|
||||||
MediaSourceInfo info;
|
var channel = GetInternalChannel(id);
|
||||||
bool isVideo;
|
isVideo = channel.ChannelType == ChannelType.TV;
|
||||||
ILiveTvService service;
|
service = GetService(channel);
|
||||||
IDirectStreamProvider directStreamProvider = null;
|
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
|
||||||
|
|
||||||
if (isChannel)
|
var supportsManagedStream = service as ISupportsDirectStreamProvider;
|
||||||
|
if (supportsManagedStream != null)
|
||||||
{
|
{
|
||||||
var channel = GetInternalChannel(id);
|
var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
|
||||||
isVideo = channel.ChannelType == ChannelType.TV;
|
info = streamInfo.Item1;
|
||||||
service = GetService(channel);
|
directStreamProvider = streamInfo.Item2;
|
||||||
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
|
|
||||||
|
|
||||||
var supportsManagedStream = service as ISupportsDirectStreamProvider;
|
|
||||||
if (supportsManagedStream != null)
|
|
||||||
{
|
|
||||||
var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
|
|
||||||
info = streamInfo.Item1;
|
|
||||||
directStreamProvider = streamInfo.Item2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
info.RequiresClosing = true;
|
|
||||||
|
|
||||||
if (info.RequiresClosing)
|
|
||||||
{
|
|
||||||
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
|
|
||||||
|
|
||||||
info.LiveStreamId = idPrefix + info.Id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
|
info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
|
||||||
isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
|
|
||||||
service = GetService(recording);
|
|
||||||
|
|
||||||
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
|
|
||||||
info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
|
|
||||||
info.RequiresClosing = true;
|
|
||||||
|
|
||||||
if (info.RequiresClosing)
|
|
||||||
{
|
|
||||||
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
|
|
||||||
|
|
||||||
info.LiveStreamId = idPrefix + info.Id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
info.RequiresClosing = true;
|
||||||
|
|
||||||
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
|
if (info.RequiresClosing)
|
||||||
Normalize(info, service, isVideo);
|
{
|
||||||
|
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
|
||||||
|
|
||||||
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider);
|
info.LiveStreamId = idPrefix + info.Id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Error getting channel stream", ex);
|
var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
|
||||||
|
isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
|
||||||
|
service = GetService(recording);
|
||||||
|
|
||||||
throw;
|
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
|
||||||
}
|
info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
|
||||||
finally
|
info.RequiresClosing = true;
|
||||||
{
|
|
||||||
_liveStreamSemaphore.Release();
|
if (info.RequiresClosing)
|
||||||
|
{
|
||||||
|
var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
|
||||||
|
|
||||||
|
info.LiveStreamId = idPrefix + info.Id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
|
||||||
|
Normalize(info, service, isVideo);
|
||||||
|
|
||||||
|
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
|
private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
|
||||||
|
@ -2560,35 +2543,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
public async Task CloseLiveStream(string id)
|
public async Task CloseLiveStream(string id)
|
||||||
{
|
{
|
||||||
await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);
|
var parts = id.Split(new[] { '_' }, 2);
|
||||||
|
|
||||||
try
|
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (service == null)
|
||||||
{
|
{
|
||||||
var parts = id.Split(new[] { '_' }, 2);
|
throw new ArgumentException("Service not found.");
|
||||||
|
|
||||||
var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (service == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Service not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
id = parts[1];
|
|
||||||
|
|
||||||
_logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
|
|
||||||
|
|
||||||
await service.CloseLiveStream(id, CancellationToken.None).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error closing live stream", ex);
|
|
||||||
|
|
||||||
throw;
|
id = parts[1];
|
||||||
}
|
|
||||||
finally
|
_logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
|
||||||
{
|
|
||||||
_liveStreamSemaphore.Release();
|
await service.CloseLiveStream(id, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public GuideInfo GetGuideInfo()
|
public GuideInfo GetGuideInfo()
|
||||||
|
|
|
@ -9,6 +9,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -139,7 +140,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false);
|
if (stream.MediaStreams.Any(i => i.Index != -1))
|
||||||
|
{
|
||||||
|
await AddMediaInfo(stream, isAudio, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -208,10 +216,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
|
private async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var originalRuntime = mediaSource.RunTimeTicks;
|
var originalRuntime = mediaSource.RunTimeTicks;
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||||
{
|
{
|
||||||
InputPath = mediaSource.Path,
|
InputPath = mediaSource.Path,
|
||||||
|
@ -221,6 +231,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_logger.Info("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
mediaSource.Bitrate = info.Bitrate;
|
mediaSource.Bitrate = info.Bitrate;
|
||||||
mediaSource.Container = info.Container;
|
mediaSource.Container = info.Container;
|
||||||
mediaSource.Formats = info.Formats;
|
mediaSource.Formats = info.Formats;
|
||||||
|
@ -272,6 +284,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
videoStream.BitRate = 1000000;
|
videoStream.BitRate = 1000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is coming up false and preventing stream copy
|
||||||
|
videoStream.IsAVC = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to estimate this
|
// Try to estimate this
|
||||||
|
|
|
@ -233,25 +233,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
||||||
protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
|
protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
private async Task AddMediaInfo(LiveStream stream, bool isAudio, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
//await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
//try
|
|
||||||
//{
|
|
||||||
// await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// // Leave the resource locked. it will be released upstream
|
|
||||||
//}
|
|
||||||
//catch (Exception)
|
|
||||||
//{
|
|
||||||
// // Release the resource if there's some kind of failure.
|
|
||||||
// resourcePool.Release();
|
|
||||||
|
|
||||||
// throw;
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract bool IsValidChannelId(string channelId);
|
protected abstract bool IsValidChannelId(string channelId);
|
||||||
|
|
||||||
protected LiveTvOptions GetConfiguration()
|
protected LiveTvOptions GetConfiguration()
|
||||||
|
|
|
@ -105,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
|
private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
|
||||||
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
|
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
lock (_modelCache)
|
lock (_modelCache)
|
||||||
|
@ -387,6 +387,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
}
|
}
|
||||||
id += "_" + url.GetMD5().ToString("N");
|
id += "_" + url.GetMD5().ToString("N");
|
||||||
|
|
||||||
|
var enableLocalBuffer = EnableLocalBuffer();
|
||||||
|
|
||||||
var mediaSource = new MediaSourceInfo
|
var mediaSource = new MediaSourceInfo
|
||||||
{
|
{
|
||||||
Path = url,
|
Path = url,
|
||||||
|
@ -420,8 +422,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
BufferMs = 0,
|
BufferMs = 0,
|
||||||
Container = "ts",
|
Container = "ts",
|
||||||
Id = id,
|
Id = id,
|
||||||
SupportsDirectPlay = false,
|
SupportsDirectPlay = !enableLocalBuffer,
|
||||||
SupportsDirectStream = true,
|
SupportsDirectStream = enableLocalBuffer,
|
||||||
SupportsTranscoding = true,
|
SupportsTranscoding = true,
|
||||||
IsInfiniteStream = true
|
IsInfiniteStream = true
|
||||||
};
|
};
|
||||||
|
@ -488,6 +490,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
|
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool EnableLocalBuffer()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
|
protected override async Task<LiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var profile = streamId.Split('_')[0];
|
var profile = streamId.Split('_')[0];
|
||||||
|
@ -502,25 +509,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
var mediaSource = await GetMediaSource(info, hdhrId, profile).ConfigureAwait(false);
|
var mediaSource = await GetMediaSource(info, hdhrId, profile).ConfigureAwait(false);
|
||||||
|
|
||||||
var liveStream = new HdHomerunLiveStream(mediaSource, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
|
if (EnableLocalBuffer())
|
||||||
if (info.AllowHWTranscoding)
|
|
||||||
{
|
{
|
||||||
var model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
|
var liveStream = new HdHomerunLiveStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
|
||||||
|
if (info.AllowHWTranscoding)
|
||||||
if ((model ?? string.Empty).IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
|
|
||||||
{
|
{
|
||||||
liveStream.EnableStreamSharing = !info.AllowHWTranscoding;
|
var model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if ((model ?? string.Empty).IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
liveStream.EnableStreamSharing = !info.AllowHWTranscoding;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
liveStream.EnableStreamSharing = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
liveStream.EnableStreamSharing = true;
|
liveStream.EnableStreamSharing = true;
|
||||||
}
|
}
|
||||||
|
return liveStream;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
liveStream.EnableStreamSharing = true;
|
var liveStream = new LiveStream(mediaSource);
|
||||||
|
liveStream.EnableStreamSharing = false;
|
||||||
|
return liveStream;
|
||||||
}
|
}
|
||||||
return liveStream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Validate(TunerHostInfo info)
|
public async Task Validate(TunerHostInfo info)
|
||||||
|
|
|
@ -13,6 +13,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
|
using MediaBrowser.Server.Implementations.LiveTv.EmbyTV;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
{
|
{
|
||||||
|
@ -26,8 +27,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
|
||||||
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
|
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
|
||||||
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
|
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
private readonly MulticastStream _multicastStream;
|
||||||
|
|
||||||
public HdHomerunLiveStream(MediaSourceInfo mediaSource, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
|
|
||||||
|
public HdHomerunLiveStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
|
||||||
: base(mediaSource)
|
: base(mediaSource)
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
@ -35,6 +38,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
OriginalStreamId = originalStreamId;
|
||||||
|
_multicastStream = new MulticastStream(_logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OpenInternal(CancellationToken openCancellationToken)
|
protected override async Task OpenInternal(CancellationToken openCancellationToken)
|
||||||
|
@ -44,22 +49,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
var mediaSource = OriginalMediaSource;
|
var mediaSource = OriginalMediaSource;
|
||||||
|
|
||||||
var url = mediaSource.Path;
|
var url = mediaSource.Path;
|
||||||
var tempFile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts");
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
|
|
||||||
|
|
||||||
_logger.Info("Opening HDHR Live stream from {0} to {1}", url, tempFile);
|
_logger.Info("Opening HDHR Live stream from {0}", url);
|
||||||
|
|
||||||
var output = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
||||||
|
|
||||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
var taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
StartStreamingToTempFile(output, tempFile, url, taskCompletionSource, _liveStreamCancellationTokenSource.Token);
|
StartStreaming(url, taskCompletionSource, _liveStreamCancellationTokenSource.Token);
|
||||||
|
|
||||||
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
//OpenedMediaSource.Protocol = MediaProtocol.File;
|
||||||
//OpenedMediaSource.Path = tempFile;
|
//OpenedMediaSource.Path = tempFile;
|
||||||
//OpenedMediaSource.ReadAtNativeFramerate = true;
|
//OpenedMediaSource.ReadAtNativeFramerate = true;
|
||||||
|
|
||||||
OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + Path.GetFileNameWithoutExtension(tempFile) + "/stream.ts";
|
OpenedMediaSource.Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||||
OpenedMediaSource.Protocol = MediaProtocol.Http;
|
OpenedMediaSource.Protocol = MediaProtocol.Http;
|
||||||
OpenedMediaSource.SupportsDirectPlay = false;
|
OpenedMediaSource.SupportsDirectPlay = false;
|
||||||
OpenedMediaSource.SupportsDirectStream = true;
|
OpenedMediaSource.SupportsDirectStream = true;
|
||||||
|
@ -78,178 +79,67 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
return _liveStreamTaskCompletionSource.Task;
|
return _liveStreamTaskCompletionSource.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StartStreamingToTempFile(Stream outputStream, string tempFilePath, string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
private async Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await Task.Run(async () =>
|
await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
using (outputStream)
|
var isFirstAttempt = true;
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var isFirstAttempt = true;
|
try
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
try
|
using (var response = await _httpClient.SendAsync(new HttpRequestOptions
|
||||||
{
|
{
|
||||||
using (var response = await _httpClient.SendAsync(new HttpRequestOptions
|
Url = url,
|
||||||
{
|
CancellationToken = cancellationToken,
|
||||||
Url = url,
|
BufferContent = false
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
BufferContent = false
|
|
||||||
|
|
||||||
}, "GET").ConfigureAwait(false))
|
}, "GET").ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
_logger.Info("Opened HDHR stream from {0}", url);
|
_logger.Info("Opened HDHR stream from {0}", url);
|
||||||
|
|
||||||
if (!cancellationToken.IsCancellationRequested)
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_logger.Info("Beginning multicastStream.CopyUntilCancelled");
|
||||||
|
|
||||||
|
Action onStarted = null;
|
||||||
|
if (isFirstAttempt)
|
||||||
{
|
{
|
||||||
_logger.Info("Beginning DirectRecorder.CopyUntilCancelled");
|
onStarted = () => openTaskCompletionSource.TrySetResult(true);
|
||||||
|
|
||||||
Action onStarted = null;
|
|
||||||
if (isFirstAttempt)
|
|
||||||
{
|
|
||||||
onStarted = () => ResolveWhenExists(openTaskCompletionSource, tempFilePath, cancellationToken);
|
|
||||||
}
|
|
||||||
await CopyUntilCancelled(response.Content, outputStream, onStarted, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _multicastStream.CopyUntilCancelled(response.Content, onStarted, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (isFirstAttempt)
|
||||||
{
|
{
|
||||||
|
_logger.ErrorException("Error opening live stream:", ex);
|
||||||
|
openTaskCompletionSource.TrySetException(ex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
if (isFirstAttempt)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error opening live stream:", ex);
|
|
||||||
openTaskCompletionSource.TrySetException(ex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.ErrorException("Error copying live stream, will reopen", ex);
|
_logger.ErrorException("Error copying live stream, will reopen", ex);
|
||||||
}
|
|
||||||
|
|
||||||
isFirstAttempt = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isFirstAttempt = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_liveStreamTaskCompletionSource.TrySetResult(true);
|
_liveStreamTaskCompletionSource.TrySetResult(true);
|
||||||
|
|
||||||
DeleteTempFile(tempFilePath);
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>> _additionalStreams = new List<Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>>();
|
|
||||||
|
|
||||||
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var taskCompletionSource = new TaskCompletionSource<bool>();
|
return _multicastStream.CopyToAsync(stream);
|
||||||
_additionalStreams.Add(new Tuple<Stream, CancellationToken, TaskCompletionSource<bool>>(stream, cancellationToken, taskCompletionSource));
|
|
||||||
|
|
||||||
return taskCompletionSource.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopAdditionalStream(Tuple<Stream, CancellationToken, TaskCompletionSource<bool>> stream, Exception exception)
|
|
||||||
{
|
|
||||||
if (_additionalStreams.Remove(stream))
|
|
||||||
{
|
|
||||||
stream.Item3.TrySetException(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const int BufferSize = 81920;
|
|
||||||
private async Task CopyUntilCancelled(Stream source, Stream target, Action onStarted, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, onStarted, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
onStarted = null;
|
|
||||||
|
|
||||||
//var position = fs.Position;
|
|
||||||
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
|
||||||
|
|
||||||
if (bytesRead == 0)
|
|
||||||
{
|
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, Action onStarted, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
byte[] buffer = new byte[bufferSize];
|
|
||||||
int bytesRead;
|
|
||||||
int totalBytesRead = 0;
|
|
||||||
|
|
||||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
|
||||||
{
|
|
||||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var additionalStreams = _additionalStreams.ToList();
|
|
||||||
foreach (var additionalStream in additionalStreams)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await additionalStream.Item1.WriteAsync(buffer, 0, bytesRead, additionalStream.Item2).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error writing HDHR data to stream", ex);
|
|
||||||
|
|
||||||
PopAdditionalStream(additionalStream, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalBytesRead += bytesRead;
|
|
||||||
|
|
||||||
if (onStarted != null)
|
|
||||||
{
|
|
||||||
onStarted();
|
|
||||||
}
|
|
||||||
onStarted = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return totalBytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void ResolveWhenExists(TaskCompletionSource<bool> taskCompletionSource, string file, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
while (!File.Exists(file) && !cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
await Task.Delay(50).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
taskCompletionSource.TrySetResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void DeleteTempFile(string path)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < 10; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete(path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (DirectoryNotFoundException)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error deleting temp file {0}", ex, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(1000).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
{
|
||||||
|
public class MulticastStream
|
||||||
|
{
|
||||||
|
private readonly List<QueueStream> _outputStreams = new List<QueueStream>();
|
||||||
|
private const int BufferSize = 81920;
|
||||||
|
private CancellationToken _cancellationToken;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public MulticastStream(ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_cancellationToken = cancellationToken;
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[BufferSize];
|
||||||
|
|
||||||
|
var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (bytesRead > 0)
|
||||||
|
{
|
||||||
|
byte[] copy = new byte[bytesRead];
|
||||||
|
Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead);
|
||||||
|
|
||||||
|
List<QueueStream> streams = null;
|
||||||
|
|
||||||
|
lock (_outputStreams)
|
||||||
|
{
|
||||||
|
streams = _outputStreams.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var stream in streams)
|
||||||
|
{
|
||||||
|
stream.Queue(copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onStarted != null)
|
||||||
|
{
|
||||||
|
var onStartedCopy = onStarted;
|
||||||
|
onStarted = null;
|
||||||
|
Task.Run(onStartedCopy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task CopyToAsync(Stream stream)
|
||||||
|
{
|
||||||
|
var result = new QueueStream(stream, _logger)
|
||||||
|
{
|
||||||
|
OnFinished = OnFinished
|
||||||
|
};
|
||||||
|
|
||||||
|
lock (_outputStreams)
|
||||||
|
{
|
||||||
|
_outputStreams.Add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Start(_cancellationToken);
|
||||||
|
|
||||||
|
return result.TaskCompletion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveOutputStream(QueueStream stream)
|
||||||
|
{
|
||||||
|
lock (_outputStreams)
|
||||||
|
{
|
||||||
|
_outputStreams.Remove(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFinished(QueueStream queueStream)
|
||||||
|
{
|
||||||
|
RemoveOutputStream(queueStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
{
|
||||||
|
public class QueueStream
|
||||||
|
{
|
||||||
|
private readonly Stream _outputStream;
|
||||||
|
private readonly ConcurrentQueue<byte[]> _queue = new ConcurrentQueue<byte[]>();
|
||||||
|
private CancellationToken _cancellationToken;
|
||||||
|
public TaskCompletionSource<bool> TaskCompletion { get; private set; }
|
||||||
|
|
||||||
|
public Action<QueueStream> OnFinished { get; set; }
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public QueueStream(Stream outputStream, ILogger logger)
|
||||||
|
{
|
||||||
|
_outputStream = outputStream;
|
||||||
|
_logger = logger;
|
||||||
|
TaskCompletion = new TaskCompletionSource<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Queue(byte[] bytes)
|
||||||
|
{
|
||||||
|
_queue.Enqueue(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_cancellationToken = cancellationToken;
|
||||||
|
Task.Run(() => StartInternal());
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] Dequeue()
|
||||||
|
{
|
||||||
|
byte[] bytes;
|
||||||
|
if (_queue.TryDequeue(out bytes))
|
||||||
|
{
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartInternal()
|
||||||
|
{
|
||||||
|
var cancellationToken = _cancellationToken;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var bytes = Dequeue();
|
||||||
|
if (bytes != null)
|
||||||
|
{
|
||||||
|
await _outputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskCompletion.TrySetResult(true);
|
||||||
|
_logger.Debug("QueueStream complete");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.Debug("QueueStream cancelled");
|
||||||
|
TaskCompletion.TrySetCanceled();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error in QueueStream", ex);
|
||||||
|
TaskCompletion.TrySetException(ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (OnFinished != null)
|
||||||
|
{
|
||||||
|
OnFinished(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,7 +47,6 @@
|
||||||
"NotificationOptionTaskFailed": "Scheduled task failure",
|
"NotificationOptionTaskFailed": "Scheduled task failure",
|
||||||
"NotificationOptionInstallationFailed": "Installation failure",
|
"NotificationOptionInstallationFailed": "Installation failure",
|
||||||
"NotificationOptionNewLibraryContent": "New content added",
|
"NotificationOptionNewLibraryContent": "New content added",
|
||||||
"NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
|
|
||||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||||
"NotificationOptionUserLockedOut": "User locked out",
|
"NotificationOptionUserLockedOut": "User locked out",
|
||||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||||
|
|
|
@ -161,7 +161,6 @@
|
||||||
<Compile Include="HttpServer\HttpListenerHost.cs" />
|
<Compile Include="HttpServer\HttpListenerHost.cs" />
|
||||||
<Compile Include="HttpServer\HttpResultFactory.cs" />
|
<Compile Include="HttpServer\HttpResultFactory.cs" />
|
||||||
<Compile Include="HttpServer\LoggerUtils.cs" />
|
<Compile Include="HttpServer\LoggerUtils.cs" />
|
||||||
<Compile Include="HttpServer\NativeWebSocket.cs" />
|
|
||||||
<Compile Include="HttpServer\RangeRequestWriter.cs" />
|
<Compile Include="HttpServer\RangeRequestWriter.cs" />
|
||||||
<Compile Include="HttpServer\ResponseFilter.cs" />
|
<Compile Include="HttpServer\ResponseFilter.cs" />
|
||||||
<Compile Include="HttpServer\Security\AuthService.cs" />
|
<Compile Include="HttpServer\Security\AuthService.cs" />
|
||||||
|
@ -247,6 +246,8 @@
|
||||||
<Compile Include="LiveTv\ProgramImageProvider.cs" />
|
<Compile Include="LiveTv\ProgramImageProvider.cs" />
|
||||||
<Compile Include="LiveTv\RecordingImageProvider.cs" />
|
<Compile Include="LiveTv\RecordingImageProvider.cs" />
|
||||||
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
|
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
|
||||||
|
<Compile Include="LiveTv\TunerHosts\MulticastStream.cs" />
|
||||||
|
<Compile Include="LiveTv\TunerHosts\QueueStream.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\SatIp\ChannelScan.cs" />
|
<Compile Include="LiveTv\TunerHosts\SatIp\ChannelScan.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\ReportBlock.cs" />
|
<Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\ReportBlock.cs" />
|
||||||
<Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpAppPacket.cs" />
|
<Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpAppPacket.cs" />
|
||||||
|
|
|
@ -89,13 +89,6 @@ namespace MediaBrowser.Server.Implementations.Notifications
|
||||||
Variables = new List<string>{"Name"}
|
Variables = new List<string>{"Name"}
|
||||||
},
|
},
|
||||||
|
|
||||||
new NotificationTypeInfo
|
|
||||||
{
|
|
||||||
Type = NotificationType.NewLibraryContentMultiple.ToString(),
|
|
||||||
DefaultTitle = "{ItemCount} new items have been added to your media library.",
|
|
||||||
Variables = new List<string>{"ItemCount"}
|
|
||||||
},
|
|
||||||
|
|
||||||
new NotificationTypeInfo
|
new NotificationTypeInfo
|
||||||
{
|
{
|
||||||
Type = NotificationType.AudioPlayback.ToString(),
|
Type = NotificationType.AudioPlayback.ToString(),
|
||||||
|
|
|
@ -128,7 +128,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
var cacheSize = _config.Configuration.SqliteCacheSize;
|
var cacheSize = _config.Configuration.SqliteCacheSize;
|
||||||
if (cacheSize <= 0)
|
if (cacheSize <= 0)
|
||||||
{
|
{
|
||||||
cacheSize = Math.Min(Environment.ProcessorCount * 50000, 200000);
|
cacheSize = Math.Min(Environment.ProcessorCount * 50000, 100000);
|
||||||
}
|
}
|
||||||
|
|
||||||
var connection = await DbConnector.Connect(DbFilePath, false, false, 0 - cacheSize).ConfigureAwait(false);
|
var connection = await DbConnector.Connect(DbFilePath, false, false, 0 - cacheSize).ConfigureAwait(false);
|
||||||
|
@ -2656,6 +2656,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query.SimilarTo != null)
|
||||||
|
{
|
||||||
|
whereClauses.Add("SimilarityScore > 0");
|
||||||
|
}
|
||||||
|
|
||||||
if (query.IsFolder.HasValue)
|
if (query.IsFolder.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("IsFolder=@IsFolder");
|
whereClauses.Add("IsFolder=@IsFolder");
|
||||||
|
|
|
@ -36,11 +36,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The _send semaphore
|
|
||||||
/// </summary>
|
|
||||||
private readonly SemaphoreSlim _sendSemaphore = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger
|
/// The logger
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -237,7 +232,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
|
||||||
/// <param name="buffer">The buffer.</param>
|
/// <param name="buffer">The buffer.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
public async Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
|
public Task SendAsync(byte[] buffer, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (buffer == null)
|
if (buffer == null)
|
||||||
{
|
{
|
||||||
|
@ -246,33 +241,10 @@ namespace MediaBrowser.Server.Implementations.ServerManager
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// Per msdn docs, attempting to send simultaneous messages will result in one failing.
|
return _socket.SendAsync(buffer, true, cancellationToken);
|
||||||
// This should help us workaround that and ensure all messages get sent
|
|
||||||
await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _socket.SendAsync(buffer, true, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint);
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint);
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_sendSemaphore.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync(string text, CancellationToken cancellationToken)
|
public Task SendAsync(string text, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
{
|
{
|
||||||
|
@ -281,30 +253,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// Per msdn docs, attempting to send simultaneous messages will result in one failing.
|
return _socket.SendAsync(text, true, cancellationToken);
|
||||||
// This should help us workaround that and ensure all messages get sent
|
|
||||||
await _sendSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _socket.SendAsync(text, true, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.Info("WebSocket message to {0} was cancelled", RemoteEndPoint);
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error sending WebSocket message {0}", ex, RemoteEndPoint);
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_sendSemaphore.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -677,19 +677,11 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
/// <returns>Task{IUserRepository}.</returns>
|
/// <returns>Task{IUserRepository}.</returns>
|
||||||
private async Task<IUserRepository> GetUserRepository()
|
private async Task<IUserRepository> GetUserRepository()
|
||||||
{
|
{
|
||||||
try
|
var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer, NativeApp.GetDbConnector(), MemoryStreamProvider);
|
||||||
{
|
|
||||||
var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer, NativeApp.GetDbConnector(), MemoryStreamProvider);
|
|
||||||
|
|
||||||
await repo.Initialize().ConfigureAwait(false);
|
await repo.Initialize().ConfigureAwait(false);
|
||||||
|
|
||||||
return repo;
|
return repo;
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.ErrorException("Error opening user db", ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -818,7 +810,6 @@ namespace MediaBrowser.Server.Startup.Common
|
||||||
GetExports<IMetadataService>(),
|
GetExports<IMetadataService>(),
|
||||||
GetExports<IMetadataProvider>(),
|
GetExports<IMetadataProvider>(),
|
||||||
GetExports<IMetadataSaver>(),
|
GetExports<IMetadataSaver>(),
|
||||||
GetExports<IImageSaver>(),
|
|
||||||
GetExports<IExternalId>());
|
GetExports<IExternalId>());
|
||||||
|
|
||||||
ImageProcessor.AddParts(GetExports<IImageEnhancer>());
|
ImageProcessor.AddParts(GetExports<IImageEnhancer>());
|
||||||
|
|
|
@ -1,272 +0,0 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
|
||||||
using MediaBrowser.Controller.Providers;
|
|
||||||
using MediaBrowser.Model.Drawing;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MediaBrowser.XbmcMetadata.Images
|
|
||||||
{
|
|
||||||
public class XbmcImageSaver : IImageFileSaver
|
|
||||||
{
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
|
||||||
|
|
||||||
public IEnumerable<string> GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index)
|
|
||||||
{
|
|
||||||
var season = item as Season;
|
|
||||||
|
|
||||||
if (!SupportsItem(item, type, season))
|
|
||||||
{
|
|
||||||
return new string[] { };
|
|
||||||
}
|
|
||||||
|
|
||||||
var extension = "." + format.ToString().ToLower();
|
|
||||||
|
|
||||||
// Backdrop paths
|
|
||||||
if (type == ImageType.Backdrop)
|
|
||||||
{
|
|
||||||
if (index == 0)
|
|
||||||
{
|
|
||||||
if (item.IsInMixedFolder)
|
|
||||||
{
|
|
||||||
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (season != null && season.IndexNumber.HasValue)
|
|
||||||
{
|
|
||||||
var seriesFolder = season.SeriesPath;
|
|
||||||
|
|
||||||
var seasonMarker = season.IndexNumber.Value == 0
|
|
||||||
? "-specials"
|
|
||||||
: season.IndexNumber.Value.ToString("00", _usCulture);
|
|
||||||
|
|
||||||
var imageFilename = "season" + seasonMarker + "-fanart" + extension;
|
|
||||||
|
|
||||||
return new[] { Path.Combine(seriesFolder, imageFilename) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return new[]
|
|
||||||
{
|
|
||||||
Path.Combine(item.ContainingFolderPath, "fanart" + extension)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.IsInMixedFolder)
|
|
||||||
{
|
|
||||||
return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + index.ToString(_usCulture), extension) };
|
|
||||||
}
|
|
||||||
|
|
||||||
var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", index);
|
|
||||||
|
|
||||||
return new[]
|
|
||||||
{
|
|
||||||
Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension),
|
|
||||||
Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + index.ToString(_usCulture) + extension)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == ImageType.Primary)
|
|
||||||
{
|
|
||||||
if (season != null && season.IndexNumber.HasValue)
|
|
||||||
{
|
|
||||||
var seriesFolder = season.SeriesPath;
|
|
||||||
|
|
||||||
var seasonMarker = season.IndexNumber.Value == 0
|
|
||||||
? "-specials"
|
|
||||||
: season.IndexNumber.Value.ToString("00", _usCulture);
|
|
||||||
|
|
||||||
var imageFilename = "season" + seasonMarker + "-poster" + extension;
|
|
||||||
|
|
||||||
return new[] { Path.Combine(seriesFolder, imageFilename) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is Episode)
|
|
||||||
{
|
|
||||||
var seasonFolder = Path.GetDirectoryName(item.Path);
|
|
||||||
|
|
||||||
var imageFilename = Path.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension;
|
|
||||||
|
|
||||||
return new[] { Path.Combine(seasonFolder, imageFilename) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.IsInMixedFolder || item is MusicVideo)
|
|
||||||
{
|
|
||||||
return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is MusicAlbum || item is MusicArtist)
|
|
||||||
{
|
|
||||||
return new[] { Path.Combine(item.ContainingFolderPath, "folder" + extension) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return new[] { Path.Combine(item.ContainingFolderPath, "poster" + extension) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == ImageType.Banner)
|
|
||||||
{
|
|
||||||
if (season != null && season.IndexNumber.HasValue)
|
|
||||||
{
|
|
||||||
var seriesFolder = season.SeriesPath;
|
|
||||||
|
|
||||||
var seasonMarker = season.IndexNumber.Value == 0
|
|
||||||
? "-specials"
|
|
||||||
: season.IndexNumber.Value.ToString("00", _usCulture);
|
|
||||||
|
|
||||||
var imageFilename = "season" + seasonMarker + "-banner" + extension;
|
|
||||||
|
|
||||||
return new[] { Path.Combine(seriesFolder, imageFilename) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == ImageType.Thumb)
|
|
||||||
{
|
|
||||||
if (season != null && season.IndexNumber.HasValue)
|
|
||||||
{
|
|
||||||
var seriesFolder = season.SeriesPath;
|
|
||||||
|
|
||||||
var seasonMarker = season.IndexNumber.Value == 0
|
|
||||||
? "-specials"
|
|
||||||
: season.IndexNumber.Value.ToString("00", _usCulture);
|
|
||||||
|
|
||||||
var imageFilename = "season" + seasonMarker + "-landscape" + extension;
|
|
||||||
|
|
||||||
return new[] { Path.Combine(seriesFolder, imageFilename) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.IsInMixedFolder)
|
|
||||||
{
|
|
||||||
return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return new[] { Path.Combine(item.ContainingFolderPath, "landscape" + extension) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetStandardSavePaths(item, type, index, extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<string> GetStandardSavePaths(IHasImages item, ImageType type, int imageIndex, string extension)
|
|
||||||
{
|
|
||||||
string filename;
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ImageType.Art:
|
|
||||||
filename = "clearart";
|
|
||||||
break;
|
|
||||||
case ImageType.BoxRear:
|
|
||||||
filename = "back";
|
|
||||||
break;
|
|
||||||
case ImageType.Disc:
|
|
||||||
filename = item is MusicAlbum ? "cdart" : "disc";
|
|
||||||
break;
|
|
||||||
case ImageType.Screenshot:
|
|
||||||
filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
filename = type.ToString().ToLower();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
string path = null;
|
|
||||||
|
|
||||||
if (item.IsInMixedFolder)
|
|
||||||
{
|
|
||||||
path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
path = Path.Combine(item.ContainingFolderPath, filename + extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
return new string[] { };
|
|
||||||
}
|
|
||||||
|
|
||||||
return new[] { path };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private string GetSavePathForItemInMixedFolder(IHasImages item, ImageType type, string imageFilename, string extension)
|
|
||||||
{
|
|
||||||
if (type == ImageType.Primary)
|
|
||||||
{
|
|
||||||
imageFilename = "poster";
|
|
||||||
}
|
|
||||||
var folder = Path.GetDirectoryName(item.Path);
|
|
||||||
|
|
||||||
return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool SupportsItem(IHasImages item, ImageType type, Season season)
|
|
||||||
{
|
|
||||||
if (item.IsOwnedItem || item is Audio || item is User)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type != ImageType.Primary && item is Episode)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item.SupportsLocalMetadata)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var locationType = item.LocationType;
|
|
||||||
if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
|
|
||||||
{
|
|
||||||
var allowSaving = false;
|
|
||||||
|
|
||||||
// If season is virtual under a physical series, save locally if using compatible convention
|
|
||||||
if (season != null)
|
|
||||||
{
|
|
||||||
var series = season.Series;
|
|
||||||
|
|
||||||
if (series != null && series.SupportsLocalMetadata)
|
|
||||||
{
|
|
||||||
allowSaving = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allowSaving)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetBackdropSaveFilename(IEnumerable<ItemImageInfo> images, string zeroIndexFilename, string numberedIndexPrefix, int? index)
|
|
||||||
{
|
|
||||||
if (index.HasValue && index.Value == 0)
|
|
||||||
{
|
|
||||||
return zeroIndexFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList();
|
|
||||||
|
|
||||||
var current = 1;
|
|
||||||
while (filenames.Contains(numberedIndexPrefix + current.ToString(_usCulture), StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
current++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return numberedIndexPrefix + current.ToString(_usCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get { return "Emby/Plex/Xbmc Images"; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -52,7 +52,6 @@
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Configuration\NfoOptions.cs" />
|
<Compile Include="Configuration\NfoOptions.cs" />
|
||||||
<Compile Include="EntryPoint.cs" />
|
<Compile Include="EntryPoint.cs" />
|
||||||
<Compile Include="Images\XbmcImageSaver.cs" />
|
|
||||||
<Compile Include="Parsers\BaseNfoParser.cs" />
|
<Compile Include="Parsers\BaseNfoParser.cs" />
|
||||||
<Compile Include="Parsers\EpisodeNfoParser.cs" />
|
<Compile Include="Parsers\EpisodeNfoParser.cs" />
|
||||||
<Compile Include="Parsers\MovieNfoParser.cs" />
|
<Compile Include="Parsers\MovieNfoParser.cs" />
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Common.Internal</id>
|
<id>MediaBrowser.Common.Internal</id>
|
||||||
<version>3.0.659</version>
|
<version>3.0.660</version>
|
||||||
<title>MediaBrowser.Common.Internal</title>
|
<title>MediaBrowser.Common.Internal</title>
|
||||||
<authors>Luke</authors>
|
<authors>Luke</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
|
<description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
|
||||||
<copyright>Copyright © Emby 2013</copyright>
|
<copyright>Copyright © Emby 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.659" />
|
<dependency id="MediaBrowser.Common" version="3.0.660" />
|
||||||
<dependency id="NLog" version="4.3.8" />
|
<dependency id="NLog" version="4.3.8" />
|
||||||
<dependency id="SimpleInjector" version="3.2.2" />
|
<dependency id="SimpleInjector" version="3.2.2" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Common</id>
|
<id>MediaBrowser.Common</id>
|
||||||
<version>3.0.659</version>
|
<version>3.0.660</version>
|
||||||
<title>MediaBrowser.Common</title>
|
<title>MediaBrowser.Common</title>
|
||||||
<authors>Emby Team</authors>
|
<authors>Emby Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Server.Core</id>
|
<id>MediaBrowser.Server.Core</id>
|
||||||
<version>3.0.659</version>
|
<version>3.0.660</version>
|
||||||
<title>Media Browser.Server.Core</title>
|
<title>Media Browser.Server.Core</title>
|
||||||
<authors>Emby Team</authors>
|
<authors>Emby Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<description>Contains core components required to build plugins for Emby Server.</description>
|
<description>Contains core components required to build plugins for Emby Server.</description>
|
||||||
<copyright>Copyright © Emby 2013</copyright>
|
<copyright>Copyright © Emby 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.659" />
|
<dependency id="MediaBrowser.Common" version="3.0.660" />
|
||||||
<dependency id="Interfaces.IO" version="1.0.0.5" />
|
<dependency id="Interfaces.IO" version="1.0.0.5" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user