add connect to startup wizard

pull/702/head
Luke Pulverenti 10 years ago
parent 813f5d9649
commit 3be4aa8dc7

@ -172,19 +172,8 @@ namespace MediaBrowser.Api
if (!string.IsNullOrWhiteSpace(deviceId))
{
var audioCodec = state.Request.AudioCodec;
var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrEmpty(audioCodec))
{
audioCodec = state.OutputAudioCodec;
}
if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) ||
string.IsNullOrEmpty(videoCodec))
{
videoCodec = state.OutputVideoCodec;
}
var audioCodec = state.ActualOutputVideoCodec;
var videoCodec = state.ActualOutputVideoCodec;
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
{

@ -172,13 +172,6 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -830,23 +830,6 @@ namespace MediaBrowser.Api.Playback
return MediaEncoder.GetInputArgument(inputPath, protocol);
}
private MediaProtocol GetProtocol(string path)
{
if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Http;
}
if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Rtsp;
}
if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase))
{
return MediaProtocol.Rtmp;
}
return MediaProtocol.File;
}
private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
{
if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
@ -1788,9 +1771,23 @@ namespace MediaBrowser.Api.Playback
}
// If client is requesting a specific video profile, it must match the source
if (!string.IsNullOrEmpty(request.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
if (!string.IsNullOrEmpty(request.Profile))
{
return false;
if (string.IsNullOrEmpty(videoStream.Profile))
{
return false;
}
if (!string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
{
var currentScore = GetVideoProfileScore(videoStream.Profile);
var requestedScore = GetVideoProfileScore(request.Profile);
if (currentScore == -1 || currentScore > requestedScore)
{
return false;
}
}
}
// Video width must fall within requested value
@ -1870,6 +1867,22 @@ namespace MediaBrowser.Api.Playback
return request.EnableAutoStreamCopy;
}
private int GetVideoProfileScore(string profile)
{
var list = new List<string>
{
"Constrained Baseline",
"Baseline",
"Extended",
"Main",
"High",
"Progressive High",
"Constrained High"
};
return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
}
private bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
{
// Source and target codecs must match
@ -1942,19 +1955,9 @@ namespace MediaBrowser.Api.Playback
return;
}
var audioCodec = state.OutputAudioCodec;
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
{
audioCodec = state.AudioStream.Codec;
}
var videoCodec = state.OutputVideoCodec;
var audioCodec = state.ActualOutputAudioCodec;
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
{
videoCodec = state.VideoStream.Codec;
}
var videoCodec = state.ActualOutputVideoCodec;
var mediaProfile = state.VideoRequest == null ?
profile.GetAudioMediaProfile(state.OutputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate) :
@ -2022,12 +2025,7 @@ namespace MediaBrowser.Api.Playback
profile = DlnaManager.GetDefaultProfile();
}
var audioCodec = state.OutputAudioCodec;
if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
{
audioCodec = state.AudioStream.Codec;
}
var audioCodec = state.ActualOutputAudioCodec;
if (state.VideoRequest == null)
{
@ -2045,12 +2043,7 @@ namespace MediaBrowser.Api.Playback
}
else
{
var videoCodec = state.OutputVideoCodec;
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
{
videoCodec = state.VideoStream.Codec;
}
var videoCodec = state.ActualOutputVideoCodec;
responseHeaders["contentFeatures.dlna.org"] = new ContentFeatureBuilder(profile)
.BuildVideoHeader(

@ -605,7 +605,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var codec = state.OutputAudioCodec;
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
return "-codec:a:0 copy";
}
@ -636,7 +636,7 @@ namespace MediaBrowser.Api.Playback.Hls
var codec = state.OutputVideoCodec;
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy";
}

@ -188,6 +188,50 @@ namespace MediaBrowser.Api.Playback
public int? OutputAudioBitrate;
public int? OutputVideoBitrate;
public string ActualOutputVideoCodec
{
get
{
var codec = OutputVideoCodec;
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
var stream = VideoStream;
if (stream != null)
{
return stream.Codec;
}
return null;
}
return codec;
}
}
public string ActualOutputAudioCodec
{
get
{
var codec = OutputAudioCodec;
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
var stream = AudioStream;
if (stream != null)
{
return stream.Codec;
}
return null;
}
return codec;
}
}
public string OutputContainer { get; set; }
public DeviceProfile DeviceProfile { get; set; }

@ -29,6 +29,9 @@ namespace MediaBrowser.Api
[ApiMember(Name = "IsDisabled", Description = "Optional filter by IsDisabled=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsDisabled { get; set; }
[ApiMember(Name = "IsGuest", Description = "Optional filter by IsGuest=true or false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsGuest { get; set; }
}
[Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")]
@ -255,6 +258,12 @@ namespace MediaBrowser.Api
users = users.Where(i => i.Configuration.IsHidden == request.IsHidden.Value);
}
if (request.IsGuest.HasValue)
{
users = users.Where(i => (i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest) == request.IsGuest.Value);
}
var result = users
.OrderBy(u => u.Name)
.Select(i => _userManager.GetUserDto(i, Request.RemoteIp))

@ -124,13 +124,6 @@
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
)</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -74,6 +74,12 @@ namespace MediaBrowser.Controller.Entities
}
}
[IgnoreDataMember]
public virtual bool AlwaysScanInternalMetadataPath
{
get { return false; }
}
/// <summary>
/// Gets a value indicating whether this instance is in mixed folder.
/// </summary>

@ -172,6 +172,12 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <returns>System.String.</returns>
string GetInternalMetadataPath();
/// <summary>
/// Gets a value indicating whether [always scan internal metadata path].
/// </summary>
/// <value><c>true</c> if [always scan internal metadata path]; otherwise, <c>false</c>.</value>
bool AlwaysScanInternalMetadataPath { get; }
}
public static class HasImagesExtensions

@ -1,5 +1,6 @@
using MediaBrowser.Model.Configuration;
using System.Linq;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.Entities
{
@ -13,6 +14,15 @@ namespace MediaBrowser.Controller.Entities
}
}
[IgnoreDataMember]
public override bool AlwaysScanInternalMetadataPath
{
get
{
return true;
}
}
protected override bool GetBlockUnratedValue(UserConfiguration config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Other);

@ -369,13 +369,6 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -15,6 +15,7 @@ namespace MediaBrowser.Controller.Playlists
{
public string OwnerUserId { get; set; }
[IgnoreDataMember]
protected override bool FilterLinkedChildrenPerUser
{
get
@ -23,6 +24,15 @@ namespace MediaBrowser.Controller.Playlists
}
}
[IgnoreDataMember]
public override bool AlwaysScanInternalMetadataPath
{
get
{
return true;
}
}
public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{
return GetPlayableItems(user);

@ -49,6 +49,19 @@ namespace MediaBrowser.Controller.Providers
/// <returns>Task.</returns>
Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken);
/// <summary>
/// Saves the image.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="source">The source.</param>
/// <param name="mimeType">Type of the MIME.</param>
/// <param name="type">The type.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <param name="internalCacheKey">The internal cache key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, string internalCacheKey, CancellationToken cancellationToken);
/// <summary>
/// Adds the metadata providers.
/// </summary>

@ -37,7 +37,7 @@ namespace MediaBrowser.LocalMetadata.Images
{
var collectionFolder = (CollectionFolder)item;
return new LocalImageProvider(_fileSystem).GetImages(item, collectionFolder.PhysicalLocations, directoryService);
return new LocalImageProvider(_fileSystem).GetImages(item, collectionFolder.PhysicalLocations, false, directoryService);
}
}
}

