aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/DisplayPreferencesService.cs6
-rw-r--r--MediaBrowser.Api/EnvironmentService.cs15
-rw-r--r--MediaBrowser.Api/GamesService.cs15
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs6
-rw-r--r--MediaBrowser.Api/LocalizationService.cs15
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj1
-rw-r--r--MediaBrowser.Api/Music/InstantMixService.cs75
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs62
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs2
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs36
-rw-r--r--MediaBrowser.Api/Playback/ProgressiveStreamService.cs73
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs5
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs5
-rw-r--r--MediaBrowser.Controller/Dlna/DeviceProfile.cs1
-rw-r--r--MediaBrowser.Controller/Dlna/TranscodingProfile.cs3
-rw-r--r--MediaBrowser.Controller/Dto/IDtoService.cs7
-rw-r--r--MediaBrowser.Controller/Library/IMusicManager.cs38
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs4
-rw-r--r--MediaBrowser.Controller/Localization/ILocalizationManager.cs39
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs79
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingResult.cs13
-rw-r--r--MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs26
-rw-r--r--MediaBrowser.Controller/Session/ISessionController.cs8
-rw-r--r--MediaBrowser.Dlna/DlnaManager.cs60
-rw-r--r--MediaBrowser.Dlna/PlayTo/DlnaController.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs91
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs233
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs168
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs95
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs323
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs64
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj5
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj3
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj3
-rw-r--r--MediaBrowser.Model/ApiClient/IApiClient.cs8
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs4
-rw-r--r--MediaBrowser.Model/Globalization/CountryInfo.cs6
-rw-r--r--MediaBrowser.Model/LiveTv/ChannelInfoDto.cs3
-rw-r--r--MediaBrowser.Model/LiveTv/ChannelQuery.cs6
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingInfoDto.cs11
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
-rw-r--r--MediaBrowser.Model/Session/GenericCommand.cs48
-rw-r--r--MediaBrowser.Model/Session/PlayRequest.cs14
-rw-r--r--MediaBrowser.Model/Session/PlaystateCommand.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs52
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs20
-rw-r--r--MediaBrowser.Server.Implementations/Library/MusicManager.cs67
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs3
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs11
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs8
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json21
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json21
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json21
-rw-r--r--MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs117
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/de.json17
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/en_US.json17
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json17
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/ru.json17
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/server.json17
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj10
-rw-r--r--MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs13
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs96
-rw-r--r--MediaBrowser.Server.Implementations/Session/WebSocketController.cs12
-rw-r--r--MediaBrowser.ServerApplication/ApplicationHost.cs12
-rw-r--r--MediaBrowser.ServerApplication/LibraryViewer.cs6
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs2
-rw-r--r--MediaBrowser.ServerApplication/ServerNotifyIcon.cs50
-rw-r--r--MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs4
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs439
-rw-r--r--MediaBrowser.WebDashboard/ApiClient.js2
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj22
-rw-r--r--MediaBrowser.WebDashboard/app.config30
-rw-r--r--MediaBrowser.WebDashboard/packages.config3
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec4
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
78 files changed, 2352 insertions, 486 deletions
diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs
index f22dc9e39..4060b42b4 100644
--- a/MediaBrowser.Api/DisplayPreferencesService.cs
+++ b/MediaBrowser.Api/DisplayPreferencesService.cs
@@ -12,8 +12,7 @@ namespace MediaBrowser.Api
/// <summary>
/// Class UpdateDisplayPreferences
/// </summary>
- [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST")]
- [Api(("Updates a user's display preferences for an item"))]
+ [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST", Summary = "Updates a user's display preferences for an item")]
public class UpdateDisplayPreferences : DisplayPreferences, IReturnVoid
{
/// <summary>
@@ -30,8 +29,7 @@ namespace MediaBrowser.Api
public string Client { get; set; }
}
- [Route("/DisplayPreferences/{Id}", "GET")]
- [Api(("Gets a user's display preferences for an item"))]
+ [Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")]
public class GetDisplayPreferences : IReturn<DisplayPreferences>
{
/// <summary>
diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs
index cb104072b..56f71fc00 100644
--- a/MediaBrowser.Api/EnvironmentService.cs
+++ b/MediaBrowser.Api/EnvironmentService.cs
@@ -13,8 +13,7 @@ namespace MediaBrowser.Api
/// <summary>
/// Class GetDirectoryContents
/// </summary>
- [Route("/Environment/DirectoryContents", "GET")]
- [Api(Description = "Gets the contents of a given directory in the file system")]
+ [Route("/Environment/DirectoryContents", "GET", Summary = "Gets the contents of a given directory in the file system")]
public class GetDirectoryContents : IReturn<List<FileSystemEntryInfo>>
{
/// <summary>
@@ -46,8 +45,7 @@ namespace MediaBrowser.Api
public bool IncludeHidden { get; set; }
}
- [Route("/Environment/NetworkShares", "GET")]
- [Api(Description = "Gets shares from a network device")]
+ [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]
public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>>
{
/// <summary>
@@ -61,8 +59,7 @@ namespace MediaBrowser.Api
/// <summary>
/// Class GetDrives
/// </summary>
- [Route("/Environment/Drives", "GET")]
- [Api(Description = "Gets available drives from the server's file system")]
+ [Route("/Environment/Drives", "GET", Summary = "Gets available drives from the server's file system")]
public class GetDrives : IReturn<List<FileSystemEntryInfo>>
{
}
@@ -70,14 +67,12 @@ namespace MediaBrowser.Api
/// <summary>
/// Class GetNetworkComputers
/// </summary>
- [Route("/Environment/NetworkDevices", "GET")]
- [Api(Description = "Gets a list of devices on the network")]
+ [Route("/Environment/NetworkDevices", "GET", Summary = "Gets a list of devices on the network")]
public class GetNetworkDevices : IReturn<List<FileSystemEntryInfo>>
{
}
- [Route("/Environment/ParentPath", "GET")]
- [Api(Description = "Gets the parent path of a given path")]
+ [Route("/Environment/ParentPath", "GET", Summary = "Gets the parent path of a given path")]
public class GetParentPath : IReturn<string>
{
/// <summary>
diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs
index e371791f8..ff2771ce1 100644
--- a/MediaBrowser.Api/GamesService.cs
+++ b/MediaBrowser.Api/GamesService.cs
@@ -1,5 +1,4 @@
-using System.Globalization;
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
@@ -7,6 +6,7 @@ using MediaBrowser.Model.Dto;
using ServiceStack;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
@@ -15,8 +15,7 @@ namespace MediaBrowser.Api
/// <summary>
/// Class GetSimilarGames
/// </summary>
- [Route("/Games/{Id}/Similar", "GET")]
- [Api(Description = "Finds games similar to a given game.")]
+ [Route("/Games/{Id}/Similar", "GET", Summary = "Finds games similar to a given game.")]
public class GetSimilarGames : BaseGetSimilarItemsFromItem
{
}
@@ -24,8 +23,7 @@ namespace MediaBrowser.Api
/// <summary>
/// Class GetGameSystemSummaries
/// </summary>
- [Route("/Games/SystemSummaries", "GET")]
- [Api(Description = "Finds games similar to a given game.")]
+ [Route("/Games/SystemSummaries", "GET", Summary = "Finds games similar to a given game.")]
public class GetGameSystemSummaries : IReturn<List<GameSystemSummary>>
{
/// <summary>
@@ -39,8 +37,7 @@ namespace MediaBrowser.Api
/// <summary>
/// Class GetGameSystemSummaries
/// </summary>
- [Route("/Games/PlayerIndex", "GET")]
- [Api(Description = "Gets an index of players (1-x) and the number of games listed under each")]
+ [Route("/Games/PlayerIndex", "GET", Summary = "Gets an index of players (1-x) and the number of games listed under each")]
public class GetPlayerIndex : IReturn<List<ItemIndex>>
{
/// <summary>
@@ -117,7 +114,7 @@ namespace MediaBrowser.Api
}
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
+
public object Get(GetPlayerIndex request)
{
var games = GetAllLibraryItems(request.UserId, _userManager, _libraryManager)
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 96fe01ab3..abeaba910 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -39,6 +39,9 @@ namespace MediaBrowser.Api.LiveTv
/// <value>The limit.</value>
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Limit { get; set; }
+
+ [ApiMember(Name = "IsFavorite", Description = "Filter by channels that are favorites, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+ public bool? IsFavorite { get; set; }
}
[Route("/LiveTv/Channels/{Id}", "GET", Summary = "Gets a live tv channel")]
@@ -290,7 +293,8 @@ namespace MediaBrowser.Api.LiveTv
ChannelType = request.Type,
UserId = request.UserId,
StartIndex = request.StartIndex,
- Limit = request.Limit
+ Limit = request.Limit,
+ IsFavorite = request.IsFavorite
}, CancellationToken.None).Result;
diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs
index 60270a389..be86090ac 100644
--- a/MediaBrowser.Api/LocalizationService.cs
+++ b/MediaBrowser.Api/LocalizationService.cs
@@ -32,6 +32,14 @@ namespace MediaBrowser.Api
}
/// <summary>
+ /// Class ParentalRatings
+ /// </summary>
+ [Route("/Localization/Options", "GET", Summary = "Gets localization options")]
+ public class GetLocalizationOptions : IReturn<List<LocalizatonOption>>
+ {
+ }
+
+ /// <summary>
/// Class CulturesService
/// </summary>
public class LocalizationService : BaseApiService
@@ -62,6 +70,13 @@ namespace MediaBrowser.Api
return ToOptimizedSerializedResultUsingCache(result);
}
+ public object Get(GetLocalizationOptions request)
+ {
+ var result = _localization.GetLocalizationOptions().ToList();
+
+ return ToOptimizedSerializedResultUsingCache(result);
+ }
+
/// <summary>
/// Gets the specified request.
/// </summary>
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 18559a68d..c03eddf99 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -103,6 +103,7 @@
<Compile Include="Playback\Hls\DynamicHlsService.cs" />
<Compile Include="Playback\Hls\HlsSegmentService.cs" />
<Compile Include="Playback\Hls\VideoHlsService.cs" />
+ <Compile Include="Playback\ProgressiveStreamService.cs" />
<Compile Include="Playback\Progressive\AudioService.cs" />
<Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
<Compile Include="Playback\BaseStreamingService.cs" />
diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs
index 9b9df3a92..c39811bb6 100644
--- a/MediaBrowser.Api/Music/InstantMixService.cs
+++ b/MediaBrowser.Api/Music/InstantMixService.cs
@@ -1,9 +1,9 @@
using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Querying;
using ServiceStack;
-using System;
using System.Collections.Generic;
using System.Linq;
@@ -36,103 +36,74 @@ namespace MediaBrowser.Api.Music
public class InstantMixService : BaseApiService
{
private readonly IUserManager _userManager;
- private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService;
+ private readonly IMusicManager _musicManager;
- public InstantMixService(IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService)
+ public InstantMixService(IUserManager userManager, IDtoService dtoService, IMusicManager musicManager)
{
_userManager = userManager;
- _libraryManager = libraryManager;
_dtoService = dtoService;
+ _musicManager = musicManager;
}
public object Get(GetInstantMixFromSong request)
{
- var item = _dtoService.GetItemByDtoId(request.Id);
+ var item = (Audio)_dtoService.GetItemByDtoId(request.Id);
- var result = GetInstantMixResult(request, item.Genres);
+ var user = _userManager.GetUserById(request.UserId.Value);
- return ToOptimizedSerializedResultUsingCache(result);
+ var items = _musicManager.GetInstantMixFromSong(item, user);
+
+ return GetResult(items, user, request);
}
public object Get(GetInstantMixFromAlbum request)
{
var album = (MusicAlbum)_dtoService.GetItemByDtoId(request.Id);
- var genres = album
- .RecursiveChildren
- .OfType<Audio>()
- .SelectMany(i => i.Genres)
- .Concat(album.Genres)
- .Distinct(StringComparer.OrdinalIgnoreCase);
+ var user = _userManager.GetUserById(request.UserId.Value);
- var result = GetInstantMixResult(request, genres);
+ var items = _musicManager.GetInstantMixFromAlbum(album, user);
- return ToOptimizedSerializedResultUsingCache(result);
+ return GetResult(items, user, request);
}
public object Get(GetInstantMixFromMusicGenre request)
{
- var genre = GetMusicGenre(request.Name, _libraryManager);
+ var user = _userManager.GetUserById(request.UserId.Value);
- var result = GetInstantMixResult(request, new[] { genre.Name });
+ var items = _musicManager.GetInstantMixFromGenres(new[] { request.Name }, user);
- return ToOptimizedSerializedResultUsingCache(result);
+ return GetResult(items, user, request);
}
public object Get(GetInstantMixFromArtist request)
{
- var artist = GetArtist(request.Name, _libraryManager);
-
- var genres = _libraryManager.RootFolder
- .RecursiveChildren
- .OfType<Audio>()
- .Where(i => i.HasArtist(artist.Name))
- .SelectMany(i => i.Genres)
- .Concat(artist.Genres)
- .Distinct(StringComparer.OrdinalIgnoreCase);
+ var user = _userManager.GetUserById(request.UserId.Value);
- var result = GetInstantMixResult(request, genres);
+ var items = _musicManager.GetInstantMixFromArtist(request.Name, user);
- return ToOptimizedSerializedResultUsingCache(result);
+ return GetResult(items, user, request);
}
- private ItemsResult GetInstantMixResult(BaseGetSimilarItems request, IEnumerable<string> genres)
+ private object GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request)
{
- var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null;
-
var fields = request.GetItemFields().ToList();
- var inputItems = user == null
- ? _libraryManager.RootFolder.RecursiveChildren
- : user.RootFolder.GetRecursiveChildren(user);
-
- var genresDictionary = genres.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- var limit = request.Limit.HasValue ? request.Limit.Value * 2 : 100;
-
- var items = inputItems
- .OfType<Audio>()
- .Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey)))
- .OrderByDescending(i => i.Item2)
- .ThenBy(i => Guid.NewGuid())
- .Select(i => i.Item1)
- .Take(limit)
- .OrderBy(i => Guid.NewGuid())
- .ToList();
+ var list = items.ToList();
var result = new ItemsResult
{
- TotalRecordCount = items.Count
+ TotalRecordCount = list.Count
};
- var dtos = items.Take(request.Limit ?? items.Count)
+ var dtos = list.Take(request.Limit ?? list.Count)
.Select(i => _dtoService.GetBaseItemDto(i, fields, user));
result.Items = dtos.ToArray();
- return result;
+ return ToOptimizedResult(result);
}
}
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 519ff7947..5ed77356b 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -835,11 +835,6 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetInputArgument(StreamState state)
{
- if (state.SendInputOverStandardInput)
- {
- return "-";
- }
-
var type = InputType.File;
var inputPath = new[] { state.MediaPath };
@@ -898,9 +893,7 @@ namespace MediaBrowser.Api.Playback
Arguments = commandLineArgs,
WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false,
-
- RedirectStandardInput = state.SendInputOverStandardInput
+ ErrorDialog = false
},
EnableRaisingEvents = true
@@ -933,11 +926,6 @@ namespace MediaBrowser.Api.Playback
throw;
}
- if (state.SendInputOverStandardInput)
- {
- StreamToStandardInput(process, state);
- }
-
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
process.BeginOutputReadLine();
@@ -965,32 +953,6 @@ namespace MediaBrowser.Api.Playback
}
}
- private async void StreamToStandardInput(Process process, StreamState state)
- {
- try
- {
- await StreamToStandardInputInternal(process, state).ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- Logger.Debug("Stream to standard input closed normally.");
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error writing to standard input", ex);
- }
- }
-
- private async Task StreamToStandardInputInternal(Process process, StreamState state)
- {
- state.StandardInputCancellationTokenSource = new CancellationTokenSource();
-
- using (var fileStream = FileSystem.GetFileStream(state.MediaPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
- {
- await new EndlessStreamCopy().CopyStream(fileStream, process.StandardInput.BaseStream, state.StandardInputCancellationTokenSource.Token).ConfigureAwait(false);
- }
- }
-
protected int? GetVideoBitrateParamValue(StreamState state)
{
var bitrate = state.VideoRequest.VideoBitRate;
@@ -1280,22 +1242,12 @@ namespace MediaBrowser.Api.Playback
}
else if (i == 14)
{
- if (videoRequest != null)
- {
- videoRequest.Framerate = int.Parse(val, UsCulture);
- }
+ request.StartTimeTicks = long.Parse(val, UsCulture);
}
else if (i == 15)
{
if (videoRequest != null)
{
- request.StartTimeTicks = long.Parse(val, UsCulture);
- }
- }
- else if (i == 16)
- {
- if (videoRequest != null)
- {
videoRequest.Level = val;
}
}
@@ -1315,11 +1267,6 @@ namespace MediaBrowser.Api.Playback
ParseParams(request);
}
- if (request.ThrowDebugError)
- {
- throw new InvalidOperationException("You asked for a debug error, you got one.");
- }
-
var user = AuthorizationRequestFilterAttribute.GetCurrentUser(Request, UserManager);
var url = Request.PathInfo;
@@ -1369,8 +1316,6 @@ namespace MediaBrowser.Api.Playback
{
state.MediaPath = path;
state.IsRemote = false;
-
- state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress;
}
else if (!string.IsNullOrEmpty(mediaUrl))
{
@@ -1378,7 +1323,8 @@ namespace MediaBrowser.Api.Playback
state.IsRemote = true;
}
- //state.RunTimeTicks = recording.RunTimeTicks;
+ state.RunTimeTicks = recording.RunTimeTicks;
+
if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsRemote)
{
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 96b36ac7f..1a190b75e 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -276,7 +276,7 @@ namespace MediaBrowser.Api.Playback.Hls
? 0
: ((GetHlsVideoStream)state.VideoRequest).TimeStampOffsetMs;
- var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds);
+ var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
var threads = GetNumberOfThreads(state, false);
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index ca206c012..55b311f86 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -17,23 +17,22 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <summary>
/// Class GetAudioStream
/// </summary>
- [Route("/Audio/{Id}/stream.mp3", "GET")]
- [Route("/Audio/{Id}/stream.wma", "GET")]
- [Route("/Audio/{Id}/stream.aac", "GET")]
- [Route("/Audio/{Id}/stream.flac", "GET")]
- [Route("/Audio/{Id}/stream.ogg", "GET")]
- [Route("/Audio/{Id}/stream.oga", "GET")]
- [Route("/Audio/{Id}/stream.webm", "GET")]
- [Route("/Audio/{Id}/stream", "GET")]
- [Route("/Audio/{Id}/stream.mp3", "HEAD")]
- [Route("/Audio/{Id}/stream.wma", "HEAD")]
- [Route("/Audio/{Id}/stream.aac", "HEAD")]
- [Route("/Audio/{Id}/stream.flac", "HEAD")]
- [Route("/Audio/{Id}/stream.ogg", "HEAD")]
- [Route("/Audio/{Id}/stream.oga", "HEAD")]
- [Route("/Audio/{Id}/stream.webm", "HEAD")]
- [Route("/Audio/{Id}/stream", "HEAD")]
- [Api(Description = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.mp3", "GET", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.wma", "GET", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.aac", "GET", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.flac", "GET", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.ogg", "GET", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.oga", "GET", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.webm", "GET", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream", "GET", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.mp3", "HEAD", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.wma", "HEAD", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.aac", "HEAD", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.flac", "HEAD", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.ogg", "HEAD", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.oga", "HEAD", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream.webm", "HEAD", Summary = "Gets an audio stream")]
+ [Route("/Audio/{Id}/stream", "HEAD", Summary = "Gets an audio stream")]
public class GetAudioStream : StreamRequest
{
@@ -44,7 +43,8 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class AudioService : BaseProgressiveStreamingService
{
- public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, httpClient, imageProcessor)
+ public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IHttpClient httpClient, IImageProcessor imageProcessor)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, httpClient, imageProcessor)
{
}
diff --git a/MediaBrowser.Api/Playback/ProgressiveStreamService.cs b/MediaBrowser.Api/Playback/ProgressiveStreamService.cs
new file mode 100644
index 000000000..531f79a22
--- /dev/null
+++ b/MediaBrowser.Api/Playback/ProgressiveStreamService.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Api.Playback.Progressive;
+
+namespace MediaBrowser.Api.Playback
+{
+ //public class GetProgressiveAudioStream : StreamRequest
+ //{
+
+ //}
+
+ //public class ProgressiveStreamService : BaseApiService
+ //{
+ // public object Get(GetProgressiveAudioStream request)
+ // {
+ // return ProcessRequest(request, false);
+ // }
+
+ // /// <summary>
+ // /// Gets the specified request.
+ // /// </summary>
+ // /// <param name="request">The request.</param>
+ // /// <returns>System.Object.</returns>
+ // public object Head(GetProgressiveAudioStream request)
+ // {
+ // return ProcessRequest(request, true);
+ // }
+
+ // protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
+ // {
+ // var state = GetState(request, CancellationToken.None).Result;
+
+ // var responseHeaders = new Dictionary<string, string>();
+
+ // if (request.Static && state.IsRemote)
+ // {
+ // AddDlnaHeaders(state, responseHeaders, true);
+
+ // return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result;
+ // }
+
+ // var outputPath = GetOutputFilePath(state);
+ // var outputPathExists = File.Exists(outputPath);
+
+ // var isStatic = request.Static ||
+ // (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive));
+
+ // AddDlnaHeaders(state, responseHeaders, isStatic);
+
+ // if (request.Static)
+ // {
+ // var contentType = state.GetMimeType(state.MediaPath);
+
+ // return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
+ // }
+
+ // if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
+ // {
+ // var contentType = state.GetMimeType(outputPath);
+
+ // return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
+ // }
+
+ // return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
+ // }
+
+ //}
+}
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 0eb2984fb..add517b5d 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -67,11 +67,6 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string DeviceProfileId { get; set; }
-
- /// <summary>
- /// For testing purposes
- /// </summary>
- public bool ThrowDebugError { get; set; }
public string Params { get; set; }
}
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 504d7d921..48285a4b1 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -51,8 +51,6 @@ namespace MediaBrowser.Api.Playback
public bool HasMediaStreams { get; set; }
- public bool SendInputOverStandardInput { get; set; }
-
public CancellationTokenSource StandardInputCancellationTokenSource { get; set; }
public string LiveTvStreamId { get; set; }
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index a787684bb..c5d0a621d 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -20,9 +20,8 @@ namespace MediaBrowser.Api.UserLibrary
/// <summary>
/// Class GetItems
/// </summary>
- [Route("/Items", "GET")]
- [Route("/Users/{UserId}/Items", "GET")]
- [Api(Description = "Gets items based on a query.")]
+ [Route("/Items", "GET", Summary = "Gets items based on a query.")]
+ [Route("/Users/{UserId}/Items", "GET", Summary = "Gets items based on a query.")]
public class GetItems : BaseItemsRequest, IReturn<ItemsResult>
{
/// <summary>
diff --git a/MediaBrowser.Controller/Dlna/DeviceProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs
index 5950698fb..c1fc713e4 100644
--- a/MediaBrowser.Controller/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Controller/Dlna/DeviceProfile.cs
@@ -3,7 +3,6 @@ using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Runtime.Serialization;
using System.Xml.Serialization;
namespace MediaBrowser.Controller.Dlna
diff --git a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs
index 289333aa7..707f0c573 100644
--- a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs
+++ b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs
@@ -18,6 +18,9 @@ namespace MediaBrowser.Controller.Dlna
[XmlAttribute("audioCodec")]
public string AudioCodec { get; set; }
+ [XmlAttribute("protocol")]
+ public string Protocol { get; set; }
+
[XmlAttribute("estimateContentLength")]
public bool EstimateContentLength { get; set; }
diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs
index a02851a9f..2704959e4 100644
--- a/MediaBrowser.Controller/Dto/IDtoService.cs
+++ b/MediaBrowser.Controller/Dto/IDtoService.cs
@@ -86,6 +86,13 @@ namespace MediaBrowser.Controller.Dto
ChapterInfoDto GetChapterInfoDto(ChapterInfo chapterInfo, BaseItem item);
/// <summary>
+ /// Gets the media sources.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>List{MediaSourceInfo}.</returns>
+ List<MediaSourceInfo> GetMediaSources(BaseItem item);
+
+ /// <summary>
/// Gets the item by name dto.
/// </summary>
/// <param name="item">The item.</param>
diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs
new file mode 100644
index 000000000..192ce2e83
--- /dev/null
+++ b/MediaBrowser.Controller/Library/IMusicManager.cs
@@ -0,0 +1,38 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Library
+{
+ public interface IMusicManager
+ {
+ /// <summary>
+ /// Gets the instant mix from song.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="user">The user.</param>
+ /// <returns>IEnumerable{Audio}.</returns>
+ IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user);
+ /// <summary>
+ /// Gets the instant mix from artist.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="user">The user.</param>
+ /// <returns>IEnumerable{Audio}.</returns>
+ IEnumerable<Audio> GetInstantMixFromArtist(string name, User user);
+ /// <summary>
+ /// Gets the instant mix from album.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="user">The user.</param>
+ /// <returns>IEnumerable{Audio}.</returns>
+ IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user);
+ /// <summary>
+ /// Gets the instant mix from genre.
+ /// </summary>
+ /// <param name="genres">The genres.</param>
+ /// <param name="user">The user.</param>
+ /// <returns>IEnumerable{Audio}.</returns>
+ IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user);
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
index edf86cae8..f8cdc6eee 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
@@ -1,8 +1,8 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Library;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.Library;
namespace MediaBrowser.Controller.LiveTv
{
@@ -14,6 +14,8 @@ namespace MediaBrowser.Controller.LiveTv
RecordingInfo RecordingInfo { get; set; }
+ long? RunTimeTicks { get; set; }
+
string GetClientTypeName();
string GetUserDataKey();
diff --git a/MediaBrowser.Controller/Localization/ILocalizationManager.cs b/MediaBrowser.Controller/Localization/ILocalizationManager.cs
index dde2f7878..f41940ed4 100644
--- a/MediaBrowser.Controller/Localization/ILocalizationManager.cs
+++ b/MediaBrowser.Controller/Localization/ILocalizationManager.cs
@@ -1,7 +1,7 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
+using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
namespace MediaBrowser.Controller.Localization
{
@@ -31,5 +31,42 @@ namespace MediaBrowser.Controller.Localization
/// <param name="rating">The rating.</param>
/// <returns>System.Int32.</returns>
int? GetRatingLevel(string rating);
+
+ /// <summary>
+ /// Gets the localized string.
+ /// </summary>
+ /// <param name="phrase">The phrase.</param>
+ /// <param name="culture">The culture.</param>
+ /// <returns>System.String.</returns>
+ string GetLocalizedString(string phrase, string culture);
+
+ /// <summary>
+ /// Gets the localized string.
+ /// </summary>
+ /// <param name="phrase">The phrase.</param>
+ /// <returns>System.String.</returns>
+ string GetLocalizedString(string phrase);
+
+ /// <summary>
+ /// Localizes the document.
+ /// </summary>
+ /// <param name="document">The document.</param>
+ /// <param name="culture">The culture.</param>
+ /// <param name="tokenBuilder">The token builder.</param>
+ /// <returns>System.String.</returns>
+ string LocalizeDocument(string document, string culture, Func<string, string> tokenBuilder);
+
+ /// <summary>
+ /// Gets the localization options.
+ /// </summary>
+ /// <returns>IEnumerable{LocalizatonOption}.</returns>
+ IEnumerable<LocalizatonOption> GetLocalizationOptions();
+
+ /// <summary>
+ /// Gets the java script localization dictionary.
+ /// </summary>
+ /// <param name="culture">The culture.</param>
+ /// <returns>Dictionary{System.StringSystem.String}.</returns>
+ Dictionary<string, string> GetJavaScriptLocalizationDictionary(string culture);
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 9915ac044..16834a945 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -134,6 +134,7 @@
<Compile Include="Library\DeleteOptions.cs" />
<Compile Include="Library\ILibraryPostScanTask.cs" />
<Compile Include="Library\IMetadataSaver.cs" />
+ <Compile Include="Library\IMusicManager.cs" />
<Compile Include="Library\ItemUpdateType.cs" />
<Compile Include="Library\IUserDataManager.cs" />
<Compile Include="Library\UserDataSaveEventArgs.cs" />
@@ -155,11 +156,14 @@
<Compile Include="LiveTv\SeriesTimerInfo.cs" />
<Compile Include="LiveTv\TimerInfo.cs" />
<Compile Include="Localization\ILocalizationManager.cs" />
+ <Compile Include="MediaEncoding\EncodingOptions.cs" />
<Compile Include="MediaEncoding\ChapterImageRefreshOptions.cs" />
+ <Compile Include="MediaEncoding\EncodingResult.cs" />
<Compile Include="MediaEncoding\IEncodingManager.cs" />
<Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
<Compile Include="MediaEncoding\IMediaEncoder.cs" />
<Compile Include="MediaEncoding\InternalMediaInfoResult.cs" />
+ <Compile Include="MediaEncoding\VideoEncodingOptions.cs" />
<Compile Include="Net\IHasResultFactory.cs" />
<Compile Include="Net\IHttpResultFactory.cs" />
<Compile Include="Net\IHttpServer.cs" />
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs
new file mode 100644
index 000000000..74235becd
--- /dev/null
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs
@@ -0,0 +1,79 @@
+using MediaBrowser.Controller.Dlna;
+
+namespace MediaBrowser.Controller.MediaEncoding
+{
+ public class EncodingOptions
+ {
+ /// <summary>
+ /// Gets or sets the item identifier.
+ /// </summary>
+ /// <value>The item identifier.</value>
+ public string ItemId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the media source identifier.
+ /// </summary>
+ /// <value>The media source identifier.</value>
+ public string MediaSourceId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the device profile.
+ /// </summary>
+ /// <value>The device profile.</value>
+ public DeviceProfile DeviceProfile { get; set; }
+
+ /// <summary>
+ /// Gets or sets the output path.
+ /// </summary>
+ /// <value>The output path.</value>
+ public string OutputPath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the container.
+ /// </summary>
+ /// <value>The container.</value>
+ public string Container { get; set; }
+
+ /// <summary>
+ /// Gets or sets the audio codec.
+ /// </summary>
+ /// <value>The audio codec.</value>
+ public string AudioCodec { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start time ticks.
+ /// </summary>
+ /// <value>The start time ticks.</value>
+ public long? StartTimeTicks { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum channels.
+ /// </summary>
+ /// <value>The maximum channels.</value>
+ public int? MaxAudioChannels { get; set; }
+
+ /// <summary>
+ /// Gets or sets the channels.
+ /// </summary>
+ /// <value>The channels.</value>
+ public int? AudioChannels { get; set; }
+
+ /// <summary>
+ /// Gets or sets the sample rate.
+ /// </summary>
+ /// <value>The sample rate.</value>
+ public int? AudioSampleRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the bit rate.
+ /// </summary>
+ /// <value>The bit rate.</value>
+ public int? AudioBitRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum audio bit rate.
+ /// </summary>
+ /// <value>The maximum audio bit rate.</value>
+ public int? MaxAudioBitRate { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs b/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs
new file mode 100644
index 000000000..75ee90e42
--- /dev/null
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingResult.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.MediaEncoding
+{
+ public class EncodingResult
+ {
+ public string OutputPath { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs
new file mode 100644
index 000000000..773f0ea46
--- /dev/null
+++ b/MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs
@@ -0,0 +1,26 @@
+
+namespace MediaBrowser.Controller.MediaEncoding
+{
+ public class VideoEncodingOptions : EncodingOptions
+ {
+ public string VideoCodec { get; set; }
+
+ public string VideoProfile { get; set; }
+
+ public double? VideoLevel { get; set; }
+
+ public int? VideoStreamIndex { get; set; }
+
+ public int? AudioStreamIndex { get; set; }
+
+ public int? SubtitleStreamIndex { get; set; }
+
+ public int? MaxWidth { get; set; }
+
+ public int? MaxHeight { get; set; }
+
+ public int? Height { get; set; }
+
+ public int? Width { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs
index 21206af75..02cc875bd 100644
--- a/MediaBrowser.Controller/Session/ISessionController.cs
+++ b/MediaBrowser.Controller/Session/ISessionController.cs
@@ -60,6 +60,14 @@ namespace MediaBrowser.Controller.Session
Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken);
/// <summary>
+ /// Sends the generic command.
+ /// </summary>
+ /// <param name="command">The command.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken);
+
+ /// <summary>
/// Sends the library update info.
/// </summary>
/// <param name="info">The info.</param>
diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs
index ec9ecb9ef..958188f37 100644
--- a/MediaBrowser.Dlna/DlnaManager.cs
+++ b/MediaBrowser.Dlna/DlnaManager.cs
@@ -20,13 +20,15 @@ namespace MediaBrowser.Dlna
private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
+ private readonly IJsonSerializer _jsonSerializer;
- public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger)
+ public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
_appPaths = appPaths;
_logger = logger;
+ _jsonSerializer = jsonSerializer;
//DumpProfiles();
}
@@ -381,10 +383,66 @@ namespace MediaBrowser.Dlna
public void CreateProfile(DeviceProfile profile)
{
+ profile = ReserializeProfile(profile);
+
+ if (string.IsNullOrWhiteSpace(profile.Name))
+ {
+ throw new ArgumentException("Profile is missing Name");
+ }
+
+ var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
+ var path = Path.Combine(UserProfilesPath, newFilename);
+
+ _xmlSerializer.SerializeToFile(profile, path);
}
public void UpdateProfile(DeviceProfile profile)
{
+ profile = ReserializeProfile(profile);
+
+ if (string.IsNullOrWhiteSpace(profile.Id))
+ {
+ throw new ArgumentException("Profile is missing Id");
+ }
+ if (string.IsNullOrWhiteSpace(profile.Name))
+ {
+ throw new ArgumentException("Profile is missing Name");
+ }
+
+ var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
+
+ if (current.Info.Type == DeviceProfileType.System)
+ {
+ throw new ArgumentException("System profiles are readonly");
+ }
+
+ var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
+ var path = Path.Combine(UserProfilesPath, newFilename);
+
+ if (!string.Equals(path, current.Path, StringComparison.Ordinal))
+ {
+ File.Delete(current.Path);
+ }
+
+ _xmlSerializer.SerializeToFile(profile, path);
+ }
+
+ /// <summary>
+ /// Recreates the object using serialization, to ensure it's not a subclass.
+ /// If it's a subclass it may not serlialize properly to xml (different root element tag name)
+ /// </summary>
+ /// <param name="profile"></param>
+ /// <returns></returns>
+ private DeviceProfile ReserializeProfile(DeviceProfile profile)
+ {
+ if (profile.GetType() == typeof(DeviceProfile))
+ {
+ return profile;
+ }
+
+ var json = _jsonSerializer.SerializeToString(profile);
+
+ return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
}
class InternalProfileInfo
diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs
index e99c7b50e..0c9f292ad 100644
--- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs
+++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs
@@ -619,5 +619,10 @@ namespace MediaBrowser.Dlna.PlayTo
_logger.Log(LogSeverity.Debug, "Controller disposed");
}
}
+
+ public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
new file mode 100644
index 000000000..08b7fbe49
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
@@ -0,0 +1,91 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class AudioEncoder
+ {
+ private readonly string _ffmpegPath;
+ private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IIsoManager _isoManager;
+ private readonly ILiveTvManager _liveTvManager;
+
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ public AudioEncoder(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
+ {
+ _ffmpegPath = ffmpegPath;
+ _logger = logger;
+ _fileSystem = fileSystem;
+ _appPaths = appPaths;
+ _isoManager = isoManager;
+ _liveTvManager = liveTvManager;
+ }
+
+ public Task BeginEncoding(InternalEncodingTask task)
+ {
+ return new FFMpegProcess(_ffmpegPath, _logger, _fileSystem, _appPaths, _isoManager, _liveTvManager).Start(task, GetArguments);
+ }
+
+ private string GetArguments(InternalEncodingTask task, string mountedPath)
+ {
+ var options = task.Request;
+
+ return string.Format("{0} -i {1} {2} -id3v2_version 3 -write_id3v1 1 \"{3}\"",
+ GetInputModifier(task),
+ GetInputArgument(task),
+ GetOutputModifier(task),
+ options.OutputPath).Trim();
+ }
+
+ private string GetInputModifier(InternalEncodingTask task)
+ {
+ return EncodingUtils.GetInputModifier(task);
+ }
+
+ private string GetInputArgument(InternalEncodingTask task)
+ {
+ return EncodingUtils.GetInputArgument(new List<string> { task.MediaPath }, task.IsInputRemote);
+ }
+
+ private string GetOutputModifier(InternalEncodingTask task)
+ {
+ var options = task.Request;
+
+ var audioTranscodeParams = new List<string>
+ {
+ "-threads " + EncodingUtils.GetNumberOfThreads(task, false).ToString(_usCulture),
+ "-vn"
+ };
+
+ var bitrate = EncodingUtils.GetAudioBitrateParam(task);
+
+ if (bitrate.HasValue)
+ {
+ audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture));
+ }
+
+ var channels = EncodingUtils.GetNumAudioChannelsParam(options, task.AudioStream);
+
+ if (channels.HasValue)
+ {
+ audioTranscodeParams.Add("-ac " + channels.Value);
+ }
+
+ if (options.AudioSampleRate.HasValue)
+ {
+ audioTranscodeParams.Add("-ar " + options.AudioSampleRate.Value);
+ }
+
+ return string.Join(" ", audioTranscodeParams.ToArray());
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
new file mode 100644
index 000000000..79d512dc1
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
@@ -0,0 +1,233 @@
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public static class EncodingUtils
+ {
+ private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+ public static string GetInputArgument(List<string> inputFiles, bool isRemote)
+ {
+ if (isRemote)
+ {
+ return GetHttpInputArgument(inputFiles);
+ }
+
+ return GetConcatInputArgument(inputFiles);
+ }
+
+ /// <summary>
+ /// Gets the concat input argument.
+ /// </summary>
+ /// <param name="inputFiles">The input files.</param>
+ /// <returns>System.String.</returns>
+ private static string GetConcatInputArgument(List<string> inputFiles)
+ {
+ // Get all streams
+ // If there's more than one we'll need to use the concat command
+ if (inputFiles.Count > 1)
+ {
+ var files = string.Join("|", inputFiles);
+
+ return string.Format("concat:\"{0}\"", files);
+ }
+
+ // Determine the input path for video files
+ return GetFileInputArgument(inputFiles[0]);
+ }
+
+ /// <summary>
+ /// Gets the file input argument.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns>System.String.</returns>
+ private static string GetFileInputArgument(string path)
+ {
+ return string.Format("file:\"{0}\"", path);
+ }
+
+ /// <summary>
+ /// Gets the HTTP input argument.
+ /// </summary>
+ /// <param name="inputFiles">The input files.</param>
+ /// <returns>System.String.</returns>
+ private static string GetHttpInputArgument(IEnumerable<string> inputFiles)
+ {
+ var url = inputFiles.First();
+
+ return string.Format("\"{0}\"", url);
+ }
+
+ public static string GetAudioInputModifier(InternalEncodingTask options)
+ {
+ return GetCommonInputModifier(options);
+ }
+
+ public static string GetInputModifier(InternalEncodingTask options)
+ {
+ var inputModifier = GetCommonInputModifier(options);
+
+ //if (state.VideoRequest != null)
+ //{
+ // inputModifier += " -fflags genpts";
+ //}
+
+ //if (!string.IsNullOrEmpty(state.InputVideoCodec))
+ //{
+ // inputModifier += " -vcodec " + state.InputVideoCodec;
+ //}
+
+ //if (!string.IsNullOrEmpty(state.InputVideoSync))
+ //{
+ // inputModifier += " -vsync " + state.InputVideoSync;
+ //}
+
+ return inputModifier;
+ }
+
+ private static string GetCommonInputModifier(InternalEncodingTask options)
+ {
+ var inputModifier = string.Empty;
+
+ if (options.EnableDebugLogging)
+ {
+ inputModifier += "-loglevel debug";
+ }
+
+ var probeSize = GetProbeSizeArgument(options.InputVideoType.HasValue && options.InputVideoType.Value == VideoType.Dvd);
+ inputModifier += " " + probeSize;
+ inputModifier = inputModifier.Trim();
+
+ if (!string.IsNullOrWhiteSpace(options.UserAgent))
+ {
+ inputModifier += " -user-agent \"" + options.UserAgent + "\"";
+ }
+
+ inputModifier += " " + GetFastSeekValue(options.Request);
+ inputModifier = inputModifier.Trim();
+
+ if (!string.IsNullOrEmpty(options.InputFormat))
+ {
+ inputModifier += " -f " + options.InputFormat;
+ }
+
+ if (!string.IsNullOrEmpty(options.InputAudioCodec))
+ {
+ inputModifier += " -acodec " + options.InputAudioCodec;
+ }
+
+ if (!string.IsNullOrEmpty(options.InputAudioSync))
+ {
+ inputModifier += " -async " + options.InputAudioSync;
+ }
+
+ if (options.ReadInputAtNativeFramerate)
+ {
+ inputModifier += " -re";
+ }
+
+ return inputModifier;
+ }
+
+ private static string GetFastSeekValue(EncodingOptions options)
+ {
+ var time = options.StartTimeTicks;
+
+ if (time.HasValue)
+ {
+ var seconds = TimeSpan.FromTicks(time.Value).TotalSeconds;
+
+ if (seconds > 0)
+ {
+ return string.Format("-ss {0}", seconds.ToString(UsCulture));
+ }
+ }
+
+ return string.Empty;
+ }
+
+ public static string GetProbeSizeArgument(bool isDvd)
+ {
+ return isDvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
+ }
+
+ public static int? GetAudioBitrateParam(InternalEncodingTask task)
+ {
+ if (task.Request.AudioBitRate.HasValue)
+ {
+ // Make sure we don't request a bitrate higher than the source
+ var currentBitrate = task.AudioStream == null ? task.Request.AudioBitRate.Value : task.AudioStream.BitRate ?? task.Request.AudioBitRate.Value;
+
+ return Math.Min(currentBitrate, task.Request.AudioBitRate.Value);
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the number of audio channels to specify on the command line
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <param name="audioStream">The audio stream.</param>
+ /// <returns>System.Nullable{System.Int32}.</returns>
+ public static int? GetNumAudioChannelsParam(EncodingOptions request, MediaStream audioStream)
+ {
+ if (audioStream != null)
+ {
+ if (audioStream.Channels > 2 && string.Equals(request.AudioCodec, "wma", StringComparison.OrdinalIgnoreCase))
+ {
+ // wmav2 currently only supports two channel output
+ return 2;
+ }
+ }
+
+ if (request.MaxAudioChannels.HasValue)
+ {
+ if (audioStream != null && audioStream.Channels.HasValue)
+ {
+ return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
+ }
+
+ return request.MaxAudioChannels.Value;
+ }
+
+ return request.AudioChannels;
+ }
+
+ public static int GetNumberOfThreads(InternalEncodingTask state, bool isWebm)
+ {
+ // Use more when this is true. -re will keep cpu usage under control
+ if (state.ReadInputAtNativeFramerate)
+ {
+ if (isWebm)
+ {
+ return Math.Max(Environment.ProcessorCount - 1, 1);
+ }
+
+ return 0;
+ }
+
+ // Webm: http://www.webmproject.org/docs/encoder-parameters/
+ // The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads
+ // for the coefficient data if the encoder selected --token-parts > 0 at encode time.
+
+ switch (state.QualitySetting)
+ {
+ case EncodingQuality.HighSpeed:
+ return 2;
+ case EncodingQuality.HighQuality:
+ return 2;
+ case EncodingQuality.MaxQuality:
+ return isWebm ? 2 : 0;
+ default:
+ throw new Exception("Unrecognized MediaEncodingQuality value.");
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs b/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs
new file mode 100644
index 000000000..05733aef0
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs
@@ -0,0 +1,168 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class FFMpegProcess : IDisposable
+ {
+ private readonly string _ffmpegPath;
+ private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IIsoManager _isoManager;
+ private readonly ILiveTvManager _liveTvManager;
+
+ private Stream _logFileStream;
+ private InternalEncodingTask _task;
+ private IIsoMount _isoMount;
+
+ public FFMpegProcess(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
+ {
+ _ffmpegPath = ffmpegPath;
+ _logger = logger;
+ _fileSystem = fileSystem;
+ _appPaths = appPaths;
+ _isoManager = isoManager;
+ _liveTvManager = liveTvManager;
+ }
+
+ public async Task Start(InternalEncodingTask task, Func<InternalEncodingTask,string,string> argumentsFactory)
+ {
+ _task = task;
+ if (!File.Exists(_ffmpegPath))
+ {
+ throw new InvalidOperationException("ffmpeg was not found at " + _ffmpegPath);
+ }
+
+ Directory.CreateDirectory(Path.GetDirectoryName(task.Request.OutputPath));
+
+ string mountedPath = null;
+ if (task.InputVideoType.HasValue && task.InputVideoType == VideoType.Iso && task.IsoType.HasValue)
+ {
+ if (_isoManager.CanMount(task.MediaPath))
+ {
+ _isoMount = await _isoManager.Mount(task.MediaPath, CancellationToken.None).ConfigureAwait(false);
+ mountedPath = _isoMount.MountedPath;
+ }
+ }
+
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+
+ // Must consume both stdout and stderr or deadlocks may occur
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+
+ FileName = _ffmpegPath,
+ WorkingDirectory = Path.GetDirectoryName(_ffmpegPath),
+ Arguments = argumentsFactory(task, mountedPath),
+
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+
+ EnableRaisingEvents = true
+ };
+
+ _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
+
+ var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-" + task.Id + ".txt");
+ Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
+
+ // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
+ _logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
+
+ process.Exited += process_Exited;
+
+ try
+ {
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error starting ffmpeg", ex);
+
+ task.OnError();
+
+ DisposeLogFileStream();
+
+ process.Dispose();
+
+ throw;
+ }
+
+ task.OnBegin();
+
+ // MUST read both stdout and stderr asynchronously or a deadlock may occurr
+ process.BeginOutputReadLine();
+
+#pragma warning disable 4014
+ // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
+ process.StandardError.BaseStream.CopyToAsync(_logFileStream);
+#pragma warning restore 4014
+ }
+
+ async void process_Exited(object sender, EventArgs e)
+ {
+ var process = (Process)sender;
+
+ if (_isoMount != null)
+ {
+ _isoMount.Dispose();
+ _isoMount = null;
+ }
+
+ DisposeLogFileStream();
+
+ try
+ {
+ _logger.Info("FFMpeg exited with code {0} for {1}", process.ExitCode, _task.Request.OutputPath);
+ }
+ catch
+ {
+ _logger.Info("FFMpeg exited with an error for {0}", _task.Request.OutputPath);
+ }
+
+ _task.OnCompleted();
+
+ if (!string.IsNullOrEmpty(_task.LiveTvStreamId))
+ {
+ try
+ {
+ await _liveTvManager.CloseLiveStream(_task.LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error closing live tv stream", ex);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ DisposeLogFileStream();
+ }
+
+ private void DisposeLogFileStream()
+ {
+ if (_logFileStream != null)
+ {
+ _logFileStream.Dispose();
+ _logFileStream = null;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs
new file mode 100644
index 000000000..826525aef
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs
@@ -0,0 +1,95 @@
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class InternalEncodingTask
+ {
+ public string Id { get; set; }
+
+ public CancellationTokenSource CancellationTokenSource { get; set; }
+
+ public double ProgressPercentage { get; set; }
+
+ public EncodingOptions Request { get; set; }
+
+ public VideoEncodingOptions VideoRequest
+ {
+ get { return Request as VideoEncodingOptions; }
+ }
+
+ public string MediaPath { get; set; }
+ public List<string> StreamFileNames { get; set; }
+ public bool IsInputRemote { get; set; }
+
+ public VideoType? InputVideoType { get; set; }
+ public IsoType? IsoType { get; set; }
+ public long? InputRunTimeTicks;
+
+ public string AudioSync = "1";
+ public string VideoSync = "vfr";
+
+ public string InputAudioSync { get; set; }
+ public string InputVideoSync { get; set; }
+
+ public bool DeInterlace { get; set; }
+
+ public bool ReadInputAtNativeFramerate { get; set; }
+
+ public string InputFormat { get; set; }
+
+ public string InputVideoCodec { get; set; }
+
+ public string InputAudioCodec { get; set; }
+
+ public string LiveTvStreamId { get; set; }
+
+ public MediaStream AudioStream { get; set; }
+ public MediaStream VideoStream { get; set; }
+ public MediaStream SubtitleStream { get; set; }
+ public bool HasMediaStreams { get; set; }
+
+ public int SegmentLength = 10;
+ public int HlsListSize;
+
+ public string MimeType { get; set; }
+ public string OrgPn { get; set; }
+ public bool EnableMpegtsM2TsMode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user agent.
+ /// </summary>
+ /// <value>The user agent.</value>
+ public string UserAgent { get; set; }
+
+ public EncodingQuality QualitySetting { get; set; }
+
+ public InternalEncodingTask()
+ {
+ Id = Guid.NewGuid().ToString("N");
+ CancellationTokenSource = new CancellationTokenSource();
+ StreamFileNames = new List<string>();
+ }
+
+ public bool EnableDebugLogging { get; set; }
+
+ internal void OnBegin()
+ {
+
+ }
+
+ internal void OnCompleted()
+ {
+
+ }
+
+ internal void OnError()
+ {
+
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs
new file mode 100644
index 000000000..fa9b87906
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs
@@ -0,0 +1,323 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class InternalEncodingTaskFactory
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly IItemRepository _itemRepo;
+ private readonly IServerConfigurationManager _config;
+
+ public InternalEncodingTaskFactory(ILibraryManager libraryManager, ILiveTvManager liveTvManager, IItemRepository itemRepo, IServerConfigurationManager config)
+ {
+ _libraryManager = libraryManager;
+ _liveTvManager = liveTvManager;
+ _itemRepo = itemRepo;
+ _config = config;
+ }
+
+ public async Task<InternalEncodingTask> Create(EncodingOptions request, CancellationToken cancellationToken)
+ {
+ ValidateInput(request);
+
+ var state = new InternalEncodingTask
+ {
+ Request = request
+ };
+
+ var item = string.IsNullOrEmpty(request.MediaSourceId) ?
+ _libraryManager.GetItemById(new Guid(request.ItemId)) :
+ _libraryManager.GetItemById(new Guid(request.MediaSourceId));
+
+ if (item is ILiveTvRecording)
+ {
+ var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
+
+ if (string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
+ {
+ state.InputVideoType = VideoType.VideoFile;
+ }
+
+ var path = recording.RecordingInfo.Path;
+ var mediaUrl = recording.RecordingInfo.Url;
+
+ if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl))
+ {
+ var streamInfo = await _liveTvManager.GetRecordingStream(request.ItemId, cancellationToken).ConfigureAwait(false);
+
+ state.LiveTvStreamId = streamInfo.Id;
+
+ path = streamInfo.Path;
+ mediaUrl = streamInfo.Url;
+ }
+
+ if (!string.IsNullOrEmpty(path) && File.Exists(path))
+ {
+ state.MediaPath = path;
+ state.IsInputRemote = false;
+ }
+ else if (!string.IsNullOrEmpty(mediaUrl))
+ {
+ state.MediaPath = mediaUrl;
+ state.IsInputRemote = true;
+ }
+
+ state.InputRunTimeTicks = recording.RunTimeTicks;
+ if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsInputRemote)
+ {
+ await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
+ }
+
+ state.ReadInputAtNativeFramerate = recording.RecordingInfo.Status == RecordingStatus.InProgress;
+ state.AudioSync = "1000";
+ state.DeInterlace = true;
+ state.InputVideoSync = "-1";
+ state.InputAudioSync = "1";
+ }
+ else if (item is LiveTvChannel)
+ {
+ var channel = _liveTvManager.GetInternalChannel(request.ItemId);
+
+ if (string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
+ {
+ state.InputVideoType = VideoType.VideoFile;
+ }
+
+ var streamInfo = await _liveTvManager.GetChannelStream(request.ItemId, cancellationToken).ConfigureAwait(false);
+
+ state.LiveTvStreamId = streamInfo.Id;
+
+ if (!string.IsNullOrEmpty(streamInfo.Path) && File.Exists(streamInfo.Path))
+ {
+ state.MediaPath = streamInfo.Path;
+ state.IsInputRemote = false;
+
+ await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
+ }
+ else if (!string.IsNullOrEmpty(streamInfo.Url))
+ {
+ state.MediaPath = streamInfo.Url;
+ state.IsInputRemote = true;
+ }
+
+ state.ReadInputAtNativeFramerate = true;
+ state.AudioSync = "1000";
+ state.DeInterlace = true;
+ state.InputVideoSync = "-1";
+ state.InputAudioSync = "1";
+ }
+ else
+ {
+ state.MediaPath = item.Path;
+ state.IsInputRemote = item.LocationType == LocationType.Remote;
+
+ var video = item as Video;
+
+ if (video != null)
+ {
+ state.InputVideoType = video.VideoType;
+ state.IsoType = video.IsoType;
+
+ state.StreamFileNames = video.PlayableStreamFileNames.ToList();
+ }
+
+ state.InputRunTimeTicks = item.RunTimeTicks;
+ }
+
+ var videoRequest = request as VideoEncodingOptions;
+
+ var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
+ {
+ ItemId = item.Id
+
+ }).ToList();
+
+ if (videoRequest != null)
+ {
+ state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
+ state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
+ state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
+
+ if (state.VideoStream != null && state.VideoStream.IsInterlaced)
+ {
+ state.DeInterlace = true;
+ }
+ }
+ else
+ {
+ state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
+ }
+
+ state.HasMediaStreams = mediaStreams.Count > 0;
+
+ state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10;
+ state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440;
+
+ state.QualitySetting = GetQualitySetting();
+
+ ApplyDeviceProfileSettings(state);
+
+ return state;
+ }
+
+ private void ValidateInput(EncodingOptions request)
+ {
+ if (string.IsNullOrWhiteSpace(request.ItemId))
+ {
+ throw new ArgumentException("ItemId is required.");
+ }
+ if (string.IsNullOrWhiteSpace(request.OutputPath))
+ {
+ throw new ArgumentException("OutputPath is required.");
+ }
+ if (string.IsNullOrWhiteSpace(request.Container))
+ {
+ throw new ArgumentException("Container is required.");
+ }
+ if (string.IsNullOrWhiteSpace(request.AudioCodec))
+ {
+ throw new ArgumentException("AudioCodec is required.");
+ }
+
+ var videoRequest = request as VideoEncodingOptions;
+
+ if (videoRequest == null)
+ {
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Determines which stream will be used for playback
+ /// </summary>
+ /// <param name="allStream">All stream.</param>
+ /// <param name="desiredIndex">Index of the desired.</param>
+ /// <param name="type">The type.</param>
+ /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
+ /// <returns>MediaStream.</returns>
+ private MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
+ {
+ var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
+
+ if (desiredIndex.HasValue)
+ {
+ var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
+
+ if (stream != null)
+ {
+ return stream;
+ }
+ }
+
+ if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
+ {
+ return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
+ streams.FirstOrDefault();
+ }
+
+ // Just return the first one
+ return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
+ }
+
+ private void ApplyDeviceProfileSettings(InternalEncodingTask state)
+ {
+ var profile = state.Request.DeviceProfile;
+
+ if (profile == null)
+ {
+ // Don't use settings from the default profile.
+ // Only use a specific profile if it was requested.
+ return;
+ }
+
+ var container = state.Request.Container;
+
+ var audioCodec = state.Request.AudioCodec;
+
+ if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
+ {
+ audioCodec = state.AudioStream.Codec;
+ }
+
+ var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
+
+ if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
+ {
+ videoCodec = state.VideoStream.Codec;
+ }
+
+ var mediaProfile = state.VideoRequest == null ?
+ profile.GetAudioMediaProfile(container, audioCodec, state.AudioStream) :
+ profile.GetVideoMediaProfile(container, audioCodec, videoCodec, state.AudioStream, state.VideoStream);
+
+ if (mediaProfile != null)
+ {
+ state.MimeType = mediaProfile.MimeType;
+ state.OrgPn = mediaProfile.OrgPn;
+ }
+
+ var transcodingProfile = state.VideoRequest == null ?
+ profile.GetAudioTranscodingProfile(container, audioCodec) :
+ profile.GetVideoTranscodingProfile(container, audioCodec, videoCodec);
+
+ if (transcodingProfile != null)
+ {
+ //state.EstimateContentLength = transcodingProfile.EstimateContentLength;
+ state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
+ //state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
+
+ foreach (var setting in transcodingProfile.Settings)
+ {
+ switch (setting.Name)
+ {
+ case TranscodingSettingType.VideoProfile:
+ {
+ if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.VideoProfile))
+ {
+ state.VideoRequest.VideoProfile = setting.Value;
+ }
+ break;
+ }
+ default:
+ throw new ArgumentException("Unrecognized TranscodingSettingType");
+ }
+ }
+ }
+ }
+
+ private EncodingQuality GetQualitySetting()
+ {
+ var quality = _config.Configuration.MediaEncodingQuality;
+
+ if (quality == EncodingQuality.Auto)
+ {
+ var cpuCount = Environment.ProcessorCount;
+
+ if (cpuCount >= 4)
+ {
+ //return EncodingQuality.HighQuality;
+ }
+
+ return EncodingQuality.HighSpeed;
+ }
+
+ return quality;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index fac54ecff..93df0c8b9 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -6,10 +6,10 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Concurrent;
-using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -122,35 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <exception cref="System.ArgumentException">Unrecognized InputType</exception>
public string GetInputArgument(string[] inputFiles, InputType type)
{
- string inputPath;
-
- switch (type)
- {
- case InputType.Bluray:
- case InputType.Dvd:
- case InputType.File:
- inputPath = GetConcatInputArgument(inputFiles);
- break;
- case InputType.Url:
- inputPath = GetHttpInputArgument(inputFiles);
- break;
- default:
- throw new ArgumentException("Unrecognized InputType");
- }
-
- return inputPath;
- }
-
- /// <summary>
- /// Gets the HTTP input argument.
- /// </summary>
- /// <param name="inputFiles">The input files.</param>
- /// <returns>System.String.</returns>
- private string GetHttpInputArgument(string[] inputFiles)
- {
- var url = inputFiles[0];
-
- return string.Format("\"{0}\"", url);
+ return EncodingUtils.GetInputArgument(inputFiles.ToList(), type == InputType.Url);
}
/// <summary>
@@ -160,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns>System.String.</returns>
public string GetProbeSizeArgument(InputType type)
{
- return type == InputType.Dvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
+ return EncodingUtils.GetProbeSizeArgument(type == InputType.Dvd);
}
/// <summary>
@@ -879,36 +851,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return memoryStream;
}
- /// <summary>
- /// Gets the file input argument.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.String.</returns>
- private string GetFileInputArgument(string path)
- {
- return string.Format("file:\"{0}\"", path);
- }
-
- /// <summary>
- /// Gets the concat input argument.
- /// </summary>
- /// <param name="playableStreamFiles">The playable stream files.</param>
- /// <returns>System.String.</returns>
- private string GetConcatInputArgument(string[] playableStreamFiles)
- {
- // Get all streams
- // If there's more than one we'll need to use the concat command
- if (playableStreamFiles.Length > 1)
- {
- var files = string.Join("|", playableStreamFiles);
-
- return string.Format("concat:\"{0}\"", files);
- }
-
- // Determine the input path for video files
- return GetFileInputArgument(playableStreamFiles[0]);
- }
-
public Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
{
return new ImageEncoder(FFMpegPath, _logger, _fileSystem, _appPaths).EncodeImage(options, cancellationToken);
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index fb1041f89..ee1658ddd 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -48,7 +48,12 @@
</ItemGroup>
<ItemGroup>
<Compile Include="BdInfo\BdInfoExaminer.cs" />
+ <Compile Include="Encoder\AudioEncoder.cs" />
+ <Compile Include="Encoder\EncodingUtils.cs" />
+ <Compile Include="Encoder\FFMpegProcess.cs" />
<Compile Include="Encoder\ImageEncoder.cs" />
+ <Compile Include="Encoder\InternalEncodingTask.cs" />
+ <Compile Include="Encoder\InternalEncodingTaskFactory.cs" />
<Compile Include="Encoder\MediaEncoder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 9aefb4f1c..6c4d9d9e2 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -428,6 +428,9 @@
<Compile Include="..\MediaBrowser.Model\Session\BrowseRequest.cs">
<Link>Session\BrowseRequest.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Session\GenericCommand.cs">
+ <Link>Session\GenericCommand.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs">
<Link>Session\MessageCommand.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index ce2a7600f..b39cecc61 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -415,6 +415,9 @@
<Compile Include="..\MediaBrowser.Model\Session\BrowseRequest.cs">
<Link>Session\BrowseRequest.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Session\GenericCommand.cs">
+ <Link>Session\GenericCommand.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs">
<Link>Session\MessageCommand.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs
index 8de54f34a..28c5822e9 100644
--- a/MediaBrowser.Model/ApiClient/IApiClient.cs
+++ b/MediaBrowser.Model/ApiClient/IApiClient.cs
@@ -590,6 +590,14 @@ namespace MediaBrowser.Model.ApiClient
Task SendPlayCommandAsync(string sessionId, PlayRequest request);
/// <summary>
+ /// Sends the command asynchronous.
+ /// </summary>
+ /// <param name="sessionId">The session identifier.</param>
+ /// <param name="request">The request.</param>
+ /// <returns>Task.</returns>
+ Task SendCommandAsync(string sessionId, GenericCommand request);
+
+ /// <summary>
/// Sends a system command to the client
/// </summary>
/// <param name="sessionId">The session id.</param>
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index d0caa3ad2..932d5d63d 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -218,6 +218,8 @@ namespace MediaBrowser.Model.Configuration
public string ServerName { get; set; }
public string WanDdns { get; set; }
+ public string UICulture { get; set; }
+
public DlnaOptions DlnaOptions { get; set; }
/// <summary>
@@ -281,6 +283,8 @@ namespace MediaBrowser.Model.Configuration
MetadataOptions = options.ToArray();
DlnaOptions = new DlnaOptions();
+
+ UICulture = "en-us";
}
}
diff --git a/MediaBrowser.Model/Globalization/CountryInfo.cs b/MediaBrowser.Model/Globalization/CountryInfo.cs
index 16aea8436..9f5f00d80 100644
--- a/MediaBrowser.Model/Globalization/CountryInfo.cs
+++ b/MediaBrowser.Model/Globalization/CountryInfo.cs
@@ -30,4 +30,10 @@ namespace MediaBrowser.Model.Globalization
/// <value>The name of the three letter ISO region.</value>
public string ThreeLetterISORegionName { get; set; }
}
+
+ public class LocalizatonOption
+ {
+ public string Name { get; set; }
+ public string Value { get; set; }
+ }
}
diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
index d17e2a7f9..fe6faf363 100644
--- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
@@ -33,6 +33,8 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The external identifier.</value>
public string ExternalId { get; set; }
+ public List<MediaSourceInfo> MediaSources { get; set; }
+
/// <summary>
/// Gets or sets the image tags.
/// </summary>
@@ -112,6 +114,7 @@ namespace MediaBrowser.Model.LiveTv
public ChannelInfoDto()
{
ImageTags = new Dictionary<ImageType, Guid>();
+ MediaSources = new List<MediaSourceInfo>();
}
public event PropertyChangedEventHandler PropertyChanged;
diff --git a/MediaBrowser.Model/LiveTv/ChannelQuery.cs b/MediaBrowser.Model/LiveTv/ChannelQuery.cs
index eb3b20ce3..9079ebacd 100644
--- a/MediaBrowser.Model/LiveTv/ChannelQuery.cs
+++ b/MediaBrowser.Model/LiveTv/ChannelQuery.cs
@@ -13,6 +13,12 @@ namespace MediaBrowser.Model.LiveTv
public ChannelType? ChannelType { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether this instance is favorite.
+ /// </summary>
+ /// <value><c>null</c> if [is favorite] contains no value, <c>true</c> if [is favorite]; otherwise, <c>false</c>.</value>
+ public bool? IsFavorite { get; set; }
+
+ /// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
diff --git a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
index 40aa5710e..de07382c0 100644
--- a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
@@ -1,11 +1,11 @@
-using System.Diagnostics;
-using System.Runtime.Serialization;
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Library;
using System;
using System.Collections.Generic;
using System.ComponentModel;
-using MediaBrowser.Model.Library;
+using System.Diagnostics;
+using System.Runtime.Serialization;
namespace MediaBrowser.Model.LiveTv
{
@@ -248,10 +248,13 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The type.</value>
public string Type { get; set; }
+ public List<MediaSourceInfo> MediaSources { get; set; }
+
public RecordingInfoDto()
{
Genres = new List<string>();
ImageTags = new Dictionary<ImageType, Guid>();
+ MediaSources = new List<MediaSourceInfo>();
}
public event PropertyChangedEventHandler PropertyChanged;
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index bf29b4bff..207543fe8 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -132,6 +132,7 @@
<Compile Include="Querying\UserQuery.cs" />
<Compile Include="Search\SearchQuery.cs" />
<Compile Include="Session\BrowseRequest.cs" />
+ <Compile Include="Session\GenericCommand.cs" />
<Compile Include="Session\MessageCommand.cs" />
<Compile Include="Session\PlaybackReports.cs" />
<Compile Include="Session\PlayRequest.cs" />
diff --git a/MediaBrowser.Model/Session/GenericCommand.cs b/MediaBrowser.Model/Session/GenericCommand.cs
new file mode 100644
index 000000000..f7ea0a84a
--- /dev/null
+++ b/MediaBrowser.Model/Session/GenericCommand.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.Session
+{
+ public class GenericCommand
+ {
+ public string Name { get; set; }
+
+ public string ControllingUserId { get; set; }
+
+ public Dictionary<string, string> Arguments { get; set; }
+
+ public GenericCommand()
+ {
+ Arguments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ }
+ }
+
+ /// <summary>
+ /// This exists simply to identify a set of known commands.
+ /// </summary>
+ public enum CoreGenericCommand
+ {
+ MoveUp = 0,
+ MoveDown = 1,
+ MoveLeft = 2,
+ MoveRight = 3,
+ PageUp = 4,
+ PageDown = 5,
+ PreviousLetter = 6,
+ NextLetter = 7,
+ ToggleOsd = 8,
+ ToggleContextMenu = 9,
+ Select = 10,
+ Back = 11,
+ TakeScreenshot = 12,
+ SendKey = 13,
+ SendString = 14,
+ GoHome = 15,
+ GoToSettings = 16,
+ VolumeUp = 17,
+ VolumeDown = 18,
+ Mute = 19,
+ Unmute = 20,
+ ToggleMute = 21
+ }
+}
diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs
index 949274a5d..74d7a70a3 100644
--- a/MediaBrowser.Model/Session/PlayRequest.cs
+++ b/MediaBrowser.Model/Session/PlayRequest.cs
@@ -39,14 +39,22 @@ namespace MediaBrowser.Model.Session
/// <summary>
/// The play now
/// </summary>
- PlayNow,
+ PlayNow = 0,
/// <summary>
/// The play next
/// </summary>
- PlayNext,
+ PlayNext = 1,
/// <summary>
/// The play last
/// </summary>
- PlayLast
+ PlayLast = 2,
+ /// <summary>
+ /// The play instant mix
+ /// </summary>
+ PlayInstantMix = 3,
+ /// <summary>
+ /// The play shuffle
+ /// </summary>
+ PlayShuffle = 4
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Session/PlaystateCommand.cs b/MediaBrowser.Model/Session/PlaystateCommand.cs
index d83c6dae5..91572ba62 100644
--- a/MediaBrowser.Model/Session/PlaystateCommand.cs
+++ b/MediaBrowser.Model/Session/PlaystateCommand.cs
@@ -33,7 +33,15 @@ namespace MediaBrowser.Model.Session
/// <summary>
/// The fullscreen
/// </summary>
- Fullscreen
+ Fullscreen,
+ /// <summary>
+ /// The rewind
+ /// </summary>
+ Rewind,
+ /// <summary>
+ /// The fast forward
+ /// </summary>
+ FastForward
}
public class PlaystateRequest
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 1833b708f..02da776bd 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Session;
@@ -1064,7 +1065,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
}
- dto.MediaSources = GetMediaSources(audio);
+ dto.MediaSources = GetAudioMediaSources(audio);
dto.MediaSourceCount = 1;
}
@@ -1100,7 +1101,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.MediaSources))
{
- dto.MediaSources = GetMediaSources(video);
+ dto.MediaSources = GetVideoMediaSources(video);
}
if (fields.Contains(ItemFields.Chapters))
@@ -1266,9 +1267,48 @@ namespace MediaBrowser.Server.Implementations.Dto
{
SetBookProperties(dto, book);
}
+
+ var tvChannel = item as LiveTvChannel;
+
+ if (tvChannel != null)
+ {
+ dto.MediaSources = GetMediaSources(tvChannel);
+ }
+ }
+
+ public List<MediaSourceInfo> GetMediaSources(BaseItem item)
+ {
+ var video = item as Video;
+
+ if (video != null)
+ {
+ return GetVideoMediaSources(video);
+ }
+
+ var audio = item as Audio;
+
+ if (audio != null)
+ {
+ return GetAudioMediaSources(audio);
+ }
+
+ var result = new List<MediaSourceInfo>
+ {
+ new MediaSourceInfo
+ {
+ Id = item.Id.ToString("N"),
+ LocationType = item.LocationType,
+ Name = item.Name,
+ Path = GetMappedPath(item),
+ MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id }).ToList(),
+ RunTimeTicks = item.RunTimeTicks
+ }
+ };
+
+ return result;
}
- private List<MediaSourceInfo> GetMediaSources(Video item)
+ private List<MediaSourceInfo> GetVideoMediaSources(Video item)
{
var result = item.GetAlternateVersions().Select(GetVersionInfo).ToList();
@@ -1293,11 +1333,11 @@ namespace MediaBrowser.Server.Implementations.Dto
.ToList();
}
- private List<MediaSourceInfo> GetMediaSources(Audio item)
+ private List<MediaSourceInfo> GetAudioMediaSources(Audio item)
{
var result = new List<MediaSourceInfo>
{
- GetVersionInfo(item, true)
+ GetVersionInfo(item)
};
return result;
@@ -1321,7 +1361,7 @@ namespace MediaBrowser.Server.Implementations.Dto
};
}
- private MediaSourceInfo GetVersionInfo(Audio i, bool isPrimary)
+ private MediaSourceInfo GetVersionInfo(Audio i)
{
return new MediaSourceInfo
{
diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
index 9279fd8d7..5b65a60c8 100644
--- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
+++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
@@ -257,8 +257,12 @@ namespace MediaBrowser.Server.Implementations.IO
InternalBufferSize = 32767
};
- newWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName |
- NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size;
+ newWatcher.NotifyFilter = NotifyFilters.CreationTime |
+ NotifyFilters.DirectoryName |
+ NotifyFilters.FileName |
+ NotifyFilters.LastWrite |
+ NotifyFilters.Size |
+ NotifyFilters.Attributes;
newWatcher.Created += watcher_Changed;
newWatcher.Deleted += watcher_Changed;
@@ -349,13 +353,13 @@ namespace MediaBrowser.Server.Implementations.IO
{
try
{
- Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath);
+ Logger.Debug("Changed detected of type " + e.ChangeType + " to " + e.FullPath);
ReportFileSystemChanged(e.FullPath);
}
catch (Exception ex)
{
- Logger.ErrorException("Exception in watcher changed. Path: {0}", ex, e.FullPath);
+ Logger.ErrorException("Exception in ReportFileSystemChanged. Path: {0}", ex, e.FullPath);
}
}
@@ -397,14 +401,6 @@ namespace MediaBrowser.Server.Implementations.IO
Logger.Debug("Ignoring change to {0}", path);
return true;
}
-
- // Go up another level
- parent = Path.GetDirectoryName(i);
- if (string.Equals(parent, path, StringComparison.OrdinalIgnoreCase))
- {
- Logger.Debug("Ignoring change to {0}", path);
- return true;
- }
}
return false;
diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs
new file mode 100644
index 000000000..9d5826454
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs
@@ -0,0 +1,67 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.Library
+{
+ public class MusicManager : IMusicManager
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public MusicManager(ILibraryManager libraryManager)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user)
+ {
+ return GetInstantMixFromGenres(item.Genres, user);
+ }
+
+ public IEnumerable<Audio> GetInstantMixFromArtist(string name, User user)
+ {
+ var artist = _libraryManager.GetArtist(name);
+
+ var genres = _libraryManager.RootFolder
+ .RecursiveChildren
+ .OfType<Audio>()
+ .Where(i => i.HasArtist(name))
+ .SelectMany(i => i.Genres)
+ .Concat(artist.Genres)
+ .Distinct(StringComparer.OrdinalIgnoreCase);
+
+ return GetInstantMixFromGenres(genres, user);
+ }
+
+ public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
+ {
+ var genres = item
+ .RecursiveChildren
+ .OfType<Audio>()
+ .SelectMany(i => i.Genres)
+ .Concat(item.Genres)
+ .Distinct(StringComparer.OrdinalIgnoreCase);
+
+ return GetInstantMixFromGenres(genres, user);
+ }
+
+ public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user)
+ {
+ var inputItems = user.RootFolder.GetRecursiveChildren(user);
+
+ var genresDictionary = genres.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+
+ return inputItems
+ .OfType<Audio>()
+ .Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey)))
+ .OrderByDescending(i => i.Item2)
+ .ThenBy(i => Guid.NewGuid())
+ .Select(i => i.Item1)
+ .Take(100)
+ .OrderBy(i => Guid.NewGuid());
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index 67c011a1f..b92e82385 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -34,8 +34,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
var collectionType = args.GetCollectionType();
- if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) ||
- string.IsNullOrEmpty(collectionType))
+ if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
{
return new Controller.Entities.Audio.Audio();
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 4e1bd860b..cf7358cce 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -222,13 +222,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
RunTimeTicks = (info.EndDate - info.StartDate).Ticks,
OriginalAirDate = info.OriginalAirDate,
- MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
- {
- ItemId = recording.Id
-
- }).ToList()
+ MediaSources = _dtoService.GetMediaSources((BaseItem)recording)
};
+ dto.MediaStreams = dto.MediaSources.SelectMany(i => i.MediaStreams).ToList();
+
if (info.Status == RecordingStatus.InProgress)
{
var now = DateTime.UtcNow.Ticks;
@@ -317,7 +315,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Type = info.GetClientTypeName(),
Id = info.Id.ToString("N"),
MediaType = info.MediaType,
- ExternalId = info.ExternalId
+ ExternalId = info.ExternalId,
+ MediaSources = _dtoService.GetMediaSources(info)
};
if (user != null)
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index db8786d62..b828dc0de 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -134,6 +134,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return number;
});
+
+ if (query.IsFavorite.HasValue)
+ {
+ var val = query.IsFavorite.Value;
+
+ channels = channels
+ .Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite == val);
+ }
}
channels = channels.OrderBy(i =>
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json
new file mode 100644
index 000000000..45f0d7e13
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json
@@ -0,0 +1,21 @@
+{
+ "SettingsSaved": "Settings saved.",
+ "AddUser": "Add User",
+ "Users": "Users",
+ "Delete": "Delete",
+ "Administrator": "Administrator",
+ "Password": "Password",
+ "CreatePassword": "Create Password",
+ "DeleteImage": "Delete Image",
+ "DeleteImageConfirmation": "Are you sure you wish to delete this image?",
+ "FileReadCancelled": "The file read has been cancelled.",
+ "FileNotFound": "File not found.",
+ "FileReadError": "An error occurred while reading the file.",
+ "DeleteUser": "Delete User",
+ "DeleteUserConfirmation": "Are you sure you wish to delete {0}?",
+ "PasswordResetHeader": "Password Reset",
+ "PasswordResetComplete": "The password has been reset.",
+ "PasswordResetConfirmation": "Are you sure you wish to reset the password?",
+ "PasswordSaved": "Password saved.",
+ "PasswordMatchError": "Password and password confirmation must match."
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
new file mode 100644
index 000000000..41f8508a5
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
@@ -0,0 +1,21 @@
+{
+ "SettingsSaved": "Settings saved.",
+ "AddUser": "Add User",
+ "Users": "Users",
+ "Delete": "Delete",
+ "Administrator": "Administrator",
+ "Password": "Password",
+ "CreatePassword": "Create Password",
+ "DeleteImage": "Delete Image",
+ "DeleteImageConfirmation": "Are you sure you wish to delete this image?",
+ "FileReadCancelled": "The file read has been cancelled.",
+ "FileNotFound": "File not found.",
+ "FileReadError": "An error occurred while reading the file.",
+ "DeleteUser": "Delete User",
+ "DeleteUserConfirmation": "Are you sure you wish to delete {0}?",
+ "PasswordResetHeader": "Password Reset",
+ "PasswordResetComplete": "The password has been reset.",
+ "PasswordResetConfirmation": "Are you sure you wish to reset the password?",
+ "PasswordSaved": "Password saved.",
+ "PasswordMatchError": "Password and password confirmation must match."
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json
new file mode 100644
index 000000000..25d3ff944
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json
@@ -0,0 +1,21 @@
+{
+ "SettingsSaved": "Configura\u00e7\u00f5es guardadas.",
+ "AddUser": "Adicionar Utilizador",
+ "Users": "Utilizadores",
+ "Delete": "Apagar",
+ "Administrator": "Administrador",
+ "Password": "Senha",
+ "CreatePassword": "Criar Senha",
+ "DeleteImage": "Apagar Imagem",
+ "DeleteImageConfirmation": "Tem a certeza que pretende apagar a imagem?",
+ "FileReadCancelled": "The file read has been cancelled.",
+ "FileNotFound": "Ficheiro n\u00e3o encontrado",
+ "FileReadError": "Ocorreu um erro ao ler o ficheiro.",
+ "DeleteUser": "Apagar Utilizador",
+ "DeleteUserConfirmation": "Tem a certeza que pretende apagar {0}?",
+ "PasswordResetHeader": "Password Reset",
+ "PasswordResetComplete": "The password has been reset.",
+ "PasswordResetConfirmation": "Are you sure you wish to reset the password?",
+ "PasswordSaved": "Senha guardada.",
+ "PasswordMatchError": "Password and password confirmation must match."
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
index 0d428742c..6f39c6759 100644
--- a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
+++ b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
@@ -1,9 +1,9 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Serialization;
using MoreLinq;
using System;
using System.Collections.Concurrent;
@@ -11,6 +11,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Reflection;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Server.Implementations.Localization
{
@@ -33,15 +35,17 @@ namespace MediaBrowser.Server.Implementations.Localization
new ConcurrentDictionary<string, Dictionary<string, ParentalRating>>(StringComparer.OrdinalIgnoreCase);
private readonly IFileSystem _fileSystem;
-
+ private readonly IJsonSerializer _jsonSerializer;
+
/// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager"/> class.
/// </summary>
/// <param name="configurationManager">The configuration manager.</param>
- public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem)
+ public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
{
_configurationManager = configurationManager;
_fileSystem = fileSystem;
+ _jsonSerializer = jsonSerializer;
ExtractAll();
}
@@ -241,5 +245,112 @@ namespace MediaBrowser.Server.Implementations.Localization
return value == null ? (int?)null : value.Value;
}
+
+ public string GetLocalizedString(string phrase)
+ {
+ return GetLocalizedString(phrase, _configurationManager.Configuration.UICulture);
+ }
+
+ public string GetLocalizedString(string phrase, string culture)
+ {
+ var dictionary = GetLocalizationDictionary(culture);
+
+ string value;
+
+ if (dictionary.TryGetValue(phrase, out value))
+ {
+ return value;
+ }
+
+ return phrase;
+ }
+
+ private readonly ConcurrentDictionary<string, Dictionary<string, string>> _dictionaries =
+ new ConcurrentDictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
+
+ public Dictionary<string, string> GetLocalizationDictionary(string culture)
+ {
+ const string prefix = "Server";
+ var key = prefix + culture;
+
+ return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, "server.json"));
+ }
+
+ public Dictionary<string, string> GetJavaScriptLocalizationDictionary(string culture)
+ {
+ const string prefix = "JavaScript";
+ var key = prefix + culture;
+
+ return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, "javascript.json"));
+ }
+
+ private Dictionary<string, string> GetDictionary(string prefix, string culture, string baseFilename)
+ {
+ var dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ var assembly = GetType().Assembly;
+ var namespaceName = GetType().Namespace + "." + prefix;
+
+ CopyInto(dictionary, namespaceName + "." + baseFilename, assembly);
+ CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture), assembly);
+
+ return dictionary;
+ }
+
+ private void CopyInto(IDictionary<string, string> dictionary, string resourcePath, Assembly assembly)
+ {
+ using (var stream = assembly.GetManifestResourceStream(resourcePath))
+ {
+ if (stream != null)
+ {
+ var dict = _jsonSerializer.DeserializeFromStream<Dictionary<string, string>>(stream);
+
+ foreach (var key in dict.Keys)
+ {
+ dictionary[key] = dict[key];
+ }
+ }
+ }
+ }
+
+ private string GetResourceFilename(string culture)
+ {
+ var parts = culture.Split('-');
+
+ if (parts.Length == 2)
+ {
+ culture = parts[0].ToLower() + "_" + parts[1].ToUpper();
+ }
+ else
+ {
+ culture = culture.ToLower();
+ }
+
+ return culture + ".json";
+ }
+
+ public IEnumerable<LocalizatonOption> GetLocalizationOptions()
+ {
+ return new List<LocalizatonOption>
+ {
+ new LocalizatonOption{ Name="English (United States)", Value="en-us"},
+ new LocalizatonOption{ Name="German", Value="de"},
+ new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
+ new LocalizatonOption{ Name="Russian", Value="ru"}
+
+ }.OrderBy(i => i.Name);
+ }
+
+ public string LocalizeDocument(string document, string culture, Func<string,string> tokenBuilder)
+ {
+ foreach (var pair in GetLocalizationDictionary(culture).ToList())
+ {
+ var token = tokenBuilder(pair.Key);
+
+ document = document.Replace(token, pair.Value, StringComparison.Ordinal);
+ }
+
+ return document;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/de.json b/MediaBrowser.Server.Implementations/Localization/Server/de.json
new file mode 100644
index 000000000..ab3271b50
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/Server/de.json
@@ -0,0 +1,17 @@
+{
+ "LabelExit": "Ende",
+ "LabelVisitCommunity": "Besuche die Community",
+ "LabelGithubWiki": "Github Wiki",
+ "LabelSwagger": "Swagger",
+ "LabelStandard": "Standard",
+ "LabelViewApiDocumentation": "Zeige Api Dokumentation",
+ "LabelBrowseLibrary": "Durchsuche Bibliothek",
+ "LabelConfigureMediaBrowser": "Konfiguriere Media Browser",
+ "LabelOpenLibraryViewer": "\u00d6ffne Bibliothekenansicht",
+ "LabelRestartServer": "Server neustarten",
+ "LabelShowLogWindow": "Zeige Log Fenster",
+ "LabelPrevious": "Vorheriges",
+ "LabelFinish": "Ende",
+ "LabelNext": "N\u00e4chstes",
+ "LabelYoureDone": "Du bist fertig!"
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/en_US.json b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json
new file mode 100644
index 000000000..f74cca1eb
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/Server/en_US.json
@@ -0,0 +1,17 @@
+{
+ "LabelExit": "Exit",
+ "LabelVisitCommunity": "Visit Community",
+ "LabelGithubWiki": "Github Wiki",
+ "LabelSwagger": "Swagger",
+ "LabelStandard": "Standard",
+ "LabelViewApiDocumentation": "View Api Documentation",
+ "LabelBrowseLibrary": "Browse Library",
+ "LabelConfigureMediaBrowser": "Configure Media Browser",
+ "LabelOpenLibraryViewer": "Open Library Viewer",
+ "LabelRestartServer": "Restart Server",
+ "LabelShowLogWindow": "Show Log Window",
+ "LabelPrevious": "Previous",
+ "LabelFinish": "Finish",
+ "LabelNext": "Next",
+ "LabelYoureDone": "You're Done!"
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json
new file mode 100644
index 000000000..700314708
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json
@@ -0,0 +1,17 @@
+{
+ "LabelExit": "Sair",
+ "LabelVisitCommunity": "Visitar a Comunidade",
+ "LabelGithubWiki": "Wiki Github",
+ "LabelSwagger": "Swagger",
+ "LabelStandard": "Padr\u00e3o",
+ "LabelViewApiDocumentation": "Ver Documenta\u00e7\u00e3o da API",
+ "LabelBrowseLibrary": "Navegar na Biblioteca",
+ "LabelConfigureMediaBrowser": "Configurar Media Browser",
+ "LabelOpenLibraryViewer": "Abrir Visualizador da Biblioteca",
+ "LabelRestartServer": "Reiniciar Servidor",
+ "LabelShowLogWindow": "Mostrar Janela de Log",
+ "LabelPrevious": "Anterior",
+ "LabelFinish": "Terminar",
+ "LabelNext": "Seguinte",
+ "LabelYoureDone": "Concluiu!"
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/ru.json b/MediaBrowser.Server.Implementations/Localization/Server/ru.json
new file mode 100644
index 000000000..4cc6b90f7
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/Server/ru.json
@@ -0,0 +1,17 @@
+{
+ "LabelExit": "\u0412\u044b\u0445\u043e\u0434",
+ "LabelVisitCommunity": "\u041f\u043e\u0441\u0435\u0442\u0438\u0442\u044c \u0421\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e",
+ "LabelGithubWiki": "\u0412\u0438\u043a\u0438 \u043d\u0430 Github",
+ "LabelSwagger": "\u041e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u0435",
+ "LabelStandard": "\u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439",
+ "LabelViewApiDocumentation": "\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f \u043f\u043e API",
+ "LabelBrowseLibrary": "\u041e\u0431\u043e\u0437\u0440\u0435\u0432\u0430\u0442\u0435\u043b\u044c \u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438",
+ "LabelConfigureMediaBrowser": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Media Browser",
+ "LabelOpenLibraryViewer": "\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440 \u041c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0438",
+ "LabelRestartServer": "\u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440",
+ "LabelShowLogWindow": "\u041e\u043a\u043d\u043e \u0416\u0443\u0440\u043d\u0430\u043b\u0430",
+ "LabelPrevious": "\u041f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0435",
+ "LabelFinish": "\u0417\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u044c",
+ "LabelNext": "\u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435",
+ "LabelYoureDone": "\u0412\u044b \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043b\u0438!"
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json
new file mode 100644
index 000000000..62a418391
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json
@@ -0,0 +1,17 @@
+{
+ "LabelExit": "Exit",
+ "LabelVisitCommunity": "Visit Community",
+ "LabelGithubWiki": "Github Wiki",
+ "LabelSwagger": "Swagger",
+ "LabelStandard": "Standard",
+ "LabelViewApiDocumentation": "View Api Documentation",
+ "LabelBrowseLibrary": "Browse Library",
+ "LabelConfigureMediaBrowser": "Configure Media Browser",
+ "LabelOpenLibraryViewer": "Open Library Viewer",
+ "LabelRestartServer": "Restart Server",
+ "LabelShowLogWindow": "Show Log Window",
+ "LabelPrevious": "Previous",
+ "LabelFinish": "Finish",
+ "LabelNext": "Next",
+ "LabelYoureDone": "You're Done!"
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index ea7ef2ed6..539b968c7 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -141,6 +141,7 @@
<Compile Include="IO\LibraryMonitor.cs" />
<Compile Include="Library\CoreResolutionIgnoreRule.cs" />
<Compile Include="Library\LibraryManager.cs" />
+ <Compile Include="Library\MusicManager.cs" />
<Compile Include="Library\Resolvers\PhotoResolver.cs" />
<Compile Include="Library\SearchEngine.cs" />
<Compile Include="Library\ResolverHelper.cs" />
@@ -281,6 +282,14 @@
<EmbeddedResource Include="Localization\Ratings\ru.txt" />
</ItemGroup>
<ItemGroup>
+ <EmbeddedResource Include="Localization\JavaScript\javascript.json" />
+ <EmbeddedResource Include="Localization\Server\server.json" />
+ <EmbeddedResource Include="Localization\Server\de.json" />
+ <EmbeddedResource Include="Localization\Server\pt_PT.json" />
+ <EmbeddedResource Include="Localization\Server\ru.json" />
+ <EmbeddedResource Include="Localization\JavaScript\en_US.json" />
+ <EmbeddedResource Include="Localization\JavaScript\pt_PT.json" />
+ <EmbeddedResource Include="Localization\Server\en_US.json" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
@@ -385,6 +394,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
+ <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
diff --git a/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs
index 0e2f9e1b5..7c8d71b4f 100644
--- a/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs
+++ b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs
@@ -30,7 +30,7 @@ namespace MediaBrowser.Server.Implementations.Roku
public bool SupportsMediaRemoteControl
{
- get { return true; }
+ get { return false; }
}
public bool IsSessionActive
@@ -146,5 +146,16 @@ namespace MediaBrowser.Server.Implementations.Roku
RequestContentType = "application/json"
});
}
+
+
+ public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<GenericCommand>
+ {
+ MessageType = "Command",
+ Data = command
+
+ }, cancellationToken);
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index 9d405a175..00d2aa992 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -41,6 +41,7 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
+ private readonly IMusicManager _musicManager;
/// <summary>
/// Gets or sets the configuration manager.
@@ -564,7 +565,7 @@ namespace MediaBrowser.Server.Implementations.Session
return playedToCompletion;
}
-
+
/// <summary>
/// Updates playstate position for an item but does not save
/// </summary>
@@ -666,7 +667,7 @@ namespace MediaBrowser.Server.Implementations.Session
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
-
+
return session.SessionController.SendSystemCommand(command, cancellationToken);
}
@@ -676,7 +677,7 @@ namespace MediaBrowser.Server.Implementations.Session
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
-
+
return session.SessionController.SendMessageCommand(command, cancellationToken);
}
@@ -684,14 +685,35 @@ namespace MediaBrowser.Server.Implementations.Session
{
var session = GetSessionForRemoteControl(sessionId);
- var items = command.ItemIds.Select(i => _libraryManager.GetItemById(new Guid(i)))
- .Where(i => i.LocationType != LocationType.Virtual)
- .ToList();
+ var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null;
- if (session.UserId.HasValue)
+ List<BaseItem> items;
+
+ if (command.PlayCommand == PlayCommand.PlayInstantMix)
{
- var user = _userManager.GetUserById(session.UserId.Value);
+ items = command.ItemIds.SelectMany(i => TranslateItemForInstantMix(i, user))
+ .Where(i => i.LocationType != LocationType.Virtual)
+ .ToList();
+ command.PlayCommand = PlayCommand.PlayNow;
+ }
+ else
+ {
+ items = command.ItemIds.SelectMany(i => TranslateItemForPlayback(i, user))
+ .Where(i => i.LocationType != LocationType.Virtual)
+ .ToList();
+ }
+
+ if (command.PlayCommand == PlayCommand.PlayShuffle)
+ {
+ items = items.OrderBy(i => Guid.NewGuid()).ToList();
+ command.PlayCommand = PlayCommand.PlayNow;
+ }
+
+ command.ItemIds = items.Select(i => i.Id.ToString("N")).ToArray();
+
+ if (user != null)
+ {
if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full))
{
throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name));
@@ -723,13 +745,69 @@ namespace MediaBrowser.Server.Implementations.Session
return session.SessionController.SendPlayCommand(command, cancellationToken);
}
+ private IEnumerable<BaseItem> TranslateItemForPlayback(string id, User user)
+ {
+ var item = _libraryManager.GetItemById(new Guid(id));
+
+ if (item.IsFolder)
+ {
+ var folder = (Folder)item;
+
+ var items = user == null ? folder.RecursiveChildren :
+ folder.GetRecursiveChildren(user);
+
+ items = items.Where(i => !i.IsFolder);
+
+ items = items.OrderBy(i => i.SortName);
+
+ return items;
+ }
+
+ return new[] { item };
+ }
+
+ private IEnumerable<BaseItem> TranslateItemForInstantMix(string id, User user)
+ {
+ var item = _libraryManager.GetItemById(new Guid(id));
+
+ var audio = item as Audio;
+
+ if (audio != null)
+ {
+ return _musicManager.GetInstantMixFromSong(audio, user);
+ }
+
+ var artist = item as MusicArtist;
+
+ if (artist != null)
+ {
+ return _musicManager.GetInstantMixFromArtist(artist.Name, user);
+ }
+
+ var album = item as MusicAlbum;
+
+ if (album != null)
+ {
+ return _musicManager.GetInstantMixFromAlbum(album, user);
+ }
+
+ var genre = item as MusicGenre;
+
+ if (genre != null)
+ {
+ return _musicManager.GetInstantMixFromGenres(new[] { genre.Name }, user);
+ }
+
+ return new BaseItem[] { };
+ }
+
public Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken)
{
var session = GetSessionForRemoteControl(sessionId);
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
-
+
return session.SessionController.SendBrowseCommand(command, cancellationToken);
}
diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs
index 70d7ac071..ddf4ec2ca 100644
--- a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs
+++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs
@@ -198,5 +198,17 @@ namespace MediaBrowser.Server.Implementations.Session
}, cancellationToken);
}
+
+ public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken)
+ {
+ var socket = GetActiveSocket();
+
+ return socket.SendAsync(new WebSocketMessage<GenericCommand>
+ {
+ MessageType = "Command",
+ Data = command
+
+ }, cancellationToken);
+ }
}
}
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index b7e9017d6..2575434f4 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -170,7 +170,7 @@ namespace MediaBrowser.ServerApplication
private ILiveTvManager LiveTvManager { get; set; }
- private ILocalizationManager LocalizationManager { get; set; }
+ internal ILocalizationManager LocalizationManager { get; set; }
private IEncodingManager EncodingManager { get; set; }
private IChannelManager ChannelManager { get; set; }
@@ -421,6 +421,9 @@ namespace MediaBrowser.ServerApplication
RegisterSingleInstance(ServerConfigurationManager);
+ LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer);
+ RegisterSingleInstance(LocalizationManager);
+
RegisterSingleInstance<IWebSocketServer>(() => new AlchemyServer(Logger));
RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer());
@@ -449,6 +452,8 @@ namespace MediaBrowser.ServerApplication
LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager);
RegisterSingleInstance(LibraryManager);
+ RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
+
LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
RegisterSingleInstance(LibraryMonitor);
@@ -470,9 +475,6 @@ namespace MediaBrowser.ServerApplication
ServerManager = new ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager);
RegisterSingleInstance(ServerManager);
- LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager);
- RegisterSingleInstance(LocalizationManager);
-
var innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15));
@@ -503,7 +505,7 @@ namespace MediaBrowser.ServerApplication
var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
RegisterSingleInstance<IAppThemeManager>(appThemeManager);
- var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"));
+ var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer);
RegisterSingleInstance<IDlnaManager>(dlnaManager);
var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);
diff --git a/MediaBrowser.ServerApplication/LibraryViewer.cs b/MediaBrowser.ServerApplication/LibraryViewer.cs
index 6c40b549b..8e9091906 100644
--- a/MediaBrowser.ServerApplication/LibraryViewer.cs
+++ b/MediaBrowser.ServerApplication/LibraryViewer.cs
@@ -16,18 +16,16 @@ namespace MediaBrowser.ServerApplication
{
private readonly IJsonSerializer _jsonSerializer;
private readonly ILibraryManager _libraryManager;
- private readonly IDisplayPreferencesRepository _displayPreferencesManager;
private readonly IItemRepository _itemRepository;
private User _currentUser;
- public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo)
+ public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IItemRepository itemRepo)
{
InitializeComponent();
_jsonSerializer = jsonSerializer;
_libraryManager = libraryManager;
- _displayPreferencesManager = displayPreferencesManager;
_itemRepository = itemRepo;
foreach (var user in userManager.Users)
@@ -44,7 +42,7 @@ namespace MediaBrowser.ServerApplication
if (e.Node != null)
{
var item = (BaseItem)e.Node.Tag;
- lblType.Text = "Type: " + item.GetType().Name;
+ lblType.Text = item.GetType().Name;
var json = FormatJson(_jsonSerializer.SerializeToString(item));
diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs
index 3d490a1f8..d8bd3938e 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -253,7 +253,7 @@ namespace MediaBrowser.ServerApplication
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
- _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.DisplayPreferencesRepository, _appHost.ItemRepository);
+ _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.DisplayPreferencesRepository, _appHost.ItemRepository, _appHost.LocalizationManager);
Application.Run();
}
diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
index 99b6db2d7..27f47787f 100644
--- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
+++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
@@ -4,6 +4,7 @@ using System.Windows.Forms;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
@@ -41,6 +42,7 @@ namespace MediaBrowser.ServerApplication
private readonly IJsonSerializer _jsonSerializer;
private readonly IDisplayPreferencesRepository _displayPreferencesManager;
private readonly IItemRepository _itemRepository;
+ private readonly ILocalizationManager _localization;
private LogForm _logForm;
public bool Visible
@@ -56,10 +58,17 @@ namespace MediaBrowser.ServerApplication
}
}
- public ServerNotifyIcon(ILogManager logManager, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo)
+ public ServerNotifyIcon(ILogManager logManager,
+ IServerApplicationHost appHost,
+ IServerConfigurationManager configurationManager,
+ IUserManager userManager, ILibraryManager libraryManager,
+ IJsonSerializer jsonSerializer,
+ IDisplayPreferencesRepository displayPreferencesManager,
+ IItemRepository itemRepo, ILocalizationManager localization)
{
_logger = logManager.GetLogger("MainWindow");
_itemRepository = itemRepo;
+ _localization = localization;
_appHost = appHost;
_logManager = logManager;
_configurationManager = configurationManager;
@@ -118,20 +127,17 @@ namespace MediaBrowser.ServerApplication
//
cmdExit.Name = "cmdExit";
cmdExit.Size = new System.Drawing.Size(208, 22);
- cmdExit.Text = "Exit";
//
// cmdCommunity
//
cmdCommunity.Name = "cmdCommunity";
cmdCommunity.Size = new System.Drawing.Size(208, 22);
- cmdCommunity.Text = "Visit Community";
//
// cmdLogWindow
//
cmdLogWindow.CheckOnClick = true;
cmdLogWindow.Name = "cmdLogWindow";
cmdLogWindow.Size = new System.Drawing.Size(208, 22);
- cmdLogWindow.Text = "Show Log Window";
//
// toolStripSeparator1
//
@@ -142,13 +148,11 @@ namespace MediaBrowser.ServerApplication
//
cmdRestart.Name = "cmdRestart";
cmdRestart.Size = new System.Drawing.Size(208, 22);
- cmdRestart.Text = "Restart Server";
//
// cmdLibraryExplorer
//
cmdLibraryExplorer.Name = "cmdLibraryExplorer";
cmdLibraryExplorer.Size = new System.Drawing.Size(208, 22);
- cmdLibraryExplorer.Text = "Open Library Explorer";
//
// toolStripSeparator2
//
@@ -159,13 +163,11 @@ namespace MediaBrowser.ServerApplication
//
cmdConfigure.Name = "cmdConfigure";
cmdConfigure.Size = new System.Drawing.Size(208, 22);
- cmdConfigure.Text = "Configure Media Browser";
//
// cmdBrowse
//
cmdBrowse.Name = "cmdBrowse";
cmdBrowse.Size = new System.Drawing.Size(208, 22);
- cmdBrowse.Text = "Browse Library";
//
// cmdApiDocs
//
@@ -175,25 +177,21 @@ namespace MediaBrowser.ServerApplication
cmdGtihub});
cmdApiDocs.Name = "cmdApiDocs";
cmdApiDocs.Size = new System.Drawing.Size(208, 22);
- cmdApiDocs.Text = "View Api Documentation";
//
// cmdStandardDocs
//
cmdStandardDocs.Name = "cmdStandardDocs";
cmdStandardDocs.Size = new System.Drawing.Size(136, 22);
- cmdStandardDocs.Text = "Standard";
//
// cmdSwagger
//
cmdSwagger.Name = "cmdSwagger";
cmdSwagger.Size = new System.Drawing.Size(136, 22);
- cmdSwagger.Text = "Swagger";
//
// cmdGtihub
//
cmdGtihub.Name = "cmdGtihub";
cmdGtihub.Size = new System.Drawing.Size(136, 22);
- cmdGtihub.Text = "Github Wiki";
cmdExit.Click += cmdExit_Click;
cmdRestart.Click += cmdRestart_Click;
@@ -211,6 +209,8 @@ namespace MediaBrowser.ServerApplication
_logManager.LoggerLoaded += LoadLogWindow;
_configurationManager.ConfigurationUpdated += Instance_ConfigurationUpdated;
+ LocalizeText();
+
if (_appHost.IsFirstRun)
{
Action action = () => notifyIcon1.ShowBalloonTip(5000, "Media Browser", "Welcome to Media Browser Server!", ToolTipIcon.Info);
@@ -219,6 +219,24 @@ namespace MediaBrowser.ServerApplication
}
}
+ private void LocalizeText()
+ {
+ _uiCulture = _configurationManager.Configuration.UICulture;
+
+ cmdExit.Text = _localization.GetLocalizedString("LabelExit");
+ cmdCommunity.Text = _localization.GetLocalizedString("LabelVisitCommunity");
+ cmdGtihub.Text = _localization.GetLocalizedString("LabelGithubWiki");
+ cmdSwagger.Text = _localization.GetLocalizedString("LabelSwagger");
+ cmdStandardDocs.Text = _localization.GetLocalizedString("LabelStandard");
+ cmdApiDocs.Text = _localization.GetLocalizedString("LabelViewApiDocumentation");
+ cmdBrowse.Text = _localization.GetLocalizedString("LabelBrowseLibrary");
+ cmdConfigure.Text = _localization.GetLocalizedString("LabelConfigureMediaBrowser");
+ cmdLibraryExplorer.Text = _localization.GetLocalizedString("LabelOpenLibraryViewer");
+ cmdRestart.Text = _localization.GetLocalizedString("LabelRestartServer");
+ cmdLogWindow.Text = _localization.GetLocalizedString("LabelShowLogWindow");
+ }
+
+ private string _uiCulture;
/// <summary>
/// Handles the ConfigurationUpdated event of the Instance control.
/// </summary>
@@ -226,6 +244,12 @@ namespace MediaBrowser.ServerApplication
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
void Instance_ConfigurationUpdated(object sender, EventArgs e)
{
+ if (!string.Equals(_configurationManager.Configuration.UICulture, _uiCulture,
+ StringComparison.OrdinalIgnoreCase))
+ {
+ LocalizeText();
+ }
+
Action action = () =>
{
var isLogWindowOpen = _logForm != null;
@@ -307,7 +331,7 @@ namespace MediaBrowser.ServerApplication
void cmdLibraryExplorer_Click(object sender, EventArgs e)
{
- new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _displayPreferencesManager, _itemRepository).Show();
+ new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _itemRepository).Show();
}
void cmdRestart_Click(object sender, EventArgs e)
diff --git a/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs b/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs
index e9ba17bc1..ef3b79699 100644
--- a/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs
+++ b/MediaBrowser.ServerApplication/Splash/SplashForm.Designer.cs
@@ -121,9 +121,9 @@
this.lblStatus.Location = new System.Drawing.Point(3, 0);
this.lblStatus.MaximumSize = new System.Drawing.Size(0, 100);
this.lblStatus.Name = "lblStatus";
- this.lblStatus.Size = new System.Drawing.Size(599, 59);
+ this.lblStatus.Size = new System.Drawing.Size(469, 59);
this.lblStatus.TabIndex = 0;
- this.lblStatus.Text = "Loading Media Browser Server";
+ this.lblStatus.Text = "Loading Media Browser";
this.lblStatus.UseWaitCursor = true;
//
// panel1
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index 99afbbdd7..7313c089f 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -3,9 +3,11 @@ using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
using ServiceStack;
using ServiceStack.Web;
using System;
@@ -15,6 +17,8 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
+using WebMarkupMin.Core.Minifiers;
+using WebMarkupMin.Core.Settings;
namespace MediaBrowser.WebDashboard.Api
{
@@ -96,6 +100,8 @@ namespace MediaBrowser.WebDashboard.Api
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IFileSystem _fileSystem;
+ private readonly ILocalizationManager _localization;
+ private readonly IJsonSerializer _jsonSerializer;
/// <summary>
/// Initializes a new instance of the <see cref="DashboardService" /> class.
@@ -103,11 +109,13 @@ namespace MediaBrowser.WebDashboard.Api
/// <param name="appHost">The app host.</param>
/// <param name="serverConfigurationManager">The server configuration manager.</param>
/// <param name="fileSystem">The file system.</param>
- public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem)
+ public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer)
{
_appHost = appHost;
_serverConfigurationManager = serverConfigurationManager;
_fileSystem = fileSystem;
+ _localization = localization;
+ _jsonSerializer = jsonSerializer;
}
/// <summary>
@@ -148,7 +156,7 @@ namespace MediaBrowser.WebDashboard.Api
{
var page = ServerEntryPoint.Instance.PluginConfigurationPages.First(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
- return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), page.Plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream()));
+ return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), page.Plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream(), null));
}
/// <summary>
@@ -210,13 +218,16 @@ namespace MediaBrowser.WebDashboard.Api
var contentType = MimeTypes.GetMimeType(path);
+ var isHtml = IsHtml(path);
+ var localizationCulture = isHtml ? GetLocalizationCulture() : null;
+
// Don't cache if not configured to do so
// But always cache images to simulate production
- if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching &&
- !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) &&
+ if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching &&
+ !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
{
- return ResultFactory.GetResult(GetResourceStream(path).Result, contentType);
+ return ResultFactory.GetResult(GetResourceStream(path, isHtml, localizationCulture).Result, contentType);
}
TimeSpan? cacheDuration = null;
@@ -230,17 +241,24 @@ namespace MediaBrowser.WebDashboard.Api
var assembly = GetType().Assembly.GetName();
- var cacheKey = (assembly.Version + path).GetMD5();
+ var cacheKey = (assembly.Version + (localizationCulture ?? string.Empty) + path).GetMD5();
- return ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path));
+ return ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path, isHtml, localizationCulture));
+ }
+
+ private string GetLocalizationCulture()
+ {
+ return _serverConfigurationManager.Configuration.UICulture;
}
/// <summary>
/// Gets the resource stream.
/// </summary>
/// <param name="path">The path.</param>
+ /// <param name="isHtml">if set to <c>true</c> [is HTML].</param>
+ /// <param name="localizationCulture">The localization culture.</param>
/// <returns>Task{Stream}.</returns>
- private async Task<Stream> GetResourceStream(string path)
+ private async Task<Stream> GetResourceStream(string path, bool isHtml, string localizationCulture)
{
Stream resourceStream;
@@ -259,13 +277,11 @@ namespace MediaBrowser.WebDashboard.Api
if (resourceStream != null)
{
- var isHtml = IsHtml(path);
-
// Don't apply any caching for html pages
// jQuery ajax doesn't seem to handle if-modified-since correctly
if (isHtml)
{
- resourceStream = await ModifyHtml(resourceStream).ConfigureAwait(false);
+ resourceStream = await ModifyHtml(resourceStream, localizationCulture).ConfigureAwait(false);
}
}
@@ -297,26 +313,48 @@ namespace MediaBrowser.WebDashboard.Api
/// </summary>
/// <param name="sourceStream">The source stream.</param>
/// <returns>Task{Stream}.</returns>
- internal async Task<Stream> ModifyHtml(Stream sourceStream)
+ private async Task<Stream> ModifyHtml(Stream sourceStream, string localizationCulture)
{
- string html;
-
- using (var memoryStream = new MemoryStream())
+ using (sourceStream)
{
- await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+ string html;
- html = Encoding.UTF8.GetString(memoryStream.ToArray());
- }
+ using (var memoryStream = new MemoryStream())
+ {
+ await sourceStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+ html = Encoding.UTF8.GetString(memoryStream.ToArray());
- var version = GetType().Assembly.GetName().Version;
+ if (!string.IsNullOrWhiteSpace(localizationCulture))
+ {
+ html = _localization.LocalizeDocument(html, localizationCulture, GetLocalizationToken);
+ }
- html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version));
+ try
+ {
+ var minifier = new HtmlMinifier(new HtmlMinificationSettings(true));
- var bytes = Encoding.UTF8.GetBytes(html);
+ html = minifier.Minify(html).MinifiedContent;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error minifying html", ex);
+ }
+ }
+
+ var version = GetType().Assembly.GetName().Version;
+
+ html = html.Replace("<head>", "<head>" + GetMetaTags() + GetCommonCss(version) + GetCommonJavascript(version));
- sourceStream.Dispose();
+ var bytes = Encoding.UTF8.GetBytes(html);
- return new MemoryStream(bytes);
+ return new MemoryStream(bytes);
+ }
+ }
+
+ private string GetLocalizationToken(string phrase)
+ {
+ return "${" + phrase + "}";
}
/// <summary>
@@ -393,154 +431,204 @@ namespace MediaBrowser.WebDashboard.Api
/// <returns>Task{Stream}.</returns>
private async Task<Stream> GetAllJavascript()
{
- var scriptFiles = new[]
- {
- "extensions.js",
- "site.js",
- "librarybrowser.js",
- "librarylist.js",
- "editorsidebar.js",
- "librarymenu.js",
- //"chromecast.js",
- "contextmenu.js",
-
- "ratingdialog.js",
- "aboutpage.js",
- "allusersettings.js",
- "alphapicker.js",
- "addpluginpage.js",
- "advancedconfigurationpage.js",
- "advancedpaths.js",
- "advancedserversettings.js",
- "metadataadvanced.js",
- "appsplayback.js",
- "appsweather.js",
- "autoorganizetv.js",
- "autoorganizelog.js",
- "channels.js",
- "channelitems.js",
- "dashboardinfo.js",
- "dashboardpage.js",
- "directorybrowser.js",
- "dlnaprofile.js",
- "dlnaprofiles.js",
- "dlnasettings.js",
- "editcollectionitems.js",
- "edititemmetadata.js",
- "edititempeople.js",
- "edititemimages.js",
- "encodingsettings.js",
- "gamesrecommendedpage.js",
- "gamesystemspage.js",
- "gamespage.js",
- "gamegenrepage.js",
- "gamestudiospage.js",
- "indexpage.js",
- "itembynamedetailpage.js",
- "itemdetailpage.js",
- "itemgallery.js",
- "itemlistpage.js",
- "librarypathmapping.js",
- "libraryreport.js",
- "librarysettings.js",
- "livetvchannel.js",
- "livetvchannels.js",
- "livetvguide.js",
- "livetvnewrecording.js",
- "livetvprogram.js",
- "livetvrecording.js",
- "livetvrecordinglist.js",
- "livetvrecordings.js",
- "livetvtimer.js",
- "livetvseriestimer.js",
- "livetvseriestimers.js",
- "livetvsettings.js",
- "livetvsuggested.js",
- "livetvstatus.js",
- "livetvtimers.js",
- "loginpage.js",
- "logpage.js",
- "medialibrarypage.js",
- "mediaplayer.js",
-
- "mediaplayer-video.js",
-
- "metadataconfigurationpage.js",
- "metadataimagespage.js",
- "moviegenres.js",
- "moviecollections.js",
- "movies.js",
- "movieslatest.js",
- "moviepeople.js",
- "moviesrecommended.js",
- "moviestudios.js",
- "movietrailers.js",
- "musicalbums.js",
- "musicalbumartists.js",
- "musicartists.js",
- "musicgenres.js",
- "musicrecommended.js",
- "musicvideos.js",
- "notifications.js",
- "playlist.js",
- "plugincatalogpage.js",
- "pluginspage.js",
- "pluginupdatespage.js",
- "remotecontrol.js",
- "scheduledtaskpage.js",
- "scheduledtaskspage.js",
- "search.js",
- "songs.js",
- "supporterkeypage.js",
- "supporterpage.js",
- "episodes.js",
- "tvgenres.js",
- "tvlatest.js",
- "tvpeople.js",
- "tvrecommended.js",
- "tvshows.js",
- "tvstudios.js",
- "tvupcoming.js",
- "useredit.js",
- "userpassword.js",
- "userimagepage.js",
- "userprofilespage.js",
- "usersettings.js",
- "userparentalcontrol.js",
- "wizardfinishpage.js",
- "wizardimagesettings.js",
- "wizardservice.js",
- "wizardstartpage.js",
- "wizardsettings.js",
- "wizarduserpage.js"
- };
-
var memoryStream = new MemoryStream();
var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
+ // jQuery + jQuery mobile
await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false);
await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.2/jquery.mobile-1.4.2.min.js", newLineBytes).ConfigureAwait(false);
+ await AppendLocalization(memoryStream).ConfigureAwait(false);
+ await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
+
+ // Write the version string for the dashboard comparison function
var versionString = string.Format("window.dashboardVersion='{0}';", _appHost.ApplicationVersion);
var versionBytes = Encoding.UTF8.GetBytes(versionString);
await memoryStream.WriteAsync(versionBytes, 0, versionBytes.Length).ConfigureAwait(false);
await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
- await AppendResource(memoryStream, "thirdparty/autonumeric/autoNumeric.min.js", newLineBytes).ConfigureAwait(false);
-
+ var builder = new StringBuilder();
var assembly = GetType().Assembly;
- await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false);
+ using (var stream = assembly.GetManifestResourceStream("MediaBrowser.WebDashboard.ApiClient.js"))
+ {
+ using (var streamReader = new StreamReader(stream))
+ {
+ var text = await streamReader.ReadToEndAsync().ConfigureAwait(false);
+ builder.Append(text);
+ builder.Append(Environment.NewLine);
+ }
+ }
+
+ foreach (var file in GetScriptFiles())
+ {
+ var path = GetDashboardResourcePath("scripts/" + file);
+
+ using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
+ {
+ using (var streamReader = new StreamReader(fs))
+ {
+ var text = await streamReader.ReadToEndAsync().ConfigureAwait(false);
+ builder.Append(text);
+ builder.Append(Environment.NewLine);
+ }
+ }
+ }
+
+ var js = builder.ToString();
+
+ try
+ {
+ var result = new CrockfordJsMinifier().Minify(js, false, Encoding.UTF8);
- foreach (var file in scriptFiles)
+ js = result.MinifiedContent;
+ }
+ catch (Exception ex)
{
- await AppendResource(memoryStream, "scripts/" + file, newLineBytes).ConfigureAwait(false);
+ Logger.ErrorException("Error minifying javascript", ex);
}
+ var bytes = Encoding.UTF8.GetBytes(js);
+ await memoryStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+
memoryStream.Position = 0;
return memoryStream;
}
+ private IEnumerable<string> GetScriptFiles()
+ {
+ return new[]
+ {
+ "extensions.js",
+ "site.js",
+ "librarybrowser.js",
+ "librarylist.js",
+ "editorsidebar.js",
+ "librarymenu.js",
+ "chromecast.js",
+ "contextmenu.js",
+
+ "mediacontroller.js",
+ "mediaplayer.js",
+ "mediaplayer-video.js",
+
+ "ratingdialog.js",
+ "aboutpage.js",
+ "allusersettings.js",
+ "alphapicker.js",
+ "addpluginpage.js",
+ "advancedconfigurationpage.js",
+ "advancedpaths.js",
+ "advancedserversettings.js",
+ "metadataadvanced.js",
+ "appsplayback.js",
+ "appsweather.js",
+ "autoorganizetv.js",
+ "autoorganizelog.js",
+ "channels.js",
+ "channelitems.js",
+ "dashboardgeneral.js",
+ "dashboardinfo.js",
+ "dashboardpage.js",
+ "directorybrowser.js",
+ "dlnaprofile.js",
+ "dlnaprofiles.js",
+ "dlnasettings.js",
+ "editcollectionitems.js",
+ "edititemmetadata.js",
+ "edititempeople.js",
+ "edititemimages.js",
+ "encodingsettings.js",
+ "gamesrecommendedpage.js",
+ "gamesystemspage.js",
+ "gamespage.js",
+ "gamegenrepage.js",
+ "gamestudiospage.js",
+ "indexpage.js",
+ "itembynamedetailpage.js",
+ "itemdetailpage.js",
+ "itemgallery.js",
+ "itemlistpage.js",
+ "librarypathmapping.js",
+ "libraryreport.js",
+ "librarysettings.js",
+ "livetvchannel.js",
+ "livetvchannels.js",
+ "livetvguide.js",
+ "livetvnewrecording.js",
+ "livetvprogram.js",
+ "livetvrecording.js",
+ "livetvrecordinglist.js",
+ "livetvrecordings.js",
+ "livetvtimer.js",
+ "livetvseriestimer.js",
+ "livetvseriestimers.js",
+ "livetvsettings.js",
+ "livetvsuggested.js",
+ "livetvstatus.js",
+ "livetvtimers.js",
+ "loginpage.js",
+ "logpage.js",
+ "medialibrarypage.js",
+ "metadataconfigurationpage.js",
+ "metadataimagespage.js",
+ "moviegenres.js",
+ "moviecollections.js",
+ "movies.js",
+ "movieslatest.js",
+ "moviepeople.js",
+ "moviesrecommended.js",
+ "moviestudios.js",
+ "movietrailers.js",
+ "musicalbums.js",
+ "musicalbumartists.js",
+ "musicartists.js",
+ "musicgenres.js",
+ "musicrecommended.js",
+ "musicvideos.js",
+ "notifications.js",
+ "playlist.js",
+ "plugincatalogpage.js",
+ "pluginspage.js",
+ "pluginupdatespage.js",
+ "remotecontrol.js",
+ "scheduledtaskpage.js",
+ "scheduledtaskspage.js",
+ "search.js",
+ "songs.js",
+ "supporterkeypage.js",
+ "supporterpage.js",
+ "episodes.js",
+ "tvgenres.js",
+ "tvlatest.js",
+ "tvpeople.js",
+ "tvrecommended.js",
+ "tvshows.js",
+ "tvstudios.js",
+ "tvupcoming.js",
+ "useredit.js",
+ "userpassword.js",
+ "userimagepage.js",
+ "userprofilespage.js",
+ "usersettings.js",
+ "userparentalcontrol.js",
+ "wizardfinishpage.js",
+ "wizardimagesettings.js",
+ "wizardservice.js",
+ "wizardstartpage.js",
+ "wizardsettings.js",
+ "wizarduserpage.js"
+ };
+ }
+
+ private async Task AppendLocalization(Stream stream)
+ {
+ var js = "window.localizationGlossary=" + _jsonSerializer.SerializeToString(_localization.GetJavaScriptLocalizationDictionary(GetLocalizationCulture()));
+
+ var bytes = Encoding.UTF8.GetBytes(js);
+ await stream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ }
+
/// <summary>
/// Gets all CSS.
/// </summary>
@@ -568,35 +656,40 @@ namespace MediaBrowser.WebDashboard.Api
"icons.css"
};
- var memoryStream = new MemoryStream();
-
- var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
+ var builder = new StringBuilder();
foreach (var file in files)
{
- await AppendResource(memoryStream, "css/" + file, newLineBytes).ConfigureAwait(false);
+ var path = GetDashboardResourcePath("css/" + file);
+
+ using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
+ {
+ using (var streamReader = new StreamReader(fs))
+ {
+ var text = await streamReader.ReadToEndAsync().ConfigureAwait(false);
+ builder.Append(text);
+ builder.Append(Environment.NewLine);
+ }
+ }
}
-
- memoryStream.Position = 0;
- return memoryStream;
- }
- /// <summary>
- /// Appends the resource.
- /// </summary>
- /// <param name="assembly">The assembly.</param>
- /// <param name="outputStream">The output stream.</param>
- /// <param name="path">The path.</param>
- /// <param name="newLineBytes">The new line bytes.</param>
- /// <returns>Task.</returns>
- private async Task AppendResource(Assembly assembly, Stream outputStream, string path, byte[] newLineBytes)
- {
- using (var stream = assembly.GetManifestResourceStream(path))
- {
- await stream.CopyToAsync(outputStream).ConfigureAwait(false);
+ var css = builder.ToString();
- await outputStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
- }
+ //try
+ //{
+ // var result = new KristensenCssMinifier().Minify(builder.ToString(), false, Encoding.UTF8);
+
+ // css = result.MinifiedContent;
+ //}
+ //catch (Exception ex)
+ //{
+ // Logger.ErrorException("Error minifying css", ex);
+ //}
+
+ var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(css));
+
+ memoryStream.Position = 0;
+ return memoryStream;
}
/// <summary>
diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js
index ea1905bad..f268c44ec 100644
--- a/MediaBrowser.WebDashboard/ApiClient.js
+++ b/MediaBrowser.WebDashboard/ApiClient.js
@@ -4058,7 +4058,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
options.ForceTitle = true;
}
- var url = self.getUrl("Packages/" + packageId + "Reviews", options);
+ var url = self.getUrl("Packages/" + packageId + "/Reviews", options);
return self.ajax({
type: "GET",
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 6a8cc49b1..0ddb05577 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -57,6 +57,9 @@
<Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
+ <Reference Include="WebMarkupMin.Core">
+ <HintPath>..\packages\WebMarkupMin.Core.0.8.18\lib\net40\WebMarkupMin.Core.dll</HintPath>
+ </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -221,6 +224,9 @@
<Content Include="dashboard-ui\css\mediaplayer.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\dashboardgeneral.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\dashboardinfopage.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -515,6 +521,9 @@
<Content Include="dashboard-ui\scripts\contextmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\dashboardgeneral.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\dashboardinfo.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -587,6 +596,9 @@
<Content Include="dashboard-ui\scripts\editorsidebar.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\mediacontroller.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\mediaplayer-video.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -638,9 +650,6 @@
<Content Include="dashboard-ui\livetvseriestimers.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.min.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jquery-2.0.3.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1474,9 +1483,6 @@
<Content Include="dashboard-ui\scripts\tvstudios.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1967,6 +1973,7 @@
</Content>
</ItemGroup>
<ItemGroup>
+ <None Include="app.config" />
<None Include="dashboard-ui\css\fonts\OpenSans-ExtraBold.woff">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -1980,6 +1987,9 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
+ <None Include="WebMarkupMin.Configuration.xsd">
+ <SubType>Designer</SubType>
+ </None>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
diff --git a/MediaBrowser.WebDashboard/app.config b/MediaBrowser.WebDashboard/app.config
new file mode 100644
index 000000000..8c2c664cd
--- /dev/null
+++ b/MediaBrowser.WebDashboard/app.config
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <configSections>
+ <sectionGroup name="webMarkupMin">
+ <section name="core" type="WebMarkupMin.Core.Configuration.CoreConfiguration, WebMarkupMin.Core" />
+ </sectionGroup>
+ </configSections>
+ <webMarkupMin xmlns="http://tempuri.org/WebMarkupMin.Configuration.xsd">
+ <core>
+ <css>
+ <minifiers>
+ <add name="NullCssMinifier" displayName="Null CSS Minifier" type="WebMarkupMin.Core.Minifiers.NullCssMinifier, WebMarkupMin.Core" />
+ <add name="KristensenCssMinifier" displayName="Mads Kristensen's CSS minifier" type="WebMarkupMin.Core.Minifiers.KristensenCssMinifier, WebMarkupMin.Core" />
+ </minifiers>
+ </css>
+ <js>
+ <minifiers>
+ <add name="NullJsMinifier" displayName="Null JS Minifier" type="WebMarkupMin.Core.Minifiers.NullJsMinifier, WebMarkupMin.Core" />
+ <add name="CrockfordJsMinifier" displayName="Douglas Crockford's JS Minifier" type="WebMarkupMin.Core.Minifiers.CrockfordJsMinifier, WebMarkupMin.Core" />
+ </minifiers>
+ </js>
+ <logging>
+ <loggers>
+ <add name="NullLogger" displayName="Null Logger" type="WebMarkupMin.Core.Loggers.NullLogger, WebMarkupMin.Core" />
+ <add name="ThrowExceptionLogger" displayName="Throw exception logger" type="WebMarkupMin.Core.Loggers.ThrowExceptionLogger, WebMarkupMin.Core" />
+ </loggers>
+ </logging>
+ </core>
+ </webMarkupMin>
+</configuration> \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config
index a5074d3c8..b867f7a92 100644
--- a/MediaBrowser.WebDashboard/packages.config
+++ b/MediaBrowser.WebDashboard/packages.config
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="MediaBrowser.ApiClient.Javascript" version="3.0.248" targetFramework="net45" />
+ <package id="MediaBrowser.ApiClient.Javascript" version="3.0.249" targetFramework="net45" />
+ <package id="WebMarkupMin.Core" version="0.8.18" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index c550b350c..a3b4533dd 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
- <version>3.0.345</version>
+ <version>3.0.346</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.345" />
+ <dependency id="MediaBrowser.Common" version="3.0.346" />
<dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.4.1" />
<dependency id="sharpcompress" version="0.10.2" />
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index a6fa5c152..b80f673e3 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
- <version>3.0.345</version>
+ <version>3.0.346</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index cc87b0030..eccfcccd2 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
- <version>3.0.345</version>
+ <version>3.0.346</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.345" />
+ <dependency id="MediaBrowser.Common" version="3.0.346" />
</dependencies>
</metadata>
<files>