diff options
36 files changed, 401 insertions, 235 deletions
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 3e49c1c9e..ed3f2c633 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -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) diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index 905ead86c..d2a4aa60c 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -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); diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 1a152790a..5d377366a 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -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; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index a333fc6e9..594b5ca93 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -419,6 +419,10 @@ namespace MediaBrowser.Controller.Entities return _sortName ?? (_sortName = CreateSortName()); } + set + { + _sortName = value; + } } public string GetInternalMetadataPath() diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 53667c6e1..0af4972f7 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -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[] { }; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index d30b105a5..503399f8d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -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 + if (stream.Type == MediaStreamType.Video && + string.Equals(stream.Codec, "h264", StringComparison.OrdinalIgnoreCase) && + !stream.IsInterlaced && + !(stream.IsAnamorphic ?? false)) { - //stream.KeyFrames = await GetKeyFrames(inputPath, stream.Index, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - - } - catch (Exception ex) - { - _logger.ErrorException("Error getting key frame interval", ex); + try + { + stream.KeyFrames = await GetKeyFrames(inputPath, stream.Index, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + + } + 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) - { - cancellationToken.ThrowIfCancellationRequested(); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); - var line = await reader.ReadLineAsync().ConfigureAwait(false); + var lines = StringHelper.RegexSplit(text, "\r\n"); + foreach (var line in lines) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } - 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)); } } } diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index c2524378e..6107c0fbe 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -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. diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 07d5905c6..9f95953cf 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -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", diff --git a/MediaBrowser.Providers/Folders/DefaultImageProvider.cs b/MediaBrowser.Providers/Folders/DefaultImageProvider.cs new file mode 100644 index 000000000..506e983c6 --- /dev/null +++ b/MediaBrowser.Providers/Folders/DefaultImageProvider.cs @@ -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)); + } + } +} diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 453e07987..fe0e4890c 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -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; } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 444567afa..1d323e567 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -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" /> diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs index 973519a77..8a659fb65 100644 --- a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs +++ b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.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) { diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs index 4569503c0..7cd96c5f3 100644 --- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs +++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs @@ -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) diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs index b83256342..683e6c5cc 100644 --- a/MediaBrowser.Server.Implementations/Library/MusicManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs @@ -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[] { }; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index b69cdacef..8b717e5d4 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -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) diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs index a297c866a..713cb9cd3 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs @@ -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() diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index e19b17ca4..3783e4b08 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -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")) - { - 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 + 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 { - 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,6 +103,35 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) { + var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false); + + return sources.First(); + } + + class M3UChannel : ChannelInfo + { + public string Path { get; set; } + + public M3UChannel() + { + } + } + + public async Task Validate(TunerHostInfo info) + { + if (!File.Exists(info.Url)) + { + throw new FileNotFoundException(); + } + } + + protected override bool IsValidChannelId(string channelId) + { + return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); + } + + protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) + { var urlHash = info.Url.GetMD5().ToString("N"); var prefix = ChannelIdPrefix + urlHash; if (!channelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) @@ -133,29 +139,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts return null; } - channelId = channelId.Substring(prefix.Length); + //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); + 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")) + if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { protocol = MediaProtocol.Http; } - else if (path.StartsWith("rtmp")) + else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase)) { protocol = MediaProtocol.Rtmp; } - else if (path.StartsWith("rtsp")) + else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase)) { protocol = MediaProtocol.Rtsp; } - return new MediaSourceInfo + var mediaSource = new MediaSourceInfo { Path = channel.Path, Protocol = protocol, @@ -179,35 +185,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts RequiresOpening = false, RequiresClosing = false }; - } - throw new ApplicationException("Host doesnt provide this channel"); - } - - class M3UChannel : ChannelInfo - { - public string Path { get; set; } - - public M3UChannel() - { - } - } - public async Task Validate(TunerHostInfo info) - { - if (!File.Exists(info.Url)) - { - throw new FileNotFoundException(); + return new List<MediaSourceInfo> { mediaSource }; } - } - - protected override bool IsValidChannelId(string channelId) - { - return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); - } - - protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) - { - throw new NotImplementedException(); + return new List<MediaSourceInfo> { }; } protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index b2da2489d..f0312cef6 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -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> diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs index 7a993b7db..d913063c8 100644 --- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs @@ -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[] diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 04f09602f..00ebf7ea6 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -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) diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs index 8cb76393e..2f219c299 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs @@ -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(); diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs index b11a7d167..844238228 100644 --- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -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; - } } } diff --git a/MediaBrowser.Server.Implementations/UserViews/livetv/1.jpg b/MediaBrowser.Server.Implementations/UserViews/livetv/1.jpg Binary files differdeleted file mode 100644 index 2594b68a4..000000000 --- a/MediaBrowser.Server.Implementations/UserViews/livetv/1.jpg +++ /dev/null diff --git a/MediaBrowser.Server.Implementations/UserViews/livetv/2.jpg b/MediaBrowser.Server.Implementations/UserViews/livetv/2.jpg Binary files differdeleted file mode 100644 index e5c87b96b..000000000 --- a/MediaBrowser.Server.Implementations/UserViews/livetv/2.jpg +++ /dev/null diff --git a/MediaBrowser.Server.Implementations/UserViews/livetv/3.jpg b/MediaBrowser.Server.Implementations/UserViews/livetv/3.jpg Binary files differdeleted file mode 100644 index c19f7e612..000000000 --- a/MediaBrowser.Server.Implementations/UserViews/livetv/3.jpg +++ /dev/null diff --git a/MediaBrowser.Server.Implementations/UserViews/livetv/4.jpg b/MediaBrowser.Server.Implementations/UserViews/livetv/4.jpg Binary files differdeleted file mode 100644 index 93ee18044..000000000 --- a/MediaBrowser.Server.Implementations/UserViews/livetv/4.jpg +++ /dev/null diff --git a/MediaBrowser.Server.Implementations/UserViews/livetv/5.jpg b/MediaBrowser.Server.Implementations/UserViews/livetv/5.jpg Binary files differdeleted file mode 100644 index 4c2cd580d..000000000 --- a/MediaBrowser.Server.Implementations/UserViews/livetv/5.jpg +++ /dev/null diff --git a/MediaBrowser.Server.Implementations/UserViews/livetv/6.jpg b/MediaBrowser.Server.Implementations/UserViews/livetv/6.jpg Binary files differdeleted file mode 100644 index 6f496b6ac..000000000 --- a/MediaBrowser.Server.Implementations/UserViews/livetv/6.jpg +++ /dev/null diff --git a/MediaBrowser.Server.Implementations/UserViews/livetv/7.jpg b/MediaBrowser.Server.Implementations/UserViews/livetv/7.jpg Binary files differdeleted file mode 100644 index e7dba2760..000000000 --- a/MediaBrowser.Server.Implementations/UserViews/livetv/7.jpg +++ /dev/null diff --git a/MediaBrowser.Server.Implementations/UserViews/livetv/8.jpg b/MediaBrowser.Server.Implementations/UserViews/livetv/8.jpg Binary files differdeleted file mode 100644 index c69ba908c..000000000 --- a/MediaBrowser.Server.Implementations/UserViews/livetv/8.jpg +++ /dev/null diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 06ade9e84..01e74d066 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs @@ -423,7 +423,6 @@ namespace MediaBrowser.WebDashboard.Api var files = new[] { - "thirdparty/fontawesome/css/font-awesome.min.css" + versionString, "css/all.css" + versionString }; diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index a0702fb68..f92e05492 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.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> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 56e6f5c69..04d4deb7c 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.632</version> + <version>3.0.633</version> <title>MediaBrowser.Common</title> <authors>Emby Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec index 654d33b5c..2a75ac3a8 100644 --- a/Nuget/MediaBrowser.Model.Signed.nuspec +++ b/Nuget/MediaBrowser.Model.Signed.nuspec @@ -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> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index b72d50d69..da8309410 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.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> diff --git a/SharedVersion.cs b/SharedVersion.cs index 732202201..8398c83be 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -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")] |
