diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index de029cd22..372a99853 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException("type"); } - if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath)) + if (ConfigurationManager.Configuration.EnableLocalizedGuids && key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath)) { // Try to normalize paths located underneath program-data in an attempt to make them more portable key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length) @@ -1927,11 +1927,18 @@ namespace Emby.Server.Implementations.Library return ItemRepository.RetrieveItem(id); } - public IEnumerable GetCollectionFolders(BaseItem item) + public List GetCollectionFolders(BaseItem item) { - while (!(item.GetParent() is AggregateFolder) && item.GetParent() != null) + while (item != null) { - item = item.GetParent(); + var parent = item.GetParent(); + + if (parent == null || parent is AggregateFolder) + { + break; + } + + item = parent; } if (item == null) @@ -1941,7 +1948,8 @@ namespace Emby.Server.Implementations.Library return GetUserRootFolder().Children .OfType() - .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase)); + .Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase)) + .ToList(); } public LibraryOptions GetLibraryOptions(BaseItem item) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 2971405b9..6cf201990 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -74,20 +74,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio return new MusicArtist(); } - if (_config.Configuration.EnableSimpleArtistDetection) - { - return null; - } + return null; + //if (_config.Configuration.EnableSimpleArtistDetection) + //{ + // return null; + //} - // Avoid mis-identifying top folders - if (args.Parent.IsRoot) return null; + //// Avoid mis-identifying top folders + //if (args.Parent.IsRoot) return null; - var directoryService = args.DirectoryService; + //var directoryService = args.DirectoryService; - var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); + //var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); - // If we contain an album assume we are an artist folder - return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null; + //// If we contain an album assume we are an artist folder + //return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null; } } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 0e05c79ca..1cad4a630 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -223,6 +223,10 @@ namespace MediaBrowser.Api.Playback { args += " -map -0:s"; } + else if (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed) + { + args += string.Format(" -map 0:{0}", state.SubtitleStream.Index); + } else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) { args += " -map 1:0 -sn"; @@ -1797,6 +1801,10 @@ namespace MediaBrowser.Api.Playback videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); } } + else if (i == 30) + { + request.SubtitleCodec = val; + } } } @@ -1915,6 +1923,13 @@ namespace MediaBrowser.Api.Playback ?? state.SupportedAudioCodecs.FirstOrDefault(); } + if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) + { + state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i)) + ?? state.SupportedSubtitleCodecs.FirstOrDefault(); + } + var item = LibraryManager.GetItemById(request.Id); state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); @@ -2109,6 +2124,7 @@ namespace MediaBrowser.Api.Playback state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video); state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false); + state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod; state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio); if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal) diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 7ae64b834..47f5d95e0 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -9,6 +9,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using System; using System.IO; +using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common.IO; using MediaBrowser.Controller.IO; @@ -111,7 +112,12 @@ namespace MediaBrowser.Api.Playback.Progressive var inputModifier = GetInputModifier(state); - return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7} -y \"{8}\"", + var subtitleArguments = state.SubtitleStream != null && + state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed + ? GetSubtitleArguments(state) + : string.Empty; + + return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", inputModifier, GetInputArgument(state), keyFrame, @@ -119,11 +125,29 @@ namespace MediaBrowser.Api.Playback.Progressive GetVideoArguments(state, videoCodec), threads, GetAudioArguments(state), + subtitleArguments, format, outputPath ).Trim(); } + private string GetSubtitleArguments(StreamState state) + { + var format = state.SupportedSubtitleCodecs.FirstOrDefault(); + string codec; + + if (string.IsNullOrWhiteSpace(format) || string.Equals(format, state.SubtitleStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + codec = "copy"; + } + else + { + codec = format; + } + + return " -codec:s:0 " + codec; + } + /// /// Gets video arguments to pass to ffmpeg /// diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 2a2ad92cd..3fcdea4ba 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -28,6 +28,8 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string AudioCodec { get; set; } + public string SubtitleCodec { get; set; } + /// /// Gets or sets the start time ticks. /// diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index ab0bfd502..84a546d35 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -47,6 +47,7 @@ namespace MediaBrowser.Api.Playback public MediaStream AudioStream { get; set; } public MediaStream VideoStream { get; set; } public MediaStream SubtitleStream { get; set; } + public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } /// /// Gets or sets the iso mount. @@ -124,6 +125,7 @@ namespace MediaBrowser.Api.Playback public string OutputAudioSync = "1"; public string OutputVideoSync = "-1"; + public List SupportedSubtitleCodecs { get; set; } public List SupportedAudioCodecs { get; set; } public List SupportedVideoCodecs { get; set; } public string UserAgent { get; set; } @@ -133,6 +135,7 @@ namespace MediaBrowser.Api.Playback { _mediaSourceManager = mediaSourceManager; _logger = logger; + SupportedSubtitleCodecs = new List(); SupportedAudioCodecs = new List(); SupportedVideoCodecs = new List(); PlayableStreamFileNames = new List(); diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 68fa7f92a..d6fa4030d 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -189,24 +189,10 @@ namespace MediaBrowser.Api result.Series = hasSeries.SeriesName; } - var season = item as Season; - if (season != null) - { - result.EpisodeCount = season.GetRecursiveChildren(i => i is Episode).Count; - } - - var series = item as Series; - if (series != null) - { - result.EpisodeCount = series.GetRecursiveChildren(i => i is Episode).Count; - } - var album = item as MusicAlbum; if (album != null) { - result.SongCount = album.Tracks.Count(); - result.Artists = album.Artists.ToArray(); result.AlbumArtist = album.AlbumArtist; } diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 9f89db9c7..f5fd94d0e 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -114,11 +114,11 @@ namespace MediaBrowser.Api config.EnableStandaloneMusicKeys = true; config.EnableCaseSensitiveItemIds = true; config.EnableFolderView = true; - config.EnableSimpleArtistDetection = true; config.SkipDeserializationForBasicTypes = true; config.SkipDeserializationForPrograms = true; config.SkipDeserializationForAudio = true; config.EnableSeriesPresentationUniqueKey = true; + config.EnableLocalizedGuids = true; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index bf9a07626..33cd4f3d1 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -456,7 +456,7 @@ namespace MediaBrowser.Controller.Library /// /// The item. /// IEnumerable<Folder>. - IEnumerable GetCollectionFolders(BaseItem item); + List GetCollectionFolders(BaseItem item); LibraryOptions GetLibraryOptions(BaseItem item); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 6a5945b76..a0f5c129b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -496,6 +496,12 @@ namespace MediaBrowser.MediaEncoding.Encoder return SupportsEncoder(codec); } + public bool CanEncodeToSubtitleCodec(string codec) + { + // TODO + return true; + } + /// /// Gets the encoder path. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 3144e8469..f9df776df 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -47,6 +47,7 @@ namespace MediaBrowser.Model.Configuration /// true if [use HTTPS]; otherwise, false. public bool EnableHttps { get; set; } public bool EnableSeriesPresentationUniqueKey { get; set; } + public bool EnableLocalizedGuids { get; set; } /// /// Gets or sets the value pointing to the file system where the ssl certiifcate is located.. @@ -189,7 +190,6 @@ namespace MediaBrowser.Model.Configuration public string[] Migrations { get; set; } public bool EnableChannelView { get; set; } public bool EnableExternalContentInSuggestions { get; set; } - public bool EnableSimpleArtistDetection { get; set; } public int ImageExtractionTimeoutMs { get; set; } /// @@ -201,6 +201,7 @@ namespace MediaBrowser.Model.Configuration CodecsUsed = new string[] { }; Migrations = new string[] { }; ImageExtractionTimeoutMs = 0; + EnableLocalizedGuids = true; DisplaySpecialsWithinSeasons = true; EnableExternalContentInSuggestions = true; diff --git a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs index 0dac23403..fd615733d 100644 --- a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs +++ b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs @@ -3,6 +3,7 @@ public interface ITranscoderSupport { bool CanEncodeToAudioCodec(string codec); + bool CanEncodeToSubtitleCodec(string codec); } public class FullTranscoderSupport : ITranscoderSupport @@ -11,5 +12,9 @@ { return true; } + public bool CanEncodeToSubtitleCodec(string codec) + { + return true; + } } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 262964404..129b49cf6 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -435,7 +435,7 @@ namespace MediaBrowser.Model.Dlna if (subtitleStream != null) { - SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value); + SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value, null, null); playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; @@ -465,10 +465,11 @@ namespace MediaBrowser.Model.Dlna if (subtitleStream != null) { - SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode); + SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode, transcodingProfile.Protocol, transcodingProfile.Container); playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method; playlistItem.SubtitleFormat = subtitleProfile.Format; + playlistItem.SubtitleCodecs = new[] { subtitleProfile.Format }; } playlistItem.PlayMethod = PlayMethod.Transcode; @@ -874,7 +875,7 @@ namespace MediaBrowser.Model.Dlna { if (subtitleStream != null) { - SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, playMethod); + SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, playMethod, null, null); if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed) { @@ -886,11 +887,11 @@ namespace MediaBrowser.Model.Dlna return IsAudioEligibleForDirectPlay(item, maxBitrate); } - public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod) + public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer) { - if (playMethod != PlayMethod.Transcode && !subtitleStream.IsExternal) + if (!subtitleStream.IsExternal && (playMethod != PlayMethod.Transcode || !string.Equals(transcodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))) { - // Look for supported embedded subs + // Look for supported embedded subs of the same format foreach (SubtitleProfile profile in subtitleProfiles) { if (!profile.SupportsLanguage(subtitleStream.Language)) @@ -903,11 +904,40 @@ namespace MediaBrowser.Model.Dlna continue; } + if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(subtitleStream, profile, transcodingSubProtocol, transcodingContainer)) + { + continue; + } + if (subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format) && StringHelper.EqualsIgnoreCase(profile.Format, subtitleStream.Codec)) { return profile; } } + + // Look for supported embedded subs of a convertible format + foreach (SubtitleProfile profile in subtitleProfiles) + { + if (!profile.SupportsLanguage(subtitleStream.Language)) + { + continue; + } + + if (profile.Method != SubtitleDeliveryMethod.Embed) + { + continue; + } + + if (playMethod == PlayMethod.Transcode && !IsSubtitleEmbedSupported(subtitleStream, profile, transcodingSubProtocol, transcodingContainer)) + { + continue; + } + + if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsSubtitleConversionTo(profile.Format)) + { + return profile; + } + } } // Look for an external or hls profile that matches the stream type (text/graphical) and doesn't require conversion @@ -918,6 +948,28 @@ namespace MediaBrowser.Model.Dlna }; } + private static bool IsSubtitleEmbedSupported(MediaStream subtitleStream, SubtitleProfile subtitleProfile, string transcodingSubProtocol, string transcodingContainer) + { + if (string.Equals(transcodingContainer, "ts", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(transcodingContainer, "mpegts", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(transcodingContainer, "mp4", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (string.Equals(transcodingContainer, "mkv", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + private static SubtitleProfile GetExternalSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, bool allowConversion) { foreach (SubtitleProfile profile in subtitleProfiles) diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 5b8d40dfb..a85e6085b 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -18,6 +18,7 @@ namespace MediaBrowser.Model.Dlna public StreamInfo() { AudioCodecs = new string[] { }; + SubtitleCodecs = new string[] { }; } public string ItemId { get; set; } @@ -74,6 +75,7 @@ namespace MediaBrowser.Model.Dlna public MediaSourceInfo MediaSource { get; set; } + public string[] SubtitleCodecs { get; set; } public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } public string SubtitleFormat { get; set; } @@ -268,6 +270,12 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty)); list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString().ToLower())); + string subtitleCodecs = item.SubtitleCodecs.Length == 0 ? + string.Empty : + string.Join(",", item.SubtitleCodecs); + + list.Add(new NameValuePair("SubtitleCodec", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Embed ? subtitleCodecs : string.Empty)); + return list; } @@ -354,7 +362,7 @@ namespace MediaBrowser.Model.Dlna private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles) { - SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, PlayMethod); + SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, PlayMethod, SubProtocol, Container); SubtitleStreamInfo info = new SubtitleStreamInfo { IsForced = stream.IsForced, diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 0eb9e2730..3cd3e7dde 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -311,29 +311,31 @@ namespace MediaBrowser.Model.Entities !StringHelper.EqualsIgnoreCase(codec, "dvb_subtitle"); } - public bool SupportsSubtitleConversionTo(string codec) + public bool SupportsSubtitleConversionTo(string toCodec) { if (!IsTextSubtitleStream) { return false; } + var fromCodec = Codec; + // Can't convert from this - if (StringHelper.EqualsIgnoreCase(Codec, "ass")) + if (StringHelper.EqualsIgnoreCase(fromCodec, "ass")) { return false; } - if (StringHelper.EqualsIgnoreCase(Codec, "ssa")) + if (StringHelper.EqualsIgnoreCase(fromCodec, "ssa")) { return false; } // Can't convert to this - if (StringHelper.EqualsIgnoreCase(codec, "ass")) + if (StringHelper.EqualsIgnoreCase(toCodec, "ass")) { return false; } - if (StringHelper.EqualsIgnoreCase(codec, "ssa")) + if (StringHelper.EqualsIgnoreCase(toCodec, "ssa")) { return false; } diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 5a195e035..9d6d3f0ad 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -457,7 +457,7 @@ namespace MediaBrowser.XbmcMetadata.Savers if (item is Video) { - var outline = (item.Tagline ?? item.Overview ?? string.Empty) + var outline = (item.Tagline ?? string.Empty) .StripHtml() .Replace(""", "'");