@ -45,7 +45,7 @@ namespace MediaBrowser.LocalMetadata.Images
try
{
return new LocalImageProvider(_fileSystem).GetImages(item, path, directoryService);
return new LocalImageProvider(_fileSystem).GetImages(item, path, false, directoryService);
}
catch (DirectoryNotFoundException)
{

@ -38,7 +38,7 @@ namespace MediaBrowser.LocalMetadata.Images
return true;
}
if (item.SupportsLocalMetadata)
if (item.SupportsLocalMetadata && !item.AlwaysScanInternalMetadataPath)
{
return false;
}
@ -59,14 +59,9 @@ namespace MediaBrowser.LocalMetadata.Images
{
var path = item.GetInternalMetadataPath();
if (item is IChannelItem)
{
var b = true;
}
try
{
return new LocalImageProvider(_fileSystem).GetImages(item, path, directoryService);
return new LocalImageProvider(_fileSystem).GetImages(item, path, true, directoryService);
}
catch (DirectoryNotFoundException)
{

@ -21,7 +21,7 @@ namespace MediaBrowser.LocalMetadata.Images
{
_fileSystem = fileSystem;
}
public string Name
{
get { return "Local Images"; }
@ -94,12 +94,12 @@ namespace MediaBrowser.LocalMetadata.Images
return list;
}
public List<LocalImageInfo> GetImages(IHasImages item, string path, IDirectoryService directoryService)
public List<LocalImageInfo> GetImages(IHasImages item, string path, bool checkForCacheKeyFiles, IDirectoryService directoryService)
{
return GetImages(item, new[] { path }, directoryService);
return GetImages(item, new[] { path }, checkForCacheKeyFiles, directoryService);
}
public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, IDirectoryService directoryService)
public List<LocalImageInfo> GetImages(IHasImages item, IEnumerable<string> paths, bool checkForCacheKeyFiles, IDirectoryService directoryService)
{
var files = paths.SelectMany(directoryService.GetFiles)
.Where(i =>
@ -115,6 +115,12 @@ namespace MediaBrowser.LocalMetadata.Images
PopulateImages(item, list, files, false, directoryService);
if (checkForCacheKeyFiles)
{
AddCacheKeyImage(files, list, ImageType.Primary);
AddCacheKeyImage(files, list, ImageType.Thumb);
}
return list;
}
@ -322,6 +328,26 @@ namespace MediaBrowser.LocalMetadata.Images
return false;
}
private void AddCacheKeyImage(IEnumerable<FileSystemInfo> files, List<LocalImageInfo> images, ImageType type)
{
var candidates = files
.Where(i => _fileSystem.GetFileNameWithoutExtension(i).StartsWith(type.ToString() + "_key_", StringComparison.OrdinalIgnoreCase))
.ToList();
var image = BaseItem.SupportedImageExtensions
.Select(i => candidates.FirstOrDefault(c => string.Equals(c.Extension, i, StringComparison.OrdinalIgnoreCase)))
.FirstOrDefault(i => i != null) as FileInfo;
if (image != null)
{
images.Add(new LocalImageInfo
{
FileInfo = image,
Type = type
});
}
}
private FileSystemInfo GetImage(IEnumerable<FileSystemInfo> files, string name)
{
var candidates = files

@ -91,13 +91,6 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -1118,13 +1118,6 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\portable\" /y /d /r /i
)</PostBuildEvent>
</PropertyGroup>
<Import Project="Fody.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -1,4 +1,5 @@
using System.Collections.Generic;
using MediaBrowser.Model.Connect;
using System.Collections.Generic;
namespace MediaBrowser.Model.ApiClient
{
@ -7,6 +8,7 @@ namespace MediaBrowser.Model.ApiClient
public ConnectionState State { get; set; }
public List<ServerInfo> Servers { get; set; }
public IApiClient ApiClient { get; set; }
public ConnectUser ConnectUser { get; set; }
public ConnectionResult()
{

@ -1,8 +1,8 @@
using System.Collections.Generic;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Events;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -14,12 +14,29 @@ namespace MediaBrowser.Model.ApiClient
/// Occurs when [connected].
/// </summary>
event EventHandler<GenericEventArgs<ConnectionResult>> Connected;
/// <summary>
/// Occurs when [local user sign in].
/// </summary>
event EventHandler<GenericEventArgs<UserDto>> LocalUserSignIn;
/// <summary>
/// Occurs when [connect user sign in].
/// </summary>
event EventHandler<GenericEventArgs<ConnectUser>> ConnectUserSignIn;
/// <summary>
/// Occurs when [local user sign out].
/// </summary>
event EventHandler<EventArgs> LocalUserSignOut;
/// <summary>
/// Occurs when [connect user sign out].
/// </summary>
event EventHandler<EventArgs> ConnectUserSignOut;
/// <summary>
/// Occurs when [remote logged out].
/// Gets the connect user.
/// </summary>
event EventHandler<EventArgs> RemoteLoggedOut;
/// <value>The connect user.</value>
ConnectUser ConnectUser { get; }
/// <summary>
/// Gets the API client.
/// </summary>

@ -238,6 +238,103 @@ namespace MediaBrowser.Model.Configuration
{
new MetadataOptions(1, 1280) {ItemType = "Book"},
new MetadataOptions(1, 1280)
{
ItemType = "Movie",
ImageOptions = new []
{
new ImageOption
{
Limit = 3,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 1,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
new MetadataOptions(1, 1280)
{
ItemType = "Series",
ImageOptions = new []
{
new ImageOption
{
Limit = 2,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 1,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
new MetadataOptions(1, 1280)
{
ItemType = "MusicAlbum",

@ -674,7 +674,7 @@ namespace MediaBrowser.Model.Dlna
}
case ProfileConditionValue.VideoProfile:
{
item.VideoProfile = value;
item.VideoProfile = (value ?? string.Empty).Split('|')[0];
break;
}
case ProfileConditionValue.Height:

@ -426,13 +426,6 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
)</PostBuildEvent>
</PropertyGroup>
<Import Project="Fody.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -1,6 +1,5 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
@ -33,12 +32,6 @@ namespace MediaBrowser.Providers.FolderImages
public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken)
{
var playlist = item as Playlist;
if (playlist != null)
{
return GetImages(string.Empty, cancellationToken);
}
var view = item as UserView;
if (view != null)
@ -119,7 +112,7 @@ namespace MediaBrowser.Providers.FolderImages
public bool Supports(IHasImages item)
{
return item is UserView || item is ICollectionFolder || item is Playlist;
return item is UserView || item is ICollectionFolder;
}
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)

@ -1,5 +1,6 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@ -63,7 +64,12 @@ namespace MediaBrowser.Providers.Manager
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">mimeType</exception>
public async Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
public Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
return SaveImage(item, source, mimeType, type, imageIndex, null, cancellationToken);
}
public async Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, string internalCacheKey, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(mimeType))
{
@ -108,6 +114,10 @@ namespace MediaBrowser.Providers.Manager
}
}
}
if (!string.IsNullOrEmpty(internalCacheKey))
{
saveLocally = false;
}
if (!imageIndex.HasValue && item.AllowsMultipleImages(type))
{
@ -116,7 +126,9 @@ namespace MediaBrowser.Providers.Manager
var index = imageIndex ?? 0;
var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally);
var paths = !string.IsNullOrEmpty(internalCacheKey) ?
new[] { GetCacheKeyPath(item, type, mimeType, internalCacheKey) } :
GetSavePaths(item, type, imageIndex, mimeType, saveLocally);
// If there are more than one output paths, the stream will need to be seekable
if (paths.Length > 1 && !source.CanSeek)
@ -180,6 +192,12 @@ namespace MediaBrowser.Providers.Manager
}
}
private string GetCacheKeyPath(IHasImages item, ImageType type, string mimeType, string key)
{
var extension = MimeTypes.ToExtension(mimeType);
return Path.Combine(item.GetInternalMetadataPath(), type.ToString().ToLower() + "_key_" + key + extension);
}
/// <summary>
/// Saves the image to location.
/// </summary>
@ -300,7 +318,7 @@ namespace MediaBrowser.Providers.Manager
private string GetStandardSavePath(IHasImages item, ImageType type, int? imageIndex, string mimeType, bool saveLocally)
{
string filename;
switch (type)
{
case ImageType.Art:
@ -399,13 +417,7 @@ namespace MediaBrowser.Providers.Manager
{
var season = item as Season;
var extension = mimeType.Split('/').Last();
if (string.Equals(extension, "jpeg", StringComparison.OrdinalIgnoreCase))
{
extension = "jpg";
}
extension = "." + extension.ToLower();
var extension = MimeTypes.ToExtension(mimeType);
// Backdrop paths
if (type == ImageType.Backdrop)

@ -137,6 +137,11 @@ namespace MediaBrowser.Providers.Manager
return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
}
public Task SaveImage(IHasImages item, Stream source, string mimeType, ImageType type, int? imageIndex, string internalCacheKey, CancellationToken cancellationToken)
{
return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, internalCacheKey, cancellationToken);
}
public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(IHasImages item, RemoteImageQuery query, CancellationToken cancellationToken)
{
var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);

@ -149,6 +149,7 @@
<Compile Include="Music\MusicBrainzAlbumProvider.cs" />
<Compile Include="People\PersonMetadataService.cs" />
<Compile Include="People\MovieDbPersonProvider.cs" />
<Compile Include="Photos\PhotoAlbumMetadataService.cs" />
<Compile Include="Photos\PhotoHelper.cs" />
<Compile Include="Photos\PhotoMetadataService.cs" />
<Compile Include="Photos\PhotoProvider.cs" />
@ -215,13 +216,6 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -0,0 +1,33 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System.Collections.Generic;
namespace MediaBrowser.Providers.Photos
{
class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo>
{
public PhotoAlbumMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager)
: base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
{
}
/// <summary>
/// Merges the specified source.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="lockedFields">The locked fields.</param>
/// <param name="replaceData">if set to <c>true</c> [replace data].</param>
/// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
protected override void MergeData(PhotoAlbum source, PhotoAlbum target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}

@ -1,6 +1,4 @@
using System.Security.Cryptography;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;

@ -159,7 +159,12 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("username");
}
var user = Users.First(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
if (user == null)
{
throw new AuthenticationException("Invalid username or password entered.");
}
if (user.Configuration.IsDisabled)
{

@ -472,7 +472,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var item = _itemRepo.RetrieveItem(id) as LiveTvChannel;
if (item == null)
if (item == null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
{
item = new LiveTvChannel
{

@ -36,10 +36,10 @@
"LabelMovie": "Film",
"LabelMusicVideo": "Video Musicali",
"LabelEpisode": "Episodio",
"LabelSeries": "Series",
"LabelSeries": "Serie TV",
"LabelStopping": "Sto fermando",
"LabelCancelled": "(cancellato)",
"LabelFailed": "(failed)",
"LabelFailed": "(fallito)",
"LabelAbortedByServerShutdown": "(Interrotto dalla chiusura del server)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Elimina Operazione pianificata",
@ -310,8 +310,8 @@
"TabAdvanced": "Avanzato",
"TabHelp": "Aiuto",
"TabScheduledTasks": "Operazioni pianificate",
"ButtonFullscreen": "Fullscreen",
"ButtonAudioTracks": "Audio Tracks",
"ButtonFullscreen": "Schermo intero",
"ButtonAudioTracks": "Tracce audio",
"ButtonSubtitles": "Sottotitoli",
"ButtonScenes": "Scene",
"ButtonQuality": "Qualit\u00e0",
@ -383,7 +383,7 @@
"PersonTypePerson": "Persona",
"LabelTitleDisplayOrder": "Titolo mostrato in ordine:",
"OptionSortName": "Nome ordinato",
"OptionReleaseDate": "Release date",
"OptionReleaseDate": "Data di uscita",
"LabelSeasonNumber": "Numero Stagione:",
"LabelDiscNumber": "Disco numero",
"LabelParentNumber": "Numero superiore",
@ -525,7 +525,7 @@
"HeaderAlbums": "Album",
"HeaderGames": "Giochi",
"HeaderBooks": "Libri",
"HeaderEpisodes": "Episodes",
"HeaderEpisodes": "Episodi",
"HeaderSeasons": "Stagioni",
"HeaderTracks": "Traccia",
"HeaderItems": "Elementi",
@ -594,8 +594,8 @@
"DashboardTourNotifications": "Inviare automaticamente notifiche di eventi server al vostro dispositivo mobile, e-mail e altro ancora.",
"DashboardTourScheduledTasks": "Gestire facilmente le operazioni di lunga esecuzione con le operazioni pianificate. Decidere quando corrono, e con quale frequenza.",
"DashboardTourMobile": "Il cruscotto Media Browser funziona alla grande su smartphone e tablet. Gestisci il tuo server dal palmo della tua mano in qualsiasi momento, ovunque.",
"MessageRefreshQueued": "Refresh queued",
"TabDevices": "Devices",
"MessageRefreshQueued": "Aggiornamento programmato",
"TabDevices": "Dispositivi",
"DeviceLastUsedByUserName": "Last used by {0}",
"HeaderDeleteDevice": "Delete Device",
"DeleteDeviceConfirmation": "Are you sure you wish to delete this device? It will reappear the next time a user signs in with it.",

@ -601,6 +601,6 @@
"DeleteDeviceConfirmation": "\u0428\u044b\u043d\u044b\u043c\u0435\u043d \u043e\u0441\u044b \u0436\u0430\u0431\u0434\u044b\u049b\u0442\u044b \u0436\u043e\u044e \u049b\u0430\u0436\u0435\u0442 \u043f\u0435? \u0411\u04b1\u043b \u043a\u0435\u043b\u0435\u0441\u0456 \u0440\u0435\u0442\u0442\u0435 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u043e\u0441\u044b\u0434\u0430\u043d \u043a\u0456\u0440\u0433\u0435\u043d\u0434\u0435 \u049b\u0430\u0439\u0442\u0430 \u043f\u0430\u0439\u0434\u0430 \u0431\u043e\u043b\u0430\u0434\u044b.",
"LabelEnableCameraUploadFor": "\u041c\u044b\u043d\u0430\u0443 \u04af\u0448\u0456\u043d \u043a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u0443:",
"HeaderSelectUploadPath": "\u041a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u0443 \u0436\u043e\u043b\u044b\u043d \u0442\u0430\u04a3\u0434\u0430\u0443",
"LabelEnableCameraUploadForHelp": "Uploads will occur automatically in the background when signed into Media Browser.",
"LabelEnableCameraUploadForHelp": "Media Browser \u0456\u0448\u0456\u043d\u0435 \u043a\u0456\u0440\u0433\u0435\u043d\u0434\u0435 \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u0443\u043b\u0430\u0440 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u04e9\u043d\u0434\u0456\u043a \u0440\u0435\u0436\u0456\u043c\u0456\u043d\u0434\u0435 \u04e9\u0442\u0435\u0434\u0456.",
"ErrorMessageStartHourGreaterThanEnd": "\u0410\u044f\u049b\u0442\u0430\u0443 \u0443\u0430\u049b\u044b\u0442\u044b \u0431\u0430\u0441\u0442\u0430\u0443 \u0443\u0430\u049b\u044b\u0442\u044b\u043d\u0430\u043d \u043a\u0435\u0439\u0456\u043d\u0440\u0435\u043a \u0431\u043e\u043b\u0443\u044b \u049b\u0430\u0436\u0435\u0442 \u0435\u0442\u0435\u0434\u0456."
}

@ -36,10 +36,10 @@
"LabelMovie": "Filme",
"LabelMusicVideo": "V\u00eddeo Musical",
"LabelEpisode": "Epis\u00f3dio",
"LabelSeries": "Series",
"LabelSeries": "S\u00e9rie",
"LabelStopping": "Parando",
"LabelCancelled": "(cancelado)",
"LabelFailed": "(failed)",
"LabelFailed": "(falhou)",
"LabelAbortedByServerShutdown": "(Abortada pelo desligamento do servidor)",
"LabelScheduledTaskLastRan": "\u00daltima execu\u00e7\u00e3o {0}, demorando {1}.",
"HeaderDeleteTaskTrigger": "Excluir Disparador da Tarefa",
@ -63,7 +63,7 @@
"ButtonPlay": "Reproduzir",
"ButtonEdit": "Editar",
"ButtonQueue": "Adicionar \u00e0 fila",
"ButtonPlayTrailer": "Play trailer",
"ButtonPlayTrailer": "Reproduzir trailer",
"ButtonPlaylist": "Lista de reprodu\u00e7\u00e3o",
"ButtonPreviousTrack": "Faixa Anterior",
"LabelEnabled": "Ativada",
@ -310,8 +310,8 @@
"TabAdvanced": "Avan\u00e7ado",
"TabHelp": "Ajuda",
"TabScheduledTasks": "Tarefas Agendadas",
"ButtonFullscreen": "Fullscreen",
"ButtonAudioTracks": "Audio Tracks",
"ButtonFullscreen": "Tela cheia",
"ButtonAudioTracks": "Faixas de \u00c1udio",
"ButtonSubtitles": "Legendas",
"ButtonScenes": "Cenas",
"ButtonQuality": "Qualidade",
@ -383,7 +383,7 @@
"PersonTypePerson": "Pessoa",
"LabelTitleDisplayOrder": "Ordem de exibi\u00e7\u00e3o do t\u00edtulo: ",
"OptionSortName": "Nome para ordena\u00e7\u00e3o",
"OptionReleaseDate": "Release date",
"OptionReleaseDate": "Data de lan\u00e7amento",
"LabelSeasonNumber": "N\u00famero da temporada:",
"LabelDiscNumber": "N\u00famero do disco",
"LabelParentNumber": "N\u00famero do superior",
@ -525,7 +525,7 @@
"HeaderAlbums": "\u00c1lbuns",
"HeaderGames": "Jogos",
"HeaderBooks": "Livros",
"HeaderEpisodes": "Episodes",
"HeaderEpisodes": "Epis\u00f3dios",
"HeaderSeasons": "Temporadas",
"HeaderTracks": "Faixas",
"HeaderItems": "Itens",
@ -602,5 +602,5 @@
"LabelEnableCameraUploadFor": "Habilitar envio atrav\u00e9s de c\u00e2mera para:",
"HeaderSelectUploadPath": "Selecione o caminho para carga",
"LabelEnableCameraUploadForHelp": "Cargas ser\u00e3o executadas automaticamente em retaguarda quando logar no Media Browser.",
"ErrorMessageStartHourGreaterThanEnd": "End time must be greater than the start time."
"ErrorMessageStartHourGreaterThanEnd": "O tempo final deve ser maior que o tempo inicial."
}

@ -0,0 +1,606 @@
{
"SettingsSaved": "\u8bbe\u7f6e\u5df2\u4fdd\u5b58",
"AddUser": "\u6dfb\u52a0\u7528\u6237",
"Users": "\u7528\u6237",
"Delete": "\u5220\u9664",
"Administrator": "\u7ba1\u7406\u5458",
"Password": "\u5bc6\u7801",
"DeleteImage": "\u5220\u9664\u56fe\u50cf",
"DeleteImageConfirmation": "\u4f60\u786e\u5b9a\u8981\u5220\u9664\u6b64\u56fe\u50cf\uff1f",
"FileReadCancelled": "\u6587\u4ef6\u8bfb\u53d6\u5df2\u88ab\u53d6\u6d88\u3002",
"FileNotFound": "\u672a\u627e\u5230\u6587\u4ef6\u3002",
"FileReadError": "\u8bfb\u53d6\u6587\u4ef6\u53d1\u751f\u9519\u8bef\u3002",
"DeleteUser": "\u5220\u9664\u7528\u6237",
"DeleteUserConfirmation": "\u4f60\u786e\u5b9a\u8981\u5220\u9664\u6b64\u7528\u6237\uff1f",
"PasswordResetHeader": "\u5bc6\u7801\u91cd\u7f6e",
"PasswordResetComplete": "\u5bc6\u7801\u5df2\u91cd\u7f6e",
"PasswordResetConfirmation": "\u4f60\u786e\u5b9a\u8981\u91cd\u7f6e\u5bc6\u7801\uff1f",
"PasswordSaved": "\u5bc6\u7801\u5df2\u4fdd\u5b58\u3002",
"PasswordMatchError": "\u5bc6\u7801\u548c\u786e\u8ba4\u5bc6\u7801\u5fc5\u987b\u5339\u914d\u3002",
"OptionRelease": "\u5b98\u65b9\u6b63\u5f0f\u7248",
"OptionBeta": "\u6d4b\u8bd5\u7248",
"OptionDev": "\u5f00\u53d1\u7248\uff08\u4e0d\u7a33\u5b9a\uff09",
"UninstallPluginHeader": "\u5378\u8f7d\u63d2\u4ef6",
"UninstallPluginConfirmation": "\u4f60\u786e\u5b9a\u8981\u5378\u8f7d {0}?",
"NoPluginConfigurationMessage": "\u6b64\u63d2\u4ef6\u6ca1\u6709\u914d\u7f6e\u9009\u9879\u3002",
"NoPluginsInstalledMessage": "\u4f60\u6ca1\u6709\u5b89\u88c5\u63d2\u4ef6\u3002",
"BrowsePluginCatalogMessage": "\u6d4f\u89c8\u6211\u4eec\u7684\u63d2\u4ef6\u76ee\u5f55\u6765\u67e5\u770b\u73b0\u6709\u63d2\u4ef6\u3002",
"MessageKeyEmailedTo": "Key emailed to {0}.",
"MessageKeysLinked": "Keys linked.",
"HeaderConfirmation": "Confirmation",
"MessageKeyUpdated": "Thank you. Your supporter key has been updated.",
"MessageKeyRemoved": "Thank you. Your supporter key has been removed.",
"ErrorLaunchingChromecast": "There was an error launching chromecast. Please ensure your device is connected to your wireless network.",
"HeaderSearch": "Search",
"LabelArtist": "Artist",
"LabelMovie": "Movie",
"LabelMusicVideo": "Music Video",
"LabelEpisode": "Episode",
"LabelSeries": "Series",
"LabelStopping": "Stopping",
"LabelCancelled": "(cancelled)",
"LabelFailed": "(failed)",
"LabelAbortedByServerShutdown": "(Aborted by server shutdown)",
"LabelScheduledTaskLastRan": "Last ran {0}, taking {1}.",
"HeaderDeleteTaskTrigger": "Delete Task Trigger",
"HeaderTaskTriggers": "Task Triggers",
"MessageDeleteTaskTrigger": "Are you sure you wish to delete this task trigger?",
"MessageNoPluginsInstalled": "You have no plugins installed.",
"LabelVersionInstalled": "{0} installed",
"LabelNumberReviews": "{0} Reviews",
"LabelFree": "Free",
"HeaderSelectAudio": "Select Audio",
"HeaderSelectSubtitles": "Select Subtitles",
"LabelDefaultStream": "(Default)",
"LabelForcedStream": "(Forced)",
"LabelDefaultForcedStream": "(Default\/Forced)",
"LabelUnknownLanguage": "Unknown language",
"ButtonMute": "Mute",
"ButtonUnmute": "Unmute",
"ButtonStop": "Stop",
"ButtonNextTrack": "Next Track",
"ButtonPause": "Pause",
"ButtonPlay": "Play",
"ButtonEdit": "Edit",
"ButtonQueue": "Queue",
"ButtonPlayTrailer": "Play trailer",
"ButtonPlaylist": "Playlist",
"ButtonPreviousTrack": "Previous Track",
"LabelEnabled": "Enabled",
"LabelDisabled": "Disabled",
"ButtonMoreInformation": "More Information",
"LabelNoUnreadNotifications": "No unread notifications.",
"ButtonViewNotifications": "View notifications",
"ButtonMarkTheseRead": "Mark these read",
"ButtonClose": "Close",
"LabelAllPlaysSentToPlayer": "All plays will be sent to the selected player.",
"MessageInvalidUser": "Invalid user or password.",
"HeaderAllRecordings": "All Recordings",
"RecommendationBecauseYouLike": "Because you like {0}",
"RecommendationBecauseYouWatched": "Because you watched {0}",
"RecommendationDirectedBy": "Directed by {0}",
"RecommendationStarring": "Starring {0}",
"HeaderConfirmRecordingCancellation": "Confirm Recording Cancellation",
"MessageConfirmRecordingCancellation": "Are you sure you wish to cancel this recording?",
"MessageRecordingCancelled": "Recording cancelled.",
"HeaderConfirmSeriesCancellation": "Confirm Series Cancellation",
"MessageConfirmSeriesCancellation": "Are you sure you wish to cancel this series?",
"MessageSeriesCancelled": "Series cancelled.",
"HeaderConfirmRecordingDeletion": "Confirm Recording Deletion",
"MessageConfirmRecordingDeletion": "Are you sure you wish to delete this recording?",
"MessageRecordingDeleted": "Recording deleted.",
"ButonCancelRecording": "Cancel Recording",
"MessageRecordingSaved": "Recording saved.",
"OptionSunday": "Sunday",
"OptionMonday": "Monday",
"OptionTuesday": "Tuesday",
"OptionWednesday": "Wednesday",
"OptionThursday": "Thursday",
"OptionFriday": "Friday",
"OptionSaturday": "Saturday",
"HeaderConfirmDeletion": "Confirm Deletion",
"MessageConfirmPathSubstitutionDeletion": "Are you sure you wish to delete this path substitution?",
"LiveTvUpdateAvailable": "(Update available)",
"LabelVersionUpToDate": "Up to date!",
"ButtonResetTuner": "Reset tuner",
"HeaderResetTuner": "Reset Tuner",
"MessageConfirmResetTuner": "Are you sure you wish to reset this tuner? Any active players or recordings will be abruptly stopped.",
"ButtonCancelSeries": "Cancel Series",
"HeaderSeriesRecordings": "Series Recordings",
"LabelAnytime": "Any time",
"StatusRecording": "Recording",
"StatusWatching": "Watching",
"StatusRecordingProgram": "Recording {0}",
"StatusWatchingProgram": "Watching {0}",
"HeaderSplitMedia": "Split Media Apart",
"MessageConfirmSplitMedia": "Are you sure you wish to split the media sources into separate items?",
"HeaderError": "Error",
"MessagePleaseSelectOneItem": "Please select at least one item.",
"MessagePleaseSelectTwoItems": "Please select at least two items.",
"MessageTheFollowingItemsWillBeGrouped": "The following titles will be grouped into one item:",
"MessageConfirmItemGrouping": "Media Browser clients will automatically choose the optimal version to play based on device and network performance. Are you sure you wish to continue?",
"HeaderResume": "Resume",
"HeaderMyViews": "My Views",
"HeaderLibraryFolders": "Media Folders",
"HeaderLatestMedia": "Latest Media",
"ButtonMoreItems": "More...",
"ButtonMore": "More",
"HeaderFavoriteMovies": "Favorite Movies",
"HeaderFavoriteShows": "Favorite Shows",
"HeaderFavoriteEpisodes": "Favorite Episodes",
"HeaderFavoriteGames": "Favorite Games",
"HeaderRatingsDownloads": "Rating \/ Downloads",
"HeaderConfirmProfileDeletion": "Confirm Profile Deletion",
"MessageConfirmProfileDeletion": "Are you sure you wish to delete this profile?",
"HeaderSelectServerCachePath": "Select Server Cache Path",
"HeaderSelectTranscodingPath": "Select Transcoding Temporary Path",
"HeaderSelectImagesByNamePath": "Select Images By Name Path",
"HeaderSelectMetadataPath": "Select Metadata Path",
"HeaderSelectServerCachePathHelp": "Browse or enter the path to use for server cache files. The folder must be writeable.",
"HeaderSelectTranscodingPathHelp": "Browse or enter the path to use for transcoding temporary files. The folder must be writeable.",
"HeaderSelectImagesByNamePathHelp": "Browse or enter the path to your items by name folder. The folder must be writeable.",
"HeaderSelectMetadataPathHelp": "Browse or enter the path you'd like to store metadata within. The folder must be writeable.",
"HeaderSelectChannelDownloadPath": "Select Channel Download Path",
"HeaderSelectChannelDownloadPathHelp": "Browse or enter the path to use for storing channel cache files. The folder must be writeable.",
"OptionNewCollection": "New...",
"ButtonAdd": "Add",
"ButtonRemove": "Remove",
"LabelChapterDownloaders": "Chapter downloaders:",
"LabelChapterDownloadersHelp": "Enable and rank your preferred chapter downloaders in order of priority. Lower priority downloaders will only be used to fill in missing information.",
"HeaderFavoriteAlbums": "Favorite Albums",
"HeaderLatestChannelMedia": "Latest Channel Items",
"ButtonOrganizeFile": "Organize File",
"ButtonDeleteFile": "Delete File",
"HeaderOrganizeFile": "Organize File",
"HeaderDeleteFile": "Delete File",
"StatusSkipped": "Skipped",
"StatusFailed": "Failed",
"StatusSuccess": "Success",
"MessageFileWillBeDeleted": "The following file will be deleted:",
"MessageSureYouWishToProceed": "Are you sure you wish to proceed?",
"MessageDuplicatesWillBeDeleted": "In addition the following dupliates will be deleted:",
"MessageFollowingFileWillBeMovedFrom": "The following file will be moved from:",
"MessageDestinationTo": "to:",
"HeaderSelectWatchFolder": "Select Watch Folder",
"HeaderSelectWatchFolderHelp": "Browse or enter the path to your watch folder. The folder must be writeable.",
"OrganizePatternResult": "Result: {0}",
"HeaderRestart": "Restart",
"HeaderShutdown": "Shutdown",
"MessageConfirmRestart": "Are you sure you wish to restart Media Browser Server?",
"MessageConfirmShutdown": "Are you sure you wish to shutdown Media Browser Server?",
"ButtonUpdateNow": "Update Now",
"NewVersionOfSomethingAvailable": "A new version of {0} is available!",
"VersionXIsAvailableForDownload": "Version {0} is now available for download.",
"LabelVersionNumber": "Version {0}",
"LabelPlayMethodTranscoding": "Transcoding",
"LabelPlayMethodDirectStream": "Direct Streaming",
"LabelPlayMethodDirectPlay": "Direct Playing",
"LabelAudioCodec": "Audio: {0}",
"LabelVideoCodec": "Video: {0}",
"LabelRemoteAccessUrl": "Remote access: {0}",
"LabelRunningOnPort": "Running on port {0}.",
"HeaderLatestFromChannel": "Latest from {0}",
"ButtonDownload": "Download",
"LabelUnknownLanaguage": "Unknown language",
"HeaderCurrentSubtitles": "Current Subtitles",
"MessageDownloadQueued": "The download has been queued.",
"MessageAreYouSureDeleteSubtitles": "Are you sure you wish to delete this subtitle file?",
"ButtonRemoteControl": "Remote Control",
"HeaderLatestTvRecordings": "Latest Recordings",
"ButtonOk": "Ok",
"ButtonCancel": "Cancel",
"ButtonRefresh": "Refresh",
"LabelCurrentPath": "Current path:",
"HeaderSelectMediaPath": "Select Media Path",
"ButtonNetwork": "Network",
"MessageDirectoryPickerInstruction": "Network paths can be entered manually in the event the Network button fails to locate your devices. For example, {0} or {1}.",
"HeaderMenu": "Menu",
"ButtonOpen": "Open",
"ButtonOpenInNewTab": "Open in new tab",
"ButtonShuffle": "Shuffle",
"ButtonInstantMix": "Instant mix",
"ButtonResume": "Resume",
"HeaderScenes": "Scenes",
"HeaderAudioTracks": "Audio Tracks",
"HeaderSubtitles": "Subtitles",
"HeaderVideoQuality": "Video Quality",
"MessageErrorPlayingVideo": "There was an error playing the video.",
"MessageEnsureOpenTuner": "Please ensure there is an open tuner availalble.",
"ButtonHome": "Home",
"ButtonDashboard": "Dashboard",
"ButtonReports": "Reports",
"ButtonMetadataManager": "Metadata Manager",
"HeaderTime": "Time",
"HeaderName": "Name",
"HeaderAlbum": "Album",
"HeaderAlbumArtist": "Album Artist",
"HeaderArtist": "Artist",
"LabelAddedOnDate": "Added {0}",
"ButtonStart": "Start",
"HeaderChannels": "Channels",
"HeaderMediaFolders": "Media Folders",
"HeaderBlockItemsWithNoRating": "Block items with no rating information:",
"OptionBlockOthers": "Others",
"OptionBlockTvShows": "TV Shows",
"OptionBlockTrailers": "Trailers",
"OptionBlockMusic": "Music",
"OptionBlockMovies": "Movies",
"OptionBlockBooks": "Books",
"OptionBlockGames": "Games",
"OptionBlockLiveTvPrograms": "Live TV Programs",
"OptionBlockLiveTvChannels": "Live TV Channels",
"OptionBlockChannelContent": "Internet Channel Content",
"ButtonRevoke": "Revoke",
"MessageConfirmRevokeApiKey": "Are you sure you wish to revoke this api key? The application's connection to Media Browser will be abruptly terminated.",
"HeaderConfirmRevokeApiKey": "Revoke Api Key",
"ValueContainer": "Container: {0}",
"ValueAudioCodec": "Audio Codec: {0}",
"ValueVideoCodec": "Video Codec: {0}",
"ValueCodec": "Codec: {0}",
"ValueConditions": "Conditions: {0}",
"LabelAll": "All",
"HeaderDeleteImage": "Delete Image",
"MessageFileNotFound": "File not found.",
"MessageFileReadError": "An error occurred reading this file.",
"ButtonNextPage": "Next Page",
"ButtonPreviousPage": "Previous Page",
"ButtonMoveLeft": "Move left",
"ButtonMoveRight": "Move right",
"ButtonBrowseOnlineImages": "Browse online images",
"HeaderDeleteItem": "Delete Item",
"ConfirmDeleteItem": "Are you sure you wish to delete this item from your library?",
"MessagePleaseEnterNameOrId": "Please enter a name or an external Id.",
"MessageValueNotCorrect": "The value entered is not correct. Please try again.",
"MessageItemSaved": "Item saved.",
"OptionEnded": "Ended",
"OptionContinuing": "Continuing",
"OptionOff": "Off",
"OptionOn": "On",
"HeaderFields": "Fields",
"HeaderFieldsHelp": "Slide a field to 'off' to lock it and prevent it's data from being changed.",
"HeaderLiveTV": "Live TV",
"MissingLocalTrailer": "Missing local trailer.",
"MissingPrimaryImage": "Missing primary image.",
"MissingBackdropImage": "Missing backdrop image.",
"MissingLogoImage": "Missing logo image.",
"MissingEpisode": "Missing episode.",
"OptionScreenshots": "Screenshots",
"OptionBackdrops": "Backdrops",
"OptionImages": "Images",
"OptionKeywords": "Keywords",
"OptionTags": "Tags",
"OptionStudios": "Studios",
"OptionName": "Name",
"OptionOverview": "Overview",
"OptionGenres": "Genres",
"OptionParentalRating": "Parental Rating",
"OptionPeople": "People",
"OptionRuntime": "Runtime",
"OptionProductionLocations": "Production Locations",
"OptionBirthLocation": "Birth Location",
"LabelAllChannels": "All channels",
"LabelLiveProgram": "LIVE",
"LabelNewProgram": "NEW",
"LabelPremiereProgram": "PREMIERE",
"LabelHDProgram": "HD",
"HeaderChangeFolderType": "Change Folder Type",
"HeaderChangeFolderTypeHelp": "To change the folder type, please remove and rebuild the collection with the new type.",
"HeaderAlert": "Alert",
"MessagePleaseRestart": "Please restart to finish updating.",
"ButtonRestart": "Restart",
"MessagePleaseRefreshPage": "Please refresh this page to receive new updates from the server.",
"ButtonHide": "Hide",
"MessageSettingsSaved": "Settings saved.",
"ButtonSignOut": "Sign Out",
"ButtonMyProfile": "My Profile",
"ButtonMyPreferences": "My Preferences",
"MessageBrowserDoesNotSupportWebSockets": "This browser does not support web sockets. For a better experience, try a newer browser such as Chrome, Firefox, IE10+, Safari (iOS) or Opera.",
"LabelInstallingPackage": "Installing {0}",
"LabelPackageInstallCompleted": "{0} installation completed.",
"LabelPackageInstallFailed": "{0} installation failed.",
"LabelPackageInstallCancelled": "{0} installation cancelled.",
"TabServer": "Server",
"TabUsers": "Users",
"TabLibrary": "Library",
"TabMetadata": "Metadata",
"TabDLNA": "DLNA",
"TabLiveTV": "Live TV",
"TabAutoOrganize": "Auto-Organize",
"TabPlugins": "Plugins",
"TabAdvanced": "Advanced",
"TabHelp": "Help",
"TabScheduledTasks": "Scheduled Tasks",
"ButtonFullscreen": "Fullscreen",
"ButtonAudioTracks": "Audio Tracks",
"ButtonSubtitles": "Subtitles",
"ButtonScenes": "Scenes",
"ButtonQuality": "Quality",
"HeaderNotifications": "Notifications",
"HeaderSelectPlayer": "Select Player:",
"ButtonSelect": "Select",
"ButtonNew": "New",
"MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM playback plugin.",
"HeaderVideoError": "Video Error",
"ButtonAddToPlaylist": "Add to playlist",
"HeaderAddToPlaylist": "Add to Playlist",
"LabelName": "Name:",
"ButtonSubmit": "Submit",
"LabelSelectPlaylist": "Playlist:",
"OptionNewPlaylist": "New playlist...",
"MessageAddedToPlaylistSuccess": "Ok",
"ButtonView": "View",
"ButtonViewSeriesRecording": "View series recording",
"ValueOriginalAirDate": "Original air date: {0}",
"ButtonRemoveFromPlaylist": "Remove from playlist",
"HeaderSpecials": "Specials",
"HeaderTrailers": "Trailers",
"HeaderAudio": "Audio",
"HeaderResolution": "Resolution",
"HeaderVideo": "Video",
"HeaderRuntime": "Runtime",
"HeaderCommunityRating": "Community rating",
"HeaderParentalRating": "Parental rating",
"HeaderReleaseDate": "Release date",
"HeaderDateAdded": "Date added",
"HeaderSeries": "Series",
"HeaderSeason": "Season",
"HeaderSeasonNumber": "Season number",
"HeaderNetwork": "Network",
"HeaderYear": "Year",
"HeaderGameSystem": "Game system",
"HeaderPlayers": "Players",
"HeaderEmbeddedImage": "Embedded image",
"HeaderTrack": "Track",
"HeaderDisc": "Disc",
"OptionMovies": "Movies",
"OptionCollections": "Collections",
"OptionSeries": "Series",
"OptionSeasons": "Seasons",
"OptionEpisodes": "Episodes",
"OptionGames": "Games",
"OptionGameSystems": "Game systems",
"OptionMusicArtists": "Music artists",
"OptionMusicAlbums": "Music albums",
"OptionMusicVideos": "Music videos",
"OptionSongs": "Songs",
"OptionHomeVideos": "Home videos",
"OptionBooks": "Books",
"OptionAdultVideos": "Adult videos",
"ButtonUp": "Up",
"ButtonDown": "Down",
"LabelMetadataReaders": "Metadata readers:",
"LabelMetadataReadersHelp": "Rank your preferred local metadata sources in order of priority. The first file found will be read.",
"LabelMetadataDownloaders": "Metadata downloaders:",
"LabelMetadataDownloadersHelp": "Enable and rank your preferred metadata downloaders in order of priority. Lower priority downloaders will only be used to fill in missing information.",
"LabelMetadataSavers": "Metadata savers:",
"LabelMetadataSaversHelp": "Choose the file formats to save your metadata to.",
"LabelImageFetchers": "Image fetchers:",
"LabelImageFetchersHelp": "Enable and rank your preferred image fetchers in order of priority.",
"ButtonQueueAllFromHere": "Queue all from here",
"ButtonPlayAllFromHere": "Play all from here",
"LabelDynamicExternalId": "{0} Id:",
"HeaderIdentify": "Identify Item",
"PersonTypePerson": "Person",
"LabelTitleDisplayOrder": "Title display order:",
"OptionSortName": "Sort name",
"OptionReleaseDate": "Release date",
"LabelSeasonNumber": "Season number:",
"LabelDiscNumber": "Disc number",
"LabelParentNumber": "Parent number",
"LabelEpisodeNumber": "Episode number:",
"LabelTrackNumber": "Track number:",
"LabelNumber": "Number:",
"LabelReleaseDate": "Release date:",
"LabelEndDate": "End date:",
"LabelYear": "Year:",
"LabelDateOfBirth": "Date of birth:",
"LabelBirthYear": "Birth year:",
"LabelDeathDate": "Death date:",
"HeaderRemoveMediaLocation": "Remove Media Location",
"MessageConfirmRemoveMediaLocation": "Are you sure you wish to remove this location?",
"HeaderRenameMediaFolder": "Rename Media Folder",
"LabelNewName": "New name:",
"HeaderAddMediaFolder": "Add Media Folder",
"HeaderAddMediaFolderHelp": "Name (Movies, Music, TV, etc):",
"HeaderRemoveMediaFolder": "Remove Media Folder",
"MessageTheFollowingLocationWillBeRemovedFromLibrary": "The following media locations will be removed from your library:",
"MessageAreYouSureYouWishToRemoveMediaFolder": "Are you sure you wish to remove this media folder?",
"ButtonRename": "Rename",
"ButtonChangeType": "Change type",
"HeaderMediaLocations": "Media Locations",
"LabelFolderTypeValue": "Folder type: {0}",
"LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
"FolderTypeMixed": "Mixed movies & tv",
"FolderTypeMovies": "Movies",
"FolderTypeMusic": "Music",
"FolderTypeAdultVideos": "Adult videos",
"FolderTypePhotos": "Photos",
"FolderTypeMusicVideos": "Music videos",
"FolderTypeHomeVideos": "Home videos",
"FolderTypeGames": "Games",
"FolderTypeBooks": "Books",
"FolderTypeTvShows": "TV shows",
"TabMovies": "Movies",
"TabSeries": "Series",
"TabEpisodes": "Episodes",
"TabTrailers": "Trailers",
"TabGames": "Games",
"TabAlbums": "Albums",
"TabSongs": "Songs",
"TabMusicVideos": "Music Videos",
"BirthPlaceValue": "Birth place: {0}",
"DeathDateValue": "Died: {0}",
"BirthDateValue": "Born: {0}",
"HeaderLatestReviews": "Latest Reviews",
"HeaderPluginInstallation": "Plugin Installation",
"MessageAlreadyInstalled": "This version is already installed.",
"ValueReviewCount": "{0} Reviews",
"MessageYouHaveVersionInstalled": "You currently have version {0} installed.",
"MessageTrialExpired": "The trial period for this feature has expired",
"MessageTrialWillExpireIn": "The trial period for this feature will expire in {0} day(s)",
"MessageInstallPluginFromApp": "This plugin must be installed from with in the app you intend to use it in.",
"ValuePriceUSD": "Price: {0} (USD)",
"MessageFeatureIncludedWithSupporter": "You are registered for this feature, and will be able to continue using it with an active supporter membership.",
"MessageChangeRecurringPlanConfirm": "After completing this transaction you will need to cancel your previous recurring donation from within your PayPal account. Thank you for supporting Media Browser.",
"MessageSupporterMembershipExpiredOn": "Your supporter membership expired on {0}.",
"MessageYouHaveALifetimeMembership": "You have a lifetime supporter membership. You can provide additional donations on a one-time or recurring basis using the options below. Thank you for supporting Media Browser.",
"MessageYouHaveAnActiveRecurringMembership": "You have an active {0} membership. You can upgrade your plan using the options below.",
"ButtonDelete": "Delete",
"HeaderMediaBrowserAccountAdded": "Media Browser Account Added",
"MessageMediaBrowserAccountAdded": "The Media Browser account has been added to this user.",
"MessagePendingMediaBrowserAccountAdded": "The Media Browser account has been added to this user. An email will be sent to the owner of the account. The invitation will need to be confirmed by clicking a link within the email.",
"HeaderMediaBrowserAccountRemoved": "Media Browser Account Removed",
"MessageMediaBrowserAccontRemoved": "The Media Browser account has been removed from this user.",
"TooltipLinkedToMediaBrowserConnect": "Linked to Media Browser Connect",
"HeaderUnrated": "Unrated",
"ValueDiscNumber": "Disc {0}",
"HeaderUnknownDate": "Unknown Date",
"HeaderUnknownYear": "Unknown Year",
"ValueMinutes": "{0} min",
"ButtonPlayExternalPlayer": "Play with external player",
"HeaderSelectExternalPlayer": "Select External Player",
"HeaderExternalPlayerPlayback": "External Player Playback",
"ButtonImDone": "I'm Done",
"OptionWatched": "Watched",
"OptionUnwatched": "Unwatched",
"ExternalPlayerPlaystateOptionsHelp": "Specify how you would like to resume playing this video next time.",
"LabelMarkAs": "Mark as:",
"OptionInProgress": "In-Progress",
"LabelResumePoint": "Resume point:",
"ValueOneMovie": "1 movie",
"ValueMovieCount": "{0} movies",
"ValueOneTrailer": "1 trailer",
"ValueTrailerCount": "{0} trailers",
"ValueOneSeries": "1 series",
"ValueSeriesCount": "{0} series",
"ValueOneEpisode": "1 episode",
"ValueEpisodeCount": "{0} episodes",
"ValueOneGame": "1 game",
"ValueGameCount": "{0} games",
"ValueOneAlbum": "1 album",
"ValueAlbumCount": "{0} albums",
"ValueOneSong": "1 song",
"ValueSongCount": "{0} songs",
"ValueOneMusicVideo": "1 music video",
"ValueMusicVideoCount": "{0} music videos",
"HeaderOffline": "Offline",
"HeaderUnaired": "Unaired",
"HeaderMissing": "Missing",
"ButtonWebsite": "Website",
"TooltipFavorite": "Favorite",
"TooltipLike": "Like",
"TooltipDislike": "Dislike",
"TooltipPlayed": "Played",
"ValueSeriesYearToPresent": "{0}-Present",
"ValueAwards": "Awards: {0}",
"ValueBudget": "Budget: {0}",
"ValueRevenue": "Revenue: {0}",
"ValuePremiered": "Premiered {0}",
"ValuePremieres": "Premieres {0}",
"ValueStudio": "Studio: {0}",
"ValueStudios": "Studios: {0}",
"ValueSpecialEpisodeName": "Special - {0}",
"LabelLimit": "Limit:",
"ValueLinks": "Links: {0}",
"HeaderPeople": "People",
"HeaderCastAndCrew": "Cast & Crew",
"ValueArtist": "Artist: {0}",
"ValueArtists": "Artists: {0}",
"HeaderTags": "Tags",
"MediaInfoCameraMake": "Camera make",
"MediaInfoCameraModel": "Camera model",
"MediaInfoAltitude": "Altitude",
"MediaInfoAperture": "Aperture",
"MediaInfoExposureTime": "Exposure time",
"MediaInfoFocalLength": "Focal length",
"MediaInfoOrientation": "Orientation",
"MediaInfoIsoSpeedRating": "Iso speed rating",
"MediaInfoLatitude": "Latitude",
"MediaInfoLongitude": "Longitude",
"MediaInfoShutterSpeed": "Shutter speed",
"MediaInfoSoftware": "Software",
"HeaderIfYouLikeCheckTheseOut": "If you like {0}, check these out...",
"HeaderPlotKeywords": "Plot Keywords",
"HeaderMovies": "Movies",
"HeaderAlbums": "Albums",
"HeaderGames": "Games",
"HeaderBooks": "Books",
"HeaderEpisodes": "Episodes",
"HeaderSeasons": "Seasons",
"HeaderTracks": "Tracks",
"HeaderItems": "Items",
"HeaderOtherItems": "Other Items",
"ButtonFullReview": "Full review",
"ValueAsRole": "as {0}",
"ValueGuestStar": "Guest star",
"MediaInfoSize": "Size",
"MediaInfoPath": "Path",
"MediaInfoFormat": "Format",
"MediaInfoContainer": "Container",
"MediaInfoDefault": "Default",
"MediaInfoForced": "Forced",
"MediaInfoExternal": "External",
"MediaInfoTimestamp": "Timestamp",
"MediaInfoPixelFormat": "Pixel format",
"MediaInfoBitDepth": "Bit depth",
"MediaInfoSampleRate": "Sample rate",
"MediaInfoBitrate": "Bitrate",
"MediaInfoChannels": "Channels",
"MediaInfoLayout": "Layout",
"MediaInfoLanguage": "Language",
"MediaInfoCodec": "Codec",
"MediaInfoProfile": "Profile",
"MediaInfoLevel": "Level",
"MediaInfoAspectRatio": "Aspect ratio",
"MediaInfoResolution": "Resolution",
"MediaInfoAnamorphic": "Anamorphic",
"MediaInfoInterlaced": "Interlaced",
"MediaInfoFramerate": "Framerate",
"MediaInfoStreamTypeAudio": "Audio",
"MediaInfoStreamTypeData": "Data",
"MediaInfoStreamTypeVideo": "Video",
"MediaInfoStreamTypeSubtitle": "Subtitle",
"MediaInfoStreamTypeEmbeddedImage": "Embedded Image",
"MediaInfoRefFrames": "Ref frames",
"TabPlayback": "Playback",
"HeaderSelectCustomIntrosPath": "Select Custom Intros Path",
"HeaderRateAndReview": "Rate and Review",
"HeaderThankYou": "Thank You",
"MessageThankYouForYourReview": "Thank you for your review.",
"LabelYourRating": "Your rating:",
"LabelFullReview": "Full review:",
"LabelShortRatingDescription": "Short rating summary:",
"OptionIRecommendThisItem": "I recommend this item",
"WebClientTourContent": "View your recently added media, next episodes, and more. The green circles indicate how many unplayed items you have.",
"WebClientTourMovies": "Play movies, trailers and more from any device with a web browser",
"WebClientTourMouseOver": "Hold the mouse over any poster for quick access to important information",
"WebClientTourTapHold": "Tap and hold or right click any poster for a context menu",
"WebClientTourMetadataManager": "Click edit to open the metadata manager",
"WebClientTourPlaylists": "Easily create playlists and instant mixes, and play them on any device",
"WebClientTourCollections": "Create movie collections to group box sets together",
"WebClientTourUserPreferences1": "User preferences allow you to customize the way your library is presented in all of your Media Browser apps",
"WebClientTourUserPreferences2": "Configure your audio and subtitle language settings once, for every Media Browser app",
"WebClientTourUserPreferences3": "Design the web client home page to your liking",
"WebClientTourUserPreferences4": "Configure backdrops, theme songs and external players",
"WebClientTourMobile1": "The web client works great on smartphones and tablets...",
"WebClientTourMobile2": "and easily controls other devices and Media Browser apps",
"MessageEnjoyYourStay": "Enjoy your stay",
"DashboardTourDashboard": "The server dashboard allows you to monitor your server and your users. You'll always know who is doing what and where they are.",
"DashboardTourUsers": "Easily create user accounts for your friends and family, each with their own permissions, library access, parental controls and more.",
"DashboardTourCinemaMode": "Cinema mode brings the theater experience straight to your living room with the ability to play trailers and custom intros before the main feature.",
"DashboardTourChapters": "Enable chapter image generation for your videos for a more pleasing presentation while viewing.",
"DashboardTourSubtitles": "Automatically download subtitles for your videos in any language.",
"DashboardTourPlugins": "Install plugins such as internet video channels, live tv, metadata scanners, and more.",
"DashboardTourNotifications": "Automatically send notifications of server events to your mobile device, email and more.",
"DashboardTourScheduledTasks": "Easily manage long running operations with scheduled tasks. Decide when they run, and how often.",
"DashboardTourMobile": "The Media Browser dashboard works great on smartphones and tablets. Manage your server from the palm of your hand anytime, anywhere.",
"MessageRefreshQueued": "Refresh queued",
"TabDevices": "Devices",
"DeviceLastUsedByUserName": "Last used by {0}",
"HeaderDeleteDevice": "Delete Device",
"DeleteDeviceConfirmation": "Are you sure you wish to delete this device? It will reappear the next time a user signs in with it.",
"LabelEnableCameraUploadFor": "Enable camera upload for:",
"HeaderSelectUploadPath": "Select Upload Path",
"LabelEnableCameraUploadForHelp": "Uploads will occur automatically in the background when signed into Media Browser.",
"ErrorMessageStartHourGreaterThanEnd": "End time must be greater than the start time."
}

@ -362,6 +362,7 @@ namespace MediaBrowser.Server.Implementations.Localization
new LocalizatonOption{ Name="English (United Kingdom)", Value="en-GB"},
new LocalizatonOption{ Name="English (United States)", Value="en-us"},
new LocalizatonOption{ Name="Catalan", Value="ca"},
new LocalizatonOption{ Name="Chinese Simplified", Value="zh-CN"},
new LocalizatonOption{ Name="Chinese Traditional", Value="zh-TW"},
new LocalizatonOption{ Name="Croatian", Value="hr"},
new LocalizatonOption{ Name="Czech", Value="cs"},

@ -152,7 +152,7 @@
"LabelWebsite": "Sito web:",
"ButtonAddLocalUser": "Aggiungi Utente locale",
"LabelTagline": "Messaggio pers:",
"ButtonInviteUser": "Invite User",
"ButtonInviteUser": "Invita un utente",
"ButtonSave": "Salva",
"LabelOverview": "Trama:",
"ButtonResetPassword": "Reset Password",
@ -404,11 +404,11 @@
"OptionFriday": "Venerdi",
"LabelNumberTrailerToPlay": "Number of trailers to play:",
"OptionSaturday": "Sabato",
"TitleDevices": "Devices",
"TitleDevices": "Dispositivi",
"HeaderManagement": "Gestione :",
"TabCameraUpload": "Camera Upload",
"LabelManagement": "Management:",
"TabDevices": "Devices",
"TabDevices": "Dispositivi",
"OptionMissingImdbId": "IMDB id mancante",
"HeaderCameraUploadHelp": "Automatically upload photos and videos taken from your mobile devices into Media Browser.",
"OptionMissingTvdbId": "TheTVDB Id mancante",
@ -436,7 +436,7 @@
"SearchKnowledgeBase": "Cerca sulla guida online",
"HeaderGuests": "Guests",
"VisitTheCommunity": "Visita la nostra comunit\u00e0",
"HeaderLocalUsers": "Local Users",
"HeaderLocalUsers": "Utenti locale",
"VisitMediaBrowserWebsite": "Visita il sito di Media Browser",
"HeaderPendingInvitations": "Pending Invitations",
"VisitMediaBrowserWebsiteLong": "Vuoi saperne di pi\u00f9 sulle ultime novit\u00e0?",
@ -689,12 +689,12 @@
"NewCollectionNameExample": "Esempio:Collezione Star wars",
"OptionSearchForInternetMetadata": "Cerca su internet le immagini e i metadati",
"ButtonCreate": "Crea",
"LabelLocalHttpServerPortNumber": "Local port number:",
"LabelLocalHttpServerPortNumber": "Numero di porta locale:",
"LabelLocalHttpServerPortNumberHelp": "The tcp port number that Media Browser's http server should bind to.",
"LabelPublicPort": "Public port number:",
"LabelPublicPortHelp": "The public port number that should be mapped to the local port.",
"LabelWebSocketPortNumber": "Porta webSocket numero:",
"LabelEnableAutomaticPortMap": "Enable automatic port mapping",
"LabelEnableAutomaticPortMap": "Abilita mappatura delle porte automatiche",
"LabelEnableAutomaticPortMapHelp": "Attempt to automatically map the public port to the local port via UPnP. This may not work with some router models.",
"LabelExternalDDNS": "DDNS Esterno:",
"LabelExternalDDNSHelp": "Se tu ha un DNS dinamico inseriscilo qui.Media browser lo utilizzera per le connessioni remote.",

@ -410,27 +410,27 @@
"LabelManagement": "\u041c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440",
"TabDevices": "\u0416\u0430\u0431\u0434\u044b\u049b\u0442\u0430\u0440",
"OptionMissingImdbId": "IMDb Id \u0436\u043e\u049b",
"HeaderCameraUploadHelp": "Automatically upload photos and videos taken from your mobile devices into Media Browser.",
"HeaderCameraUploadHelp": "\u04b0\u0442\u049b\u044b\u0440 \u0436\u0430\u0431\u0434\u044b\u049b\u0442\u0430\u0440\u044b\u04a3\u044b\u0437\u0431\u0435\u043d \u0442\u04af\u0441\u0456\u0440\u0456\u043b\u0433\u0435\u043d \u0444\u043e\u0442\u043e\u0441\u0443\u0440\u0435\u0442\u0442\u0435\u0440 \u043c\u0435\u043d \u0431\u0435\u0439\u043d\u0435\u0444\u0430\u0439\u043b\u0434\u0430\u0440\u0434\u044b Media Browser \u0456\u0448\u0456\u043d\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0442\u044b \u0442\u04af\u0440\u0434\u0435 \u049b\u043e\u0442\u0430\u0440\u044b\u043f \u0431\u0435\u0440\u0443.",
"OptionMissingTvdbId": "TheTVDB Id \u0436\u043e\u049b",
"MessageNoDevicesSupportCameraUpload": "You currently don't have any devices that support camera upload.",
"MessageNoDevicesSupportCameraUpload": "\u0410\u0493\u044b\u043c\u0434\u0430 \u043a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u049b\u043e\u0442\u0430\u0440\u044b\u043f \u0431\u0435\u0440\u0435\u0442\u0456\u043d \u0435\u0448\u049b\u0430\u043d\u0434\u0430\u0439 \u0436\u0430\u0431\u0434\u044b\u049b\u0442\u0430\u0440\u044b\u04a3\u044b\u0437 \u0436\u043e\u049b.",
"OptionMissingOverview": "\u0416\u0430\u043b\u043f\u044b \u0448\u043e\u043b\u0443 \u0436\u043e\u049b",
"LabelCameraUploadPath": "\u041a\u0430\u043c\u0435\u0440\u0430\u0434\u0430\u043d \u043a\u0435\u0440\u0456 \u049b\u043e\u0442\u0430\u0440\u0443 \u0436\u043e\u043b\u044b:",
"OptionFileMetadataYearMismatch": "\u0424\u0430\u0439\u043b\/\u043c\u0435\u0442\u0430\u0434\u0435\u0440\u0435\u043a \u0436\u044b\u043b\u044b \u0441\u04d9\u0439\u043a\u0435\u0441 \u0435\u043c\u0435\u0441",
"LabelCameraUploadPathHelp": "Select a custom upload path, if desired. If unspecified a default folder will be used.",
"LabelCameraUploadPathHelp": "\u0415\u0433\u0435\u0440 \u043a\u0435\u0440\u0435\u043a \u0431\u043e\u043b\u0441\u0430, \u049b\u043e\u0442\u0430\u0440\u044b\u043f \u0431\u0435\u0440\u0443 \u04af\u0448\u0456\u043d \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0436\u043e\u043b\u0434\u044b \u0442\u0430\u04a3\u0434\u0430\u04a3\u044b\u0437. \u0415\u0433\u0435\u0440 \u043a\u04e9\u0440\u0441\u0435\u0442\u0456\u043b\u043c\u0435\u0441\u0435, \u04d9\u0434\u0435\u043f\u043a\u0456 \u049b\u0430\u043b\u0442\u0430 \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u044b\u043b\u0430\u0434\u044b.",
"TabGeneral": "\u0416\u0430\u043b\u043f\u044b",
"LabelCreateCameraUploadSubfolder": "\u04d8\u0440\u049b\u0430\u0439\u0441\u044b \u0436\u0430\u0431\u0434\u044b\u049b \u04af\u0448\u0456\u043d \u0456\u0448\u043a\u0456 \u049b\u0430\u043b\u0442\u0430 \u0436\u0430\u0441\u0430\u0443",
"TitleSupport": "\u049a\u043e\u043b\u0434\u0430\u0443",
"LabelCreateCameraUploadSubfolderHelp": "Specific folders can be assigned to a device by clicking on it from the Devices page.",
"LabelCreateCameraUploadSubfolderHelp": "\u0416\u0430\u0431\u0434\u044b\u049b\u0442\u0430\u0440 \u0431\u0435\u0442\u0456\u043d\u0434\u0435 \u043d\u04b1\u049b\u044b\u0493\u0430\u043d\u0434\u0430 \u0436\u0430\u0431\u0434\u044b\u049b\u049b\u0430 \u043d\u0430\u049b\u0442\u044b \u049b\u0430\u043b\u0442\u0430\u043b\u0430\u0440 \u0442\u0430\u0493\u0430\u0439\u044b\u043d\u0434\u0430\u043b\u0443\u044b \u043c\u04af\u043c\u043a\u0456\u043d.",
"TabLog": "\u0416\u04b1\u0440\u043d\u0430\u043b",
"LabelCustomDeviceDisplayName": "\u0411\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0443 \u0430\u0442\u044b:",
"TabAbout": "\u0422\u0443\u0440\u0430\u043b\u044b",
"LabelCustomDeviceDisplayNameHelp": "Supply a custom display name or leave empty to use the name reported by the device.",
"LabelCustomDeviceDisplayNameHelp": "\u0411\u0435\u0439\u043d\u0435\u043b\u0435\u043d\u0435\u0442\u0456\u043d \u0442\u0435\u04a3\u0448\u0435\u043b\u0433\u0435\u043d \u0430\u0442\u044b\u043d \u04b1\u0441\u044b\u043d\u044b\u04a3\u044b\u0437 \u043d\u0435\u043c\u0435\u0441\u0435 \u0436\u0430\u0431\u0434\u044b\u049b \u0430\u0440\u049b\u044b\u043b\u044b \u0431\u0430\u044f\u043d\u0434\u0430\u043b\u0493\u0430\u043d \u0430\u0442\u044b\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443 \u04af\u0448\u0456\u043d \u0431\u043e\u0441 \u049b\u0430\u043b\u0434\u044b\u0440\u044b\u04a3\u044b\u0437.",
"TabSupporterKey": "\u049a\u043e\u043b\u0434\u0430\u0443\u0448\u044b \u043a\u0456\u043b\u0442\u0456",
"HeaderInviteUser": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043d\u044b \u0448\u0430\u049b\u044b\u0440\u0443",
"TabBecomeSupporter": "\u049a\u043e\u043b\u0434\u0430\u0443\u0448\u044b \u0431\u043e\u043b\u0443",
"LabelConnectInviteUserHelp": "\u0411\u04b1\u043b \u0434\u043e\u0441\u0442\u0430\u0440\u044b\u04a3\u044b\u0437\u0434\u044b\u04a3 Media Browser \u0493\u0430\u043b\u0430\u043c\u0442\u043e\u0440 \u0441\u0430\u0439\u0442\u044b\u043d\u0430 \u043a\u0456\u0440\u0433\u0435\u043d\u0434\u0435 \u049b\u043e\u043b\u0434\u0430\u043d\u0430\u0442\u044b\u043d \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b \u0430\u0442\u044b \u043d\u0435\u043c\u0435\u0441\u0435 \u0435-\u043f\u043e\u0448\u0442\u0430 \u043c\u0435\u043a\u0435\u043d\u0436\u0430\u0439\u044b \u0431\u043e\u043b\u044b\u043f \u0442\u0430\u0431\u044b\u043b\u0430\u0434\u044b.",
"MediaBrowserHasCommunity": "Media Browser \u043f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b\u043b\u0430\u0440\u044b \u0431\u0435\u043d \u049b\u043e\u043b\u0434\u0430\u0443\u0448\u044b\u043b\u0430\u0440\u0434\u044b\u04a3 \u0434\u0430\u043c\u0443\u0434\u0430\u0493\u044b \u049b\u0430\u0443\u044b\u043c\u0434\u0430\u0441\u0442\u044b\u0493\u044b \u0431\u0430\u0440.",
"HeaderInviteUserHelp": "Sharing your media with friends is easier than ever before with Media Browser Connect.",
"HeaderInviteUserHelp": "Media Browser Connect \u0430\u0440\u049b\u044b\u043b\u044b \u0442\u0430\u0441\u0443\u0448\u044b\u0434\u0435\u0440\u0435\u043a\u0442\u0435\u0440\u0434\u0456 \u0434\u043e\u0441\u0442\u0430\u0440\u044b\u04a3\u044b\u0437\u0431\u0435\u043d \u043e\u0440\u0442\u0430\u049b\u0442\u0430\u0441\u0443 \u0431\u04b1\u0440\u044b\u043d\u043d\u0430\u043d \u0434\u0430 \u0436\u0435\u04a3\u0456\u043b\u0434\u0435\u0443 \u0431\u043e\u043b\u0434\u044b.",
"CheckoutKnowledgeBase": "Media Browser \u0435\u04a3 \u04af\u043b\u043a\u0435\u043d \u049b\u0430\u0439\u0442\u0430\u0440\u044b\u043c\u0434\u044b\u043b\u044b\u0493\u044b\u043d \u0430\u043b\u0443 \u0436\u04e9\u043d\u0456\u043d\u0434\u0435 \u043a\u04e9\u043c\u0435\u043a\u0442\u0435\u0441\u0443 \u04af\u0448\u0456\u043d \u0411\u0456\u043b\u0456\u043c \u049b\u043e\u0440\u044b\u043d \u049b\u0430\u0440\u0430\u043f \u0448\u044b\u0493\u044b\u04a3\u044b\u0437.",
"ButtonSendInvitation": "\u0428\u0430\u049b\u044b\u0440\u044b\u043c\u0434\u044b \u0436\u0456\u0431\u0435\u0440\u0443",
"SearchKnowledgeBase": "\u0411\u0456\u043b\u0456\u043c \u049b\u043e\u0440\u044b\u043d\u0430\u043d \u0456\u0437\u0434\u0435\u0443",

@ -440,19 +440,19 @@
"VisitMediaBrowserWebsite": "Visitar o Web Site do Media Browser",
"HeaderPendingInvitations": "Convites pendentes",
"VisitMediaBrowserWebsiteLong": "Visite o Web Site do Media Browser para obter as \u00faltimas novidades e atualizar-se com o blog de desenvolvedores.",
"TabParentalControl": "Parental Control",
"TabParentalControl": "Controle Parental",
"OptionHideUser": "Ocultar este usu\u00e1rio das telas de login",
"HeaderAccessSchedule": "Access Schedule",
"HeaderAccessSchedule": "Agendamento de Acesso",
"OptionDisableUser": "Desativar este usu\u00e1rio",
"HeaderAccessScheduleHelp": "Create an access schedule to limit access to certain hours.",
"HeaderAccessScheduleHelp": "Criar um agendamento de acesso para limitar o acesso a certas horas.",
"OptionDisableUserHelp": "Se estiver desativado o servidor n\u00e3o permitir\u00e1 nenhuma conex\u00e3o deste usu\u00e1rio. Conex\u00f5es existentes ser\u00e3o abruptamente terminadas.",
"ButtonAddSchedule": "Add Schedule",
"ButtonAddSchedule": "Adicionar Agendamento",
"HeaderAdvancedControl": "Controle Avan\u00e7ado",
"LabelAccessDay": "Day of week:",
"LabelAccessDay": "Dia da semana:",
"LabelName": "Nome:",
"LabelAccessStart": "Start hour:",
"LabelAccessStart": "Hora inicial:",
"OptionAllowUserToManageServer": "Permitir a este usu\u00e1rio administrar o servidor",
"LabelAccessEnd": "End hour:",
"LabelAccessEnd": "Hora final:",
"HeaderFeatureAccess": "Acesso aos Recursos",
"OptionAllowMediaPlayback": "Permitir reprodu\u00e7\u00e3o de m\u00eddia",
"OptionAllowBrowsingLiveTv": "Permitir navega\u00e7\u00e3o na tv ao vivo",

@ -1167,7 +1167,7 @@
"LabelExtractChaptersDuringLibraryScan": "Extract chapter images during the library scan",
"LabelExtractChaptersDuringLibraryScanHelp": "If enabled, chapter images will be extracted when videos are imported during the library scan. If disabled they will be extracted during the chapter images scheduled task, allowing the regular library scan to complete faster.",
"LabelConnectUserName": "Media Browser username/email:",
"LabelConnectUserNameHelp": "Connect this user to a Media Browser account to enable easy sign-in access from any app without having to know the server ip address.",
"LabelConnectUserNameHelp": "Connect this user to a Media Browser account to enable easy sign-in access from Media Browser any app without having to know the server ip address.",
"ButtonLearnMoreAboutMediaBrowserConnect": "Learn more about Media Browser Connect",
"LabelExternalPlayers": "External players:",
"LabelExternalPlayersHelp": "Display buttons to play content in external players. This is only available on devices that support url schemes, generally Android and iOS. With external players there is generally no support for remote control or resuming.",
@ -1246,5 +1246,6 @@
"OptionEveryday": "Every day",
"OptionWeekdays": "Weekdays",
"OptionWeekends": "Weekends",
"MessageProfileInfoSynced": "User profile information synced with Media Browser Connect."
"MessageProfileInfoSynced": "User profile information synced with Media Browser Connect.",
"HeaderOptionalLinkMediaBrowserAccount": "Optional: Link your Media Browser account"
}

@ -240,7 +240,8 @@
<Compile Include="Persistence\SqliteShrinkMemoryTimer.cs" />
<Compile Include="Persistence\TypeMapper.cs" />
<Compile Include="Playlists\ManualPlaylistsFolder.cs" />
<Compile Include="Playlists\PlaylistImageEnhancer.cs" />
<Compile Include="Photos\PhotoAlbumImageProvider.cs" />
<Compile Include="Playlists\PlaylistImageProvider.cs" />
<Compile Include="Playlists\PlaylistManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ScheduledTasks\PeopleValidationTask.cs" />
@ -400,6 +401,8 @@
<EmbeddedResource Include="Localization\Server\tr.json" />
<EmbeddedResource Include="Localization\JavaScript\hr.json" />
<EmbeddedResource Include="Localization\Server\hr.json" />
<EmbeddedResource Include="Localization\JavaScript\zh_CN.json" />
<EmbeddedResource Include="Localization\Server\zh_CN.json" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
@ -511,13 +514,6 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -1,44 +1,115 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MoreLinq;
using System;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MoreLinq;
namespace MediaBrowser.Server.Implementations.Playlists
namespace MediaBrowser.Server.Implementations.Photos
{
public class PlaylistImageEnhancer : IImageEnhancer
public class PhotoAlbumImageProvider : ICustomMetadataProvider<PhotoAlbum>, IHasChangeMonitor
{
private readonly IFileSystem _fileSystem;
private readonly IProviderManager _provider;
public PlaylistImageEnhancer(IFileSystem fileSystem)
public PhotoAlbumImageProvider(IFileSystem fileSystem, IProviderManager provider)
{
_fileSystem = fileSystem;
_provider = provider;
}
public bool Supports(IHasImages item, ImageType imageType)
public async Task<ItemUpdateType> FetchAsync(PhotoAlbum item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return (imageType == ImageType.Primary || imageType == ImageType.Thumb) && item is Playlist;
var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false);
var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false);
return primaryResult | thumbResult;
}
public MetadataProviderPriority Priority
private Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
var items = GetItemsWithImages(item);
var cacheKey = GetConfigurationCacheKey(items);
if (!HasChanged(item, imageType, cacheKey))
{
return Task.FromResult(ItemUpdateType.None);
}
return FetchAsyncInternal(item, imageType, cacheKey, options, cancellationToken);
}
private async Task<ItemUpdateType> FetchAsyncInternal(IHasImages item, ImageType imageType, string cacheKey, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
var img = await CreateImageAsync(item, imageType, 0).ConfigureAwait(false);
if (img == null)
{
return ItemUpdateType.None;
}
using (var ms = new MemoryStream())
{
img.Save(ms, ImageFormat.Png);
ms.Position = 0;
await _provider.SaveImage(item, ms, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false);
}
return ItemUpdateType.ImageUpdate;
}
private bool HasChanged(IHasImages item, ImageType type, string cacheKey)
{
var image = item.GetImageInfo(type, 0);
if (image != null)
{
if (!_fileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
{
return false;
}
var currentPathCacheKey = (Path.GetFileNameWithoutExtension(image.Path) ?? string.Empty).Split('_').LastOrDefault();
if (string.Equals(cacheKey, currentPathCacheKey, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
return true;
}
private const string Version = "3";
public string GetConfigurationCacheKey(List<BaseItem> items)
{
get { return MetadataProviderPriority.First; }
return (Version + "_" + string.Join(",", items.Select(i => i.Id.ToString("N")).ToArray())).GetMD5().ToString("N");
}
private List<BaseItem> GetItemsWithImages(IHasImages item)
{
var photoAlbum = item as PhotoAlbum;
if (photoAlbum != null)
{
return GetFinalItems(photoAlbum.RecursiveChildren.Where(i => i is Photo).ToList());
}
var playlist = (Playlist)item;
var items = playlist.GetManageableItems()
@ -78,6 +149,11 @@ namespace MediaBrowser.Server.Implementations.Playlists
.DistinctBy(i => i.Id)
.ToList();
return GetFinalItems(items);
}
private List<BaseItem> GetFinalItems(List<BaseItem> items)
{
// Rotate the images no more than once per day
var random = new Random(DateTime.Now.DayOfYear).Next();
@ -88,66 +164,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
.ToList();
}
private const string Version = "3";
public string GetConfigurationCacheKey(List<BaseItem> items)
{
return Version + "_" + string.Join(",", items.Select(i => i.Id.ToString("N")).ToArray());
}
public string GetConfigurationCacheKey(IHasImages item, ImageType imageType)
{
var items = GetItemsWithImages(item);
return GetConfigurationCacheKey(items);
}
private const int SquareImageSize = 800;
private const int ThumbImageWidth = 1600;
private const int ThumbImageHeight = 900;
public ImageSize GetEnhancedImageSize(IHasImages item, ImageType imageType, int imageIndex, ImageSize originalImageSize)
{
var items = GetItemsWithImages(item);
if (items.Count == 0)
{
return originalImageSize;
}
if (imageType == ImageType.Thumb)
{
return new ImageSize
{
Height = ThumbImageHeight,
Width = ThumbImageWidth
};
}
return new ImageSize
{
Height = SquareImageSize,
Width = SquareImageSize
};
}
public async Task<Image> EnhanceImageAsync(IHasImages item, Image originalImage, ImageType imageType, int imageIndex)
public async Task<Image> CreateImageAsync(IHasImages item, ImageType imageType, int imageIndex)
{
var items = GetItemsWithImages(item);
if (items.Count == 0)
{
return originalImage;
return null;
}
var img = imageType == ImageType.Thumb ?
return imageType == ImageType.Thumb ?
await GetThumbCollage(items).ConfigureAwait(false) :
await GetSquareCollage(items).ConfigureAwait(false);
using (originalImage)
{
return img;
}
}
private Task<Image> GetThumbCollage(List<BaseItem> items)
@ -217,6 +245,10 @@ namespace MediaBrowser.Server.Implementations.Playlists
return img;
}
private const int SquareImageSize = 800;
private const int ThumbImageWidth = 1600;
private const int ThumbImageHeight = 900;
private async Task<Image> GetSquareCollage(List<string> files)
{
if (files.Count < 4)
@ -288,5 +320,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
return Image.FromStream(memoryStream, true, false);
}
}
public string Name
{
get { return "Dynamic Image Provider"; }
}
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
{
var items = GetItemsWithImages(item);
var cacheKey = GetConfigurationCacheKey(items);
return HasChanged(item, ImageType.Primary, cacheKey) || HasChanged(item, ImageType.Thumb, cacheKey);
}
}
}

@ -0,0 +1,345 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MoreLinq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Playlists
{
public class PlaylistImageProvider : ICustomMetadataProvider<Playlist>, IHasChangeMonitor
{
private readonly IFileSystem _fileSystem;
private readonly IProviderManager _provider;
public PlaylistImageProvider(IFileSystem fileSystem, IProviderManager provider)
{
_fileSystem = fileSystem;
_provider = provider;
}
public async Task<ItemUpdateType> FetchAsync(Playlist item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false);
var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false);
return primaryResult | thumbResult;
}
public async Task<ItemUpdateType> FetchAsync(PhotoAlbum item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false);
var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false);
return primaryResult | thumbResult;
}
private Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
var items = GetItemsWithImages(item);
var cacheKey = GetConfigurationCacheKey(items);
if (!HasChanged(item, imageType, cacheKey))
{
return Task.FromResult(ItemUpdateType.None);
}
return FetchAsyncInternal(item, imageType, cacheKey, options, cancellationToken);
}
private async Task<ItemUpdateType> FetchAsyncInternal(IHasImages item, ImageType imageType, string cacheKey, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
var img = await CreateImageAsync(item, imageType, 0).ConfigureAwait(false);
if (img == null)
{
return ItemUpdateType.None;
}
using (var ms = new MemoryStream())
{
img.Save(ms, ImageFormat.Png);
ms.Position = 0;
await _provider.SaveImage(item, ms, "image/png", imageType, null, cacheKey, cancellationToken).ConfigureAwait(false);
}
return ItemUpdateType.ImageUpdate;
}
private bool HasChanged(IHasImages item, ImageType type, string cacheKey)
{
var image = item.GetImageInfo(type, 0);
if (image != null)
{
if (!_fileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
{
return false;
}
var currentPathCacheKey = (Path.GetFileNameWithoutExtension(image.Path) ?? string.Empty).Split('_').LastOrDefault();
if (string.Equals(cacheKey, currentPathCacheKey, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
return true;
}
private const string Version = "3";
public string GetConfigurationCacheKey(List<BaseItem> items)
{
return (Version + "_" + string.Join(",", items.Select(i => i.Id.ToString("N")).ToArray())).GetMD5().ToString("N");
}
private List<BaseItem> GetItemsWithImages(IHasImages item)
{
var photoAlbum = item as PhotoAlbum;
if (photoAlbum != null)
{
return GetFinalItems(photoAlbum.RecursiveChildren.Where(i => i is Photo).ToList());
}
var playlist = (Playlist)item;
var items = playlist.GetManageableItems()
.Select(i =>
{
var subItem = i.Item2;
var episode = subItem as Episode;
if (episode != null)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))
{
return series;
}
}
if (subItem.HasImage(ImageType.Primary))
{
return subItem;
}
var parent = subItem.Parent;
if (parent != null && parent.HasImage(ImageType.Primary))
{
if (parent is MusicAlbum)
{
return parent;
}
}
return null;
})
.Where(i => i != null)
.DistinctBy(i => i.Id)
.ToList();
return GetFinalItems(items);
}
private List<BaseItem> GetFinalItems(List<BaseItem> items)
{
// Rotate the images no more than once per day
var random = new Random(DateTime.Now.DayOfYear).Next();
return items
.OrderBy(i => random - items.IndexOf(i))
.Take(4)
.OrderBy(i => i.Name)
.ToList();
}
public async Task<Image> CreateImageAsync(IHasImages item, ImageType imageType, int imageIndex)
{
var items = GetItemsWithImages(item);
if (items.Count == 0)
{
return null;
}
return imageType == ImageType.Thumb ?
await GetThumbCollage(items).ConfigureAwait(false) :
await GetSquareCollage(items).ConfigureAwait(false);
}
private Task<Image> GetThumbCollage(List<BaseItem> items)
{
return GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList());
}
private Task<Image> GetSquareCollage(List<BaseItem> items)
{
return GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary)).ToList());
}
private async Task<Image> GetThumbCollage(List<string> files)
{
if (files.Count < 3)
{
return await GetSingleImage(files).ConfigureAwait(false);
}
const int rows = 1;
const int cols = 3;
const int cellWidth = 2 * (ThumbImageWidth / 3);
const int cellHeight = ThumbImageHeight;
var index = 0;
var img = new Bitmap(ThumbImageWidth, ThumbImageHeight, PixelFormat.Format32bppPArgb);
using (var graphics = Graphics.FromImage(img))
{
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceCopy;
for (var row = 0; row < rows; row++)
{
for (var col = 0; col < cols; col++)
{
var x = col * (cellWidth / 2);
var y = row * cellHeight;
if (files.Count > index)
{
using (var fileStream = _fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
{
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
using (var imgtemp = Image.FromStream(memoryStream, true, false))
{
graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
}
}
}
}
index++;
}
}
}
return img;
}
private const int SquareImageSize = 800;
private const int ThumbImageWidth = 1600;
private const int ThumbImageHeight = 900;
private async Task<Image> GetSquareCollage(List<string> files)
{
if (files.Count < 4)
{
return await GetSingleImage(files).ConfigureAwait(false);
}
const int rows = 2;
const int cols = 2;
const int singleSize = SquareImageSize / 2;
var index = 0;
var img = new Bitmap(SquareImageSize, SquareImageSize, PixelFormat.Format32bppPArgb);
using (var graphics = Graphics.FromImage(img))
{
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingMode = CompositingMode.SourceCopy;
for (var row = 0; row < rows; row++)
{
for (var col = 0; col < cols; col++)
{
var x = col * singleSize;
var y = row * singleSize;
using (var fileStream = _fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
{
using (var memoryStream = new MemoryStream())
{
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
using (var imgtemp = Image.FromStream(memoryStream, true, false))
{
graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
}
}
}
index++;
}
}
}
return img;
}
private Task<Image> GetSingleImage(List<string> files)
{
return GetImage(files[0]);
}
private async Task<Image> GetImage(string file)
{
using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true))
{
var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
return Image.FromStream(memoryStream, true, false);
}
}
public string Name
{
get { return "Dynamic Image Provider"; }
}
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
{
var items = GetItemsWithImages(item);
var cacheKey = GetConfigurationCacheKey(items);
return HasChanged(item, ImageType.Primary, cacheKey) || HasChanged(item, ImageType.Thumb, cacheKey);
}
}
}

