Merge branch 'dev' of https://github.com/MediaBrowser/MediaBrowser into dev
|
@ -270,6 +270,7 @@ namespace MediaBrowser.Api.Library
|
|||
private readonly ILiveTvManager _liveTv;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly ITVSeriesManager _tvManager;
|
||||
private readonly ILibraryMonitor _libraryMonitor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibraryService" /> class.
|
||||
|
@ -422,7 +423,25 @@ namespace MediaBrowser.Api.Library
|
|||
|
||||
public void Post(PostUpdatedSeries request)
|
||||
{
|
||||
Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None));
|
||||
var series = _libraryManager.GetItems(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name }
|
||||
|
||||
}).Items;
|
||||
|
||||
series = series.Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
if (series.Length > 0)
|
||||
{
|
||||
foreach (var item in series)
|
||||
{
|
||||
_libraryMonitor.ReportFileSystemChanged(item.Path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None));
|
||||
}
|
||||
}
|
||||
|
||||
public object Get(GetDownload request)
|
||||
|
|
|
@ -54,6 +54,11 @@ namespace MediaBrowser.Api.Music
|
|||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Items/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given item")]
|
||||
public class GetInstantMixFromItem : BaseGetSimilarItemsFromItem
|
||||
{
|
||||
}
|
||||
|
||||
[Authenticated]
|
||||
public class InstantMixService : BaseApiService
|
||||
{
|
||||
|
@ -71,6 +76,17 @@ namespace MediaBrowser.Api.Music
|
|||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public object Get(GetInstantMixFromItem request)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(request.Id);
|
||||
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
|
||||
var items = _musicManager.GetInstantMixFromItem(item, user);
|
||||
|
||||
return GetResult(items, user, request);
|
||||
}
|
||||
|
||||
public object Get(GetInstantMixFromArtistId request)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(request.Id);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Linq;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
|
@ -317,6 +316,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
{
|
||||
if (videoStream.KeyFrames == null || videoStream.KeyFrames.Count == 0)
|
||||
{
|
||||
Logger.Debug("Cannot stream copy video due to missing keyframe info");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -328,6 +328,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
// Don't allow really long segments because this could result in long download times
|
||||
if (length > 10000)
|
||||
{
|
||||
Logger.Debug("Cannot stream copy video due to long segment length of {0}ms", length);
|
||||
return false;
|
||||
}
|
||||
previousSegment = frame;
|
||||
|
|
|
@ -419,6 +419,10 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
return _sortName ?? (_sortName = CreateSortName());
|
||||
}
|
||||
set
|
||||
{
|
||||
_sortName = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetInternalMetadataPath()
|
||||
|
|
|
@ -97,7 +97,8 @@ namespace MediaBrowser.Controller.Entities
|
|||
public int? MaxParentalRating { get; set; }
|
||||
|
||||
public bool? IsCurrentSchema { get; set; }
|
||||
|
||||
public bool? HasDeadParentId { get; set; }
|
||||
|
||||
public InternalItemsQuery()
|
||||
{
|
||||
Tags = new string[] { };
|
||||
|
|
|
@ -8,6 +8,7 @@ using MediaBrowser.Controller.Session;
|
|||
using MediaBrowser.MediaEncoding.Probing;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
@ -242,21 +243,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
if (extractKeyFrameInterval && mediaInfo.RunTimeTicks.HasValue)
|
||||
{
|
||||
foreach (var stream in mediaInfo.MediaStreams)
|
||||
if (ConfigurationManager.Configuration.EnableVideoFrameAnalysis && mediaInfo.Size.HasValue && mediaInfo.Size.Value <= ConfigurationManager.Configuration.VideoFrameAnalysisLimitBytes)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Video && string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
foreach (var stream in mediaInfo.MediaStreams)
|
||||
{
|
||||
try
|
||||
{
|
||||
//stream.KeyFrames = await GetKeyFrames(inputPath, stream.Index, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
if (stream.Type == MediaStreamType.Video &&
|
||||
string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase) &&
|
||||
!stream.IsInterlaced &&
|
||||
!(stream.IsAnamorphic ?? false))
|
||||
{
|
||||
try
|
||||
{
|
||||
stream.KeyFrames = await GetKeyFrames(inputPath, stream.Index, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting key frame interval", ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting key frame interval", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +289,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
private async Task<List<int>> GetKeyFrames(string inputPath, int videoStreamIndex, CancellationToken cancellationToken)
|
||||
{
|
||||
const string args = "-i {0} -select_streams v:{1} -show_packets -print_format compact -show_entries packet=flags -show_entries packet=pts_time";
|
||||
inputPath = inputPath.Split(new[] { ':' }, 2).Last().Trim('"');
|
||||
|
||||
const string args = "-show_packets -print_format compact -select_streams v:{1} -show_entries packet=flags -show_entries packet=pts_time \"{0}\"";
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
|
@ -294,7 +303,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
FileName = FFProbePath,
|
||||
Arguments = string.Format(args, inputPath, videoStreamIndex.ToString(CultureInfo.InvariantCulture)).Trim(),
|
||||
|
||||
|
@ -307,9 +315,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
using (var processWrapper = new ProcessWrapper(process, this, _logger))
|
||||
using (process)
|
||||
{
|
||||
StartProcess(processWrapper);
|
||||
var start = DateTime.UtcNow;
|
||||
|
||||
process.Start();
|
||||
|
||||
var lines = new List<int>();
|
||||
|
||||
|
@ -326,28 +336,32 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
StopProcess(processWrapper, 100, true);
|
||||
}
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
_logger.Debug("Keyframe extraction took {0} seconds", (DateTime.UtcNow - start).TotalSeconds);
|
||||
//_logger.Debug("Found keyframes {0}", string.Join(",", lines.ToArray()));
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartReadingOutput(Stream source, List<int> lines, CancellationToken cancellationToken)
|
||||
private async Task StartReadingOutput(Stream source, List<int> keyframes, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var reader = new StreamReader(source))
|
||||
{
|
||||
while (!reader.EndOfStream)
|
||||
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
|
||||
var lines = StringHelper.RegexSplit(text, "\r\n");
|
||||
foreach (var line in lines)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var line = await reader.ReadLineAsync().ConfigureAwait(false);
|
||||
|
||||
var values = (line ?? string.Empty).Split('|')
|
||||
var values = line.Split('|')
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Select(i => i.Split('='))
|
||||
.Where(i => i.Length == 2)
|
||||
|
@ -361,7 +375,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
if (values.TryGetValue("pts_time", out pts_time) && double.TryParse(pts_time, NumberStyles.Any, CultureInfo.InvariantCulture, out frameSeconds))
|
||||
{
|
||||
var ms = frameSeconds * 1000;
|
||||
lines.Add(Convert.ToInt32(ms));
|
||||
keyframes.Add(Convert.ToInt32(ms));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -320,32 +320,11 @@ namespace MediaBrowser.Model.ApiClient
|
|||
Task<ItemsResult> GetUserViews(string userId, CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instant mix from song async.
|
||||
/// Gets the instant mix from item asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>Task{ItemsResult}.</returns>
|
||||
Task<ItemsResult> GetInstantMixFromSongAsync(SimilarItemsQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instant mix from album async.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>Task{ItemsResult}.</returns>
|
||||
Task<ItemsResult> GetInstantMixFromAlbumAsync(SimilarItemsQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instant mix from artist async.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>Task{ItemsResult}.</returns>
|
||||
Task<ItemsResult> GetInstantMixFromArtistAsync(SimilarItemsQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instant mix from music genre async.
|
||||
/// </summary>
|
||||
/// <param name="query">The query.</param>
|
||||
/// <returns>Task{ItemsResult}.</returns>
|
||||
Task<ItemsResult> GetInstantMixFromMusicGenreAsync(SimilarItemsQuery query);
|
||||
/// <returns>Task<ItemsResult>.</returns>
|
||||
Task<ItemsResult> GetInstantMixFromItemAsync(SimilarItemsQuery query);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the similar movies async.
|
||||
|
|
|
@ -222,11 +222,13 @@ namespace MediaBrowser.Model.Configuration
|
|||
public bool DisableXmlSavers { get; set; }
|
||||
public bool EnableWindowsShortcuts { get; set; }
|
||||
|
||||
public bool EnableVideoFrameAnalysis { get; set; }
|
||||
public long VideoFrameAnalysisLimitBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
|
||||
/// </summary>
|
||||
public ServerConfiguration()
|
||||
: base()
|
||||
{
|
||||
ImageSavingConvention = ImageSavingConvention.Compatible;
|
||||
PublicPort = 8096;
|
||||
|
@ -271,6 +273,9 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
PeopleMetadataOptions = new PeopleMetadataOptions();
|
||||
|
||||
EnableVideoFrameAnalysis = true;
|
||||
VideoFrameAnalysisLimitBytes = 600000000;
|
||||
|
||||
InsecureApps9 = new[]
|
||||
{
|
||||
"Chromecast",
|
||||
|
|
151
MediaBrowser.Providers/Folders/DefaultImageProvider.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Providers.Genres;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Folders
|
||||
{
|
||||
public class DefaultImageProvider : IRemoteImageProvider, IHasItemChangeMonitor
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
|
||||
public DefaultImageProvider(IHttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
|
||||
{
|
||||
return new List<ImageType>
|
||||
{
|
||||
ImageType.Primary,
|
||||
ImageType.Thumb
|
||||
};
|
||||
}
|
||||
|
||||
public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken)
|
||||
{
|
||||
var view = item as UserView;
|
||||
|
||||
if (view != null)
|
||||
{
|
||||
return GetImages(view.ViewType, cancellationToken);
|
||||
}
|
||||
|
||||
var folder = (ICollectionFolder)item;
|
||||
return GetImages(folder.CollectionType, cancellationToken);
|
||||
}
|
||||
|
||||
private Task<IEnumerable<RemoteImageInfo>> GetImages(string viewType, CancellationToken cancellationToken)
|
||||
{
|
||||
var url = GetImageUrl(viewType);
|
||||
|
||||
var list = new List<RemoteImageInfo>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
list.AddRange(new List<RemoteImageInfo>{
|
||||
new RemoteImageInfo
|
||||
{
|
||||
ProviderName = Name,
|
||||
Url = url,
|
||||
Type = ImageType.Primary
|
||||
},
|
||||
|
||||
new RemoteImageInfo
|
||||
{
|
||||
ProviderName = Name,
|
||||
Url = url,
|
||||
Type = ImageType.Thumb
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<RemoteImageInfo>>(list);
|
||||
}
|
||||
|
||||
private string GetImageUrl(string viewType)
|
||||
{
|
||||
const string urlPrefix = "https://raw.githubusercontent.com/MediaBrowser/Emby.Resources/master/images/folders/";
|
||||
|
||||
if (string.Equals(viewType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//return urlPrefix + "books.png";
|
||||
}
|
||||
if (string.Equals(viewType, CollectionType.Games, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//return urlPrefix + "games.png";
|
||||
}
|
||||
if (string.Equals(viewType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//return urlPrefix + "music.png";
|
||||
}
|
||||
if (string.Equals(viewType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//return urlPrefix + "photos.png";
|
||||
}
|
||||
if (string.Equals(viewType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//return urlPrefix + "tv.png";
|
||||
}
|
||||
if (string.Equals(viewType, CollectionType.Channels, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//return urlPrefix + "channels.png";
|
||||
}
|
||||
if (string.Equals(viewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return urlPrefix + "livetv.png";
|
||||
}
|
||||
if (string.Equals(viewType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
//return urlPrefix + "movies.png";
|
||||
}
|
||||
if (string.Equals(viewType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return urlPrefix + "playlists.png";
|
||||
}
|
||||
|
||||
return null;
|
||||
//return urlPrefix + "generic.png";
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Default Image Provider"; }
|
||||
}
|
||||
|
||||
public bool Supports(IHasImages item)
|
||||
{
|
||||
var view = item as UserView;
|
||||
|
||||
if (view != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return item is ICollectionFolder;
|
||||
}
|
||||
|
||||
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
return _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
CancellationToken = cancellationToken,
|
||||
Url = url,
|
||||
ResourcePool = GenreImageProvider.ImageDownloadResourcePool
|
||||
});
|
||||
}
|
||||
|
||||
public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
|
||||
{
|
||||
return GetSupportedImages(item).Any(i => !item.HasImage(i));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -189,7 +189,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
var images = results.SelectMany(i => i);
|
||||
var images = results.SelectMany(i => i.ToList());
|
||||
|
||||
return images;
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
<Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
|
||||
<Compile Include="Channels\ChannelMetadataService.cs" />
|
||||
<Compile Include="Chapters\ChapterManager.cs" />
|
||||
<Compile Include="Folders\DefaultImageProvider.cs" />
|
||||
<Compile Include="Folders\FolderMetadataService.cs" />
|
||||
<Compile Include="Channels\AudioChannelItemMetadataService.cs" />
|
||||
<Compile Include="Folders\UserViewMetadataService.cs" />
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Connect
|
|||
_timer = new Timer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3));
|
||||
}
|
||||
|
||||
private readonly string[] _ipLookups = { "http://bot.whatismyipaddress.com", "https://connect.mediabrowser.tv/service/ip" };
|
||||
private readonly string[] _ipLookups = { "http://bot.whatismyipaddress.com", "https://connect.emby.media/service/ip" };
|
||||
|
||||
private async void TimerCallback(object state)
|
||||
{
|
||||
|
|
|
@ -371,7 +371,7 @@ namespace MediaBrowser.Server.Implementations.Connect
|
|||
|
||||
private string GetConnectUrl(string handler)
|
||||
{
|
||||
return "https://connect.mediabrowser.tv/service/" + handler;
|
||||
return "https://connect.emby.media/service/" + handler;
|
||||
}
|
||||
|
||||
public async Task<UserLinkResult> LinkUser(string userId, string connectUsername)
|
||||
|
|
|
@ -52,6 +52,18 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
return GetInstantMixFromGenres(genres, user);
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
|
||||
{
|
||||
var genres = item
|
||||
.GetRecursiveChildren(user, i => i is Audio)
|
||||
.Cast<Audio>()
|
||||
.SelectMany(i => i.Genres)
|
||||
.Concat(item.Genres)
|
||||
.DistinctNames();
|
||||
|
||||
return GetInstantMixFromGenres(genres, user);
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user)
|
||||
{
|
||||
var genres = item
|
||||
|
@ -113,6 +125,12 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
{
|
||||
return GetInstantMixFromSong(song, user);
|
||||
}
|
||||
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
{
|
||||
return GetInstantMixFromFolder(folder, user);
|
||||
}
|
||||
|
||||
return new Audio[] { };
|
||||
}
|
||||
|
|
|
@ -455,7 +455,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
|||
}
|
||||
}
|
||||
|
||||
throw new ApplicationException("Tuner not found.");
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(string recordingId, CancellationToken cancellationToken)
|
||||
|
|
|
@ -1,41 +1,12 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Security;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
public class EntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ISecurityManager _manager;
|
||||
|
||||
public EntryPoint(IConfigurationManager config, ISecurityManager manager)
|
||||
{
|
||||
_config = config;
|
||||
_manager = manager;
|
||||
}
|
||||
|
||||
public async void Run()
|
||||
public void Run()
|
||||
{
|
||||
EmbyTV.Current.Start();
|
||||
|
||||
if (GetConfiguration().ListingProviders.Count > 0 || GetConfiguration().TunerHosts.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _manager.GetRegistrationStatus("livetvguide").ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LiveTvOptions GetConfiguration()
|
||||
{
|
||||
return _config.GetConfiguration<LiveTvOptions>("livetv");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
@ -39,68 +39,45 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
|
|||
var url = info.Url;
|
||||
var urlHash = url.GetMD5().ToString("N");
|
||||
|
||||
int position = 0;
|
||||
string line;
|
||||
// Read the file and display it line by line.
|
||||
var file = new StreamReader(url);
|
||||
var channels = new List<M3UChannel>();
|
||||
|
||||
string channnelName = null;
|
||||
string channelNumber = null;
|
||||
|
||||
while ((line = file.ReadLine()) != null)
|
||||
{
|
||||
line = line.Trim();
|
||||
if (!String.IsNullOrWhiteSpace(line))
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
if (position == 0 && !line.StartsWith("#EXTM3U"))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var parts = line.Split(new[] { ':' }, 2).Last().Split(new[] { ',' }, 2);
|
||||
channelNumber = parts[0];
|
||||
channnelName = parts[1];
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(channelNumber))
|
||||
{
|
||||
channels.Add(new M3UChannel
|
||||
{
|
||||
throw new ApplicationException("wrong file");
|
||||
}
|
||||
if (position % 2 == 0)
|
||||
{
|
||||
if (position != 0)
|
||||
{
|
||||
channels.Last().Path = line;
|
||||
}
|
||||
else
|
||||
{
|
||||
line = line.Replace("#EXTM3U", "");
|
||||
line = line.Trim();
|
||||
var vars = line.Split(' ').ToList();
|
||||
foreach (var variable in vars)
|
||||
{
|
||||
var list = variable.Replace('"', ' ').Split('=');
|
||||
switch (list[0])
|
||||
{
|
||||
case ("id"):
|
||||
//_id = list[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!line.StartsWith("#EXTINF:")) { throw new ApplicationException("Bad file"); }
|
||||
line = line.Replace("#EXTINF:", "");
|
||||
var nameStart = line.LastIndexOf(',');
|
||||
line = line.Substring(0, nameStart);
|
||||
var vars = line.Split(' ').ToList();
|
||||
vars.RemoveAt(0);
|
||||
channels.Add(new M3UChannel());
|
||||
foreach (var variable in vars)
|
||||
{
|
||||
var list = variable.Replace('"', ' ').Split('=');
|
||||
switch (list[0])
|
||||
{
|
||||
case "tvg-id":
|
||||
channels.Last().Id = ChannelIdPrefix + urlHash + list[1];
|
||||
channels.Last().Number = list[1];
|
||||
break;
|
||||
case "tvg-name":
|
||||
channels.Last().Name = list[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
position++;
|
||||
Name = channnelName,
|
||||
Number = channelNumber,
|
||||
Id = ChannelIdPrefix + urlHash + channelNumber,
|
||||
Path = line
|
||||
});
|
||||
|
||||
channelNumber = null;
|
||||
channnelName = null;
|
||||
}
|
||||
}
|
||||
file.Close();
|
||||
|
@ -126,61 +103,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
|
|||
|
||||
protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
|
||||
{
|
||||
var urlHash = info.Url.GetMD5().ToString("N");
|
||||
var prefix = ChannelIdPrefix + urlHash;
|
||||
if (!channelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
channelId = channelId.Substring(prefix.Length);
|
||||
|
||||
var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
|
||||
var m3uchannels = channels.Cast<M3UChannel>();
|
||||
var channel = m3uchannels.FirstOrDefault(c => c.Id == channelId);
|
||||
if (channel != null)
|
||||
{
|
||||
var path = channel.Path;
|
||||
MediaProtocol protocol = MediaProtocol.File;
|
||||
if (path.StartsWith("http"))
|
||||
{
|
||||
protocol = MediaProtocol.Http;
|
||||
}
|
||||
else if (path.StartsWith("rtmp"))
|
||||
{
|
||||
protocol = MediaProtocol.Rtmp;
|
||||
}
|
||||
else if (path.StartsWith("rtsp"))
|
||||
{
|
||||
protocol = MediaProtocol.Rtsp;
|
||||
}
|
||||
|
||||
return new MediaSourceInfo
|
||||
{
|
||||
Path = channel.Path,
|
||||
Protocol = protocol,
|
||||
MediaStreams = new List<MediaStream>
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Video,
|
||||
// Set the index to -1 because we don't know the exact index of the video stream within the container
|
||||
Index = -1,
|
||||
IsInterlaced = true
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Audio,
|
||||
// Set the index to -1 because we don't know the exact index of the audio stream within the container
|
||||
Index = -1
|
||||
|
||||
}
|
||||
},
|
||||
RequiresOpening = false,
|
||||
RequiresClosing = false
|
||||
};
|
||||
}
|
||||
throw new ApplicationException("Host doesnt provide this channel");
|
||||
return sources.First();
|
||||
}
|
||||
|
||||
class M3UChannel : ChannelInfo
|
||||
|
@ -205,9 +130,65 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
|
|||
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
|
||||
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var urlHash = info.Url.GetMD5().ToString("N");
|
||||
var prefix = ChannelIdPrefix + urlHash;
|
||||
if (!channelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
//channelId = channelId.Substring(prefix.Length);
|
||||
|
||||
var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
|
||||
var m3uchannels = channels.Cast<M3UChannel>();
|
||||
var channel = m3uchannels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
|
||||
if (channel != null)
|
||||
{
|
||||
var path = channel.Path;
|
||||
MediaProtocol protocol = MediaProtocol.File;
|
||||
if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
protocol = MediaProtocol.Http;
|
||||
}
|
||||
else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
protocol = MediaProtocol.Rtmp;
|
||||
}
|
||||
else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
protocol = MediaProtocol.Rtsp;
|
||||
}
|
||||
|
||||
var mediaSource = new MediaSourceInfo
|
||||
{
|
||||
Path = channel.Path,
|
||||
Protocol = protocol,
|
||||
MediaStreams = new List<MediaStream>
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Video,
|
||||
// Set the index to -1 because we don't know the exact index of the video stream within the container
|
||||
Index = -1,
|
||||
IsInterlaced = true
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Audio,
|
||||
// Set the index to -1 because we don't know the exact index of the audio stream within the container
|
||||
Index = -1
|
||||
|
||||
}
|
||||
},
|
||||
RequiresOpening = false,
|
||||
RequiresClosing = false
|
||||
};
|
||||
|
||||
return new List<MediaSourceInfo> { mediaSource };
|
||||
}
|
||||
return new List<MediaSourceInfo> { };
|
||||
}
|
||||
|
||||
protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
|
||||
|
|
|
@ -495,14 +495,6 @@
|
|||
<Link>swagger-ui\swagger-ui.min.js</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<EmbeddedResource Include="UserViews\livetv\1.jpg" />
|
||||
<EmbeddedResource Include="UserViews\livetv\2.jpg" />
|
||||
<EmbeddedResource Include="UserViews\livetv\3.jpg" />
|
||||
<EmbeddedResource Include="UserViews\livetv\4.jpg" />
|
||||
<EmbeddedResource Include="UserViews\livetv\5.jpg" />
|
||||
<EmbeddedResource Include="UserViews\livetv\6.jpg" />
|
||||
<EmbeddedResource Include="UserViews\livetv\7.jpg" />
|
||||
<EmbeddedResource Include="UserViews\livetv\8.jpg" />
|
||||
<EmbeddedResource Include="Localization\iso6392.txt" />
|
||||
<EmbeddedResource Include="Localization\Ratings\be.txt" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -46,9 +46,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(progress.Report);
|
||||
innerProgress.RegisterAction(p => progress.Report(.95 * p));
|
||||
|
||||
await UpdateToLatestSchema(cancellationToken, innerProgress).ConfigureAwait(false);
|
||||
|
||||
innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(p => progress.Report(95 + (.05 * p)));
|
||||
|
||||
//await CleanDeadItems(cancellationToken, innerProgress).ConfigureAwait(false);
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
|
@ -92,6 +99,43 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
progress.Report(100);
|
||||
}
|
||||
|
||||
private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
HasDeadParentId = true
|
||||
});
|
||||
|
||||
var numComplete = 0;
|
||||
var numItems = itemIds.Count;
|
||||
|
||||
_logger.Debug("Cleaning {0} items with dead parent links", numItems);
|
||||
|
||||
foreach (var itemId in itemIds)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var item = _libraryManager.GetItemById(itemId);
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
_logger.Debug("Cleaning item {0} type: {1} path: {2}", item.Name, item.GetType().Name, item.Path ?? string.Empty);
|
||||
|
||||
await _libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
});
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= numItems;
|
||||
progress.Report(percent * 100);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
|
||||
{
|
||||
return new ITaskTrigger[]
|
||||
|
|
|
@ -1096,6 +1096,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||
}
|
||||
}
|
||||
|
||||
if (query.HasDeadParentId.HasValue)
|
||||
{
|
||||
if (query.HasDeadParentId.Value)
|
||||
{
|
||||
whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)");
|
||||
}
|
||||
}
|
||||
|
||||
if (addPaging)
|
||||
{
|
||||
if (query.StartIndex.HasValue && query.StartIndex.Value > 0)
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
|
|||
{
|
||||
var list = new ITaskTrigger[] {
|
||||
|
||||
new IntervalTrigger{ Interval = TimeSpan.FromHours(8)}
|
||||
new IntervalTrigger{ Interval = TimeSpan.FromHours(12)}
|
||||
|
||||
}.ToList();
|
||||
|
||||
|
|
|
@ -11,8 +11,6 @@ using MediaBrowser.Server.Implementations.Photos;
|
|||
using MoreLinq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -152,9 +150,7 @@ namespace MediaBrowser.Server.Implementations.UserViews
|
|||
CollectionType.Games,
|
||||
CollectionType.Music,
|
||||
CollectionType.BoxSets,
|
||||
CollectionType.Playlists,
|
||||
CollectionType.Channels,
|
||||
CollectionType.LiveTv,
|
||||
CollectionType.Books,
|
||||
CollectionType.Photos,
|
||||
CollectionType.HomeVideos,
|
||||
|
@ -170,7 +166,7 @@ namespace MediaBrowser.Server.Implementations.UserViews
|
|||
var view = (UserView)item;
|
||||
if (imageType == ImageType.Primary && IsUsingCollectionStrip(view))
|
||||
{
|
||||
if (itemsWithImages.Count == 0 && !string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
|
||||
if (itemsWithImages.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -180,39 +176,5 @@ namespace MediaBrowser.Server.Implementations.UserViews
|
|||
|
||||
return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override IEnumerable<String> GetStripCollageImagePaths(IHasImages primaryItem, IEnumerable<BaseItem> items)
|
||||
{
|
||||
var userView = primaryItem as UserView;
|
||||
|
||||
if (userView != null && string.Equals(userView.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var list = new List<string>();
|
||||
for (int i = 1; i <= 8; i++)
|
||||
{
|
||||
list.Add(ExtractLiveTvResource(i.ToString(CultureInfo.InvariantCulture), ApplicationPaths));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
return base.GetStripCollageImagePaths(primaryItem, items);
|
||||
}
|
||||
|
||||
private string ExtractLiveTvResource(string name, IApplicationPaths paths)
|
||||
{
|
||||
var namespacePath = GetType().Namespace + ".livetv." + name + ".jpg";
|
||||
var tempPath = Path.Combine(paths.TempDirectory, Guid.NewGuid().ToString("N") + ".jpg");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
|
||||
|
||||
using (var stream = GetType().Assembly.GetManifestResourceStream(namespacePath))
|
||||
{
|
||||
using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 66 KiB |
|
@ -423,7 +423,6 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
|
||||
var files = new[]
|
||||
{
|
||||
"thirdparty/fontawesome/css/font-awesome.min.css" + versionString,
|
||||
"css/all.css" + versionString
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>MediaBrowser.Common.Internal</id>
|
||||
<version>3.0.632</version>
|
||||
<version>3.0.633</version>
|
||||
<title>MediaBrowser.Common.Internal</title>
|
||||
<authors>Luke</authors>
|
||||
<owners>ebr,Luke,scottisafool</owners>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
|
||||
<copyright>Copyright © Emby 2013</copyright>
|
||||
<dependencies>
|
||||
<dependency id="MediaBrowser.Common" version="3.0.632" />
|
||||
<dependency id="MediaBrowser.Common" version="3.0.633" />
|
||||
<dependency id="NLog" version="3.2.1" />
|
||||
<dependency id="SimpleInjector" version="2.8.0" />
|
||||
</dependencies>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>MediaBrowser.Common</id>
|
||||
<version>3.0.632</version>
|
||||
<version>3.0.633</version>
|
||||
<title>MediaBrowser.Common</title>
|
||||
<authors>Emby Team</authors>
|
||||
<owners>ebr,Luke,scottisafool</owners>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>MediaBrowser.Model.Signed</id>
|
||||
<version>3.0.632</version>
|
||||
<version>3.0.633</version>
|
||||
<title>MediaBrowser.Model - Signed Edition</title>
|
||||
<authors>Emby Team</authors>
|
||||
<owners>ebr,Luke,scottisafool</owners>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>MediaBrowser.Server.Core</id>
|
||||
<version>3.0.632</version>
|
||||
<version>3.0.633</version>
|
||||
<title>Media Browser.Server.Core</title>
|
||||
<authors>Emby Team</authors>
|
||||
<owners>ebr,Luke,scottisafool</owners>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<description>Contains core components required to build plugins for Emby Server.</description>
|
||||
<copyright>Copyright © Emby 2013</copyright>
|
||||
<dependencies>
|
||||
<dependency id="MediaBrowser.Common" version="3.0.632" />
|
||||
<dependency id="MediaBrowser.Common" version="3.0.633" />
|
||||
<dependency id="Interfaces.IO" version="1.0.0.5" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System.Reflection;
|
||||
|
||||
//[assembly: AssemblyVersion("3.0.*")]
|
||||
[assembly: AssemblyVersion("3.0.5713.4")]
|
||||
[assembly: AssemblyVersion("3.0.*")]
|
||||
//[assembly: AssemblyVersion("3.0.5713.5")]
|
||||
|
|