@ -189,11 +189,4 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
</Project>

@ -266,11 +266,4 @@ del "$(SolutionDir)..\Deploy\MBServer.zip"
</GetAssemblyIdentity>
<Exec Command="copy &quot;$(SolutionDir)..\Deploy\MBServer.zip&quot; &quot;$(SolutionDir)..\Deploy\MBServer_%(CurrentAssembly.Version).zip&quot; /y" Condition="'$(ConfigurationName)' == 'Release'" />
</Target>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
</Project>

@ -2264,13 +2264,6 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
<version>3.0.494</version>
<version>3.0.496</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
<dependency id="MediaBrowser.Common" version="3.0.494" />
<dependency id="MediaBrowser.Common" version="3.0.496" />
<dependency id="NLog" version="3.1.0.0" />
<dependency id="SimpleInjector" version="2.6.0" />
<dependency id="sharpcompress" version="0.10.2" />

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
<version>3.0.494</version>
<version>3.0.496</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Model.Signed</id>
<version>3.0.494</version>
<version>3.0.496</version>
<title>MediaBrowser.Model - Signed Edition</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
<version>3.0.494</version>
<version>3.0.496</version>
<title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
<dependency id="MediaBrowser.Common" version="3.0.494" />
<dependency id="MediaBrowser.Common" version="3.0.496" />
</dependencies>
</metadata>
<files>

Loading…
Cancel
Save